Two-Shot Kernel Shellcode: Bypassing CR Pinning with KProbes + Video

Listen to this Post

Featured Image

Introduction:

Modern Linux kernels deploy CR Pinning to enforce critical bits like SMEP and SMAP, preventing classic control‑flow hijacking attacks that write to the `cr4` register. However, a newly revisited technique shows that a tiny window still exists: by leveraging KProbes to hook native_write_cr4, an attacker can execute shellcode before the pinning correction takes effect, achieving reliable kernel‑mode code execution with only two control‑flow redirections.

Learning Objectives:

  • Understand the original “two‑shot” kernel shellcode execution technique and why CR Pinning was introduced to block it.
  • Learn how a subtle gap in the CR Pinning implementation can be exploited using KProbes.
  • Explore practical exploitation primitives, including NPerm for forging kernel data and helper gadgets for controlling registers.

You Should Know:

1. Two‑Shot Kernel Shellcode Execution: The Original Attack

The original technique, documented by Project Zero, abused the `native_write_cr4` function to disable SMEP and SMAP. At that time, `native_write_cr4` was essentially a thin wrapper:

mov cr4, rdi
ret

By hijacking control flow twice – first to call `native_write_cr4` with a value that clears SMEP/SMAP, and a second time to jump into userspace shellcode – an attacker could execute arbitrary code with kernel privileges.

Step‑by‑step guide to understand the classic attack:

  1. Gain initial control‑flow hijack (e.g., via a use‑after‑free or stack overflow in a kernel module).
  2. Redirect execution to `native_write_cr4` with `RDI` set to a value that disables SMEP/SMAP (e.g., RDI = cr4 & ~(X86_CR4_SMEP | X86_CR4_SMAP)).
  3. After the `mov cr4, rdi` instruction, the kernel now runs with SMEP/SMAP disabled, allowing userspace shellcode to be executed directly.
  4. Trigger a second control‑flow hijack that jumps to a userspace address containing the final shellcode.

This attack worked because there was no mitigation that restored the critical bits after the write.

2. CR Pinning Mitigation and Its Gap

To block the above attack, Linux introduced CR Pinning. The mitigation ensures that after any write to cr4, the critical bits (SMEP, SMAP, UMIP, etc.) are immediately restored if they were cleared. The current `native_write_cr4` implementation looks like this:

static const unsigned long cr4_pinned_mask = X86_CR4_SMEP | X86_CR4_SMAP | ...;
static DEFINE_STATIC_KEY_FALSE_RO(cr_pinning);

void native_write_cr4(unsigned long val) {
unsigned long bits_changed = 0;
set_register:
asm volatile("mov %0,%%cr4": "+r" (val) : : "memory");
if (static_branch_likely(&cr_pinning)) {
if (unlikely((val & cr4_pinned_mask) != cr4_pinned_bits)) {
bits_changed = (val & cr4_pinned_mask) ^ cr4_pinned_bits;
val = (val & ~cr4_pinned_mask) | cr4_pinned_bits;
goto set_register;
}
WARN_ONCE(bits_changed, "pinned CR4 bits changed: 0x%lx!?\n", bits_changed);
}
}

Step‑by‑step analysis of the gap:

  1. The function writes the attacker‑supplied `val` directly to `cr4` (the `mov` instruction).
  2. Only after the write does it check whether the pinned bits were altered.
  3. If they were, it corrects them and jumps back to re‑execute the mov.
  4. The window: Between the initial `mov` and the correction, `cr4` contains the attacker‑controlled value (with SMEP/SMAP disabled). If an attacker can execute code within that tiny window, they can still run shellcode in kernel mode.

3. Leveraging KProbes for Reliable Execution

KProbes is a kernel tracing mechanism that allows inserting breakpoints into arbitrary kernel functions. By registering a KProbe on `native_write_cr4` and pointing its handler to a userspace address, an attacker can gain execution inside that narrow window.

