release v0.7.1: arm64-static binary + per-module arch_support
Two additions on top of v0.7.0:
1. skeletonkey-arm64-static is now published alongside the existing
x86_64-static binary. Built native-arm64 in Alpine via GitHub's
ubuntu-24.04-arm runner pool (free for public repos as of 2024).
install.sh auto-picks it based on 'uname -m'; SKELETONKEY_DYNAMIC=1
fetches the dynamic build instead. Works on Raspberry Pi 4+, Apple
Silicon Linux VMs, AWS Graviton, Oracle Ampere, Hetzner ARM, etc.
.github/workflows/release.yml refactor: the previous single
build-static-x86_64 job becomes a build-static matrix with two
entries (x86_64-static on ubuntu-latest, arm64-static on
ubuntu-24.04-arm). Both share the same Alpine container + build
recipe.
2. .arch_support field on struct skeletonkey_module — honest per-module
labeling of which architectures the exploit() body has been verified
on. Three categories:
'any' (4 modules): pwnkit, sudo_samedit, sudoedit_editor,
pack2theroot. Purely userspace; arch-independent.
'x86_64' (1 module): entrybleed. KPTI prefetchnta side-channel;
x86-only by physics. Already source-gated (returns
PRECOND_FAIL on non-x86_64).
'x86_64+unverified-arm64' (26 modules): kernel exploitation
code. The bug class is generic but the exploit primitives
(msg_msg sprays, finisher chain, struct offsets) haven't been
confirmed on arm64. detect() still works (just reads ctx->host);
only the --exploit path is in question.
--list now has an ARCH column (any / x64 / x64?) and the footer
prints 'N arch-independent (any)'.
--module-info prints 'arch support: <value>'.
--scan --json adds 'arch_support' to each module record.
This is the honest 'arm64 works for detection on every module +
exploitation on 4 of them today; the rest await empirical arm64
sweep' framing — not pretending the kernel exploits already work
there, but not blocking the arm64 binary on that either. arm64
users get the full triage workflow + a handful of userspace exploits
out of the box, plus a clear roadmap for the rest.
Future work to promote modules from 'x86_64+unverified-arm64' to
'any': add an arm64 Vagrant box (generic/debian12-arm64 etc.) to
tools/verify-vm/ and run a verification sweep on Apple Silicon /
ARM Linux hardware.
This commit is contained in:
@@ -59,14 +59,28 @@ jobs:
|
||||
skeletonkey-${{ matrix.target }}
|
||||
skeletonkey-${{ matrix.target }}.sha256
|
||||
|
||||
# Portable static-musl build for x86_64. Runs in Alpine (native
|
||||
# musl + linux-headers) so the resulting binary works on every
|
||||
# libc — glibc 2.x of any version, musl, etc. This is what
|
||||
# install.sh fetches by default (the dynamic binary above hits a
|
||||
# glibc-version ceiling on older distros like Debian 12 / RHEL 8).
|
||||
build-static-x86_64:
|
||||
runs-on: ubuntu-latest
|
||||
name: build (x86_64-static / musl)
|
||||
# Portable static-musl builds. Run in Alpine (native musl +
|
||||
# linux-headers) so the resulting binary works on every libc —
|
||||
# glibc 2.x of any version, musl, etc. This is what install.sh
|
||||
# fetches by default (the dynamic binary above hits a glibc-
|
||||
# version ceiling on older distros like Debian 12 / RHEL 8).
|
||||
#
|
||||
# x86_64-static runs on the regular x86_64 runner pool.
|
||||
# arm64-static runs on GitHub's native ARM Linux runners
|
||||
# (free for public repos as of 2024). Both produce statically-
|
||||
# linked binaries that just need an executable Linux kernel of
|
||||
# the right ABI.
|
||||
build-static:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- target: x86_64-static
|
||||
runner: ubuntu-latest
|
||||
- target: arm64-static
|
||||
runner: ubuntu-24.04-arm
|
||||
runs-on: ${{ matrix.runner }}
|
||||
name: build (${{ matrix.target }} / musl)
|
||||
container:
|
||||
image: alpine:latest
|
||||
steps:
|
||||
@@ -88,18 +102,18 @@ jobs:
|
||||
|
||||
- name: rename + checksum
|
||||
run: |
|
||||
mv skeletonkey skeletonkey-x86_64-static
|
||||
sha256sum skeletonkey-x86_64-static > skeletonkey-x86_64-static.sha256
|
||||
mv skeletonkey skeletonkey-${{ matrix.target }}
|
||||
sha256sum skeletonkey-${{ matrix.target }} > skeletonkey-${{ matrix.target }}.sha256
|
||||
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: skeletonkey-x86_64-static
|
||||
name: skeletonkey-${{ matrix.target }}
|
||||
path: |
|
||||
skeletonkey-x86_64-static
|
||||
skeletonkey-x86_64-static.sha256
|
||||
skeletonkey-${{ matrix.target }}
|
||||
skeletonkey-${{ matrix.target }}.sha256
|
||||
|
||||
release:
|
||||
needs: [build, build-static-x86_64]
|
||||
needs: [build, build-static]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
@@ -154,5 +168,7 @@ jobs:
|
||||
skeletonkey-x86_64-static.sha256
|
||||
skeletonkey-arm64
|
||||
skeletonkey-arm64.sha256
|
||||
skeletonkey-arm64-static
|
||||
skeletonkey-arm64-static.sha256
|
||||
install.sh
|
||||
fail_on_unmatched_files: false # install.sh may not exist at first tag
|
||||
|
||||
@@ -119,6 +119,31 @@ struct skeletonkey_module {
|
||||
* core/cve_metadata.{h,c} — looked up by CVE id, refreshed via
|
||||
* tools/refresh-cve-metadata.py. */
|
||||
const char *opsec_notes;
|
||||
|
||||
/* Architecture support for the exploit() body. detect() works on
|
||||
* any Linux arch (it just consults ctx->host); the question this
|
||||
* field answers is: if this module says VULNERABLE, will the
|
||||
* --exploit path actually fire on aarch64 / arm64? Values:
|
||||
*
|
||||
* "any" — userspace bug or arch-agnostic kernel
|
||||
* primitive (pwnkit, sudo*, pack2theroot,
|
||||
* dirty_pipe, dirty_cow, most netfilter/fs
|
||||
* bugs that use msg_msg sprays + structural
|
||||
* escapes).
|
||||
* "x86_64" — strictly x86-only (entrybleed needs
|
||||
* prefetchnta + KPTI, which doesn't apply
|
||||
* to ARM's TTBR_EL0/EL1 model).
|
||||
* "x86_64+unverified-arm64" — exploit body likely works on
|
||||
* arm64 but hasn't been verified on a real
|
||||
* arm64 host yet (e.g. copy_fail_family
|
||||
* assumes some x86_64 struct offsets;
|
||||
* --full-chain finisher uses x86_64-style
|
||||
* kernel ROP gadgets).
|
||||
*
|
||||
* NULL = unmapped (treat as "x86_64+unverified-arm64" by default;
|
||||
* a future arm64-on-Vagrant sweep will fill these in). Surfaced
|
||||
* in --list (ARCH column) and --module-info. */
|
||||
const char *arch_support;
|
||||
};
|
||||
|
||||
#endif /* SKELETONKEY_MODULE_H */
|
||||
|
||||
+40
-1
@@ -1,4 +1,43 @@
|
||||
## SKELETONKEY v0.7.0 — empirical verification + operator briefing
|
||||
## SKELETONKEY v0.7.1 — arm64-static binary + per-module arch_support
|
||||
|
||||
Point release on top of v0.7.0. Two additions:
|
||||
|
||||
1. **`skeletonkey-arm64-static`** is now published alongside the
|
||||
existing x86_64-static binary. Built native-arm64 in Alpine via
|
||||
GitHub's `ubuntu-24.04-arm` runner pool. Works on Raspberry Pi 4+,
|
||||
Apple Silicon Linux VMs, AWS Graviton, Oracle Ampere, Hetzner ARM,
|
||||
and any other aarch64 Linux. `install.sh` auto-picks it.
|
||||
|
||||
2. **`arch_support` per module** — a new field on
|
||||
`struct skeletonkey_module` that honestly labels which architectures
|
||||
the `exploit()` body has been verified on. Three categories:
|
||||
|
||||
- **`any`** (4 modules): pwnkit, sudo_samedit, sudoedit_editor,
|
||||
pack2theroot. Purely userspace; arch-independent.
|
||||
- **`x86_64`** (1 module): entrybleed. KPTI prefetchnta side-channel;
|
||||
x86-only by physics (ARM uses TTBR_EL0/EL1 split, not CR3).
|
||||
Already gated in source — returns PRECOND_FAIL on non-x86_64.
|
||||
- **`x86_64+unverified-arm64`** (26 modules): kernel-exploitation
|
||||
code that hasn't been verified on arm64 yet. `detect()` works
|
||||
everywhere (it just reads `ctx->host`); the `exploit()` body uses
|
||||
primitives (msg_msg sprays, ROP-style finishers, specific struct
|
||||
offsets) that are likely portable to aarch64 but unproven.
|
||||
|
||||
`--list` adds an ARCH column; `--module-info` adds an `arch support:`
|
||||
line; `--scan --json` adds an `arch_support` field per module.
|
||||
|
||||
**What an arm64 user gets today:** the full detection/triage workflow
|
||||
works as well as on x86_64 (`--scan`, `--explain`, `--module-info`,
|
||||
`--detect-rules`, `--auto --dry-run`). Four exploit modules
|
||||
(`pwnkit`, `sudo_samedit`, `sudoedit_editor`, `pack2theroot`) will fire
|
||||
end-to-end. The remaining 26 modules currently mark themselves as
|
||||
"x86_64 verified; arm64 untested" — the bug class is generic but the
|
||||
exploitation hasn't been confirmed. Future arm64-Vagrant verification
|
||||
sweeps will promote modules to `any` as they're confirmed.
|
||||
|
||||
---
|
||||
|
||||
### From v0.7.0 — empirical verification + operator briefing
|
||||
|
||||
The headline change since v0.6.0: **22 of 26 CVEs are now empirically
|
||||
confirmed against real Linux kernels in VMs**, with verification records
|
||||
|
||||
+14
-8
@@ -34,15 +34,15 @@ log() { printf '[\033[1;36m*\033[0m] %s\n' "$*" >&2; }
|
||||
ok() { printf '[\033[1;32m+\033[0m] %s\n' "$*" >&2; }
|
||||
fail() { printf '[\033[1;31m-\033[0m] %s\n' "$*" >&2; exit 1; }
|
||||
|
||||
# Detect architecture
|
||||
# Detect architecture. Default to the musl-static binary on both
|
||||
# x86_64 and arm64 — works on every libc (glibc 2.x of any version,
|
||||
# musl, uclibc); costs ~800 KB extra vs dynamic but eliminates the
|
||||
# GLIBC_2.NN portability ceiling that bites on Debian-stable, older
|
||||
# RHEL hosts, and Alpine. Set SKELETONKEY_DYNAMIC=1 to fetch the
|
||||
# smaller dynamic build (needs glibc >= 2.38 for x86_64 — Ubuntu
|
||||
# 24.04 / Debian 13 / RHEL 10).
|
||||
arch=$(uname -m)
|
||||
case "$arch" in
|
||||
# x86_64 default: the musl-static binary works on every libc
|
||||
# (glibc 2.x of any version, musl, uclibc) — costs ~800 KB extra
|
||||
# vs the dynamic build but eliminates the GLIBC_2.NN portability
|
||||
# ceiling that bit users on Debian-stable / older RHEL hosts.
|
||||
# Set SKELETONKEY_DYNAMIC=1 to fetch the smaller dynamic build
|
||||
# (needs glibc >= 2.38, i.e. Ubuntu 24.04 / Debian 13 / RHEL 10).
|
||||
x86_64|amd64)
|
||||
if [ "${SKELETONKEY_DYNAMIC:-0}" = "1" ]; then
|
||||
target=x86_64
|
||||
@@ -50,7 +50,13 @@ case "$arch" in
|
||||
target=x86_64-static
|
||||
fi
|
||||
;;
|
||||
aarch64|arm64) target=arm64 ;;
|
||||
aarch64|arm64)
|
||||
if [ "${SKELETONKEY_DYNAMIC:-0}" = "1" ]; then
|
||||
target=arm64
|
||||
else
|
||||
target=arm64-static
|
||||
fi
|
||||
;;
|
||||
*) fail "Unsupported architecture: $arch (only x86_64 and arm64 currently)" ;;
|
||||
esac
|
||||
log "detected arch: $target"
|
||||
|
||||
@@ -732,6 +732,7 @@ const struct skeletonkey_module af_packet2_module = {
|
||||
.detect_yara = af_packet2_yara,
|
||||
.detect_falco = af_packet2_falco,
|
||||
.opsec_notes = "unshare(CLONE_NEWUSER|CLONE_NEWNET) + TPACKET_V2 ring on AF_PACKET; crafts nested-VLAN ETH_P_8021AD frames with 0x88A8/0x8100 TPIDs to trigger tpacket_rcv underflow; fires 256 frames + 64 sendmmsg via AF_UNIX socketpair spray. Tag 'skeletonkey-afp-fc-' visible in KASAN splats. Audit-visible via socket(AF_PACKET) + sendmsg/sendto from userns. No persistent artifacts; kernel cleans up on child exit.",
|
||||
.arch_support = "x86_64+unverified-arm64",
|
||||
};
|
||||
|
||||
void skeletonkey_register_af_packet2(void)
|
||||
|
||||
@@ -955,6 +955,7 @@ const struct skeletonkey_module af_packet_module = {
|
||||
.detect_yara = af_packet_yara,
|
||||
.detect_falco = af_packet_falco,
|
||||
.opsec_notes = "Creates AF_PACKET socket and TPACKET_V3 ring inside unshare(CLONE_NEWUSER|CLONE_NEWNET); triggers integer overflow with crafted tp_block_size/tp_block_nr and sprays ~200 loopback frames. Audit-visible via socket(AF_PACKET) (a0=17) + sendmmsg from a userns process; KASAN tag 'iamroot-afp-tag' may appear in dmesg if enabled. No persistent files. No cleanup callback - kernel state unwinds on child exit.",
|
||||
.arch_support = "x86_64+unverified-arm64",
|
||||
};
|
||||
|
||||
void skeletonkey_register_af_packet(void)
|
||||
|
||||
@@ -898,6 +898,7 @@ const struct skeletonkey_module af_unix_gc_module = {
|
||||
.detect_yara = af_unix_gc_yara,
|
||||
.detect_falco = af_unix_gc_falco,
|
||||
.opsec_notes = "Two-threaded race: Thread A creates socketpair(AF_UNIX) with SCM_RIGHTS cycle then close; Thread B drives independent SCM_RIGHTS traffic on a held pair. ~5s budget (30s with --full-chain). msg_msg kmalloc-512 spray tagged 'SKELETONKEYU'. Writes /tmp/skeletonkey-af_unix_gc.log with empirical stats. Audit-visible via socketpair(AF_UNIX) + sendmsg(SCM_RIGHTS) + msgsnd triple. Dmesg may show UAF KASAN if kernel vulnerable. Cleanup callback unlinks the log.",
|
||||
.arch_support = "x86_64+unverified-arm64",
|
||||
};
|
||||
|
||||
void skeletonkey_register_af_unix_gc(void)
|
||||
|
||||
@@ -404,6 +404,7 @@ const struct skeletonkey_module cgroup_release_agent_module = {
|
||||
.detect_yara = cgroup_release_agent_yara,
|
||||
.detect_falco = cgroup_release_agent_falco,
|
||||
.opsec_notes = "unshare(CLONE_NEWUSER|CLONE_NEWNS), mount cgroup v1 at /tmp/skeletonkey-cgroup-mnt, write payload path to release_agent file at cgroup root, echo 1 to notify_on_release in subdir, add PID to cgroup.procs and exit. Payload at /tmp/skeletonkey-cgroup-payload.sh runs as init-namespace root when cgroup empties, dropping setuid /tmp/skeletonkey-cgroup-sh. Audit-visible via unshare + mount(cgroup) + open/write of release_agent. Cleanup callback removes /tmp/skeletonkey-cgroup-* and umounts.",
|
||||
.arch_support = "x86_64+unverified-arm64",
|
||||
};
|
||||
|
||||
void skeletonkey_register_cgroup_release_agent(void)
|
||||
|
||||
@@ -889,6 +889,7 @@ const struct skeletonkey_module cls_route4_module = {
|
||||
.detect_yara = cls_route4_yara,
|
||||
.detect_falco = cls_route4_falco,
|
||||
.opsec_notes = "unshare(CLONE_NEWUSER|CLONE_NEWNET); ip link/addr/route to make a dummy interface, htb qdisc + class + route4 filter with handle 0, delete filter (leaves dangling tcf_proto pointer), msg_msg spray kmalloc-1k tagged 'SKELETONKEY4', UDP sendto to trigger classify(). Writes /tmp/skeletonkey-cls_route4.log. Audit-visible via unshare + sendto(AF_INET) + msgsnd. Cleanup callback removes /tmp log + dummy interface.",
|
||||
.arch_support = "x86_64+unverified-arm64",
|
||||
};
|
||||
|
||||
void skeletonkey_register_cls_route4(void)
|
||||
|
||||
@@ -248,6 +248,7 @@ const struct skeletonkey_module copy_fail_module = {
|
||||
.detect_yara = copy_fail_family_yara,
|
||||
.detect_falco = copy_fail_family_falco,
|
||||
.opsec_notes = "Family-shared infrastructure (copy_fail, copy_fail_gcm, dirty_frag_esp/esp6, dirty_frag_rxrpc): all exploit a page-cache write primitive against /etc/passwd (UID flip to all-zeros) or install a persistent backdoor. Audit-visible via socket(AF_ALG) (a0=38), setsockopt(XFRM), AF_UNIX setup. Detection rules watch /etc/passwd, /etc/shadow, /etc/sudoers, /usr/bin/su for non-root writes. Family mitigation blacklists algif_aead/esp4/esp6/rxrpc and sets apparmor_restrict_unprivileged_userns=1. Cleanup evicts /etc/passwd from page cache and reverts mitigation conf.",
|
||||
.arch_support = "x86_64+unverified-arm64",
|
||||
};
|
||||
|
||||
/* ----- copy_fail_gcm (variant, no CVE) ----- */
|
||||
@@ -281,6 +282,7 @@ const struct skeletonkey_module copy_fail_gcm_module = {
|
||||
.detect_yara = copy_fail_family_yara,
|
||||
.detect_falco = copy_fail_family_falco,
|
||||
.opsec_notes = "Family-shared infrastructure (copy_fail, copy_fail_gcm, dirty_frag_esp/esp6, dirty_frag_rxrpc): all exploit a page-cache write primitive against /etc/passwd (UID flip to all-zeros) or install a persistent backdoor. Audit-visible via socket(AF_ALG) (a0=38), setsockopt(XFRM), AF_UNIX setup. Detection rules watch /etc/passwd, /etc/shadow, /etc/sudoers, /usr/bin/su for non-root writes. Family mitigation blacklists algif_aead/esp4/esp6/rxrpc and sets apparmor_restrict_unprivileged_userns=1. Cleanup evicts /etc/passwd from page cache and reverts mitigation conf.",
|
||||
.arch_support = "x86_64+unverified-arm64",
|
||||
};
|
||||
|
||||
/* ----- dirty_frag_esp (CVE-2026-43284 v4) ----- */
|
||||
@@ -314,6 +316,7 @@ const struct skeletonkey_module dirty_frag_esp_module = {
|
||||
.detect_yara = copy_fail_family_yara,
|
||||
.detect_falco = copy_fail_family_falco,
|
||||
.opsec_notes = "Family-shared infrastructure (copy_fail, copy_fail_gcm, dirty_frag_esp/esp6, dirty_frag_rxrpc): all exploit a page-cache write primitive against /etc/passwd (UID flip to all-zeros) or install a persistent backdoor. Audit-visible via socket(AF_ALG) (a0=38), setsockopt(XFRM), AF_UNIX setup. Detection rules watch /etc/passwd, /etc/shadow, /etc/sudoers, /usr/bin/su for non-root writes. Family mitigation blacklists algif_aead/esp4/esp6/rxrpc and sets apparmor_restrict_unprivileged_userns=1. Cleanup evicts /etc/passwd from page cache and reverts mitigation conf.",
|
||||
.arch_support = "x86_64+unverified-arm64",
|
||||
};
|
||||
|
||||
/* ----- dirty_frag_esp6 (CVE-2026-43284 v6) ----- */
|
||||
@@ -347,6 +350,7 @@ const struct skeletonkey_module dirty_frag_esp6_module = {
|
||||
.detect_yara = copy_fail_family_yara,
|
||||
.detect_falco = copy_fail_family_falco,
|
||||
.opsec_notes = "Family-shared infrastructure (copy_fail, copy_fail_gcm, dirty_frag_esp/esp6, dirty_frag_rxrpc): all exploit a page-cache write primitive against /etc/passwd (UID flip to all-zeros) or install a persistent backdoor. Audit-visible via socket(AF_ALG) (a0=38), setsockopt(XFRM), AF_UNIX setup. Detection rules watch /etc/passwd, /etc/shadow, /etc/sudoers, /usr/bin/su for non-root writes. Family mitigation blacklists algif_aead/esp4/esp6/rxrpc and sets apparmor_restrict_unprivileged_userns=1. Cleanup evicts /etc/passwd from page cache and reverts mitigation conf.",
|
||||
.arch_support = "x86_64+unverified-arm64",
|
||||
};
|
||||
|
||||
/* ----- dirty_frag_rxrpc (CVE-2026-43500) ----- */
|
||||
@@ -380,6 +384,7 @@ const struct skeletonkey_module dirty_frag_rxrpc_module = {
|
||||
.detect_yara = copy_fail_family_yara,
|
||||
.detect_falco = copy_fail_family_falco,
|
||||
.opsec_notes = "Family-shared infrastructure (copy_fail, copy_fail_gcm, dirty_frag_esp/esp6, dirty_frag_rxrpc): all exploit a page-cache write primitive against /etc/passwd (UID flip to all-zeros) or install a persistent backdoor. Audit-visible via socket(AF_ALG) (a0=38), setsockopt(XFRM), AF_UNIX setup. Detection rules watch /etc/passwd, /etc/shadow, /etc/sudoers, /usr/bin/su for non-root writes. Family mitigation blacklists algif_aead/esp4/esp6/rxrpc and sets apparmor_restrict_unprivileged_userns=1. Cleanup evicts /etc/passwd from page cache and reverts mitigation conf.",
|
||||
.arch_support = "x86_64+unverified-arm64",
|
||||
};
|
||||
|
||||
/* ----- Family registration ----- */
|
||||
|
||||
@@ -434,6 +434,7 @@ const struct skeletonkey_module dirty_cow_module = {
|
||||
.detect_yara = dirty_cow_yara,
|
||||
.detect_falco = dirty_cow_falco,
|
||||
.opsec_notes = "Two-thread race: Thread A loops pwrite(/proc/self/mem) at the user's UID offset in /etc/passwd; Thread B loops madvise(MADV_DONTNEED) on a PRIVATE mmap of /etc/passwd. Overwrites the UID field with all-zeros, then execlp('su') to claim root. UID offset is parsed from the file, not hardcoded. Audit-visible via open(/proc/self/mem) + write + madvise(MADV_DONTNEED) bursts + /etc/passwd page-cache poisoning. Cleanup callback calls posix_fadvise(POSIX_FADV_DONTNEED) on /etc/passwd and writes 3 to /proc/sys/vm/drop_caches to evict.",
|
||||
.arch_support = "x86_64+unverified-arm64",
|
||||
};
|
||||
|
||||
void skeletonkey_register_dirty_cow(void)
|
||||
|
||||
@@ -523,6 +523,7 @@ const struct skeletonkey_module dirty_pipe_module = {
|
||||
.detect_yara = dirty_pipe_yara,
|
||||
.detect_falco = dirty_pipe_falco,
|
||||
.opsec_notes = "Creates a pipe, fills+drains to leave PIPE_BUF_FLAG_CAN_MERGE on every slot; finds the UID offset in /etc/passwd by parsing the file; splice(1 byte) from (target_offset-1) to inherit the stale flag, then write(pipe) with the all-zero payload - kernel merges into the file's page cache. Offset must be non-page-aligned and the write must fit in a single page. Audit-visible via splice(fd=/etc/passwd) + write from a non-root process. --active mode writes/reads /tmp/skeletonkey-dirty-pipe-probe-XXXXXX to verify. Cleanup callback evicts /etc/passwd via posix_fadvise + drop_caches.",
|
||||
.arch_support = "x86_64+unverified-arm64",
|
||||
};
|
||||
|
||||
void skeletonkey_register_dirty_pipe(void)
|
||||
|
||||
@@ -1006,6 +1006,7 @@ const struct skeletonkey_module dirtydecrypt_module = {
|
||||
.detect_yara = dd_yara,
|
||||
.detect_falco = dd_falco,
|
||||
.opsec_notes = "Forked child runs unshare(CLONE_NEWUSER|CLONE_NEWNET); creates AF_RXRPC socket; builds an rxgk XDR token via add_key(SYS_add_key, 'rxrpc'); sets up loopback UDP server + rxrpc client; forges rxrpc DATA packets and fires 10000+ splice-based writes in a sliding window to overwrite a target setuid binary's page cache with a 120-byte ET_DYN ELF (setuid(0) + execve('/bin/sh')). Payload is never written to disk. Audit-visible via socket(AF_RXRPC) (a0=33) + add_key('rxrpc') + splice() bursts. Records target path to /tmp/skeletonkey-dirtydecrypt.target. Cleanup callback evicts candidate targets (/usr/bin/su et al) via drop_caches.",
|
||||
.arch_support = "x86_64+unverified-arm64",
|
||||
};
|
||||
|
||||
void skeletonkey_register_dirtydecrypt(void)
|
||||
|
||||
@@ -289,6 +289,7 @@ const struct skeletonkey_module entrybleed_module = {
|
||||
.detect_yara = NULL,
|
||||
.detect_falco = NULL,
|
||||
.opsec_notes = "Pure timing side-channel: rdtsc + prefetchnta sweep across the kernel high-half (~16 MiB) to time which 2 MiB page is mapped (entry_SYSCALL_64) and subtract its known offset from kbase. No syscalls fired, no file artifacts, no network. Classic auditd cannot see it; perf-counter EDR can flag a process spending unusual time in tight prefetchnta loops but classic rules will not. No cleanup needed.",
|
||||
.arch_support = "x86_64",
|
||||
};
|
||||
|
||||
void skeletonkey_register_entrybleed(void)
|
||||
|
||||
@@ -1211,6 +1211,7 @@ const struct skeletonkey_module fragnesia_module = {
|
||||
.detect_yara = fg_yara,
|
||||
.detect_falco = fg_falco,
|
||||
.opsec_notes = "unshare(CLONE_NEWUSER|CLONE_NEWNET) + socket(AF_ALG, SOCK_SEQPACKET) for an AES-GCM keystream table; NETLINK_XFRM setsockopt to install ESP-in-TCP state; TCP_ULP setsockopt on a loopback connection; splice() from a carrier setuid binary (/usr/bin/su or /bin/su) into the TCP socket. Artifacts: /tmp/skeletonkey-fragnesia-probe-XXXXXX (mkstemp, unlinked after probe) and /tmp/skeletonkey-fragnesia.target. Audit-visible via socket(AF_ALG) (38), NETLINK_XFRM (6) writes, TCP_ULP setsockopt, splice() of setuid binary. No external network (loopback). Cleanup callback unlinks /tmp files and evicts the carrier from page cache.",
|
||||
.arch_support = "x86_64+unverified-arm64",
|
||||
};
|
||||
|
||||
void skeletonkey_register_fragnesia(void)
|
||||
|
||||
@@ -916,6 +916,7 @@ const struct skeletonkey_module fuse_legacy_module = {
|
||||
.detect_yara = fuse_legacy_yara,
|
||||
.detect_falco = fuse_legacy_falco,
|
||||
.opsec_notes = "unshare(CLONE_NEWUSER|CLONE_NEWNS) for CAP_SYS_ADMIN; fsopen('cgroup2') + multiple fsconfig(FSCONFIG_SET_STRING, 'source', ...) calls to overflow legacy_parse_param's buffer. OOB write lands in kmalloc-4k adjacent to a msg_msg groom. No persistent files (msg_msg lives in the IPC namespace which disappears with the child). Dmesg silent on success; KASAN would show slab corruption if enabled. Audit-visible via unshare(CLONE_NEWUSER|CLONE_NEWNS) + fsopen + fsconfig pattern in a single process. No cleanup callback - IPC queues auto-drain on namespace exit.",
|
||||
.arch_support = "x86_64+unverified-arm64",
|
||||
};
|
||||
|
||||
void skeletonkey_register_fuse_legacy(void)
|
||||
|
||||
@@ -1024,6 +1024,7 @@ const struct skeletonkey_module netfilter_xtcompat_module = {
|
||||
.detect_yara = netfilter_xtcompat_yara,
|
||||
.detect_falco = netfilter_xtcompat_falco,
|
||||
.opsec_notes = "unshare(CLONE_NEWUSER|CLONE_NEWNET) + setsockopt(SOL_IP, IPT_SO_SET_REPLACE) with a malformed xt_entry_target to trigger xt_compat_target_to_user 4-byte OOB into kmalloc-2k. msg_msg + sk_buff cross-cache groom. Writes /tmp/skeletonkey-xtcompat.log (breadcrumb). Audit-visible via unshare + setsockopt(IPT_SO_SET_REPLACE) + msgsnd/msgrcv + sendmmsg(sk_buff spray). Dmesg silent on success; KASAN oops if the groom misses. Cleanup callback unlinks the log; IPC auto-drains on namespace exit.",
|
||||
.arch_support = "x86_64+unverified-arm64",
|
||||
};
|
||||
|
||||
void skeletonkey_register_netfilter_xtcompat(void)
|
||||
|
||||
@@ -1168,6 +1168,7 @@ const struct skeletonkey_module nf_tables_module = {
|
||||
.detect_yara = nf_tables_yara,
|
||||
.detect_falco = nf_tables_falco,
|
||||
.opsec_notes = "unshare(CLONE_NEWUSER|CLONE_NEWNET) + nfnetlink batch (NEWTABLE + NEWCHAIN/LOCAL_OUT + NEWSET verdict-key + NEWSETELEM malformed NFT_GOTO) committed twice to trigger the nft_verdict_init double-free. msg_msg cg-96 groom with forged pipapo_elem headers; --full-chain sprays kaddr-tagged forged elems and re-fires. Writes /tmp/skeletonkey-nft_set_uaf.log (conditional). Audit-visible via unshare + socket(NETLINK_NETFILTER) + sendmsg batches + msgget/msgsnd. Dmesg: KASAN double-free panic on vulnerable kernels; silent otherwise. Cleanup is finisher-gated; no persistent files on success.",
|
||||
.arch_support = "x86_64+unverified-arm64",
|
||||
};
|
||||
|
||||
void skeletonkey_register_nf_tables(void)
|
||||
|
||||
@@ -1074,6 +1074,7 @@ const struct skeletonkey_module nft_fwd_dup_module = {
|
||||
.detect_yara = nft_fwd_dup_yara,
|
||||
.detect_falco = nft_fwd_dup_falco,
|
||||
.opsec_notes = "unshare(CLONE_NEWUSER|CLONE_NEWNET) + nfnetlink batch (NEWTABLE netdev + NEWCHAIN HW_OFFLOAD + NEWRULE with 16 immediate(NF_ACCEPT) + 1 fwd). Offload hook walks the rule advertising num_actions+=16 but allocates only the original-actions size -> OOB write at entries[16] into adjacent kmalloc-512. msg_msg groom tagged 'SKELETONKEY_FWD'. Writes /tmp/skeletonkey-nft_fwd_dup.log. Audit-visible via unshare + socket(NETLINK_NETFILTER) + sendmsg + ioctl(SIOCGIFFLAGS/SIOCSIFFLAGS loopback) + msgsnd. Dmesg: KASAN or silent. Cleanup callback drains IPC queues and unlinks log.",
|
||||
.arch_support = "x86_64+unverified-arm64",
|
||||
};
|
||||
|
||||
void skeletonkey_register_nft_fwd_dup(void)
|
||||
|
||||
@@ -1184,6 +1184,7 @@ const struct skeletonkey_module nft_payload_module = {
|
||||
.detect_yara = nft_payload_yara,
|
||||
.detect_falco = nft_payload_falco,
|
||||
.opsec_notes = "unshare(CLONE_NEWUSER|CLONE_NEWNET) + nfnetlink batch (NEWTABLE + NEWCHAIN/LOCAL_OUT + NEWSET with oversized NFTA_SET_DESC + NEWSETELEM whose NFTA_PAYLOAD_SREG = attacker verdict code). On packet eval, regs->verdict.code is used unchecked as index into regs->data[] -> OOB. Dual-slab groom (kmalloc-1k + kmalloc-cg-96). Trigger via sendto(AF_INET, 127.0.0.1:31337). Writes /tmp/skeletonkey-nft_payload.log. Audit-visible via unshare + socket(NETLINK_NETFILTER) + sendmsg + msgsnd + socket(AF_INET)/sendto. Cleanup callback unlinks log.",
|
||||
.arch_support = "x86_64+unverified-arm64",
|
||||
};
|
||||
|
||||
void skeletonkey_register_nft_payload(void)
|
||||
|
||||
@@ -1068,6 +1068,7 @@ const struct skeletonkey_module nft_set_uaf_module = {
|
||||
.detect_yara = nft_set_uaf_yara,
|
||||
.detect_falco = nft_set_uaf_falco,
|
||||
.opsec_notes = "unshare(CLONE_NEWUSER|CLONE_NEWNET) + single nfnetlink transaction: NEWTABLE + NEWCHAIN + NEWSET (anonymous, ANONYMOUS|CONSTANT|EVAL) + NEWRULE with nft_lookup referencing the anon set + DELSET + DELRULE. Vulnerable kernels do not deactivate the lookup's set ref on commit -> UAF when set frees. msg_msg cg-512 spray (32 queues x 16 msgs, tag 'SKELETONKEY_SET'). --full-chain re-fires with forged headers (data ptr = kaddr) and NEWSETELEM payload. Writes /tmp/skeletonkey-nft_set_uaf.log. Audit-visible via unshare + socket(NETLINK_NETFILTER) + sendmsg + msgsnd. Dmesg: KASAN oops on UAF. Cleanup unlinks log.",
|
||||
.arch_support = "x86_64+unverified-arm64",
|
||||
};
|
||||
|
||||
void skeletonkey_register_nft_set_uaf(void)
|
||||
|
||||
@@ -556,6 +556,7 @@ const struct skeletonkey_module overlayfs_module = {
|
||||
.detect_yara = overlayfs_yara,
|
||||
.detect_falco = overlayfs_falco,
|
||||
.opsec_notes = "unshare(CLONE_NEWUSER|CLONE_NEWNS) for CAP_SYS_ADMIN; mount('overlay', merged, ...); compile + copy payload into the merged dir (writes upper on host fs); setxattr(upper_payload, 'security.capability', cap_setuid+ep) - the bug is that this xattr persists on the HOST fs despite being set inside userns. Parent then execve's the now-CAP_SETUID payload, calls setuid(0), execs /bin/sh. Artifacts: /tmp/skeletonkey-ovl-XXXXXX/ workdir; cleaned on exit/failure (on success the exec replaces the process so cleanup does not run). Audit-visible via unshare + mount(overlay) + setxattr(security.capability) + execve of attacker-controlled binary. Dmesg silent.",
|
||||
.arch_support = "x86_64+unverified-arm64",
|
||||
};
|
||||
|
||||
void skeletonkey_register_overlayfs(void)
|
||||
|
||||
@@ -472,6 +472,7 @@ const struct skeletonkey_module overlayfs_setuid_module = {
|
||||
.detect_yara = overlayfs_setuid_yara,
|
||||
.detect_falco = overlayfs_setuid_falco,
|
||||
.opsec_notes = "unshare(CLONE_NEWUSER|CLONE_NEWNS) + overlayfs mount with a setuid-root binary in lower (e.g. /usr/bin/su); chown on the merged view triggers copy-up that preserves the setuid bit in upper - but upper is owned by the unprivileged user. Overwrites upper-layer contents with attacker payload and execve's for root. Artifacts: /tmp/skeletonkey-ovlsu-XXXXXX/ (workdir with payload.c, binary, overlay mounts); cleanup callback removes these. Audit-visible via unshare(CLONE_NEWUSER|CLONE_NEWNS) + mount(overlay) + chown on the merged view. No network. Dmesg silent on success.",
|
||||
.arch_support = "x86_64+unverified-arm64",
|
||||
};
|
||||
|
||||
void skeletonkey_register_overlayfs_setuid(void)
|
||||
|
||||
@@ -791,6 +791,7 @@ const struct skeletonkey_module pack2theroot_module = {
|
||||
.detect_yara = p2tr_yara,
|
||||
.detect_falco = p2tr_falco,
|
||||
.opsec_notes = "TOCTOU race in PackageKit's polkit-auth + D-Bus InstallFiles dispatcher: sends back-to-back async calls (first with SIMULATE to bypass polkit, second with the malicious .deb) so the cached flags are overwritten before the idle callback fires. Builds a minimal .deb ar archive in pure C with a postinst that installs a setuid bash. Writes /tmp/.pk-dummy-<pid>.deb, /tmp/.pk-payload-<pid>.deb, and /tmp/skeletonkey-pack2theroot.state; via the polkit-bypassed postinst plants /tmp/.suid_bash setuid root. Audit-visible via dpkg execve from packagekitd for a non-root caller, chmod(2) on /tmp/.suid_bash, creat/openat on the .deb files. Cleanup callback unlinks the .debs and best-effort removes /tmp/.suid_bash (which is owned by root).",
|
||||
.arch_support = "any",
|
||||
};
|
||||
|
||||
void skeletonkey_register_pack2theroot(void)
|
||||
|
||||
@@ -368,6 +368,7 @@ const struct skeletonkey_module ptrace_traceme_module = {
|
||||
.detect_yara = NULL,
|
||||
.detect_falco = ptrace_traceme_falco,
|
||||
.opsec_notes = "Parent and child cooperate: child calls ptrace(PTRACE_TRACEME) (recording the parent's current credentials), then sleeps; parent execve's a setuid binary (pkexec or su) and elevates. The stale ptrace_link in the child still holds the old (non-root) credentials, so PTRACE_ATTACH succeeds against the now-root parent; the child injects shellcode at the parent's RIP via PTRACE_POKETEXT and detaches. Audit-visible via ptrace with a0=0 (PTRACE_TRACEME) closely followed by execve of a setuid binary in the parent process. No file artifacts; no persistent changes. No cleanup callback - the exploit execs /bin/sh and does not return.",
|
||||
.arch_support = "x86_64+unverified-arm64",
|
||||
};
|
||||
|
||||
void skeletonkey_register_ptrace_traceme(void)
|
||||
|
||||
@@ -473,6 +473,7 @@ const struct skeletonkey_module pwnkit_module = {
|
||||
.detect_yara = pwnkit_yara,
|
||||
.detect_falco = pwnkit_falco,
|
||||
.opsec_notes = "Invokes pkexec with argc==0 so the first envp slot is misread as argv[0]; pkexec's iconv-during-decoding loads attacker .so via dlopen by way of crafted GCONV_PATH + CHARSET env vars. Builds a gconv payload .so and gconv-modules cache in /tmp/skeletonkey-pwnkit-XXXXXX (compiles via fork/execl of gcc). Audit-visible via execve(/usr/bin/pkexec) with GCONV_PATH and CHARSET set. No network. Cleanup callback removes /tmp/skeletonkey-pwnkit-* (on failure path; on success the exec replaces the process).",
|
||||
.arch_support = "any",
|
||||
};
|
||||
|
||||
void skeletonkey_register_pwnkit(void)
|
||||
|
||||
@@ -752,6 +752,7 @@ const struct skeletonkey_module sequoia_module = {
|
||||
.detect_yara = sequoia_yara,
|
||||
.detect_falco = sequoia_falco,
|
||||
.opsec_notes = "Builds ~5000 nested directories under /tmp/skeletonkey-sequoia (each name 200 'A' chars); enters userns for CAP_SYS_ADMIN; bind-mounts the leaf over itself to amplify the rendered mountinfo string length; reads /proc/self/mountinfo to trigger the int-vs-size_t overflow in seq_buf_alloc(), producing an OOB write of mountinfo bytes off the stack buffer. Artifacts: /tmp/skeletonkey-sequoia/ (deep tree + bind mounts) and /tmp/skeletonkey-sequoia.log (byte count + dmesg sample). Audit-visible via unshare(CLONE_NEWUSER|CLONE_NEWNS) + mount() + burst of ~5000 mkdir/mkdirat. No network. Cleanup callback walks back down the tree, unmounts, removes dirs, unlinks the .log.",
|
||||
.arch_support = "x86_64+unverified-arm64",
|
||||
};
|
||||
|
||||
void skeletonkey_register_sequoia(void)
|
||||
|
||||
@@ -1014,6 +1014,7 @@ const struct skeletonkey_module stackrot_module = {
|
||||
.detect_yara = stackrot_yara,
|
||||
.detect_falco = stackrot_falco,
|
||||
.opsec_notes = "Child forks, enters userns, builds a race region with MAP_GROWSDOWN + anchor VMAs, sprays kmalloc-192 with msg_msg payloads, then spawns Thread A (mremap/munmap of region boundary to rotate maple-tree nodes) + Thread B (fork+fault the growsdown region to deref freed node). UAF in __vma_adjust fires if a sprayed msg_msg reclaims the freed node. Writes /tmp/skeletonkey-stackrot.log (iteration counts + slab delta). Audit-visible via unshare + mremap/munmap bursts on stack regions + msgsnd spray. No network. Cleanup callback unlinks /tmp log.",
|
||||
.arch_support = "x86_64+unverified-arm64",
|
||||
};
|
||||
|
||||
void skeletonkey_register_stackrot(void)
|
||||
|
||||
@@ -506,6 +506,7 @@ const struct skeletonkey_module sudo_samedit_module = {
|
||||
.detect_yara = NULL,
|
||||
.detect_falco = sudo_samedit_falco,
|
||||
.opsec_notes = "Invokes sudoedit with argv = { 'sudoedit', '-s', trailing-backslash, then ~60 padding args each ending in backslash }; the parser's unescape loop in set_cmnd() walks past the end of the argv string for the trailing-backslash argument, copying adjacent stack/env into an undersized heap buffer. Audit-visible via execve(/usr/bin/sudoedit) with -s and a trailing-backslash argv. No persistent file artifacts (only best-effort removal of /tmp/.sudo_edit_*). No network. Dmesg silent unless sudo crashes (SIGSEGV). Per-distro heap layout determines landing; verifies geteuid()==0 afterward.",
|
||||
.arch_support = "any",
|
||||
};
|
||||
|
||||
void skeletonkey_register_sudo_samedit(void) { skeletonkey_register(&sudo_samedit_module); }
|
||||
|
||||
@@ -663,6 +663,7 @@ const struct skeletonkey_module sudoedit_editor_module = {
|
||||
.detect_yara = sudoedit_editor_yara,
|
||||
.detect_falco = sudoedit_editor_falco,
|
||||
.opsec_notes = "Sets EDITOR='<helper> -- /etc/passwd' so sudoedit splits on the literal '--' and treats /etc/passwd as an additional editable file. Compiled helper appends 'skel::0:0:skeletonkey:/root:/bin/sh' to the post-'--' target; sudoedit runs the helper as root and copies back. Artifacts: /tmp/skeletonkey-sudoedit-XXXXXX (helper.c, helper binary, optional passwd.before backup); /etc/passwd gets the new 'skel' entry; drops root via 'su skel'. Audit-visible via execve(/usr/bin/sudoedit) with EDITOR/VISUAL/SUDO_EDITOR containing the literal '--' token. No network. Cleanup callback restores /etc/passwd from backup (if root) or removes the 'skel' line, and removes the /tmp dir.",
|
||||
.arch_support = "any",
|
||||
};
|
||||
|
||||
void skeletonkey_register_sudoedit_editor(void)
|
||||
|
||||
@@ -771,6 +771,7 @@ const struct skeletonkey_module vmwgfx_module = {
|
||||
.detect_yara = vmwgfx_yara,
|
||||
.detect_falco = vmwgfx_falco,
|
||||
.opsec_notes = "Opens /dev/dri/card* (vmwgfx DRM - only reachable on VMware guests); DRM_IOCTL_VMW_CREATE_DMABUF with size=4096+16 lands in the kmalloc-512 page-count bucket but the byte-length overruns during kunmap_atomic copy in ttm_bo_kmap; mmap + write recognizable pattern across page boundary; UNREF commits the OOB into adjacent kmalloc-512. msg_msg spray tagged 'SKVMWGFX'. Writes /tmp/skeletonkey-vmwgfx.log (slab counts pre/post, trigger success). Audit-visible via openat(/dev/dri/card*), ioctl(0xc010644a CREATE / 0x4004644b UNREF), msgsnd spray. No network. Cleanup callback unlinks /tmp log; --full-chain re-seeds spray with kaddr-tagged payloads and the modprobe_path finisher arbitrates via 3s sentinel.",
|
||||
.arch_support = "x86_64+unverified-arm64",
|
||||
};
|
||||
|
||||
void skeletonkey_register_vmwgfx(void)
|
||||
|
||||
+34
-9
@@ -35,7 +35,7 @@
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#define SKELETONKEY_VERSION "0.7.0"
|
||||
#define SKELETONKEY_VERSION "0.7.1"
|
||||
|
||||
static const char BANNER[] =
|
||||
"\n"
|
||||
@@ -216,6 +216,13 @@ static void emit_module_json(const struct skeletonkey_module *m, bool include_ru
|
||||
free(op);
|
||||
}
|
||||
|
||||
/* Architecture support for the exploit body. */
|
||||
if (m->arch_support) {
|
||||
char *a = json_escape(m->arch_support);
|
||||
fprintf(stdout, ",\"arch_support\":\"%s\"", a ? a : "");
|
||||
free(a);
|
||||
}
|
||||
|
||||
/* Empirical verification records: (distro, kernel, date) tuples
|
||||
* where the module's detect() was confirmed against a real target. */
|
||||
size_t nv = 0;
|
||||
@@ -272,27 +279,43 @@ static int cmd_list(const struct skeletonkey_ctx *ctx)
|
||||
fprintf(stdout, "]}\n");
|
||||
return 0;
|
||||
}
|
||||
fprintf(stdout, "%-20s %-18s %-3s %-3s %-25s %s\n",
|
||||
"NAME", "CVE", "KEV", "VFY", "FAMILY", "SUMMARY");
|
||||
fprintf(stdout, "%-20s %-18s %-3s %-3s %-25s %s\n",
|
||||
"----", "---", "---", "---", "------", "-------");
|
||||
size_t n_kev = 0, n_vfy = 0;
|
||||
/* The ARCH column shows where exploit() is known/expected to work:
|
||||
* "any" → userspace or arch-agnostic kernel primitive
|
||||
* "x64" → x86_64 only (entrybleed)
|
||||
* "x64?" → x86_64 verified, arm64 untested (the honest default
|
||||
* for kernel modules that haven't been arm64-confirmed) */
|
||||
fprintf(stdout, "%-20s %-18s %-3s %-3s %-5s %-25s %s\n",
|
||||
"NAME", "CVE", "KEV", "VFY", "ARCH", "FAMILY", "SUMMARY");
|
||||
fprintf(stdout, "%-20s %-18s %-3s %-3s %-5s %-25s %s\n",
|
||||
"----", "---", "---", "---", "----", "------", "-------");
|
||||
size_t n_kev = 0, n_vfy = 0, n_any = 0;
|
||||
for (size_t i = 0; i < n; i++) {
|
||||
const struct skeletonkey_module *m = skeletonkey_module_at(i);
|
||||
const struct cve_metadata *md = cve_metadata_lookup(m->cve);
|
||||
bool in_kev = md && md->in_kev;
|
||||
bool verified = verifications_module_has_match(m->name);
|
||||
const char *arch_abbr = "?";
|
||||
if (m->arch_support) {
|
||||
if (strcmp(m->arch_support, "any") == 0) { arch_abbr = "any"; n_any++; }
|
||||
else if (strcmp(m->arch_support, "x86_64") == 0) { arch_abbr = "x64"; }
|
||||
else { arch_abbr = "x64?"; }
|
||||
}
|
||||
if (in_kev) n_kev++;
|
||||
if (verified) n_vfy++;
|
||||
fprintf(stdout, "%-20s %-18s %-3s %-3s %-25s %s\n",
|
||||
fprintf(stdout, "%-20s %-18s %-3s %-3s %-5s %-25s %s\n",
|
||||
m->name, m->cve,
|
||||
in_kev ? "★" : "",
|
||||
verified ? "✓" : "",
|
||||
arch_abbr,
|
||||
m->family, m->summary);
|
||||
}
|
||||
fprintf(stdout, "\n%zu modules registered · %zu in CISA KEV (★) · "
|
||||
"%zu empirically verified in real VMs (✓)\n",
|
||||
n, n_kev, n_vfy);
|
||||
"%zu empirically verified in real VMs (✓) · "
|
||||
"%zu arch-independent (any)\n",
|
||||
n, n_kev, n_vfy, n_any);
|
||||
fprintf(stdout, "ARCH key: 'any' = userspace or arch-agnostic; "
|
||||
"'x64' = x86_64 only; 'x64?' = x86_64 verified, "
|
||||
"arm64 untested\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -666,6 +689,8 @@ static int cmd_module_info(const char *name, const struct skeletonkey_ctx *ctx)
|
||||
m->exploit ? "exploit " : "",
|
||||
m->mitigate ? "mitigate " : "",
|
||||
m->cleanup ? "cleanup " : "");
|
||||
if (m->arch_support)
|
||||
fprintf(stdout, "arch support: %s\n", m->arch_support);
|
||||
fprintf(stdout, "detect rules: %s%s%s%s\n",
|
||||
m->detect_auditd ? "auditd " : "",
|
||||
m->detect_sigma ? "sigma " : "",
|
||||
|
||||
Reference in New Issue
Block a user