feat(ssh-capture): LD_PRELOAD shim to zero inotifywait argv

The kmsg-watch (inotifywait) process was the last honest giveaway in
`ps aux` — its watch paths and event flags betrayed the honeypot. The
argv_zap.so shim hooks __libc_start_main, heap-copies argv for the real
main, then memsets the contiguous argv[1..] region to NUL so the kernel's
cmdline reader returns just argv[0].

gcc is installed and purged in the same Docker layer to keep the image
slim. The shim also calls prctl(PR_SET_NAME) so /proc/self/comm mirrors
the argv[0] disguise.
This commit is contained in:
2026-04-18 01:52:30 -04:00
parent e356829234
commit f462835373
4 changed files with 100 additions and 1 deletions

View File

@@ -322,6 +322,33 @@ def test_capture_script_uses_masked_inotify_bin():
assert "kmsg-watch" in body
# ---------------------------------------------------------------------------
# argv_zap LD_PRELOAD shim (hides inotifywait args from ps)
# ---------------------------------------------------------------------------
def test_argv_zap_source_shipped():
ctx = get_service("ssh").dockerfile_context()
src = ctx / "argv_zap.c"
assert src.exists(), "argv_zap.c missing from SSH template context"
body = src.read_text()
assert "__libc_start_main" in body
assert "PR_SET_NAME" in body
def test_dockerfile_compiles_argv_zap():
df = _dockerfile_text()
assert "argv_zap.c" in df
assert "argv_zap.so" in df
# gcc must be installed AND purged in the same layer (image-size hygiene).
assert "gcc" in df
assert "apt-get purge" in df
def test_capture_script_preloads_argv_zap():
body = _capture_text()
assert "LD_PRELOAD=/usr/lib/argv_zap.so" in body
# ---------------------------------------------------------------------------
# File-catcher: compose_fragment volume
# ---------------------------------------------------------------------------