Step‑by‑step KProbe exploitation:

  1. Forge kernel data using NPerm (a technique to write kernel memory without direct pointer access) – for example, allocate a `struct kprobe` in kernel memory and populate it with your handler address.
  2. Register the KProbe by calling `register_kprobe()` (this may require a helper gadget to control `RDI` with the address of your forged struct kprobe).
  3. Trigger a control‑flow hijack that calls `native_write_cr4` with a value that clears SMEP/SMAP.
  4. Immediately after the `mov` to cr4, the KProbe breakpoint fires, and the kernel jumps to your handler while SMEP/SMAP are still disabled.
  5. From the handler, you can now execute arbitrary userspace shellcode (e.g., a privilege escalation payload).

Practical PoC snippet (conceptual):

// Forged kprobe structure in kernel memory
struct kprobe kp = {
.addr = (kprobe_opcode_t )native_write_cr4,
.pre_handler = (kprobe_pre_handler_t)0x41414141, // userspace shellcode address
};

// Helper gadget to control RDI (e.g., pop rdi; ret;)
// register_kprobe(&kp);

// Trigger native_write_cr4 with SMEP/SMAP disabled
unsigned long bad_cr4 = read_cr4() & ~(X86_CR4_SMEP | X86_CR4_SMAP);
native_write_cr4(bad_cr4);
// KProbe fires here, shellcode executes

4. Defensive Strategies and Detection

Detection on Linux:

  • Monitor `dmesg` for WARN_ONCE messages: "pinned CR4 bits changed: 0x%lx!?\n". This indicates an attempt to clear pinned bits.
  • Use `auditd` to watch for unauthorized `kprobe` registration:
    sudo auditctl -a always,exit -F arch=b64 -S kprobe_register -k kprobe_events
    
  • Check loaded kprobes via /sys/kernel/debug/kprobes/list.

Hardening recommendations:

  • Enable `CONFIG_KPROBES=n` in production kernels (disables kprobes entirely).
  • Use Lockdown security module (e.g., lockdown=integrity), which restricts kprobe usage.
  • Apply kernel live patches that close the race window (e.g., by using a write‑once register that cannot be altered).

Windows equivalent commands (for defenders):

  • Monitor for similar tracing abuse:
    Get-WinEvent -LogName "Microsoft-Windows-Kernel-EventTracing/Admin" | Where-Object { $_.Id -eq 2 }
    
  • Disable kernel debugging features in production:
    bcdedit /set {current} debug off
    

5. Mitigation Bypass via KProbe Oriented Programming (KPOP)

The article suggests that the technique can be extended into KProbe Oriented Programming (KPOP) – a form of return‑oriented programming where kprobe handlers are chained together to build complex exploits without writing full shellcode.

Step‑by‑step KPOP construction:

  1. Identify multiple kernel functions that can be hooked with kprobes.
  2. For each, craft a handler that performs a small operation (e.g., clear a bit, set a register, copy a value).
  3. Chain them by having each handler trigger the next kprobe (e.g., by calling `jprobe` or kretprobe).
  4. Execute the chain to perform arbitrary kernel operations.

This shows that even modern tracing features can be repurposed into powerful exploitation primitives.

  1. Practical Exercise: Attempting the Attack in a Lab

Step‑by‑step lab setup (Ubuntu 22.04 LTS with a vulnerable kernel):
1. Build a custom kernel with `CONFIG_KPROBES=y` and an older version without the CR Pinning fix (e.g., 4.15.0).
2. Use a debugger like `kgdb` or `qemu` + `gdb` to simulate a control‑flow hijack.
3. Write a small kernel module that triggers the attack:

// Module that calls native_write_cr4 with a bad value
include <linux/module.h>
include <asm/special_insns.h>

