Digging Into Linux Kernel Exploit Development

Why the Kernel Still Matters for Exploit Devs
Userspace is a padded cell. You want real privilege, you go kernel. Linux kernel exploit dev isn’t about popping calc — it’s about ring 0. It’s about subverting the layer that enforces process isolation, file permissions, and network stack controls. Modern kernels ship with a heap of mitigations. KASLR, SMEP, SMAP, KPTI, CFI. But bugs still land. Memory corruption in netfilter, race conditions in io_uring, use-after-free in the eBPF verifier. The attack surface grows with every new subsystem. As a dev, you need more than just awareness of a vulnerability class. You need to understand the object lifecycle and the allocator behavior that turns a crash into control.
This guide walks through the practical stack. Setting up a minimal debugging kernel. Heap grooming with kmalloc caches. Bypassing Supervisor Mode Execution Prevention (SMEP) with stack pivoting and ROP. Every step is grounded in real primitives, not theoretical models. Expect to get your hands dirty with GDB, QEMU, and custom payloads written in C and assembly.
Lab Setup: Building a Vulnerable Kernel
You can’t learn exploit dev on a production kernel with all mitigations enabled. You need a controlled environment. Start with a 5.x or 6.x upstream kernel. Disable KASLR and SMEP during boot. Compile your own module with a deliberate bug. For example, a simple character device that copies user data into a fixed-size kernel buffer without bounds checking gives you a classic stack-based overflow. Build with CONFIG_DEBUG_INFO and CONFIG_GDB_SCRIPTS to get pretty-printing of kernel structures in GDB.
Run the kernel in QEMU with a minimal initramfs built via Buildroot. Attach GDB with target remote :1234. Once you have a crash, inspect the saved instruction pointer on the stack. If you can overwrite it, you have control flow hijack. But that’s just the entry ticket. The real work begins when you realize you’re executing in supervisor mode with all the constraints that entails.

Heap Exploitation: slab, slub, and kmalloc Internals
Most modern kernel exploits target heap objects. The Linux kernel uses slab/slub allocators that group objects of the same size into caches. A kmalloc-256 cache, for instance, holds allocations between 192 and 256 bytes. When you trigger a use-after-free or a heap overflow, you’re corrupting an object within one of these caches. The trick is understanding freelist poisoning and partial slab recovery. If you can overwrite the next pointer of a freed object, you can make the allocator return an arbitrary address on the next allocation — a classic heap spray target.
Grooming the Heap for Deterministic Layout
Heap grooming is the art of forcing the allocator into a predictable state. You spray objects of the target size, free every other one, then trigger the vulnerability to corrupt the freelist of the freed slots. If the victim object gets allocated right after, it lands on your controlled address. Common targets include struct file_operations or struct tty_struct — anything with function pointers. Overwrite ioctl or write pointers and you redirect execution when userspace makes the corresponding syscall.
On modern kernels, freelist randomization adds noise. It’s not a hard barrier, though. You can often leak a heap address via an info leak bug — an uninitialized memory read, say — and use that to calculate the offset for your fake object. The key is patience and iterative testing with a GDB script that dumps slab state after each allocation step.
Bypassing SMEP and SMAP
Supervisor Mode Execution Prevention (SMEP) stops the kernel from executing code in userspace pages. Supervisor Mode Access Prevention (SMAP) stops even reading userspace memory while in kernel mode. Both are controlled by CR4 register flags. If you have an arbitrary write primitive, you can overwrite the kernel’s copy of CR4. That’s rarely direct. More common is a ROP chain that flips the bits before returning to a userspace shellcode mapped at a known address.
Stack Pivoting via xchg eax, esp
When you control the instruction pointer but the stack is non-deterministic, you pivot to a controlled area. In x86_64, gadgets like xchg eax, esp; ret let you move the stack pointer to a value in RAX, which you set via a prior gadget. Your ROP chain then lives in a userspace buffer that you’ve mapped and filled. This requires that you know the address of that buffer, which means you need a KASLR bypass first. Even without a full leak, partial overwrites of return addresses can defeat KASLR by exploiting the fact that kernel text is mapped within a 1GB range.
Once the stack is pivoted, your ROP chain does the following: pop a value into RAX that has the SMEP/SMAP bits cleared, move it into CR4, then return to a shellcode that escalates privileges by overwriting cred structures. The classic payload copies the init_cred struct over the current task’s cred, setting uid=0, gid=0, and all capabilities.

Stable Exploitation via Kernel Read/Write Primitives
Arbitrary read and arbitrary write are the holy grail. An arbitrary read lets you leak the kernel base address — defeating KASLR — and scan for useful structures. An arbitrary write lets you modify anything. You often build these from a limited vulnerability. For example, a heap overflow that corrupts a length field in a neighboring object can be turned into an out-of-bounds read, which then leaks a pointer. That pointer gives you the heap layout, and from there you can locate a structure containing a kernel text address.
Once you have the kernel base, you can compute the address of modprobe_path, core_pattern, or poweroff_cmd. Overwriting modprobe_path with a path to a userspace script gives you root execution when the kernel runs call_usermodehelper. This technique is reliable and doesn’t require SMEP bypass. It’s the go-to for many modern exploits when direct code execution is blocked.
Targeting modprobe_path for Privilege Escalation
The kernel invokes modprobe when it encounters an unknown binary format. By overwriting the global modprobe_path string — typically stored in a read-write data section — you redirect that execution to a script of your choice. The script runs as root. You trigger it by executing a file with an unrecognized magic number. This requires a single arbitrary write. You can achieve that from a heap corruption if you know the address of modprobe_path relative to the kernel base. No ROP, no shellcode, no SMEP bypass. Just a clean, surgical write.
Debugging and Stability: Avoiding Kernel Panics
A crashing kernel is a loud crash. In a lab, it’s a learning tool; on a real target, it’s detection. You need to stabilize the system after exploitation. After overwriting creds or modprobe_path, restore any corrupted structures. If you trashed a slab freelist, repair it or force the slab to be discarded. Use set_fs() tricks (on older kernels) or override_creds() to temporarily gain privileges without permanently altering structures. The cleaner the exit, the longer the exploit remains viable.
Also, consider the kernel’s panic-on-oops setting. If the vulnerability triggers a BUG() or a null pointer dereference, the kernel may halt. You can sometimes suppress this by patching the oops handler in memory — if you already have write access. Otherwise, you must trigger the bug without causing an unrecoverable fault. This is where understanding the failure modes of your target subsystem pays off.
FAQ
What’s the first vulnerability class to learn for kernel exploitation?
Start with a simple stack-based buffer overflow in a kernel module. It gives you direct control of the saved return address and teaches the basics of kernel debugging, ROP in supervisor mode, and the role of SMEP/SMAP. Heap vulnerabilities are more common in real-world bugs, but the learning curve is steeper because of allocator internals.
How do I bypass KASLR without an info leak?
Partial overwrites of return addresses are effective if the kernel base is aligned to a 2MB boundary. You overwrite only the lower bytes of a saved pointer, leaving the higher bytes intact. Since the offset within the kernel image is fixed, you can redirect execution to a known gadget. This works best when you have a reliable stack layout and multiple attempts are allowed.
Is eBPF exploitation the future of kernel attacks?
eBPF presents a large attack surface because it allows unprivileged users to submit code that runs in kernel context after verification. Bugs in the verifier can lead to type confusion or out-of-bounds access. Exploiting eBPF vulnerabilities often involves crafting malicious bytecode that passes verification but misbehaves at runtime, giving arbitrary read/write. It’s a specialized skill, but the privilege boundary it crosses makes it high-value.









