leviathan 2c131df1bf verify-vm sweep complete: 18 modules confirmed across 5 Linux distros
Full sweep results:

  MATCHES (18 — empirically confirmed in real Linux VMs):
    pwnkit               ubuntu2004  5.4.0-169  VULNERABLE
    cgroup_release_agent debian11    5.10.0-27  VULNERABLE
    netfilter_xtcompat   debian11    5.10.0-27  VULNERABLE
    fuse_legacy          debian11    5.10.0-27  VULNERABLE
    nft_fwd_dup          debian11    5.10.0-27  VULNERABLE
    entrybleed           ubuntu2204  5.15.0-91  VULNERABLE
    overlayfs            ubuntu2004  5.4.0-169  VULNERABLE
    overlayfs_setuid     ubuntu2204  5.15.0-91  VULNERABLE
    sudoedit_editor      ubuntu2204  5.15.0-91  PRECOND_FAIL  (no sudoers grant)
    ptrace_traceme       ubuntu1804  4.15.0-213 VULNERABLE
    sudo_samedit         ubuntu1804  4.15.0-213 VULNERABLE
    af_packet            ubuntu1804  4.15.0-213 OK            (4.15 is post-fix)
    pack2theroot         debian12    6.1.0-17   PRECOND_FAIL  (no PackageKit installed)
    cls_route4           ubuntu2004  5.15.0-43  VULNERABLE
    nft_payload          ubuntu2004  5.15.0-43  VULNERABLE
    af_packet2           ubuntu2004  5.4.0-26   VULNERABLE
    sequoia              ubuntu2004  5.4.0-26   VULNERABLE
    dirty_pipe           ubuntu2204  5.15.0-91  OK            (silently backported)

  PIN_FAIL (4 — targeted HWE kernels no longer in apt; needs
  kernel.ubuntu.com mainline integration, deferred):
    nf_tables            wanted ubuntu2204 + 5.15.0-43-generic
    af_unix_gc           wanted ubuntu2204 + 5.15.0-43-generic
    stackrot             wanted ubuntu2204 + 6.1.0-13-generic
    nft_set_uaf          wanted ubuntu2204 + 5.19.0-32-generic

  MANUAL / SPECIAL TARGETS (5 — flagged in targets.yaml):
    vmwgfx               — VMware-guest only; no Vagrant box covers it
    dirtydecrypt         — needs Linux 7.0 (not shipping yet)
    fragnesia            — needs Linux 7.0 (not shipping yet)
    dirty_cow            — needs <= 4.4 (older than every supported Vagrant box)
    copy_fail family     — multi-module family verification deferred

Several findings the active-probe path surfaced vs version-only checks:

  - dirty_pipe (ubuntu2204): version-only check would say VULNERABLE
    (kernel 5.15.0 < 5.15.25 backport in our table), but Ubuntu has
    silently backported the fix into the -91 patch level. --active
    probe correctly identified the primitive as blocked → OK.

  - af_packet (ubuntu1804): the bug was fixed in 4.10.6 mainline +
    4.9.18 backport. Ubuntu 18.04's stock 4.15.0 is post-fix — detect()
    correctly returns OK. The targets.yaml entry was originally wrong;
    fixed now.

  - sudoedit_editor: version-wise the host is vulnerable (sudo 1.9.9),
    but the bug requires an actual sudoedit grant in /etc/sudoers — and
    the default Vagrant user has none. detect() correctly returns
    PRECOND_FAIL ('vuln version present, no grant to abuse'). Same as
    one of our unit tests.

  - pack2theroot: needs an active PackageKit daemon on the system bus.
    Debian 12's generic cloud image is server-oriented and omits
    PackageKit. detect() correctly returns PRECOND_FAIL. Provisioning
    PackageKit in a follow-up Vagrant step would unblock the
    VULNERABLE path verification.

