leviathan 92396a0d6d tests: fix 2 test rows with wrong expected verdicts (v0.9.0 regression)
The build workflow (sanitizer job) has been red since v0.9.0 because two
test rows asserted verdicts that don't match what detect() actually
returns:

- udisks_libblockdev: I expected PRECOND_FAIL (udisksd absent in CI), got
  VULNERABLE. GHA ubuntu-24.04 runners ship udisks2 by default; detect()
  does direct path_exists() stat() calls (not host-fixture lookups) so
  it sees the binary and gates pass. Rewritten as 'udisksd present → VULNERABLE'.

- sudo_runas_neg1: I expected PRECOND_FAIL (no (ALL,!root) grant), got OK.
  detect() treats 'no grant' as 'not exploitable from this user' → OK, not
  'missing precondition' → PRECOND_FAIL. Updated expectation.

The release workflow doesn't run the sanitizer job and has been passing
through these failures; the build workflow caught them. Both expectations
are now honest about what detect() does on CI.
2026-05-23 23:38:55 -04:00

SKELETONKEY

Latest release License: MIT Modules Platform: Linux

One curated binary. 39 Linux LPE modules covering 34 CVEs from 2016 → 2026. Every year 2016 → 2026 covered. 27 confirmed end-to-end against real Linux VMs via tools/verify-vm/. Detection rules in the box. One command picks the safest one and runs it.

curl -sSL https://github.com/KaraZajac/SKELETONKEY/releases/latest/download/install.sh | sh \
  && skeletonkey --auto --i-know

⚠️ Authorized testing only. SKELETONKEY runs real exploits. By using it you assert you have explicit authorization to test the target system. See docs/ETHICS.md.

Why use this

Most Linux privesc tooling is broken in one of three ways:

  • linux-exploit-suggester / linpeas — tell you what might work, run nothing
  • auto-root-exploit / kernelpop — bundle exploits but ship no detection signatures and went stale years ago
  • Per-CVE PoC repos — one author, one distro, abandoned within months

SKELETONKEY is one binary, actively maintained, with detection rules for every CVE in the bundle — same project for red and blue teams.

Who it's for

Audience What you get
Red team / pentesters One tested binary. --auto ranks vulnerable modules by safety and runs the safest. Honest scope reporting — never claims root it didn't actually get.
Sysadmins skeletonkey --scan (no sudo needed) tells you which boxes still need patching. Fleet-scan tool included. JSON output for CI gates (schema).
Blue team / SOC Auditd + sigma + yara + falco rules for every CVE. --detect-rules --format=auditd | sudo tee … ships SIEM coverage in one command.
CTF / training Reproducible LPE environment with public CVEs across a 10-year timeline. Each module documents the bug, the trigger, and the fix.

Corpus at a glance

39 modules covering 34 distinct CVEs across the 2016 → 2026 LPE timeline. 27 of the 34 CVEs have been empirically verified in real Linux VMs via tools/verify-vm/; the 7 still-pending entries are blocked by their target environment (legacy hypervisor, EOL kernel, or not-yet-shipped Linux 7.0), not by missing code.

Tier Count What it means
🟢 Full chain 14 Lands root (or its canonical capability) end-to-end. No per-kernel offsets needed.
🟡 Primitive 14 Fires the kernel primitive + grooms the slab + records a witness. Default returns EXPLOIT_FAIL honestly. Pass --full-chain to engage the shared modprobe_path finisher (needs offsets — see docs/OFFSETS.md).

🟢 Modules that land root on a vulnerable host: copy_fail family ×5 · dirty_pipe · dirty_cow · pwnkit · overlayfs (CVE-2021-3493) · overlayfs_setuid (CVE-2023-0386) · cgroup_release_agent · ptrace_traceme · sudoedit_editor · entrybleed (KASLR leak primitive)

🟡 Modules with opt-in --full-chain: af_packet · af_packet2 · af_unix_gc · cls_route4 · fuse_legacy · nf_tables · nft_set_uaf · nft_fwd_dup · nft_payload · netfilter_xtcompat · stackrot · sudo_samedit · sequoia · vmwgfx

Empirical verification (27 of 34 CVEs)

Records in docs/VERIFICATIONS.jsonl prove each verdict against a known-target VM. Coverage:

