Documentation Index
Fetch the complete documentation index at: https://mintlify.com/V4bel/dirtyfrag/llms.txt
Use this file to discover all available pages before exploring further.
exp.c is a single self-contained C file that implements both the ESP and RxRPC exploit variants and chains them automatically. Compile it with gcc -O0 -Wall -o exp exp.c -lutil and run it as an unprivileged user. This page walks through the key mechanisms in the source so you can understand exactly what the binary does at each stage.
Entry point and dispatch
main() checks for --force-esp / --force-rxrpc flags, then dispatches to the appropriate variant(s). By default it runs su_lpe_main() (ESP), and if su_already_patched() is not true afterward, falls back to rxrpc_lpe_main() (RxRPC, retried up to three times):
exp.c
getuid() == 0) when invoked, main() immediately calls execlp("/bin/bash", "bash", NULL) — useful when testing from a pre-elevated shell. Once either target is patched, run_root_pty() spawns the interactive root PTY bridge.
su_lpe_main() (the ESP variant) forks a child that runs corrupt_su(), waits for it, then checks bytes at file offset ENTRY_OFFSET (0x78) of /usr/bin/su. If those bytes are 0x31 0xff (xor edi, edi), the ELF was successfully planted.
ESP variant: namespace and network setup
corrupt_su() calls setup_userns_netns() to gain CAP_NET_ADMIN inside a new user+network namespace. This is needed to register XFRM security associations via Netlink:
exp.c
unshare(CLONE_NEWUSER) returns -EPERM (e.g. Ubuntu with AppArmor restrictions), the child exits with status 2 and the parent falls back to RxRPC.
ESP variant: XFRM SA registration
add_xfrm_sa(spi, patch_seqhi) sends a XFRM_MSG_NEWSA Netlink message. The critical field is seq_hi inside the XFRMA_REPLAY_ESN_VAL attribute — this is the 4-byte value that will be STOREd into the page cache:
exp.c
corrupt_su() registers 48 SAs in a loop. Each SA carries 4 bytes of the 192-byte root-shell ELF in its seq_hi:
exp.c
authencesn(hmac(sha256),cbc(aes)) with UDP encapsulation on port 4500. The HMAC and cipher keys are arbitrary — authentication will fail, but the STORE happens before the failure is checked.
ESP variant: the splice trigger
do_one_write() triggers one 4-byte page cache STORE by routing a crafted ESP packet through loopback:
exp.c
splice_to_socket() sets MSG_SPLICE_PAGES automatically, so the page cache page of /usr/bin/su is planted directly as frags[0] of the sender skb — no copy is made. The receiving socket has UDP_ENCAP_ESPINUDP set, so the packet is routed through esp_input(), which performs the vulnerable in-place AEAD decrypt that STOREs 4 bytes into the page cache.
The root-shell ELF payload
shell_elf[192] is a minimal x86-64 ELF that fits in the first 192 bytes of /usr/bin/su. Its entry point at file offset 0x78 (vaddr 0x400078) executes:
exp.c
/usr/bin/su has the setuid-root bit set, execve("/usr/bin/su") elevates euid to 0 before the shellcode runs, so setuid(0) then fixes ruid as well.
RxRPC variant: user-space fcrypt brute-force
The RxRPC STORE value isfcrypt_decrypt(C, K) — the attacker controls K but not directly the plaintext. exp.c ports crypto/fcrypt.c from the kernel into user-space and uses a splitmix64 PRNG to search the 56-bit key space:
exp.c
- K_A:
P[0] == ':' && P[1] == ':'(probability ~1/65536, found in ~5 ms) - K_B:
P[0] == '0' && P[1] == ':'(same, ~5 ms) - K_C:
P[0]=='0' && P[1]==':' && P[7]==':' && P[2..6] ∉ {':','\0','\n'}(~1 s)
P_A at offsets 4–11, splice B sees P_A[2..7] as its first 6 bytes rather than the original file bytes. The exploit accounts for this:
exp.c
RxRPC variant: kernel trigger
do_one_trigger() performs the full handshake needed to get the kernel to run rxkad_verify_packet_1() on a splice-planted page:
Register RxRPC key
Build an RxRPC v1 token with the brute-forced key K in the
session_key slot and call add_key("rxrpc", desc, buf, n, KEY_SPEC_PROCESS_KEYRING). No privilege required.Set up sockets
Create an
AF_RXRPC client socket bound to the key with RXRPC_SECURITY_KEY and RXRPC_MIN_SECURITY_LEVEL = RXRPC_SECURITY_AUTH. Create a plain UDP socket on port port_S as a fake server.Initiate RPC call and receive CHALLENGE
sendmsg on the RxRPC client sends a CONNECT packet to the fake server. The fake server extracts (epoch, cid, callNumber) and sends back a forged CHALLENGE packet (type=6, nonce=0xDEADBEEF).Let the kernel handle RESPONSE
The RxRPC client kernel code automatically sends a RESPONSE containing K. The fake server drains and ignores it. The kernel now believes the connection uses pcbc(fcrypt) with key K.
Precompute wire checksum
Use
AF_ALG pcbc(fcrypt) to compute csum_iv and cksum in user-space — required to pass rxkad_verify_packet’s integrity check before reaching the in-place decrypt.Splice and trigger
vmsplice the forged DATA header into a pipe, splice 8 bytes of /etc/passwd at splice_off into the pipe, then splice the pipe to the UDP server socket. The page cache page is planted into the skb frag. recvmsg on the RxRPC client routes the packet to rxkad_verify_packet_1, which performs the 8-byte fcrypt_decrypt STORE directly onto the page cache page./etc/passwd reads root::0:0:GGGGGG:/root:/bin/bash. The exploit opens a PTY and execlp("su", "su", NULL) — PAM’s pam_unix.so nullok accepts the empty password and drops into a root shell.
Set
DIRTYFRAG_VERBOSE=1 to see detailed progress output from both exploit stages, including per-SA registration status and per-trigger results.