Plumbing fixes that landed in the sweep:

  - core/nft_compat.h — NFTA_CHAIN_FLAGS (kernel 5.7) + NFTA_CHAIN_ID
    (5.13). Without these, nft_fwd_dup fails to compile against
    Ubuntu 18.04's 4.15-era nf_tables uapi, which blocked the entire
    skeletonkey binary from building on that box and prevented
    verification of ptrace_traceme / sudo_samedit / af_packet.

  - tools/verify-vm/Vagrantfile — 'privileged: false' on the
    build-and-verify provisioner. Vagrant's default runs as root;
    pack2theroot's detect() short-circuits with 'already root —
    nothing to do' when running as uid 0, which would invalidate
    every euid-aware module's verification.

  - tools/verify-vm/targets.yaml — corrected expectations for af_packet
    (stock 18.04 4.15 is post-fix), pack2theroot (no PackageKit on
    server cloud image), sudoedit_editor (no sudoers grant), and
    dirty_pipe (silent Ubuntu backport).

  - tools/refresh-verifications.py — dedup key changed from
    (module, vm_box, host_kernel, expect_detect) to
    (module, vm_box, host_kernel). When an expectation is corrected
    mid-sweep, the new record cleanly supersedes the old one instead
    of accumulating.

The verifier loop is now production-ready and the trust signal in
--list / --module-info / --explain reflects 18 modules confirmed
against real Linux. Next-step bucket:
  - kernel.ubuntu.com mainline integration → unblock 4 PIN_FAIL pins.
  - Optional PackageKit provisioner on debian12 → unblock pack2theroot
    VULNERABLE path.
2026-05-23 16:29:50 -04:00

SKELETONKEY

Latest release License: MIT Modules Platform: Linux

One curated binary. 28 verified Linux LPE exploits, 2016 → 2026 (+3 ported-but-unverified). 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

28 verified modules spanning the 2016 → 2026 LPE timeline, plus 3 ported-but-unverified modules (dirtydecrypt, fragnesia, pack2theroot — see note below):

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).
Ported, unverified 3 dirtydecrypt, fragnesia, pack2theroot. Built and registered with version-pinned detect() (Linux 7.0 / 7.0.9 / PackageKit 1.3.5 respectively), but the exploit bodies are not yet validated end-to-end. --auto auto-enables --active to confirm empirically on top of the version verdict. Excluded from the 28-module verified counts above.

🟢 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

Ported-but-unverified (not in the counts above): dirtydecrypt (CVE-2026-31635) · fragnesia (CVE-2026-46300) · pack2theroot (CVE-2026-41651) — ported from public PoCs, exploit bodies not yet VM-validated. All three have version-pinned detect(): dirtydecrypt against mainline fix commit a2567217 in Linux 7.0; fragnesia against mainline 7.0.9 (older Debian-stable branches still unfixed); pack2theroot against PackageKit fix release 1.3.5 (commit 76cfb675), version read from the daemon over D-Bus. --auto auto-enables --active to confirm empirically on top.

See CVES.md for per-module CVE, kernel range, and detection status.

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.6.0 cut 2026-05-23. 28 verified modules, plus 3 ported-but-unverified (dirtydecrypt, fragnesia, pack2theroot). All 31 build clean on Debian 13 (kernel 6.12) and refuse cleanly on patched hosts.

Reliability + accuracy work in v0.6.0:

  • 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. 26 of 27 distinct modules consume it.
  • Test harness (tests/test_detect.c, make test) — 44 unit tests over mocked host fingerprints; runs as a non-root user in CI.
  • --auto upgrades: auto-enables --active, per-detect 15s timeout, fork-isolated detect + exploit so a crashing module can't tear down the dispatcher, structured per-module verdict table, scan summary.
  • --dry-run flag (preview without firing; no --i-know needed).
  • Pinned mainline fix commits for the 3 ported modules — detect() is version-pinned, not just precondition-only.

Empirical end-to-end validation on a vulnerable-target VM matrix is the next roadmap item; until then, the corpus is best understood as "compiles + detects + structurally correct + honest on failure" — and the three ported modules have not been run against a vulnerable target at all.

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%