Distro / kernel Modules verified
Ubuntu 18.04 (4.15.0, sudo 1.8.21p2) af_packet · ptrace_traceme · sudo_samedit · sudo_runas_neg1
Ubuntu 20.04 (5.4.0-26 pinned + 5.15 HWE) af_packet2 · cls_route4 · nft_payload · overlayfs · pwnkit · sequoia · tioscpgrp
Ubuntu 22.04 (5.15 stock + mainline 5.15.5 / 6.1.10) af_unix_gc · dirty_pipe · entrybleed · nf_tables · nft_set_uaf · nft_pipapo · overlayfs_setuid · stackrot · sudoedit_editor · sudo_chwoot
Debian 11 (5.10 stock) cgroup_release_agent · fuse_legacy · netfilter_xtcompat · nft_fwd_dup
Debian 12 (6.1 stock + udisks2 / polkit allow rule) pack2theroot · udisks_libblockdev

Not yet verified (7): vmwgfx (VMware-guest-only — no public Vagrant box), dirty_cow (needs ≤ 4.4 kernel — older than every supported box), mutagen_astronomy (mainline 4.14.70 kernel-panics on Ubuntu 18.04 rootfs — needs CentOS 6 / Debian 7), pintheft & vsock_uaf (kernel modules not loaded on common Vagrant boxes), dirtydecrypt & fragnesia (need Linux 7.0 — not shipping as any distro kernel yet). All seven are flagged in tools/verify-vm/targets.yaml with rationale.

See CVES.md for per-module CVE, kernel range, and detection status. Run skeletonkey --module-info <name> for the embedded verification records per module.

Quickstart

# Install (x86_64 / arm64; checksum-verified)
curl -sSL https://github.com/KaraZajac/SKELETONKEY/releases/latest/download/install.sh | sh

# What's this box vulnerable to?  (no sudo)
skeletonkey --scan

# One-page operator briefing for a single CVE: CWE / MITRE ATT&CK /
# CISA KEV status, live detect() trace, OPSEC footprint, detection
# coverage. Useful for triage tickets and SOC analyst handoffs.
skeletonkey --explain nf_tables

# Pick the safest LPE and run it
skeletonkey --auto --i-know

# Deploy detection rules (needs sudo to write into /etc/audit/rules.d/)
skeletonkey --detect-rules --format=auditd \
  | sudo tee /etc/audit/rules.d/99-skeletonkey.rules

# Fleet scan — many hosts via SSH, aggregated JSON for SIEM
./tools/skeletonkey-fleet-scan.sh --binary skeletonkey \
  --ssh-key ~/.ssh/id_rsa hosts.txt

SKELETONKEY runs as a normal unprivileged user — that's the point. --scan, --audit, --exploit, and --detect-rules all work without sudo. Only --mitigate and rule-file installation write root-owned paths.

Example: unprivileged → root

$ id
uid=1000(kara) gid=1000(kara) groups=1000(kara)

$ skeletonkey --auto --i-know
[*] auto: host=demo distro=ubuntu/24.04 kernel=5.15.0-56-generic arch=x86_64
[*] auto: active probes enabled — brief /tmp file touches and fork-isolated namespace probes
[*] auto: scanning 31 modules for vulnerabilities...
[+] auto: dirty_pipe             VULNERABLE (safety rank 90)
[+] auto: cgroup_release_agent   VULNERABLE (safety rank 98)
[+] auto: pwnkit                 VULNERABLE (safety rank 100)
[ ] auto: copy_fail              patched or not applicable
[ ] auto: nf_tables              precondition not met
...

[*] auto: scan summary — 3 vulnerable, 21 patched/n.a., 7 precondition-fail, 0 indeterminate
[*] auto: 3 vulnerable modules found. Safest is 'pwnkit' (rank 100).
[*] auto: launching --exploit pwnkit...

[+] pwnkit: writing gconv-modules cache + payload.so...
[+] pwnkit: execve(pkexec) with NULL argv + crafted envp...
# id
uid=0(root) gid=0(root) groups=0(root)

The safety ranking goes: structural escapes (no kernel state touched) → page-cache writesuserspace cred-raceskernel primitiveskernel races (least predictable). The goal is to never crash a production box looking for root.

How it works

Each CVE (or tightly-related family) is a module under modules/. Modules export a standard interface (detect / exploit / mitigate / cleanup) plus metadata (kernel range, detection rule text). The top-level binary dispatches per command:

  • --scan walks every module's detect() against the running host
  • --exploit <name> --i-know runs the named module's exploit (the --i-know flag is the authorization gate)
  • --auto --i-know does the scan, ranks by safety, runs the safest
  • --detect-rules --format=<auditd|sigma|yara|falco> emits the embedded rule corpus
  • --mitigate <name> / --cleanup <name> apply / undo temporary mitigations (module-dependent — most kernel modules say "upgrade")
  • --dump-offsets reads /proc/kallsyms + /boot/System.map and emits a ready-to-paste C entry for the --full-chain offset table

