rename: IAMROOT → SKELETONKEY across the entire project
release / build (arm64) (push) Waiting to run
release / build (x86_64) (push) Waiting to run
release / release (push) Blocked by required conditions

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.
This commit is contained in:
2026-05-16 22:43:49 -04:00
parent 9d88b475c1
commit 9593d90385
109 changed files with 1711 additions and 1701 deletions
+11 -11
View File
@@ -14,7 +14,7 @@ modules/<module_name>/
├── 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/<module_name>/
└── 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 <name>`, `--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/<module_name>`
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
+34 -34
View File
@@ -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.
+53 -53
View File
@@ -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 <module>
sudo skeletonkey --mitigate <module>
# Stage 3: monitor — auditd rules already deployed
sudo ausearch -k 'iamroot-*' -ts today | grep <module>
sudo ausearch -k 'skeletonkey-*' -ts today | grep <module>
# 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
+13 -13
View File
@@ -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."
+27 -27
View File
@@ -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-<pid>.sh")
→ execve("/tmp/iamroot-trig-<pid>") # unknown-format binary
attacker → arb_write(modprobe_path, "/tmp/skeletonkey-mp-<pid>.sh")
→ execve("/tmp/skeletonkey-trig-<pid>") # unknown-format binary
→ kernel call_modprobe() # spawns modprobe_path as init
→ /tmp/iamroot-mp-<pid>.sh runs as root
→ cp /bin/bash /tmp/iamroot-pwn-<pid>; chmod 4755 /tmp/iamroot-pwn-<pid>
→ caller exec /tmp/iamroot-pwn-<pid> -p
→ /tmp/skeletonkey-mp-<pid>.sh runs as root
→ cp /bin/bash /tmp/skeletonkey-pwn-<pid>; chmod 4755 /tmp/skeletonkey-pwn-<pid>
→ caller exec /tmp/skeletonkey-pwn-<pid> -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-<pid>` after `modprobe` runs our
The shared finisher (`skeletonkey_finisher_modprobe_path()`) drops a
sentinel file at `/tmp/skeletonkey-pwn-<pid>` 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