
Copy Fail - CVE-2026-31431 - Hardened C implementation for redteam and authorized penetration testing operations. ⚠️ Legal Notice: This tool is intended solely for authorized security research, authorized penetration testing, and defensive analysis. Use on systems you own or have explicit written permission to test. Unauthorized access to computer systems is illegal.
Hardened C implementation for redteam and authorized penetration testing operations.
⚠️ Legal Notice: This tool is intended solely for authorized security research, authorized penetration testing, and defensive analysis. Use on systems you own or have explicit written permission to test. Unauthorized access to computer systems is illegal.
🚀 Primary development moved to GitLab.
This repository is actively maintained here. For issues and contributions, please use GitLab.
This project is a hardened, production-ready C port of the CVE-2026-31431 ("Copy Fail") local privilege escalation exploit, originally disclosed by Theori and Xint on April 29, 2026.
The original proof-of-concept was written in Python and designed for research demonstration. This implementation transforms it into a redteam-grade toolkit with:
| Attribute | Value |
|---|---|
| Disclosure Date | April 29, 2026 |
| Researchers | Theori (Jinoh Kang, Yonghwi Jin, Seunghyun Lee), Xint |
| Writeup | https://copy.fail/ |
| Original PoC | theori-io/copy-fail-CVE-2026-31431 (Python) |
| C Port (Baseline) | tgies/copy-fail-c by Tony Gies |
| Affected Kernels | Linux 4.14 -- 6.14 (before fix commit a664bf3d603d) |
| Severity | CVSS 7.8 (High) -- Local Privilege Escalation |
The vulnerability resides in the Linux kernel's AF_ALG crypto subsystem. The authencesn AEAD template implements an in-place optimization for decryption: when ciphertext is supplied via splice() from a file's page cache, the kernel reuses the same page as both source and destination.
The attack flow:
/usr/bin/su) read-onlyauthencesn(hmac(sha256),cbc(aes)) AEAD operation via AF_ALGsplice() from the target file's page cacheexecve() the target -- the kernel loads mutated pages from cache, grants setuid-root credsKey insight: The on-disk inode is never modified. Only the in-memory page cache is mutated, making forensic detection significantly harder than traditional file overwrite exploits.
This project extends the original research with redteam-oriented hardening across nine modules.
patch_chunk.c)| Feature | Original | Our Implementation |
|---|---|---|
| Socket management | Fresh socket per chunk | Socket reuse (~60% fewer syscalls) |
| Verification | None | mmap + memcmp with auto-retry (3 attempts) |
| Parallel writes | Sequential only | Fork-based parallel (up to 16 procs) |
| Error codes | Binary success/fail | Granular: 0=verified, 1=patched kernel, -1=fatal |
| Heap allocations | malloc in hot path | Stack-only (no alloc jitter) |
target_discovery.c)su, sudo, passwd, pkexec, mount, ping, etc./snap (non-traditional setuid)anti_forensics.c)| Technique | Purpose |
|---|---|
posix_fadvise(POSIX_FADV_DONTNEED) | Per-file page cache eviction |
echo 3 > /proc/sys/vm/drop_caches | Global cache drop (post-root) |
utimensat() timestomp | Restore original atime/mtime |
| Self-destruct | Overwrite dropper binary with zeros before exec |
| Memory wipe | volatile zeroing of sensitive buffers |
memfd_exec.c)kworker, anon_inode, eventfd (blends in /proc/$pid/fd/)stage1.c)| Channel | Stealth | Speed | Fallback Priority |
|---|---|---|---|
| Embedded | Maximum | Instant | Last (airgap) |
| HTTP direct | Medium | <1s | First |
| System curl/wget | Low | 1-3s | Second (HTTPS support) |
| DNS TXT | High | Slow | Third (firewall bypass) |
stage2_template.c)SIGUSR1 (trigger), SIGUSR2 (status), SIGTERM (shutdown)[kworker/N:0] in ps/topproc_hide.c)/proc/$pid/cmdlineSSH_*, AWS_*, TOKEN*, etc.setsid() + setpgid() for terminal independencesignal_trigger.c)| Mode | Behavior | Use Case |
|---|---|---|
trigger_oneshot() | Sleep → trigger → execute → exit | Hit-and-run |
trigger_daemon() | Sleep → trigger → execute → loop | Persistent implant |
trigger_auto() | Sleep with timeout fallback | Unattended ops |
Zero-CPU waiting: sigsuspend() instead of polling loops.
sleep_jitter.c)getrandom(2), /dev/urandom, rdtsc fallbacksleep_scheduled() maintains average interval despite jittercheck_sandbox_acceleration() detects patched sleep()