See docs/ARCHITECTURE.md for the module-loader design.

The verified-vs-claimed bar

Most public PoC repos hardcode offsets for one kernel build and silently break elsewhere. SKELETONKEY refuses to ship fabricated offsets. The shared --full-chain finisher only returns EXPLOIT_OK after a setuid bash sentinel file actually appears; otherwise modules return EXPLOIT_FAIL with a diagnostic. Operators populate the offset table once per target kernel via skeletonkey --dump-offsets and either set env vars or upstream the entry via PR (CONTRIBUTING.md).

Build from source

git clone https://github.com/KaraZajac/SKELETONKEY.git
cd SKELETONKEY
make
./skeletonkey --version

Builds clean with gcc or clang on any modern Linux. macOS dev builds also compile (modules with Linux-only headers stub out gracefully).

Status

v0.9.0 cut 2026-05-24. 39 modules across 34 CVEs — every year 2016 → 2026 now covered. v0.9.0 adds 5 gap-fillers: mutagen_astronomy (CVE-2018-14634 — closes 2018), sudo_runas_neg1 (CVE-2019-14287), tioscpgrp (CVE-2020-29661), vsock_uaf (CVE-2024-50264 — Pwnie 2025 winner), nft_pipapo (CVE-2024-26581 — Notselwyn II). v0.8.0 added 3 (sudo_chwoot/CVE-2025-32463, udisks_libblockdev/CVE-2025-6019, pintheft/CVE-2026-43494). 27 empirically verified against real Linux VMs (Ubuntu 18.04 / 20.04 / 22.04 + Debian 11 / 12 + mainline kernels 5.15.5 / 6.1.10 from kernel.ubuntu.com). 88-test unit harness + ASan/UBSan + clang-tidy on every push. 4 prebuilt binaries (x86_64 + arm64, each in dynamic + static-musl flavors).

Reliability + accuracy work in v0.7.x:

  • Shared host fingerprint (core/host.{h,c}) populated once at startup — kernel/distro/userns gates/sudo+polkit versions — exposed to every module via ctx->host.
  • Test harness (tests/, make test) — 88 tests: 33 kernel_range unit tests + 55 detect() integration tests over mocked host fingerprints. Runs in CI on every push.
  • VM verifier (tools/verify-vm/) — Vagrant + Parallels scaffold that boots known-vulnerable kernels (stock distro + mainline via kernel.ubuntu.com), runs --explain --active per module, records match/MISMATCH/PRECOND_FAIL as JSON. 27 modules confirmed end-to-end.
  • --explain <module> — single-page operator briefing: CVE / CWE / MITRE ATT&CK / CISA KEV status, host fingerprint, live detect() trace, OPSEC footprint, detection-rule coverage, verified-on records. Paste-into-ticket ready.
  • CVE metadata pipeline (tools/refresh-cve-metadata.py) — fetches CISA KEV catalog + NVD CWE; 10 of 26 modules cover KEV-listed CVEs.
  • 119 detection rules across auditd / sigma / yara / falco; one command exports the corpus to your SIEM.
  • --auto upgrades: per-detect 15s timeout, fork-isolated detect + exploit, structured verdict table, scan summary, --dry-run.

Not yet verified (4 of 26 CVEs): vmwgfx (VMware-guest only), dirty_cow (needs ≤ 4.4 kernel), dirtydecrypt + fragnesia (need Linux 7.0 — not shipping yet). Rationale in tools/verify-vm/targets.yaml.

See ROADMAP.md for the next planned modules and infrastructure work.

Contributing

PRs welcome for: kernel offsets (run --dump-offsets on a target kernel, paste into core/offsets.c), new modules, detection rules, and CVE-status corrections. See CONTRIBUTING.md.

Keeping kernel_range tables current. tools/refresh-kernel-ranges.py polls Debian's security tracker and reports drift between each module's hardcoded kernel_patched_from thresholds and the fixed-versions Debian actually ships. Run periodically (or in CI) to catch new backports that need to land in the corpus:

tools/refresh-kernel-ranges.py            # human report
tools/refresh-kernel-ranges.py --json     # machine-readable
tools/refresh-kernel-ranges.py --patch    # proposed C-source edits

Acknowledgments

Each module credits the original CVE reporter and PoC author in its NOTICE.md. SKELETONKEY is the bundling and bookkeeping layer; the research credit belongs to the people who found the bugs.

License

MIT — see LICENSE.

S
Description
Mirror from github.com/KaraZajac/SKELETONKEY
Readme 2.8 MiB
Languages
C 93.8%
Shell 2.9%
Python 2.1%
Makefile 1%
Assembly 0.2%