static int __init test_init(void) {
unsigned long bad = native_read_cr4() & ~X86_CR4_SMEP;
printk(KERN_INFO "Writing bad CR4: 0x%lx\n", bad);
native_write_cr4(bad);
return 0;
}
module_init(test_init);

4. Load the module and observe `dmesg` for the WARN_ONCE message.
5. If kprobes are enabled and no Lockdown is active, you can attempt to register a kprobe on `native_write_cr4` and verify that your handler executes.

Windows defense commands (for sysadmins):

  • Disable kernel debugging and tracing in production:
    bcdedit /set {current} nx AlwaysOn
    bcdedit /set {current} nointegritychecks off
    
  • Use Device Guard and Credential Guard to block unsigned code execution.

7. Future of Kernel Exploitation Techniques

The revisited technique highlights a broader trend: mitigations often introduce subtle windows that can be exploited by abusing debugging or tracing features. As kernel developers add more restrictions, attackers shift to abusing the very mechanisms designed to help administrators (e.g., kprobes, eBPF, perf events).

Prediction:

In the next 2‑3 years, we will see a rise in “debugging‑based exploitation” where attackers leverage legitimate tracing interfaces to bypass kernel protection. This will force a rethink of how we design debugging APIs – moving from “opt‑in” to “default‑deny” models. Expect to see new Linux security modules (LSMs) that restrict kprobe usage only to signed kernel modules, and Windows will likely adopt similar restrictions for its Event Tracing for Windows (ETW) subsystems. Moreover, the industry will likely move towards hardware‑assisted control‑flow integrity (e.g., Intel CET) that makes such register‑level hijacking impossible, even if a window exists.

What Undercode Say:

  • Key Takeaway 1: The “two‑shot” kernel shellcode technique is not dead – it has merely evolved. CR Pinning closes the direct path, but the brief window before correction remains exploitable using kprobes.
  • Key Takeaway 2: Debugging and tracing features are a double‑edged sword. They provide invaluable visibility for defenders but also create new attack surfaces that can turn into reliable exploitation primitives.

Analysis: The article by Jennifer Miller masterfully demonstrates that a seemingly robust mitigation (CR Pinning) can be circumvented by thinking “outside the box” – using a feature intended for debugging as a springboard for shellcode execution. This is a classic example of mitigation bypass via feature abuse, a pattern we’ve seen with SMEP/SMAP bypasses, ret2usr, and now kprobes. The practical implication is that defenders must not only apply patches but also carefully evaluate which debugging interfaces are enabled in production. The Linux kernel’s move toward `CONFIG_KPROBES=n` in hardened builds is a step in the right direction, but legacy systems remain vulnerable. This technique also underscores the importance of defense in depth: even with CR Pinning, an attacker who can register a kprobe still wins. Therefore, restricting kprobe registration (e.g., via SELinux or Lockdown) is just as critical as the pinning itself.

Prediction:

As more kernels adopt hardware features like Intel’s Control‑flow Enforcement Technology (CET), register‑level hijacking of `cr4` will become impossible because control‑flow transfers will be validated at the CPU level. However, this will push attackers toward data‑only attacks that manipulate kernel data structures without changing control flow. Expect to see a surge in research on “data‑oriented programming” (DOP) and attacks that abuse legitimate kernel APIs (like kprobes) as execution vectors. The cat‑and‑mouse game will continue, but the next battleground will be the integrity of the kernel’s own debugging and tracing subsystems.

▶️ Related Video (92% Match):

🎯Let’s Practice For Free:

IT/Security Reporter URL:

Reported By: Abelousova Revisiting – Hackers Feeds
Extra Hub: Undercode MoN
Basic Verification: Pass ✅

🔐JOIN OUR CYBER WORLD [ CVE News • HackMonitor • UndercodeNews ]

💬 Whatsapp | 💬 Telegram

📢 Follow UndercodeTesting & Stay Tuned:

𝕏 formerly Twitter 🐦 | @ Threads | 🔗 Linkedin | 🦋BlueSky