Add Docker-based static musl build pipeline
ober
ca9255dd4ef34ad48a835f5cea6a4575e573db5b
--- a/.gitignore +++ b/.gitignore @@ -1,3 +1,9 @@ *.so *.wpo +*.o keys/ +secmon-agent +secmon-agent.sha256 +secmon-program.c +secmon-agent.boot +jerboa-stage/ new file mode 100644 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,107 @@ +# Dockerfile — Build secmon-agent as a fully static binary +# +# All source repos are cloned and built inside the container under /build/mine, +# with HOME=/build so no real usernames or home directories leak into the binary. +# +# Usage: +# docker build -t secmon-builder . +# id=$(docker create secmon-builder) +# docker cp $id:/out/secmon-agent ./secmon-agent +# docker rm $id + +FROM ubuntu:24.04 AS builder + +ARG DEBIAN_FRONTEND=noninteractive + +# ── System dependencies ────────────────────────────────────────────────────── +RUN apt-get update && apt-get install -y --no-install-recommends \ + build-essential \ + musl-tools \ + musl-dev \ + git \ + ca-certificates \ + curl \ + uuid-dev \ + pkg-config \ + file \ + && rm -rf /var/lib/apt/lists/* + +# ── Rust toolchain (for jerboa-native-rs) ──────────────────────────────────── +RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | \ + sh -s -- -y --default-toolchain stable --profile minimal && \ + . /root/.cargo/env && \ + rustup target add x86_64-unknown-linux-musl + +ENV PATH="/root/.cargo/bin:${PATH}" +ENV RUSTUP_HOME="/root/.rustup" + +# Set HOME early — everything under /build so paths are clean +ENV HOME=/build +WORKDIR /build + +# ── Build Chez Scheme (stock glibc, for compilation steps) ─────────────────── +RUN git clone --depth 1 https://github.com/ober/ChezScheme.git && \ + cd ChezScheme && \ + git submodule update --init --depth 1 && \ + ./configure --threads --disable-x11 --installprefix=/usr/local && \ + make -j$(nproc) && \ + make install && \ + cd /build && rm -rf ChezScheme + +# ── Build Chez Scheme (musl, for static linking) ──────────────────────────── +# Two passes: glibc for boot files, then musl for libkernel.a +RUN git clone https://github.com/ober/ChezScheme.git chez-musl-src && \ + cd chez-musl-src && \ + git submodule update --init && \ + ./configure --threads --disable-x11 --installprefix=/build/chez-musl && \ + make -j$(nproc) && \ + cp ta6le/boot/ta6le/petite.boot /tmp/petite.boot && \ + cp ta6le/boot/ta6le/scheme.boot /tmp/scheme.boot && \ + make clean && \ + ./configure --threads --disable-x11 --static CC=musl-gcc --installprefix=/build/chez-musl && \ + mkdir -p ta6le/boot/ta6le && \ + cp /tmp/petite.boot ta6le/boot/ta6le/ && \ + cp /tmp/scheme.boot ta6le/boot/ta6le/ && \ + make -j$(nproc) kernel && \ + make install && \ + cd /build && rm -rf chez-musl-src /tmp/petite.boot /tmp/scheme.boot + +# ── Clone jerboa framework ────────────────────────────────────────────────── +WORKDIR /build/mine +RUN git clone --depth 1 https://github.com/ober/jerboa.git + +# ── Build Rust native library (static, musl target) ───────────────────────── +RUN cd /build/mine/jerboa/jerboa-native-rs && \ + CARGO_HOME=/build/.cargo \ + RUSTFLAGS="--remap-path-prefix /build/.cargo/registry/src=crate --remap-path-prefix /build/mine=src" \ + cargo build --release --target x86_64-unknown-linux-musl && \ + strip -S target/x86_64-unknown-linux-musl/release/libjerboa_native.a + +# ── Copy secmon source ─────────────────────────────────────────────────────── +COPY . /build/mine/jerboa-secmon + +# ── Set environment for build ──────────────────────────────────────────────── +ENV JERBOA_MUSL_CHEZ_PREFIX=/build/chez-musl +ENV JERBOA=/build/mine/jerboa/lib + +# ── Build secmon-agent ─────────────────────────────────────────────────────── +WORKDIR /build/mine/jerboa-secmon +RUN make secmon-musl-local + +# ── Verify ─────────────────────────────────────────────────────────────────── +RUN echo "--- Binary info ---" && \ + ls -lh secmon-agent && \ + file secmon-agent && \ + echo "--- Hardening checks ---" && \ + (file secmon-agent | grep -qE 'stripped|no section header') && echo " PASS: stripped" || echo " FAIL: not stripped" && \ + test -f secmon-agent.sha256 && echo " PASS: integrity hash present" || echo " FAIL: no hash" && \ + echo "--- Username leak check ---" && \ + count=$(strings secmon-agent | grep -c '/home/jafourni' || true) && \ + echo "Occurrences of username path: $count" && \ + if [ "$count" -gt 0 ]; then echo "WARNING: username still present"; fi + +# ── Output ─────────────────────────────────────────────────────────────────── +FROM ubuntu:24.04 +COPY --from=builder /build/mine/jerboa-secmon/secmon-agent /out/secmon-agent +COPY --from=builder /build/mine/jerboa-secmon/secmon-agent.sha256 /out/secmon-agent.sha256 +CMD ["cat", "/out/secmon-agent"] --- a/Makefile +++ b/Makefile @@ -4,7 +4,7 @@ LIBDIRS = lib:$(JERBOA) NATIVE_RS ?= $(HOME)/mine/jerboa/jerboa-native-rs/target/release export LD_LIBRARY_PATH := $(NATIVE_RS):$(LD_LIBRARY_PATH) -.PHONY: build clean test keygen agent collector analyze +.PHONY: build clean test keygen agent collector analyze docker secmon-musl secmon-musl-local build: $(SCHEME) -q --libdirs $(LIBDIRS) --compile-imported-libraries < build-all.ss @@ -32,3 +32,25 @@ test: $(SCHEME) --libdirs $(LIBDIRS) --script $$f || exit 1; \ done @echo "All tests passed." + +# ─── musl Static Binary ────────────────────────────────────────────────────── + +secmon-musl: docker + +secmon-musl-local: build + @echo "=== Building static secmon-agent with musl (local) ===" + ./build-secmon-musl.sh + +# ─── Docker Build ──────────────────────────────────────────────────────────── + +docker: + @echo "=== Building secmon-agent in Docker ===" + docker build -t secmon-builder . + @id=$$(docker create secmon-builder) && \ + docker cp $$id:/out/secmon-agent ./secmon-agent && \ + docker cp $$id:/out/secmon-agent.sha256 ./secmon-agent.sha256 && \ + docker rm $$id >/dev/null && \ + chmod +x secmon-agent + @echo "=== Docker build complete ===" + @ls -lh secmon-agent + @file secmon-agent new file mode 100755 --- /dev/null +++ b/build-secmon-musl.sh @@ -0,0 +1,60 @@ +#!/bin/bash +# build-secmon-musl.sh — Build secmon-agent as a fully static binary using musl libc +# +# Prerequisites: +# - musl-gcc installed (apt install musl-tools) +# - Chez Scheme built with: ./configure --threads --static CC=musl-gcc +# and installed to ~/chez-musl (or set JERBOA_MUSL_CHEZ_PREFIX) +# - Jerboa libraries compiled +# - libjerboa_native.a built for x86_64-unknown-linux-musl +set -euo pipefail + +JERBOA_DIR="${JERBOA_DIR:-$HOME/mine/jerboa}" +JERBOA_LIB="${JERBOA_DIR}/lib" + +echo "===================================" +echo "Building secmon-agent with musl libc (static)" +echo "===================================" +echo "" +echo "Jerboa: $JERBOA_LIB" +echo "" + +# Check musl availability +if ! command -v musl-gcc &>/dev/null; then + echo "ERROR: musl-gcc not found" + echo "Install: sudo apt install musl-tools" + exit 1 +fi + +# Use jerboa's musl module to validate +echo "[1/2] Validating musl toolchain via jerboa..." +scheme -q --libdirs "lib:${JERBOA_LIB}" <<'VALIDATE' +(import (chezscheme) (jerboa build musl)) +(let ([result (validate-musl-setup)]) + (printf " ~a: ~a~n" (car result) (cdr result)) + (unless (eq? (car result) 'ok) + (exit 1))) +VALIDATE + +echo "" +echo "[2/2] Running musl build..." +NATIVE_RS="${NATIVE_RS:-$HOME/mine/jerboa/jerboa-native-rs/target/x86_64-unknown-linux-musl/release}" +LD_LIBRARY_PATH="${NATIVE_RS}:${LD_LIBRARY_PATH:-}" \ + scheme -q --libdirs "lib:${JERBOA_LIB}" \ + <build-secmon-musl.ss + +# Verify +if [ -f "secmon-agent" ]; then + echo "" + echo "===================================" + echo "secmon-agent built successfully!" + echo "===================================" + ls -lh secmon-agent + echo "" + file secmon-agent + echo "" + ldd secmon-agent 2>&1 || echo " (Fully static - no dependencies)" +else + echo "ERROR: secmon-agent not created" + exit 1 +fi new file mode 100644 --- /dev/null +++ b/build-secmon-musl.ss @@ -0,0 +1,485 @@ +#!chezscheme +;;; build-secmon-musl.ss — Build a fully static secmon-agent binary using musl libc +;;; +;;; Usage: scheme -q --libdirs lib:<jerboa-lib> < build-secmon-musl.ss +;;; +;;; This script: +;;; 1. Patches load-shared-object calls for static build +;;; 2. Compiles all secmon modules +;;; 3. Creates boot file + optimized program .so +;;; 4. Generates C files with embedded boot data + FFI symbol registration +;;; 5. Compiles C with musl-gcc against musl-built Chez's scheme.h +;;; 6. Links fully static binary with libkernel.a + libjerboa_native.a +;;; +;;; The resulting secmon-agent binary has zero runtime dependencies. + +(import + (except (chezscheme) void box box? unbox set-box! + andmap ormap iota last-pair find + 1+ 1- fx/ fx1+ fx1- + error error? raise with-exception-handler identifier? + hash-table? make-hash-table) + (jerboa build) + (jerboa build musl)) + +;; ========== Validate musl setup ========== + +(let ([result (validate-musl-setup)]) + (unless (eq? (car result) 'ok) + (printf "Error: ~a~n" (cdr result)) + (exit 1))) + +(printf "musl Chez found: ~a~n~n" (musl-chez-lib-dir)) + +;; ========== Locate directories ========== + +(define jerboa-dir + (or (getenv "JERBOA_DIR") + (format "~a/mine/jerboa/lib" (getenv "HOME")))) + +(define native-lib-path + (format "~a/mine/jerboa/jerboa-native-rs/target/x86_64-unknown-linux-musl/release/libjerboa_native.a" + (getenv "HOME"))) + +(define has-native-lib? (file-exists? native-lib-path)) +(unless has-native-lib? + (printf "ERROR: libjerboa_native.a not found at ~a~n" native-lib-path) + (printf "Build with: cd ~/mine/jerboa/jerboa-native-rs && cargo build --release --target x86_64-unknown-linux-musl~n") + (exit 1)) + +(printf "Native lib: ~a~n~n" native-lib-path) + +;; ========== Step 0: Stage and patch jerboa std modules for static builds ========== +;; Modules that call (load-shared-object ...) need to be patched to (void) +;; since dlopen is unavailable in static musl builds. FFI symbols are +;; pre-registered via Sforeign_symbol() in the C bootstrap. + +(printf "[0/7] Patching jerboa modules for static build (no dlopen)...~n") + +(define jerboa-stage (format "~a/jerboa-stage" (current-directory))) +(system (format "rm -rf '~a'" jerboa-stage)) +(system (format "mkdir -p '~a'" jerboa-stage)) + +;; Copy the jerboa lib tree to staging +(system (format "cp -a '~a/' '~a/'" jerboa-dir jerboa-stage)) + +;; Patch all load-shared-object calls in staged copies +(system (format "find '~a' -name '*.sls' -exec sed -i 's/(load-shared-object[^)]*)/(void)/g' {} +" jerboa-stage)) +;; Delete pre-compiled .so/.wpo files to force recompilation from patched sources +(system (format "find '~a' -name '*.so' -delete" jerboa-stage)) +(system (format "find '~a' -name '*.wpo' -delete" jerboa-stage)) + +(printf " Patched jerboa sources staged in ~a~n" jerboa-stage) + +;; ========== Step 1: Compile all secmon modules ========== + +(printf "~n[1/7] Compiling secmon modules...~n") + +;; Use the staged (patched) jerboa as primary library path +(parameterize ([optimize-level 2] + [generate-inspector-information #f] + [compile-imported-libraries #t] + [library-directories + (cons (cons jerboa-stage jerboa-stage) + (library-directories))]) + ;; Crypto modules (dependency order) + (for-each + (lambda (m) + (let ([path (format "lib/secmon/crypto/~a.sls" m)]) + (printf " Compiling ~a~n" path) + (compile-library path))) + '("keys" "ecies" "psk")) + + ;; Monitor events (base types first) + (printf " Compiling lib/secmon/monitor/events.sls~n") + (compile-library "lib/secmon/monitor/events.sls") + (printf " Compiling lib/secmon/monitor/suspicious.sls~n") + (compile-library "lib/secmon/monitor/suspicious.sls") + + ;; Platform + (for-each + (lambda (m) + (let ([path (format "lib/secmon/platform/~a.sls" m)]) + (printf " Compiling ~a~n" path) + (compile-library path))) + '("provider" "linux")) + + ;; Buffer + (printf " Compiling lib/secmon/buffer/ring.sls~n") + (compile-library "lib/secmon/buffer/ring.sls") + + ;; Server + (for-each + (lambda (m) + (let ([path (format "lib/secmon/server/~a.sls" m)]) + (printf " Compiling ~a~n" path) + (compile-library path))) + '("protocol" "listener")) + + ;; Config + (printf " Compiling lib/secmon/config.sls~n") + (compile-library "lib/secmon/config.sls") + + ;; Storage + (printf " Compiling lib/secmon/storage/store.sls~n") + (compile-library "lib/secmon/storage/store.sls") + + ;; All monitor modules + (for-each + (lambda (m) + (let ([path (format "lib/secmon/monitor/~a.sls" m)]) + (printf " Compiling ~a~n" path) + (compile-library path))) + '("process" "network" "files" "auth" "kernel" "cron" + "container" "dns" "rootkit" "persistence" "revshell" + "lateral" "logtamper" "webshell" "podman" "selinux")) + + ;; Stealth modules + (for-each + (lambda (m) + (let ([path (format "lib/secmon/stealth/~a.sls" m)]) + (printf " Compiling ~a~n" path) + (compile-library path))) + '("anti-debug" "masquerade" "env-sanitize" "integrity" "init"))) + +;; ========== Step 2: Compile agent program ========== + +(printf "~n[2/7] Compiling bin/agent.ss (optimize-level 3)...~n") + +(parameterize ([compile-imported-libraries #t] + [optimize-level 3] + [cp0-effort-limit 500] + [cp0-score-limit 50] + [cp0-outer-unroll-limit 1] + [commonization-level 4] + [enable-unsafe-application #t] + [enable-unsafe-variable-reference #t] + [enable-arithmetic-left-associative #t] + [debug-level 0] + [generate-inspector-information #f] + [library-directories + (cons (cons jerboa-stage jerboa-stage) + (library-directories))]) + (compile-program "bin/agent.ss")) + +;; ========== Step 3: Pre-compile boot-file dependencies ========== +;; Ensure all jerboa std modules used by secmon are compiled + +(printf "~n[3/7] Pre-compiling boot file dependencies...~n") + +(define jerboa-boot-modules + '("jerboa/core" "jerboa/runtime" + "std/error" "std/format" "std/sort" "std/pregexp" "std/sugar" + "std/misc/string" "std/misc/list" "std/misc/alist" "std/misc/thread" + "std/foreign" + "std/os/path" "std/os/file-info" + "std/crypto/native-rust" + "std/db/sqlite-native" + "std/gambit-compat")) + +(parameterize ([compile-imported-libraries #t] + [optimize-level 2] + [generate-inspector-information #f] + [library-directories + (cons (cons jerboa-stage jerboa-stage) + (library-directories))]) + (for-each + (lambda (m) + (let ([sls (format "~a/~a.sls" jerboa-stage m)] + [so (format "~a/~a.so" jerboa-stage m)]) + (when (and (file-exists? sls) (not (file-exists? so))) + (printf " Pre-compiling ~a~n" sls) + (compile-library sls)))) + jerboa-boot-modules)) + +;; ========== Step 4: Create libs-only boot file ========== + +(printf "~n[4/7] Creating boot file...~n") + +(define boot-libs + (append + ;; Jerboa runtime + stdlib + (map (lambda (m) (format "~a/~a.so" jerboa-stage m)) + '("jerboa/core" + "jerboa/runtime" + "std/error" + "std/format" + "std/sort" + "std/pregexp" + "std/sugar" + "std/misc/string" + "std/misc/list" + "std/misc/alist" + "std/misc/thread" + "std/foreign" + "std/os/path" + "std/os/file-info" + "std/gambit-compat" + "std/crypto/native-rust" + "std/db/sqlite-native")) + ;; Secmon modules + (map (lambda (m) (format "lib/secmon/crypto/~a.so" m)) + '("keys" "ecies" "psk")) + (list "lib/secmon/monitor/events.so" + "lib/secmon/monitor/suspicious.so") + (map (lambda (m) (format "lib/secmon/platform/~a.so" m)) + '("provider" "linux")) + (list "lib/secmon/buffer/ring.so" + "lib/secmon/server/protocol.so" + "lib/secmon/server/listener.so" + "lib/secmon/config.so" + "lib/secmon/storage/store.so") + (map (lambda (m) (format "lib/secmon/monitor/~a.so" m)) + '("process" "network" "files" "auth" "kernel" "cron" + "container" "dns" "rootkit" "persistence" "revshell" + "lateral" "logtamper" "webshell" "podman" "selinux")) + (map (lambda (m) (format "lib/secmon/stealth/~a.so" m)) + '("anti-debug" "masquerade" "env-sanitize" "integrity" "init")))) + +;; Verify all .so files exist +(for-each + (lambda (so) + (unless (file-exists? so) + (printf "ERROR: Missing boot file dependency: ~a~n" so) + (exit 1))) + boot-libs) + +(apply make-boot-file "secmon-agent.boot" '("scheme" "petite") boot-libs) +(printf " Created secmon-agent.boot~n") + +;; ========== Step 5: Generate C code ========== + +(printf "~n[5/7] Generating C code with embedded boot files + FFI symbols...~n") + +(define musl-lib-dir (musl-chez-lib-dir)) +(define petite-boot-path (format "~a/petite.boot" musl-lib-dir)) +(define scheme-boot-path (format "~a/scheme.boot" musl-lib-dir)) + +;; Generate the C source +(call-with-output-file "secmon-program.c" + (lambda (out) + ;; Headers + (display "#include \"scheme.h\"\n" out) + (display "#include <string.h>\n" out) + (display "#include <stdlib.h>\n" out) + (display "#include <stdio.h>\n" out) + (display "#include <signal.h>\n" out) + (display "#include <unistd.h>\n" out) + (display "#include <errno.h>\n" out) + (display "#include <sys/types.h>\n" out) + (display "#include <sys/socket.h>\n" out) + (display "#include <sys/wait.h>\n" out) + (display "#include <sys/mman.h>\n" out) + (display "#include <sys/prctl.h>\n" out) + (display "#include <sys/ptrace.h>\n" out) + (display "#include <netinet/in.h>\n" out) + (display "#include <arpa/inet.h>\n\n" out) + + ;; Embed boot files as C arrays + (display (file->c-array petite-boot-path "petite_boot") out) + (newline out) + (display (file->c-array scheme-boot-path "scheme_boot") out) + (newline out) + (display (file->c-array "secmon-agent.boot" "app_boot") out) + (newline out) + + ;; Embed the compiled program + (display (file->c-array "bin/agent.so" "program_so") out) + (newline out) + + ;; Extern declarations for Rust native library symbols + (display "/* Rust native library symbols (libjerboa_native.a) */\n" out) + (for-each + (lambda (sym) + (fprintf out "extern int ~a();\n" sym)) + '("jerboa_last_error" + "jerboa_sha1" "jerboa_sha256" "jerboa_sha384" "jerboa_sha512" + "jerboa_random_bytes" + "jerboa_hmac_sha256" "jerboa_hmac_sha256_verify" + "jerboa_timing_safe_equal" + "jerboa_aead_seal" "jerboa_aead_open" + "jerboa_chacha20_seal" "jerboa_chacha20_open" + "jerboa_scrypt" + "jerboa_pbkdf2_derive" "jerboa_pbkdf2_verify" + "jerboa_x25519_generate_keypair" + "jerboa_x25519_public_from_private" + "jerboa_x25519_diffie_hellman" + "jerboa_hkdf_sha256" + ;; SQLite + "jerboa_sqlite_open" "jerboa_sqlite_close" "jerboa_sqlite_exec" + "jerboa_sqlite_prepare" "jerboa_sqlite_finalize" "jerboa_sqlite_reset" + "jerboa_sqlite_bind_int" "jerboa_sqlite_bind_double" + "jerboa_sqlite_bind_text" "jerboa_sqlite_bind_blob" + "jerboa_sqlite_bind_null" + "jerboa_sqlite_step" + "jerboa_sqlite_column_count" "jerboa_sqlite_column_type" + "jerboa_sqlite_column_int" "jerboa_sqlite_column_double" + "jerboa_sqlite_column_text" "jerboa_sqlite_column_blob" + "jerboa_sqlite_column_name" + "jerboa_sqlite_last_insert_rowid" "jerboa_sqlite_changes" + "jerboa_sqlite_errmsg")) + (newline out) + + ;; FFI symbol registration function + (display "static void register_ffi_symbols(void) {\n" out) + ;; libc symbols (needed by secmon monitors and jerboa std) + (for-each + (lambda (sym) + (fprintf out " Sforeign_symbol(\"~a\", (void*)~a);\n" sym sym)) + '("socket" "connect" "setsockopt" "bind" "listen" "accept" + "fork" "ptrace" "waitpid" "_exit" "kill" + "getpid" "getppid" "prctl" "mlockall" + "readlink" "unsetenv" "access" + "__errno_location" + "read" "write" "close" "open" + "memfd_create")) + ;; Rust native symbols + (for-each + (lambda (sym) + (fprintf out " Sforeign_symbol(\"~a\", (void*)~a);\n" sym sym)) + '("jerboa_last_error" + "jerboa_sha1" "jerboa_sha256" "jerboa_sha384" "jerboa_sha512" + "jerboa_random_bytes" + "jerboa_hmac_sha256" "jerboa_hmac_sha256_verify" + "jerboa_timing_safe_equal" + "jerboa_aead_seal" "jerboa_aead_open" + "jerboa_chacha20_seal" "jerboa_chacha20_open" + "jerboa_scrypt" + "jerboa_pbkdf2_derive" "jerboa_pbkdf2_verify" + "jerboa_x25519_generate_keypair" + "jerboa_x25519_public_from_private" + "jerboa_x25519_diffie_hellman" + "jerboa_hkdf_sha256" + "jerboa_sqlite_open" "jerboa_sqlite_close" "jerboa_sqlite_exec" + "jerboa_sqlite_prepare" "jerboa_sqlite_finalize" "jerboa_sqlite_reset" + "jerboa_sqlite_bind_int" "jerboa_sqlite_bind_double" + "jerboa_sqlite_bind_text" "jerboa_sqlite_bind_blob" + "jerboa_sqlite_bind_null" + "jerboa_sqlite_step" + "jerboa_sqlite_column_count" "jerboa_sqlite_column_type" + "jerboa_sqlite_column_int" "jerboa_sqlite_column_double" + "jerboa_sqlite_column_text" "jerboa_sqlite_column_blob" + "jerboa_sqlite_column_name" + "jerboa_sqlite_last_insert_rowid" "jerboa_sqlite_changes" + "jerboa_sqlite_errmsg")) + (display "}\n\n" out) + + ;; dlopen/dlsym stubs — needed because Chez tries to use them + ;; even when all symbols are pre-registered + (display "/* dlopen/dlsym stubs for static musl builds */\n" out) + (display "void *dlopen(const char *file, int mode) {\n" out) + (display " (void)file; (void)mode;\n" out) + (display " return (void*)1; /* non-NULL = success */\n" out) + (display "}\n\n" out) + + (display "void *dlsym(void *handle, const char *name) {\n" out) + (display " (void)handle; (void)name;\n" out) + (display " return NULL;\n" out) + (display "}\n\n" out) + + (display "int dlclose(void *handle) {\n" out) + (display " (void)handle;\n" out) + (display " return 0;\n" out) + (display "}\n\n" out) + + (display "char *dlerror(void) {\n" out) + (display " return \"dlopen not supported in static build\";\n" out) + (display "}\n\n" out) + + ;; memfd_create stub if not available + (display "#ifndef __NR_memfd_create\n" out) + (display "#define __NR_memfd_create 319\n" out) + (display "#endif\n\n" out) + + ;; Main function: bootstrap Chez and run the agent + ;; Uses Sscheme_script for threading support (unlike Sscheme_program + ;; which evaluates at heap build time before threads are ready) + (display "int main(int argc, const char *argv[]) {\n" out) + (display " Sscheme_init(NULL);\n" out) + (display " register_ffi_symbols();\n\n" out) + (display " Sregister_boot_file_bytes(\"petite\", petite_boot, petite_boot_len);\n" out) + (display " Sregister_boot_file_bytes(\"scheme\", scheme_boot, scheme_boot_len);\n" out) + (display " Sregister_boot_file_bytes(\"app\", app_boot, app_boot_len);\n\n" out) + (display " Sbuild_heap(argv[0], NULL);\n\n" out) + ;; Write program .so to a temp memfd and load via Sscheme_script + ;; This allows the program to run with threading support + (display " /* Load program via memfd for threading support */\n" out) + (display " int fd = memfd_create(\"secmon\", MFD_CLOEXEC);\n" out) + (display " if (fd < 0) { fd = memfd_create(\"secmon\", 0); }\n" out) + (display " if (fd >= 0) {\n" out) + (display " write(fd, program_so, program_so_len);\n" out) + (display " char fdpath[64];\n" out) + (display " snprintf(fdpath, sizeof(fdpath), \"/proc/self/fd/%d\", fd);\n" out) + (display " Sscheme_script(fdpath);\n" out) + (display " close(fd);\n" out) + (display " } else {\n" out) + (display " /* Fallback: write to /tmp */\n" out) + (display " FILE *f = fopen(\"/tmp/.secmon-program.so\", \"wb\");\n" out) + (display " if (f) {\n" out) + (display " fwrite(program_so, 1, program_so_len, f);\n" out) + (display " fclose(f);\n" out) + (display " Sscheme_script(\"/tmp/.secmon-program.so\");\n" out) + (display " unlink(\"/tmp/.secmon-program.so\");\n" out) + (display " }\n" out) + (display " }\n\n" out) + (display " Sscheme_deinit();\n" out) + (display " return 0;\n" out) + (display "}\n" out)) + 'replace) + +(printf " Generated secmon-program.c~n") + +;; ========== Step 6: Compile C with musl-gcc ========== + +(printf "~n[6/7] Compiling C with musl-gcc...~n") + +(define gcc (musl-gcc-path)) +(define scheme-h-dir musl-lib-dir) + +(let ([cmd (format "~a -c -O2 -I'~a' -o secmon-program.o secmon-program.c" gcc scheme-h-dir)]) + (printf " ~a~n" cmd) + (unless (= (system cmd) 0) + (printf "ERROR: C compilation failed~n") + (exit 1))) + +;; ========== Step 7: Link static binary ========== + +(printf "~n[7/7] Linking static binary...~n") + +(define main-o (format "~a/main.o" musl-lib-dir)) +(define libkernel (format "~a/libkernel.a" musl-lib-dir)) +(define libz (let ([p (format "~a/libz.a" musl-lib-dir)]) + (if (file-exists? p) p #f))) +(define liblz4 (let ([p (format "~a/liblz4.a" musl-lib-dir)]) + (if (file-exists? p) p #f))) + +(let* ([libs (filter values + (list libkernel libz liblz4 native-lib-path))] + [lib-flags (apply string-append + (map (lambda (l) (format " '~a'" l)) libs))] + [cmd (format "~a -static secmon-program.o~a -lm -lrt -lpthread -o secmon-agent" + gcc lib-flags)]) + (printf " ~a~n" cmd) + (unless (= (system cmd) 0) + (printf "ERROR: Linking failed~n") + (exit 1))) + +;; Strip the binary +(printf "~n Stripping...~n") +(system "strip secmon-agent") + +;; Generate integrity hash +(printf " Generating SHA256 hash...~n") +(system "sha256sum secmon-agent > secmon-agent.sha256") + +;; Verify +(printf "~n=== Build complete ===~n") +(system "ls -lh secmon-agent") +(system "file secmon-agent") + +;; Cleanup +(printf "~n Cleaning up staging directory...~n") +(system (format "rm -rf '~a'" jerboa-stage)) +(system "rm -f secmon-program.c secmon-program.o secmon-agent.boot") + +(printf "~nDone.~n") --- a/lib/secmon/monitor/cron.sls +++ b/lib/secmon/monitor/cron.sls @@ -4,7 +4,7 @@ (chezscheme) (jerboa prelude clean) (secmon monitor events) - (std crypto digest) + (std crypto native-rust) (std os file-info)) ;; Cron/scheduled task monitor: detects changes to cron files, systemd timers, at-jobs @@ -60,7 +60,7 @@ (let ([bv (get-bytevector-all p)]) (close-port p) (if (eof-object? bv) "" - (digest->hex-string (sha256 bv))))))) + (bytevector->hex-string (rust-sha256 bv))))))) (define (file-mtime-safe path) (guard (e [#t 0]) --- a/lib/secmon/monitor/files.sls +++ b/lib/secmon/monitor/files.sls @@ -5,7 +5,7 @@ (jerboa prelude clean) (secmon monitor events) (secmon monitor suspicious) - (std crypto digest) + (std crypto native-rust) (std os file-info)) ;; File integrity monitor: detects changes to critical system files @@ -66,7 +66,7 @@ (guard (e [#t ""]) (let ([content (read-file-bytes path)]) (if (and content (< (bytevector-length content) 10485760)) ;; 10MB max - (digest->hex-string (sha256 content)) + (bytevector->hex-string (rust-sha256 content)) "")))) (define (read-file-bytes path) --- a/lib/secmon/monitor/persistence.sls +++ b/lib/secmon/monitor/persistence.sls @@ -5,7 +5,7 @@ (jerboa prelude clean) (secmon monitor events) (secmon monitor suspicious) - (std crypto digest)) + (std crypto native-rust)) ;; Persistence monitor: detects modifications to persistence mechanism locations (define (spawn-persistence-monitor emit! poll-ms hostname) @@ -136,7 +136,7 @@ (let ([bv (get-bytevector-all p)]) (close-port p) (if (eof-object? bv) #f - (digest->hex-string (sha256 bv))))))) + (bytevector->hex-string (rust-sha256 bv))))))) (define (bytevector->hex-string bv) (let ([len (bytevector-length bv)]) --- a/lib/secmon/stealth/integrity.sls +++ b/lib/secmon/stealth/integrity.sls @@ -3,7 +3,7 @@ (import (chezscheme) (jerboa prelude clean) - (std crypto digest)) + (std crypto native-rust)) ;; Self-integrity checking: detect binary tampering on disk (define *disk-hash* (box #f)) @@ -33,7 +33,7 @@ (let ([bv (get-bytevector-all p)]) (close-port p) (if (eof-object? bv) #f - (sha256 bv)))))))) + (rust-sha256 bv)))))))) ;; Hash the executable code section from /proc/self/maps (define (hash-code-section) @@ -70,7 +70,7 @@ (let ([bv (get-bytevector-n p size)]) (close-port p) (if (or (eof-object? bv) (not bv)) #f - (sha256 bv)))))))) + (rust-sha256 bv)))))))) ;; Start background integrity checker (every 60s) (define (start-integrity-watchdog!)