build: build secmon-agent with installed jerbuild (no ~/mine/jerboa)
ober
82a963c7413d9371e8adfcd33ebe40b1afe8ee3f
--- a/.gitignore +++ b/.gitignore @@ -1,6 +1,8 @@ *.so *.wpo *.o +target/ +*-main.c keys/ secmon-agent secmon-agent.sha256 --- a/.jerbuild +++ b/.jerbuild @@ -2,8 +2,11 @@ (entry "bin/agent.ss") (output "secmon-agent") -(requires "cc" "cargo") -(notes "Uses the jerbuild-bundled jerboa-native-rs crate with crypto and SQLite enabled.") (libdirs "lib") +;; Statically register the linked-in jerboa-native symbols via Sforeign_symbol +;; so the (std crypto native-rust) + (std db sqlite-native) FFI calls resolve +;; in this static binary (dlsym(RTLD_DEFAULT) can't see dead-stripped archive +;; symbols). The list also forces those archive members to be linked in. +(ffi-symbols "support/ffi-symbols.list") (rust-crates ("@bundle/jerboa-native-rs/Cargo.toml" features: "crypto,sqlite")) --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,25 +1,28 @@ # CLAUDE.md — jerboa-secmon -## MANDATORY: Verify Docker Build +## MANDATORY: Verify the jerbuild Build -**ALWAYS run `make docker` after ANY change and verify the static binary works before committing.** +**ALWAYS run `make binary` after ANY change and verify it builds before committing.** -This is non-negotiable. Never commit without confirming: -1. `make build` succeeds (all 33 modules compile) -2. `make docker` succeeds (static binary builds in Docker) -3. The resulting `secmon-agent` binary runs +The build is self-contained: `jerbuild` bundles Chez + the jerboa stdlib + the +jerboa-native Rust crate, so it needs only `jerbuild`, a C compiler, and cargo — +no `~/mine/jerboa` checkout, no separately-built Chez, no Docker. -If the Docker build fails, fix it before doing anything else. +1. `make binary` succeeds (`jerbuild build` → static `secmon-agent`) +2. The resulting `secmon-agent` binary runs (on a Linux/FreeBSD target) + +Note: the agent links jerboa-native (crypto+sqlite) statically; its FFI symbols +are registered from `support/ffi-symbols.list` (regenerate with +`nm -gjU` on the bundled `libjerboa_native.a` if the feature set changes). +secmon targets Linux/FreeBSD; the bundled stdlib's `load-shared-object "libc.so"` +paths mean it does not run on macOS, but it builds there. ## Build Commands ```bash -make # Show help / list targets -make build # Compile all .sls → .so modules -make secmon-static # Build static binary on FreeBSD -make docker # Build fully static binary in Docker (Linux) -make secmon-musl-local # Build static binary locally (Linux, needs musl) +make binary # Build the standalone secmon-agent via jerbuild make clean # Remove all build artifacts +make agent | keygen | collector | analyze | test # Run from source (jerbuild exec) ``` ## Project Overview deleted file mode 100644 --- a/Dockerfile +++ /dev/null @@ -1,112 +0,0 @@ -# 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 \ - libncurses-dev \ - 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) ─────────────────── -# Chez is now vendored inside jerboa at vendor/ChezScheme. -RUN git clone --depth 1 https://git.sr.ht/~lisp/jerboa /tmp/jerboa-src && \ - cd /tmp/jerboa-src/vendor/ChezScheme && \ - ./configure --threads --disable-x11 --installprefix=/usr/local && \ - make -j$(nproc) && \ - make install && \ - cd /build && rm -rf /tmp/jerboa-src - -# ── Build Chez Scheme (musl, for static linking) ──────────────────────────── -# Two passes: glibc for boot files, then musl for libkernel.a -RUN git clone https://git.sr.ht/~lisp/jerboa /tmp/jerboa-musl-src && \ - cd /tmp/jerboa-musl-src/vendor/ChezScheme && \ - ./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 /tmp/jerboa-musl-src /tmp/petite.boot /tmp/scheme.boot - -# ── Clone jerboa framework ────────────────────────────────────────────────── -WORKDIR /build/mine -ARG JERBOA_CACHE_BUST=1 -RUN git clone --depth 1 https://git.sr.ht/~lisp/jerboa && echo "bust=$JERBOA_CACHE_BUST" - -# ── Patch jerboa-native-rs with files not yet pushed to remote ────────────── -# x25519.rs, process_ctl.rs, updated Cargo.toml/lib.rs needed for secmon -COPY patches/jerboa-native-rs/ /build/mine/jerboa/jerboa-native-rs/ - -# ── 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 @@ -1,121 +1,65 @@ -SCHEME ?= scheme -JERBOA ?= $(HOME)/jerboa/lib -LIBDIRS = lib:$(JERBOA) -NATIVE_RS ?= $(HOME)/mine/jerboa/jerboa-native-rs/target/release -export LD_LIBRARY_PATH := $(NATIVE_RS):$(LD_LIBRARY_PATH) +# jerbuild bundles Chez Scheme + the jerboa stdlib + the jerboa-native Rust +# crate, so building secmon-agent needs only `jerbuild`, a C compiler, and +# cargo — no jerboa source checkout and no separately-built Chez/native lib. +JERBUILD ?= jerbuild +JH := $(shell $(JERBUILD) --jerboa-home 2>/dev/null) +ifeq ($(JH),) +$(error jerbuild not found on PATH (or '$(JERBUILD) --jerboa-home' failed). Install jerbuild, or set JERBUILD=/path/to/jerbuild) +endif -.PHONY: all help build compile clean test keygen agent collector analyze docker secmon-musl secmon-musl-local secmon-static verify-harden +LIBDIRS := --libdirs lib:$(JH)/lib +JEXEC := $(JERBUILD) exec $(LIBDIRS) +BIN := secmon-agent +BIN_DIR := $(HOME)/.local/bin -all: help +.PHONY: all help build binary keygen agent collector analyze test install clean +.DEFAULT_GOAL := help + +all: binary help: - @echo "jerboa-secmon — Security monitoring agent (Scheme)" + @echo "jerboa-secmon — Security monitoring agent (Jerboa)" @echo "" - @echo "Build:" - @echo " make build Compile all .sls → .so modules" - @echo " make secmon-static Build static binary on FreeBSD" - @echo " make docker Build fully static binary in Docker (Linux)" - @echo " make secmon-musl-local Build static binary locally (Linux, needs musl)" - @echo " make clean Remove all build artifacts" + @echo "Build (needs only jerbuild + cc + cargo):" + @echo " make binary Build the standalone ./secmon-agent (.jerbuild)" + @echo " make clean Remove build artifacts" @echo "" - @echo "Run:" - @echo " make keygen Generate ECIES keypair + PSK" + @echo "Run from source (Linux/FreeBSD target):" @echo " make agent Run the monitoring agent" + @echo " make keygen Generate ECIES keypair + PSK" @echo " make collector ARGS=.. Run the event collector" @echo " make analyze ARGS=.. Run the offline analyzer" - @echo "" - @echo "Verify:" - @echo " make test Run test suite" - @echo " make verify-harden Check binary hardening (strip, leaks, hash)" - -# ─── Compilation ───────────────────────────────────────────────────────────── + @echo " make test Run the test suite" -build: compile +# Standalone native binary via .jerbuild (entry bin/agent.ss -> secmon-agent). +# Cargo-builds jerboa-native (crypto+sqlite) from jerbuild's bundled crate and +# registers its FFI symbols (support/ffi-symbols.list) into the static binary. +binary: + $(JERBUILD) build -compile: - @echo "=== Compiling .sls → .so ===" - $(SCHEME) -q --libdirs $(LIBDIRS) --compile-imported-libraries < build-all.ss +build: binary -# ─── Run Targets ───────────────────────────────────────────────────────────── +agent: + $(JEXEC) bin/agent.ss keygen: - $(SCHEME) --libdirs $(LIBDIRS) --script bin/keygen.ss - -agent: - $(SCHEME) --libdirs $(LIBDIRS) --script bin/agent.ss + $(JEXEC) bin/keygen.ss collector: - $(SCHEME) --libdirs $(LIBDIRS) --script bin/collector.ss $(ARGS) + $(JEXEC) bin/collector.ss $(ARGS) analyze: - $(SCHEME) --libdirs $(LIBDIRS) --script bin/analyze.ss $(ARGS) - -# ─── FreeBSD Static Binary ─────────────────────────────────────────────────── - -secmon-static: - @echo "=== Building static secmon-agent for FreeBSD ===" - ./build-secmon-static.sh - -# ─── musl Static Binary ───────────────────────────────────────────────────── -# Default: build via Docker for reproducibility and correct toolchain deps. -# Use `make secmon-musl-local` to build directly on the host (requires musl-gcc, -# Rust musl target, and all dependencies installed locally). - -secmon-musl: docker - -secmon-musl-local: - @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 - -# ─── Hardening Verification ───────────────────────────────────────────────── - -verify-harden: secmon-agent - @echo "=== Verifying binary hardening ===" - @echo "--- Symbol check (should show 'stripped') ---" - @(file secmon-agent | grep -qE 'stripped|no section header') && echo " PASS: binary is stripped" || echo " FAIL: binary not stripped" - @echo "--- Build path leak check ---" - @if strings secmon-agent | grep -q "$(HOME)"; then \ - echo " WARN: home directory path found in binary"; \ - else \ - echo " PASS: no home directory paths leaked"; \ - fi - @echo "--- Integrity hash check ---" - @if [ -f secmon-agent.sha256 ]; then \ - echo " PASS: secmon-agent.sha256 exists ($$(wc -c < secmon-agent.sha256) bytes)"; \ - else \ - echo " FAIL: secmon-agent.sha256 not found"; \ - fi - @echo "=== Hardening verification complete ===" - -# ─── Testing ───────────────────────────────────────────────────────────────── + $(JEXEC) bin/analyze.ss $(ARGS) test: - @echo "=== Running tests ===" - @for f in tests/*-test.ss; do \ - echo "Testing $$f..."; \ - $(SCHEME) --libdirs $(LIBDIRS) --script $$f || exit 1; \ - done + @for f in tests/*-test.ss; do echo "== $$f =="; $(JEXEC) $$f || exit 1; done @echo "All tests passed." -# ─── Cleanup ───────────────────────────────────────────────────────────────── +install: binary + mkdir -p $(BIN_DIR) + install -m 0755 $(BIN) $(BIN_DIR)/$(BIN) + @echo "Installed $(BIN) to $(BIN_DIR)/$(BIN)" clean: - find lib -name '*.so' -delete - find lib -name '*.wpo' -delete - rm -f secmon-agent secmon-agent.sha256 - rm -f secmon-program.c secmon-program.o secmon-agent.boot - rm -rf jerboa-stage/ + find lib \( -name '*.so' -o -name '*.wpo' \) -delete 2>/dev/null || true + rm -f $(BIN) $(BIN).sha256 $(BIN)-main.c deleted file mode 100644 --- a/build-all.ss +++ /dev/null @@ -1,45 +0,0 @@ -;; build-all.ss — Import all modules to trigger --compile-imported-libraries -(import - ;; Crypto - (secmon crypto keys) - (secmon crypto ecies) - (secmon crypto psk) - ;; Monitor core - (secmon monitor events) - (secmon monitor suspicious) - ;; Platform - (secmon platform provider) - (secmon platform freebsd) - ;; Buffer - (secmon buffer ring) - ;; Server - (secmon server protocol) - (secmon server listener) - ;; Config - (secmon config) - ;; Storage - (secmon storage store) - ;; Monitors - (secmon monitor process) - (secmon monitor network) - (secmon monitor files) - (secmon monitor auth) - (secmon monitor kernel) - (secmon monitor cron) - (secmon monitor container) - (secmon monitor dns) - (secmon monitor rootkit) - (secmon monitor persistence) - (secmon monitor revshell) - (secmon monitor lateral) - (secmon monitor logtamper) - (secmon monitor webshell) - (secmon monitor podman) - (secmon monitor selinux) - ;; Stealth - (secmon stealth obfuscate) - (secmon stealth anti-debug) - (secmon stealth masquerade) - (secmon stealth env-sanitize) - (secmon stealth integrity) - (secmon stealth init)) deleted file mode 100755 --- a/build-secmon-musl.sh +++ /dev/null @@ -1,60 +0,0 @@ -#!/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 deleted file mode 100644 --- a/build-secmon-musl.ss +++ /dev/null @@ -1,507 +0,0 @@ -#!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. Stages jerboa modules with load-shared-object patched out -;;; 2. Compiles all jerboa + secmon modules from staged/patched sources -;;; 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 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 contents to staging -(system (format "cp -a '~a/.' '~a/'" jerboa-dir jerboa-stage)) - -;; Patch all load-shared-object calls in staged copies -;; NOTE: Space after "load-shared-object" prevents matching "(load-shared-object* name)" -;; in jerboa/ffi.sls which would corrupt the define form -(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) - -;; ========== Override library-directories ========== -;; CRITICAL: Use ONLY the stage dir for jerboa, NOT the original. -;; The original jerboa dir has unpatched load-shared-object calls that would -;; fail during compilation (no .so to dlopen in static builds). -;; We set this globally so ALL compilation steps use the right paths. - -(define build-lib-dirs - (list (cons "lib" "lib") - (cons jerboa-stage jerboa-stage))) - -;; ========== Step 1: Compile jerboa boot modules from staged sources ========== - -(printf "~n[1/7] Compiling jerboa modules (patched, from stage)...~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/misc/ports" - "std/foreign" - "std/os/path" "std/os/file-info" - "std/text/hex" "std/text/json" - "std/match2" - "std/crypto/native-rust" - "std/db/sqlite-native" - "std/gambit-compat" - "jerboa/ffi" - "jerboa/prelude/clean")) - -(parameterize ([compile-imported-libraries #t] - [optimize-level 2] - [generate-inspector-information #f] - [library-directories build-lib-dirs]) - (for-each - (lambda (m) - (let ([sls (format "~a/~a.sls" jerboa-stage m)]) - (when (file-exists? sls) - (printf " Compiling ~a~n" m) - (compile-library sls)))) - jerboa-boot-modules)) - -;; ========== Step 2: Compile all secmon modules ========== - -(printf "~n[2/7] Compiling secmon modules...~n") - -(parameterize ([optimize-level 2] - [generate-inspector-information #f] - [compile-imported-libraries #t] - [library-directories build-lib-dirs]) - ;; Obfuscate macro (needed by many modules — compile first) - (printf " Compiling lib/secmon/stealth/obfuscate.sls~n") - (compile-library "lib/secmon/stealth/obfuscate.sls") - - ;; 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 3: Compile agent program ========== - -(printf "~n[3/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 build-lib-dirs]) - (compile-program "bin/agent.ss")) - -;; ========== Step 4: Create libs-only boot file ========== - -(printf "~n[4/7] Creating boot file...~n") - -(define boot-libs - (append - ;; Jerboa runtime + stdlib (from STAGE dir — patched, no load-shared-object) - (map (lambda (m) (format "~a/~a.so" jerboa-stage m)) - '("jerboa/core" - "jerboa/runtime" - "jerboa/ffi" - "jerboa/prelude/clean" - "std/error" - "std/format" - "std/sort" - "std/pregexp" - "std/sugar" - "std/match2" - "std/misc/string" - "std/misc/list" - "std/misc/alist" - "std/misc/thread" - "std/misc/ports" - "std/foreign" - "std/os/path" - "std/os/file-info" - "std/text/hex" - "std/text/json" - "std/gambit-compat" - "std/crypto/native-rust" - "std/db/sqlite-native")) - ;; Secmon modules (obfuscate first — many modules depend on it) - (list "lib/secmon/stealth/obfuscate.so") - (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 — _GNU_SOURCE must come before any includes for memfd_create - (display "#define _GNU_SOURCE\n" out) - (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 <fcntl.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")) - ;; 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) - - ;; _dl_find_object — referenced by libgcc_eh.a unwinding code (from Rust static lib) - ;; Not available in musl; stub returns failure (unwinding falls back to frame pointers) - (display "int _dl_find_object(void *a, void *b) { (void)a; (void)b; return -1; }\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 " 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(\"secmon\", app_boot, app_boot_len);\n" out) - (display " Sbuild_heap(NULL, NULL);\n\n" out) - (display " /* Register FFI symbols after heap is built */\n" out) - (display " register_ffi_symbols();\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\", 1 /* 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 " const char *script_args[] = { argv[0] };\n" out) - (display " Sscheme_script(fdpath, 1, script_args);\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 " const char *script_args[] = { argv[0] };\n" out) - (display " Sscheme_script(\"/tmp/.secmon-program.so\", 1, script_args);\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") deleted file mode 100755 --- a/build-secmon-static.sh +++ /dev/null @@ -1,22 +0,0 @@ -#!/bin/sh -# build-secmon-static.sh — Build static secmon-agent binary on FreeBSD -set -eu - -JERBOA_LIB="${JERBOA_DIR:-$HOME/jerboa/lib}" -NATIVE_RS="${NATIVE_RS:-$HOME/jerboa/jerboa-native-rs/target/release}" - -echo "=== Building secmon-agent (FreeBSD static) ===" -echo " Jerboa lib: $JERBOA_LIB" -echo " Native RS: $NATIVE_RS" -echo "" - -export LD_LIBRARY_PATH="${NATIVE_RS}:${LD_LIBRARY_PATH:-}" - -scheme -q --libdirs "lib:${JERBOA_LIB}" < build-secmon-static.ss - -echo "" -echo "=== Build complete ===" -if [ -f secmon-agent ]; then - echo " Size: $(ls -lh secmon-agent | awk '{print $5}')" - file secmon-agent -fi deleted file mode 100644 --- a/build-secmon-static.ss +++ /dev/null @@ -1,490 +0,0 @@ -#!chezscheme -;;; build-secmon-static.ss — Build a fully static secmon-agent binary on FreeBSD -;;; -;;; Usage: scheme -q --libdirs lib:<jerboa-lib> < build-secmon-static.ss -;;; -;;; This script: -;;; 1. Stages jerboa modules with load-shared-object patched out -;;; 2. Compiles all jerboa + secmon modules from staged/patched sources -;;; 3. Creates boot file + optimized program .so -;;; 4. Generates C files with embedded boot data + FFI symbol registration