Install
$ agentstack add skill-wang200935-security-agent-skills-ctf-kernel-exploitation ✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ● Network access Used
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
Verified badge
Passed review? Show it. Paste this badge into your README, it links to the public security report.
Reliability & compatibility
Declared compatibility
Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.
We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.
How agent discovery & health will work →About
CTF Kernel Exploitation
Kernel exploitation CTF challenges: privilege escalation from unprivileged user to root inside a QEMU VM. Two major families: (A) actual kernel vulnerabilities (UAF, heap overflow in kernel modules), and (B) QEMU emulator bugs that let userspace bypass kernel protections.
When to Load
- Any CTF challenge tagged "kernel", "pwn", "misc 0day", or involving a bzImage + rootfs.cpio
- The user asks about exploiting a Linux kernel, QEMU escape, or kernel privilege escalation
- The challenge provides a QEMU run script and kernel/rootfs files
Core Approach
Step 1: Analyze the challenge files
unzip dist.zip
cat run.sh # QEMU flags: KVM vs TCG, protections
cat rootfs/init # System config, kptr_restrict, setuid binaries
file vmlinux # ELF or compressed?
Step 2: Extract kernel offsets
For modprobe_path technique, find the virtual address:
# Parse vmlinux ELF, find "/sbin/modprobe" string, compute virt addr
# modprobe_path is in .data section
# offset from kernel text base (0xffffffff81000000) is the key value
Step 3: Choose exploit strategy
Strategy A: QEMU TCG iret/far-call (most common for misc/kernel CTFs)
- Works on QEMU rdi gadget> # MOV RDI, RAX essential — see Pitfalls
buffer[4]: commitcreds # elevate to root buffer[5]: swapgsrestoreaddr # swapgs; iretq back to ring3 buffer[6]: userrip # shellcode buffer[7]: 0x33 # user CS buffer[8]: userrflags (0x202) # interrupt flag set buffer[9]: userrsp buffer[10]: 0x2b # user SS ... buffer[16]: iretq frame: RIP=poprdiret, CS=0x10, RFLAGS, RSP=&buffer[1], SS=0x18
**init_cred bypass**: If no rax→rdi gadget exists, commit `init_cred` directly:
pop rdi; ret → &initcred → commitcreds → swapgs_restore → user shellcode
Skips `prepare_kernel_cred` entirely. Needs `init_cred` address (a global `struct cred` in .data with full capabilities, uid=0).
### Step 4: Remote connection (if applicable)
Most kernel CTFs use: HTTP PoW → WebSocket → terminal inside VM
- Solve PoW (SHA-256, typically 20-bit difficulty)
- Connect WebSocket with token + pow_token
- Handle queue/status/ready/error messages
- Interact with terminal (busybox shell)
### Step 5: Upload and run exploit
- Binary must be **statically linked** (VM has only busybox)
- Compress with gzip, encode as base64
- Upload in chunks via `printf` or `echo -n`
- Decode: `base64 -d /tmp/e.b64 | gzip -d > /tmp/e`
- VM session timeout is typically 300s — keep total operation under this
## Key Pitfalls
### `xchg eax, edi` / 32-bit swap is USELESS for kernel addresses
`xchg eax, edi; ret` (bytes `97 c3`) is common but only swaps **lower 32 bits** and **zero-extends to 64 bits**. If RAX holds `0xffff88800a1b2c00` (kernel heap cred pointer), after `xchg eax, edi`, RDI becomes `0x000000000a1b2c00` — a **userspace address**, not a valid kernel pointer. Always search for a **64-bit** rax→rdi gadget:
- `48 89 c7 c3` = `mov rdi, rax; ret` (rare in kernel .text)
- `48 97 c3` = `xchg rax, rdi; ret` (extremely rare)
- `50 5f c3` = `push rax; pop rdi; ret` (occasionally found)
- Fallback: use `init_cred` bypass (see Strategy C)
### KPTI trampoline address verification
The `swapgs_restore_addr` hardcoded in the module (e.g., `0xffffffff820015d0`) may NOT point to `swapgs; iretq`. Always **disassemble** around that address with `objdump -d` to verify. In Linux 6.12 with KPTI, the code at that offset may be MSR writes (`wrmsr`) for page-table switching, not the expected `swapgs` instruction. The actual `swapgs; iretq` sequence may be elsewhere in the trampoline.
### commit_creds and prepare_kernel_cred are NOT exported
Since kernel ~5.x, these functions have no `EXPORT_SYMBOL`. They are **absent from `__ksymtab`**. Do not waste time searching __ksymtab — use:
- **Symbol extraction from stripped vmlinux** (see `references/symbol-extraction-stripped-vmlinux.md`)
- **QEMU boot with modified initramfs** (dump `/proc/kallsyms` before `kptr_restrict=1` is set)
- **ENDBR64 function boundary scan** (modern IBT kernels start every function with `f3 0f 1e fa`)
### EFI-stub bzImage extraction
Modern kernels with `CONFIG_EFI_STUB=y` produce bzImages that are valid PE executables (start with `MZ`). The compressed kernel is inside the `.text` PE section, **not** at the traditional `(setup_sects+1)*512` offset. Use:
```python
# Parse PE header to find .text section, then gzip offset within it
# Verified for Linux 6.12.85: gzip at bzImage offset 21188 (0x52C4)
# Decompresses to 43,911,280 byte ELF
The extract-vmlinux script from kernel source may fail on EFI-stub images — parse PE sections manually.
Docker QEMU TCG mode is IMPRACTICALLY slow
Booting a full Linux kernel in Docker with QEMU TCG (no KVM inside containers on macOS) can take 10-30+ minutes. Key tactic: the slow part is apt-get install qemu-system-x86 (pulls in graphical dependencies). Pre-build a Docker image with QEMU or reuse the same container for multiple boots. For symbol extraction, prefer:
- Static binary analysis (ENDBR64 scan, byte-pattern gadget search)
- Modify initramfs to dump kallsyms before kptr_restrict — most reliable method; trivial init script change repack with cpio+gzip
- Building kernel from source to get System.map (~20 min but guaranteed correct)
- Using pre-built Ubuntu kernel debug packages for the same version
Ubuntu QEMU 6.2.0 has BACKPORTED iret/far-call fix
CRITICAL: Ubuntu 22.04 ships QEMU 6.2.0 (qemu-system-x86 from apt) with a backported security patch that fixes the ring3→ring0 iretq privilege-escalation bug. The upstream fix landed in QEMU 9.1, but Ubuntu backported it. The ring3→ring0 iretq exploit will SIGSEGV immediately on Ubuntu-packaged QEMU. This means local testing in Docker with ubuntu:22.04 gives false negatives.
Verification: A minimal iretq test (RSP=userspace, CS=0x10) gets SIGSEGV (exit 139) on Ubuntu's QEMU 6.2.0. On unpatched QEMU (e.g., compiled from source before 9.1, or older distro packages), the same test succeeds.
Workaround for local testing: The remote CTF server may use a DIFFERENT QEMU version. For local testing, either:
- Use a Docker image with unpatched QEMU (Alpine, Debian, or compile from source)
- Test on the remote directly (mind the 1-hour cooldown)
- Use a non-Ubuntu QEMU package
Physical address calculation
The CORRECT formula:
uint64_t modprobe_phys = (kbase + MODPROBE_PATH_OFF) - 0xffffffff80000000ULL;
DO NOT add an extra PHYSICALSTART (0x1000000) — PAGEOFFSET already accounts for the mapping.
Binary upload timing
- Send all upload chunks without intermediate reads (shell echo interferes)
- After READY, wait 10-15s for VM to finish booting
- Use
gzip -dnotgunzip(busybox compatibility) - Don't use
2>/dev/nullduring decode — errors must be visible - Terminal prompt detection is unreliable; use fixed waits instead
Static compilation for Linux target
On macOS, use Docker:
# Alpine (musl): gcc -static -Os -s -o exp exp.c
# Alpine musl lacks REG_ERR/REG_RSP in ucontext — use raw gregs index 19 for REG_ERR on x86_64
docker run --rm -v $PWD:/work -w /work alpine sh -c '
apk add --no-cache gcc musl-dev && gcc -static -Os -s -o exp exp.c'
Alpine musl may lack REG_ERR/REG_RSP defines, and `` is absent. Workarounds:
ferr = ((ucontext_t*)ctx)->uc_mcontext.gregs[19];— raw index 19 for REGERR on x8664- Remove `` include; the phys_read/iretq exploit doesn't need LDT
WebSocket lifecycle
- The VM session timeout starts from VM boot, not from READY
- Don't read terminal output during upload — it captures stale echoes
- Send all commands (upload, decode, run, flag) in one burst, then read
- Total operation MUST fit in ~300s window
Verified Offsets Reference
For common kernel builds, modprobe_path offset from kbase:
- Linux 6.1.81 (AIS3 build):
0x2877760 - Linux 6.12.85 (AIS3 2026): full verified address table in
references/linux-6.12.85-verified-addresses.md. Key addresses:modprobe_path@0xffffffff82b44f60,commit_creds@0xffffffff810c1dd0,prepare_kernel_cred@0xffffffff810c2060,init_cred@0xffffffff82a52ba0
Physical addresses from kallsyms 'A' symbols
Kallsyms entries with type A (Absolute) are physical addresses, not virtual! Critical for QEMU TCG phys_read exploits:
000000000000b000 A gdt_page ← GDT at physical 0xb000
0000000000006000 A cpu_tss_rw ← TSS at physical 0x6000
Use these directly as physical addresses for iretq RSP targets. Do NOT subtract PAGE_OFFSET from 'A' symbols — they are already physical.
Netcat-based CTFd remote protocol (non-WebSocket)
Some CTFd instancers use a raw netcat protocol instead of WebSocket:
→ Server: PoW prompt ("Prefix: ")
→ Client: PoW answer
→ Server: "CTFd token:" prompt
→ Client: ctfd_
→ Server: "NEED_UPLOAD_EXPLOIT (y/n)"
→ Client: y
→ Server: (accepts exploit as RAW BINARY, boots VM)
Token is rate-limited: "This account can verify only once every 1 hour." Plan attempts carefully. The exploit binary is provided as a raw disk image (becomes /dev/sda in VM, copied to /tmp/e by init script).
PITFALL — raw binary, NOT base64: Sending base64-encoded data after 'y' causes "[X] Invalid URL." The server expects the raw binary (ELF file bytes) directly on the socket. It shows a progress bar (backspace characters) while receiving. After upload, the VM boots — set socket timeout to 300+ seconds (not 60) to capture the full VM boot + exploit output. The 60s default is too short; the exploit runs but you miss the flag. See references/netcat-raw-binary-upload-pitfall.md.
PITFALL — base64 echo confusion: If you send base64 INSTEAD of first answering 'y', the server reads 'f' (first char of base64) as the answer, defaults to 'n', echos the base64, and boots WITHOUT your exploit ("No exploit supplied"). Always send 'y\\n' BEFORE the binary data.
Apple Silicon + Docker QEMU = broken (Rosetta)
On Apple Silicon Macs, docker run --platform linux/amd64 with QEMU inside the container fails with rosetta error: Unimplemented syscall number 282. Rosetta 2 does not support the full x86_64 syscall table needed by QEMU's system emulation. For local kernel exploitation testing on Apple Silicon:
- Native macOS QEMU via Homebrew (may fail to build for x86_64 target)
- UTM.app or similar virtualization
- Test on the remote CTF server directly (mind the cooldown)
- Use a remote x86_64 cloud VM
Key gadget byte patterns (search .text section after objcopy -O binary -j .text)
| Gadget | Bytes | Notes | |--------|-------|-------| | pop rdi; ret | 5f c3 | Common, many instances | | xchg eax, edi; ret | 97 c3 | 32-bit only — zero-extends, useless for kernel addrs | | mov rdi, rax; ret | 48 89 c7 c3 | Rare in kernel .text | | push rax; pop rdi; ret | 50 5f c3 | Best rax→rdi gadget | | xchg rax, rdi; ret | 48 97 c3 | Extremely rare | | swapgs; iretq | 0f 01 f8 48 cf | May be split in KPTI trampoline | | ENDBR64 (function start) | f3 0f 1e fa | IBT-enabled kernels (CONFIGX86KERNEL_IBT=y) |
2025-2026 Kernel Exploitation Update: New CVEs & Techniques
Critical Kernel CVEs for CTF (2025-2026)
| CVE | Component | CVSS | Description | CTF Relevance | |---|---|---|---|---| | CVE-2024-1086 (Flipping Pages) | nftables | 7.8 | UAF in netfilter, decade-old bug, actively exploited by ransomware groups | Heap exploitation in kernel, msg_msg spray, cred overwrite | | CVE-2025-21756 (Attack of the Vsock) | vsock subsystem | 8.8 | VM escape via vsock transport reassignment, refcount UAF | VM escape challenges, container breakout | | CVE-2021-22555 | netfilter | 7.8 | Privilege escalation, added to CISA KEV Oct 2025 (4 years post-disclosure) | Classic kernel pwn, still used in CTF | | OverlayFS bugs | OverlayFS | varies | Container escape via OverlayFS | Container escape CTF category | | pipebuffer UAF | pipe subsystem | varies | UAF in pipe_buffer structure | Modern kernel heap exploitation |
nf_tables UAF Exploitation (CVE-2024-1086 "Flipping Pages")
The most significant kernel exploit of 2025 — a decade-old UAF in nf_tables:
- Trigger: Double-free via
NFT_MSG_NEWRULE+NFT_MSG_DELRULErace - Heap spray: Use
msg_msgstructure for controlled heap allocation at freed object's slab - Arbitrary read/write: Overwrite
msg_msg.m_tsfor OOB read,msg_msg.nextfor arbitrary read - Privilege escalation: Overwrite
task_struct.credor usesetresuidROP - KASLR bypass:
single_startleak via/proc/self/statormsg_msgpartial overwrite
vsock VM Escape (CVE-2025-21756)
For CTF challenges involving VM escape:
- vsock transport reassignment causes refcount corruption
- UAF on
vsock_sockstructure after transport module unload - Kernel memory corruption → arbitrary code execution with root privileges
- PoC published alongside disclosure
QEMU Updates (2025-2026)
- QEMU 9.1+ fixed the ring3→ring0
iretqprivilege escalation bug (Ubuntu backported fix to 6.2.0) - QEMU TCG mode still used in CTFs without KVM — check version before attempting iretq exploits
- Apple Silicon + Docker + QEMU: Still broken (Rosetta doesn't support full x8664 syscall table). Use remote testing or cloud x8664 VM.
- New:
--cpu kvm64includes PCID → KPTI is ON by default. Onlypti=off/nopticmdline disables it.
Modern Kernel Hardening (2025-2026)
| Mitigation | Status | Bypass | |---|---|---| | KPTI | Default on (x8664, QEMU kvm64) | Page table switch during syscall; use entry Sears trampoline or fw_cfg DMA | | KASLR | Default on | EntryBleed leak (CVE-2022-4543), /proc/kallsyms before kptr_restrict | | IBT (Indirect Branch Tracking) | CONFIGX86KERNELIBT=y | ENDBR64 function boundary scan; only bti c/endbr64 are valid indirect branch targets | | CFI (Control Flow Integrity) | Growing adoption | COOP (Counterfeit Object-Oriented Programming) — use existing virtual function chains | | MTE (Memory Tagging) | AArch64 only | Tag mismatch detection; spray matching tags or find tag corruption primitive | | Sealed libc | glibc 2.38+ | mprotect all libc pages RO after init — probe writability first | | LKRG (Runtime Guard) | RLC-Hardened only | Behavior-based detection; detect cred modification or privilege escalation |
Practical Kernel CTF Resources (2025-2026)
- linux-kernel-exploitation GitHub:
github.com/xairy/linux-kernel-exploitation— comprehensive link collection (6.5k stars) - kernelCTF VRP (Google): Continuous kernel exploitation challenge with experimental mitigations
- RVAsec 2025: "Linux Kernel Exploitation for Beginners" — training using kernel CTFs as entry point
- Reddit r/ExploitDev: Community kernel exploitation CTF labs
- Google kernelCTF: First blood on experimental mitigation instance — single byte OOB → privilege escalation
modprobepath AFALG Bypass (2025 — searchbinaryhandler Patch)
As of Linux v6.14-rc1 (patch fa1bdca98d74), the search_binary_handler() flow that called request_module() has been completely removed. Executing a dummy file with unknown magic bytes (e.g., \xff\xff\xff\xff) no longer triggers modprobe_path on upstream kernels. The old technique is dead on mainline.
New trigger: AF_ALG socket bind()
The AF_ALG socket subsystem calls request_module("algif-%s", type) in alg_bind() when sa->salg_type doesn't match any known crypto type. This reaches call_modprobe() without needing a dummy file.
// Trigger — no capabilities required, no file needed
int sock = socket(AF_ALG, SOCK_SEQPACKET, 0);
struct sockaddr_alg sa = {0};
strcpy((char*)sa.salg_type, "V4bel"); // dummy string
bind(sock, (struct sockaddr*)&sa, sizeof(sa));
Fileless
…
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: Wang200935
- Source: Wang200935/security-agent-skills
- License: MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet, be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.