Fix build: port from Gerbil to proper Jerboa/Chez Scheme conventions
ober
91d53156513c5ba18b68ffb7721fb27767716286
new file mode 100644 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +*.so +*.wpo +keys/ --- a/Makefile +++ b/Makefile @@ -1,31 +1,34 @@ -JERBOA_DIR ?= ../jerboa -GERBIL_LOADPATH := $(JERBOA_DIR)/lib:$(CURDIR)/lib -export GERBIL_LOADPATH +SCHEME ?= scheme +JERBOA ?= $(HOME)/mine/jerboa/lib +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 build: - gerbil build + $(SCHEME) -q --libdirs $(LIBDIRS) --compile-imported-libraries < build-all.ss clean: - gerbil build clean + find lib -name '*.so' -delete + find lib -name '*.wpo' -delete keygen: - gxi bin/keygen.ss + $(SCHEME) --libdirs $(LIBDIRS) --script bin/keygen.ss agent: - gxi bin/agent.ss + $(SCHEME) --libdirs $(LIBDIRS) --script bin/agent.ss collector: - gxi bin/collector.ss $(ARGS) + $(SCHEME) --libdirs $(LIBDIRS) --script bin/collector.ss $(ARGS) analyze: - gxi bin/analyze.ss $(ARGS) + $(SCHEME) --libdirs $(LIBDIRS) --script bin/analyze.ss $(ARGS) test: @echo "Running tests..." @for f in tests/*-test.ss; do \ echo "Testing $$f..."; \ - gxi $$f || exit 1; \ + $(SCHEME) --libdirs $(LIBDIRS) --script $$f || exit 1; \ done @echo "All tests passed." --- a/bin/agent.ss +++ b/bin/agent.ss @@ -1,8 +1,8 @@ -#!/usr/bin/env gxi +#!chezscheme ;; secmon-agent: Security monitoring agent (import (chezscheme) - (jerboa prelude) + (jerboa prelude clean) (secmon config) (secmon crypto ecies) (secmon crypto psk) @@ -30,7 +30,7 @@ (secmon stealth init) (std text hex)) -(def (main . args) +(define (main . args) (let ([debug-mode (and (getenv "SECMON_DEBUG") #t)]) ;; Stealth init (unless debug mode) @@ -110,3 +110,5 @@ (let ([server (make-poll-server psk-auth buffer (agent-config-listen-addr config))]) (poll-server-run! server))))))) + +(apply main (cdr (command-line))) --- a/bin/analyze.ss +++ b/bin/analyze.ss @@ -1,48 +1,19 @@ -#!/usr/bin/env gxi +#!chezscheme ;; secmon-analyze: Offline event analysis and detection (import (chezscheme) - (jerboa prelude) + (jerboa prelude clean) (secmon storage store) (std text json)) -(def (main . args) - (let ([cmd (and (not (null? args)) (car args))] - [rest (if (null? args) '() (cdr args))]) - (case (and cmd (string->symbol cmd)) - [(summary) (run-summary rest)] - [(query) (run-query rest)] - [(timeline) (run-timeline rest)] - [(ioc) (run-ioc rest)] - [(entity) (run-entity rest)] - [(detect) (run-detect rest)] - [(hosts) (run-hosts rest)] - [(retention) (run-retention rest)] - [else - (displayln "Usage: secmon-analyze <command> <events.db> [options]") - (displayln "") - (displayln "Commands:") - (displayln " summary <db> Database summary") - (displayln " query <db> [filters] Query events") - (displayln " timeline <db> --host <name> Host timeline") - (displayln " ioc <db> --list <file> IOC matching") - (displayln " entity <db> <type> <value> Entity timeline") - (displayln " detect <db> [--rule name] Run detection rules") - (displayln " hosts <db> List known hosts") - (displayln " retention <db> --delete-before <time> Delete old events") - (displayln "") - (displayln "Filters: --host, --type, --severity, --since, --until,") - (displayln " --pid, --process, --search, --limit, --format json") - (exit 1)]))) - ;; --- Argument parsing --- -(def (get-db-path args) +(define (get-db-path args) (if (null? args) (begin (displayln "ERROR: database path required") (exit 1)) (car args))) -(def (get-flag args flag) +(define (get-flag args flag) (let loop ([as args]) (cond [(null? as) #f] @@ -50,55 +21,53 @@ (if (null? (cdr as)) #f (cadr as))] [else (loop (cdr as))]))) -(def (has-flag? args flag) +(define (has-flag? args flag) (member flag args)) -(def (parse-filter-args args) - ;; Returns keyword arg list for query-events +(define (parse-filter-args args) (let ([result '()]) (let loop ([as args]) (cond [(null? as) result] [(equal? (car as) "--host") (when (not (null? (cdr as))) - (set! result (append result (list host: (cadr as)))) + (set! result (append result (list 'host: (cadr as)))) (loop (cddr as)))] [(equal? (car as) "--type") (when (not (null? (cdr as))) - (set! result (append result (list type: (cadr as)))) + (set! result (append result (list 'type: (cadr as)))) (loop (cddr as)))] [(equal? (car as) "--severity") (when (not (null? (cdr as))) - (set! result (append result (list severity: (cadr as)))) + (set! result (append result (list 'severity: (cadr as)))) (loop (cddr as)))] [(equal? (car as) "--since") (when (not (null? (cdr as))) - (set! result (append result (list since-ms: (parse-time-arg (cadr as))))) + (set! result (append result (list 'since-ms: (parse-time-arg (cadr as))))) (loop (cddr as)))] [(equal? (car as) "--until") (when (not (null? (cdr as))) - (set! result (append result (list until-ms: (parse-time-arg (cadr as))))) + (set! result (append result (list 'until-ms: (parse-time-arg (cadr as))))) (loop (cddr as)))] [(equal? (car as) "--pid") (when (not (null? (cdr as))) - (set! result (append result (list pid: (string->number (cadr as))))) + (set! result (append result (list 'pid: (string->number (cadr as))))) (loop (cddr as)))] [(equal? (car as) "--process") (when (not (null? (cdr as))) - (set! result (append result (list process: (cadr as)))) + (set! result (append result (list 'process: (cadr as)))) (loop (cddr as)))] [(equal? (car as) "--search") (when (not (null? (cdr as))) - (set! result (append result (list search: (cadr as)))) + (set! result (append result (list 'search: (cadr as)))) (loop (cddr as)))] [(equal? (car as) "--limit") (when (not (null? (cdr as))) - (set! result (append result (list limit: (string->number (cadr as))))) + (set! result (append result (list 'limit: (string->number (cadr as))))) (loop (cddr as)))] [else (loop (cdr as))])))) -(def (parse-time-arg s) - ;; Parse "24h", "7d", "30d" or epoch ms +(define (parse-time-arg s) (let ([now (current-time-ms)]) (cond [(string-suffix? "h" s) @@ -112,7 +81,7 @@ ;; --- Commands --- -(def (run-summary args) +(define (run-summary args) (let* ([db (get-db-path args)] [store (open-event-store db)] [summary (store-summary store)]) @@ -149,7 +118,7 @@ (format-ts (cdr (assoc "min_ts" tr))) (format-ts (cdr (assoc "max_ts" tr))))))))) -(def (run-query args) +(define (run-query args) (let* ([db (get-db-path args)] [rest (cdr args)] [store (open-event-store db)] @@ -160,7 +129,7 @@ (lambda (row) (print-row row format-json?)) rows))) -(def (run-timeline args) +(define (run-timeline args) (let* ([db (get-db-path args)] [rest (cdr args)] [host (get-flag rest "--host")] @@ -170,12 +139,12 @@ (unless host (displayln "ERROR: --host required for timeline") (exit 1)) - (let ([rows (apply query-events store host: host filters)]) + (let ([rows (apply query-events store 'host: host filters)]) (for-each (lambda (row) (print-row row format-json?)) (reverse rows))))) -(def (run-ioc args) +(define (run-ioc args) (let* ([db (get-db-path args)] [rest (cdr args)] [ioc-file (get-flag rest "--list")] @@ -201,8 +170,7 @@ matches)))) iocs)))) -(def (load-ioc-file path) - ;; Returns list of (value . type) +(define (load-ioc-file path) (guard (e [#t (displayln (format "ERROR: cannot read ~a" path)) '()]) (let ([lines (filter (lambda (line) @@ -218,34 +186,31 @@ (cons val (auto-detect-ioc-type val)))) lines)))) -(def (auto-detect-ioc-type val) +(define (auto-detect-ioc-type val) (cond - ;; IPv4 [(and (string-contains val ".") (let ([parts (string-split val #\.)]) (and (= (length parts) 4) (andmap (lambda (p) (string->number p)) parts)))) 'ip] - ;; Hex hash (32=MD5, 40=SHA1, 64=SHA256) [(and (memv (string-length val) '(32 40 64)) - (string-every (lambda (c) (or (char-numeric? c) - (memv c '(#\a #\b #\c #\d #\e #\f)))) val)) + (string-every-char + (lambda (c) (or (char-numeric? c) + (memv c '(#\a #\b #\c #\d #\e #\f)))) val)) 'hash] - ;; Domain (contains dot, no spaces) [(and (string-contains val ".") (not (string-contains val " "))) 'domain] - ;; Default to process [else 'process])) -(def (string-every pred s) +(define (string-every-char pred s) (let loop ([i 0]) (if (= i (string-length s)) #t (if (pred (string-ref s i)) (loop (+ i 1)) #f)))) -(def (run-entity args) +(define (run-entity args) (when (< (length args) 3) (displayln "Usage: secmon-analyze entity <db> <type> <value> [filters]") (exit 1)) @@ -262,7 +227,7 @@ (lambda (row) (print-row row format-json?)) rows))) -(def detection-rules +(define detection-rules '((brute_force . "5+ auth failures for same user in 10min") (credential_stuffing . "5+ distinct usernames failing from same source in 10min") (dns_tunnel . "50+ DNS queries from same process in 5min") @@ -274,7 +239,7 @@ (log_cover . "Critical event -> log tampering within 1h") (lateral_after_shell . "Reverse shell -> lateral movement within 1h"))) -(def (run-detect args) +(define (run-detect args) (let* ([db (get-db-path args)] [rest (cdr args)] [store (open-event-store db)] @@ -293,21 +258,21 @@ detection-rules)] [rule-name (let ([results (run-detection-rules store - (string->symbol rule-name) since-ms: since-ms)]) + (string->symbol rule-name) 'since-ms: since-ms)]) (displayln (format "--- Rule: ~a - ~a hits ---" rule-name (length results))) (for-each (lambda (row) (print-detection-row row format-json?)) results))] [else - (let ([results (run-detection-rules store 'all since-ms: since-ms)]) + (let ([results (run-detection-rules store 'all 'since-ms: since-ms)]) (displayln (format "--- All rules - ~a total hits ---" (length results))) (for-each (lambda (row) (print-detection-row row format-json?)) results))]))) -(def (run-hosts args) +(define (run-hosts args) (let* ([db (get-db-path args)] [store (open-event-store db)] [hosts (store-list-hosts store)]) @@ -321,7 +286,7 @@ (cdr (assoc "hostname" row))))) hosts))) -(def (run-retention args) +(define (run-retention args) (let* ([db (get-db-path args)] [rest (cdr args)] [before (get-flag rest "--delete-before")] @@ -336,7 +301,7 @@ ;; --- Output helpers --- -(def (print-row row format-json?) +(define (print-row row format-json?) (if format-json? (displayln (row->json row)) (begin @@ -351,12 +316,12 @@ (display (cdr (assoc "summary" row))) (newline)))) -(def (print-detection-row row format-json?) +(define (print-detection-row row format-json?) (if format-json? (displayln (row->json row)) (displayln (format " ~a" row)))) -(def (row->json row) +(define (row->json row) (let ([ht (make-hashtable string-hash string=?)]) (for-each (lambda (pair) @@ -364,14 +329,14 @@ row) (json-object->string ht))) -(def (severity-marker sev) +(define (severity-marker sev) (cond [(equal? sev "critical") "!!!"] [(equal? sev "high") "!! "] [(equal? sev "medium") "! "] [else " "])) -(def (format-ts ms) +(define (format-ts ms) (guard (e [#t (format "~a" ms)]) (let* ([secs (quotient ms 1000)] [t (make-time 'time-utc 0 secs)] @@ -380,7 +345,40 @@ (date-year d) (date-month d) (date-day d) (date-hour d) (date-minute d) (date-second d))))) -(def (current-time-ms) +(define (current-time-ms) (let ([t (current-time)]) (+ (* (time-second t) 1000) (quotient (time-nanosecond t) 1000000)))) + +;; --- Main --- + +(define (main . args) + (let ([cmd (and (not (null? args)) (car args))] + [rest (if (null? args) '() (cdr args))]) + (case (and cmd (string->symbol cmd)) + [(summary) (run-summary rest)] + [(query) (run-query rest)] + [(timeline) (run-timeline rest)] + [(ioc) (run-ioc rest)] + [(entity) (run-entity rest)] + [(detect) (run-detect rest)] + [(hosts) (run-hosts rest)] + [(retention) (run-retention rest)] + [else + (displayln "Usage: secmon-analyze <command> <events.db> [options]") + (displayln "") + (displayln "Commands:") + (displayln " summary <db> Database summary") + (displayln " query <db> [filters] Query events") + (displayln " timeline <db> --host <name> Host timeline") + (displayln " ioc <db> --list <file> IOC matching") + (displayln " entity <db> <type> <value> Entity timeline") + (displayln " detect <db> [--rule name] Run detection rules") + (displayln " hosts <db> List known hosts") + (displayln " retention <db> --delete-before <time> Delete old events") + (displayln "") + (displayln "Filters: --host, --type, --severity, --since, --until,") + (displayln " --pid, --process, --search, --limit, --format json") + (exit 1)]))) + +(apply main (cdr (command-line))) --- a/bin/collector.ss +++ b/bin/collector.ss @@ -1,8 +1,8 @@ -#!/usr/bin/env gxi +#!chezscheme ;; secmon-collector: Collect and decrypt events from remote agents (import (chezscheme) - (jerboa prelude) + (jerboa prelude clean) (secmon config) (secmon crypto ecies) (secmon crypto psk) @@ -12,25 +12,9 @@ (std text json) (std text hex)) -(def (main . args) - (let ([cmd (and (not (null? args)) (car args))] - [rest (if (null? args) '() (cdr args))]) - (case (and cmd (string->symbol cmd)) - [(watch) (run-watch rest)] - [(poll) (run-poll rest)] - [(status) (run-status rest)] - [else - (displayln "Usage: secmon-collector <command> [options]") - (displayln "") - (displayln "Commands:") - (displayln " watch <host:port...> [--format json] [--db path]") - (displayln " poll <host:port> [--after seq] [--format json]") - (displayln " status <host:port>") - (exit 1)]))) - ;; --- Key loading --- -(def (load-keys) +(define (load-keys) (let-values ([(private-key psk) (load-collector-config)]) (unless (and private-key (= (bytevector-length private-key) 32)) (displayln "ERROR: Missing or invalid private key") @@ -44,10 +28,10 @@ ;; --- TCP client --- -(def (connect-to-agent host port) +(define (connect-to-agent host port) (let ([c-socket (foreign-procedure "socket" (int int int) int)] [c-connect (foreign-procedure "connect" (int u8* int) int)]) - (let ([fd (c-socket 2 1 0)]) ;; AF_INET SOCK_STREAM + (let ([fd (c-socket 2 1 0)]) (when (< fd 0) (error 'connect "socket() failed")) (let ([addr (make-connect-addr host port)]) @@ -57,11 +41,10 @@ (values (open-fd-input-port fd) (open-fd-output-port fd))))))) -(def (make-connect-addr host port) - ;; struct sockaddr_in +(define (make-connect-addr host port) (let ([addr (make-bytevector 16 0)] [ip-parts (map string->number (string-split host #\.))]) - (bytevector-u16-set! addr 0 2 (endianness native)) ;; AF_INET + (bytevector-u16-set! addr 0 2 (endianness little)) (bytevector-u16-set! addr 2 port (endianness big)) (when (= (length ip-parts) 4) (bytevector-u8-set! addr 4 (list-ref ip-parts 0)) @@ -70,16 +53,16 @@ (bytevector-u8-set! addr 7 (list-ref ip-parts 3))) addr)) -;; --- Transport I/O (same as server side) --- +;; --- Transport I/O --- -(def (send-msg! port psk-auth payload) +(define (send-msg! port psk-auth payload) (let* ([encrypted (psk-encrypt-transport psk-auth payload)] [len-bytes (pack-u32-le (bytevector-length encrypted))]) (put-bytevector port len-bytes) (put-bytevector port encrypted) (flush-output-port port))) -(def (recv-msg! port psk-auth) +(define (recv-msg! port psk-auth) (guard (e [#t #f]) (let* ([len-bv (get-bytevector-n port 4)] [len (unpack-u32-le len-bv 0)]) @@ -90,28 +73,24 @@ ;; --- Authenticate to agent --- -(def (authenticate! in-port out-port psk-auth) - ;; 1. Receive challenge +(define (authenticate! in-port out-port psk-auth) (let ([challenge-msg (recv-msg! in-port psk-auth)]) (unless challenge-msg (error 'authenticate "no challenge received")) (let ([tag (bytevector-u8-ref challenge-msg 0)]) (unless (= tag MSG-CHALLENGE) (error 'authenticate "unexpected message" tag)) - ;; Parse challenge (let* ([challenge-data (make-bytevector (- (bytevector-length challenge-msg) 1))] [_ (bytevector-copy! challenge-msg 1 challenge-data 0 (bytevector-length challenge-data))] [challenge (bytevector->psk-challenge challenge-data)] - ;; 2. Respond [response (psk-respond-to-challenge psk-auth challenge)] [resp-bv (psk-response->bytevector response)] [msg (bv-tag MSG-CHALLENGE-RESPONSE resp-bv)]) (send-msg! out-port psk-auth msg) - ;; 3. Wait for auth result or first request #t)))) -(def (bv-tag tag . parts) +(define (bv-tag tag . parts) (let* ([total (+ 1 (apply + (map bytevector-length parts)))] [out (make-bytevector total)]) (bytevector-u8-set! out 0 tag) @@ -123,15 +102,13 @@ ;; --- Send request and get response --- -(def (send-request! in-port out-port psk-auth req-tag . data-bvs) +(define (send-request! in-port out-port psk-auth req-tag . data-bvs) (let ([request (apply bv-tag MSG-REQUEST (make-bytevector 1 req-tag) data-bvs)]) (send-msg! out-port psk-auth request) - ;; Get response (let ([resp (recv-msg! in-port psk-auth)]) (unless resp (error 'send-request "no response")) - ;; Strip MSG-RESPONSE tag, parse response (let ([tag (bytevector-u8-ref resp 0)]) (cond [(= tag MSG-RESPONSE) @@ -145,7 +122,7 @@ ;; --- Event decryption --- -(def (decrypt-event decryptor stored-ev) +(define (decrypt-event decryptor stored-ev) (guard (e [#t #f]) (let* ([enc-bv (stored-event-encrypted-data stored-ev)] [enc-payload (bytevector->encrypted-payload enc-bv)] @@ -154,7 +131,7 @@ ;; --- Print event --- -(def (print-event ev format-json?) +(define (print-event ev format-json?) (if format-json? (displayln (json-object->string (security-event->json ev))) (begin @@ -169,8 +146,7 @@ (print-event-body ev) (newline)))) -(def (format-timestamp ms) - ;; Simple timestamp format +(define (format-timestamp ms) (let* ([secs (quotient ms 1000)] [t (make-time 'time-utc 0 secs)] [d (time-utc->date t 0)]) @@ -178,14 +154,14 @@ (date-year d) (date-month d) (date-day d) (date-hour d) (date-minute d) (date-second d)))) -(def (severity-marker sev) +(define (severity-marker sev) (case sev [(critical) "!!!"] [(high) "!! "] [(medium) "! "] [else " "])) -(def (print-event-body ev) +(define (print-event-body ev) (let ([data (security-event-data ev)] [type (security-event-event-type ev)]) (case type @@ -261,7 +237,7 @@ [else (display (json-object->string data))]))) -(def (ht-ref ht key . default) +(define (ht-ref ht key . default) (let ([d (if (null? default) "" (car default))]) (if (hashtable? ht) (hashtable-ref ht key d) @@ -269,13 +245,13 @@ ;; --- Commands --- -(def (parse-host-port str) +(define (parse-host-port str) (let ([parts (string-split str #\:)]) (if (>= (length parts) 2) (values (car parts) (or (string->number (cadr parts)) 31337)) (values str 31337)))) -(def (run-status args) +(define (run-status args) (when (null? args) (displayln "Usage: secmon-collector status <host:port>") (exit 1)) @@ -291,11 +267,10 @@ (close-port in) (close-port out)))))) -(def (run-poll args) +(define (run-poll args) (let ([format-json? (member "--format" args)] [after-seq 0] [host-port #f]) - ;; Parse args (let loop ([as args]) (cond [(null? as) (void)] @@ -328,11 +303,10 @@ (close-port in) (close-port out))))))) -(def (run-watch args) +(define (run-watch args) (let ([format-json? (member "--format" args)] [db-path #f] [hosts '()]) - ;; Parse args (let loop ([as args]) (cond [(null? as) (void)] @@ -353,7 +327,6 @@ (let ([psk-auth (make-psk-auth psk)] [decryptor (make-ecies-decryptor private-key)] [store (and db-path (open-event-store db-path))]) - ;; Spawn watcher thread per host (for-each (lambda (host-str) (fork-thread @@ -365,21 +338,19 @@ (sleep (make-time 'time-duration 0 3600)) (loop)))))) -(def (watch-host host-str psk-auth decryptor store format-json?) +(define (watch-host host-str psk-auth decryptor store format-json?) (let-values ([(host port) (parse-host-port host-str)]) (let ([last-seq (box 0)] [backoff (box 1)]) (let loop () (guard (e [#t - ;; Exponential backoff on error (let ([wait (unbox backoff)]) (sleep (make-time 'time-duration 0 wait)) (set-box! backoff (min 30 (* wait 2))) (loop))]) (let-values ([(in out) (connect-to-agent host port)]) (authenticate! in out psk-auth) - (set-box! backoff 1) ;; Reset backoff on successful connect - ;; Continuous poll loop + (set-box! backoff 1) (let poll-loop () (let ([resp (send-request! in out psk-auth REQ-GET-EVENTS-AFTER @@ -390,7 +361,6 @@ (let ([ev (decrypt-event decryptor stored-ev)]) (when ev (print-event ev format-json?) - ;; Store to DB if configured (when store (store-decrypted-event! store stored-ev ev host-str))) (set-box! last-seq (stored-event-seq stored-ev)))) @@ -398,7 +368,7 @@ (sleep (make-time 'time-duration 0 1)) (poll-loop)))))))) -(def (store-decrypted-event! store stored-ev ev host-str) +(define (store-decrypted-event! store stored-ev ev host-str) (guard (e [#t (void)]) (let ([data (security-event-data ev)]) (store-event! store @@ -416,7 +386,7 @@ (stored-event-seq stored-ev) (security-event-hostname ev))))) -(def (format-event-summary ev) +(define (format-event-summary ev) (let ([type (security-event-event-type ev)] [data (security-event-data ev)]) (case type @@ -428,3 +398,23 @@ (ht-ref data "process_name") (ht-ref data "remote_addr") (ht-ref data "remote_port"))] [else (symbol->string type)]))) + +;; --- Main --- + +(define (main . args) + (let ([cmd (and (not (null? args)) (car args))] + [rest (if (null? args) '() (cdr args))]) + (case (and cmd (string->symbol cmd)) + [(watch) (run-watch rest)] + [(poll) (run-poll rest)] + [(status) (run-status rest)] + [else + (displayln "Usage: secmon-collector <command> [options]") + (displayln "") + (displayln "Commands:") + (displayln " watch <host:port...> [--format json] [--db path]") + (displayln " poll <host:port> [--after seq] [--format json]") + (displayln " status <host:port>") + (exit 1)]))) + +(apply main (cdr (command-line))) --- a/bin/keygen.ss +++ b/bin/keygen.ss @@ -1,15 +1,14 @@ -#!/usr/bin/env gxi +#!chezscheme ;; secmon-keygen: Generate ECIES keypair and PSK (import + (chezscheme) + (jerboa prelude clean) (secmon crypto keys) (std text hex)) -(def (main . args) - ;; Generate ECIES keypair +(define (main . args) (let-values ([(private-key public-key) (generate-keypair)]) - ;; Generate PSK (let ([psk (generate-psk)]) - ;; Print keys (displayln "=== SecMon Key Generation ===") (displayln "") (displayln (string-append "ECIES_PUBLIC_KEY=" @@ -43,3 +42,5 @@ (displayln (string-append " export SECMON_PSK=" (u8vector->hex-string psk))) (displayln "")))) + +(apply main (cdr (command-line))) new file mode 100644 --- /dev/null +++ b/build-all.ss @@ -0,0 +1,44 @@ +;; 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 linux) + ;; 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 anti-debug) + (secmon stealth masquerade) + (secmon stealth env-sanitize) + (secmon stealth integrity) + (secmon stealth init)) deleted file mode 100644 --- a/build.ss +++ /dev/null @@ -1,64 +0,0 @@ -#!/usr/bin/env gxi -;; build.ss - Build script for secmon -(import :gerbil/compiler :gerbil/gambit) - -(def (main . args) - (let ([cmd (if (null? args) "build" (car args))]) - (case cmd - [("build") (build-all)] - [("clean") (clean-all)] - [else (displayln (format "Unknown command: ~a" cmd))]))) - -(def (build-all) - ;; Libraries - (for-each compile-module - '("lib/secmon/crypto/keys" - "lib/secmon/crypto/ecies" - "lib/secmon/crypto/psk" - "lib/secmon/monitor/events" - "lib/secmon/monitor/suspicious" - "lib/secmon/platform/provider" - "lib/secmon/platform/linux" - "lib/secmon/buffer/ring" - "lib/secmon/server/protocol" - "lib/secmon/server/listener" - "lib/secmon/config" - "lib/secmon/storage/store" - "lib/secmon/monitor/process" - "lib/secmon/monitor/network" - "lib/secmon/monitor/files" - "lib/secmon/monitor/auth" - "lib/secmon/monitor/kernel" - "lib/secmon/monitor/cron" - "lib/secmon/monitor/container" - "lib/secmon/monitor/dns" - "lib/secmon/monitor/rootkit" - "lib/secmon/monitor/persistence" - "lib/secmon/monitor/revshell" - "lib/secmon/monitor/lateral" - "lib/secmon/monitor/logtamper" - "lib/secmon/monitor/webshell" - "lib/secmon/monitor/podman" - "lib/secmon/monitor/selinux" - "lib/secmon/stealth/anti-debug" - "lib/secmon/stealth/masquerade" - "lib/secmon/stealth/env-sanitize" - "lib/secmon/stealth/integrity" - "lib/secmon/stealth/init")) - ;; Executables - (for-each compile-exe - '("bin/keygen" - "bin/agent" - "bin/collector" - "bin/analyze"))) - -(def (compile-module path) - (displayln (format "Compiling ~a ..." path)) - (compile-file (string-append path ".sls"))) - -(def (compile-exe path) - (displayln (format "Compiling exe ~a ..." path)) - (compile-file (string-append path ".ss"))) - -(def (clean-all) - (displayln "Cleaning...")) deleted file mode 100644 --- a/gerbil.pkg +++ /dev/null @@ -1,3 +0,0 @@ -(package: secmon - version: "0.1" - depend: (jerboa)) deleted file mode 100755 Binary files a/lib/libjerboa_native.so and /dev/null differ --- a/lib/secmon/buffer/ring.sls +++ b/lib/secmon/buffer/ring.sls @@ -3,11 +3,11 @@ make-event-buffer event-buffer? buffer-store! buffer-get-after buffer-get-in-range buffer-clear-before! buffer-latest-seq buffer-count - stored-event? stored-event-seq stored-event-timestamp-ms + make-stored-event stored-event? stored-event-seq stored-event-timestamp-ms stored-event-encrypted-data) (import (chezscheme) - (jerboa prelude) + (jerboa prelude clean) (secmon monitor events) (secmon crypto ecies)) @@ -18,15 +18,11 @@ (define-record-type event-buffer (fields encryptor max-size (mutable events) (mutable next-seq) mutex) - (nongenerative event-buffer-type)) - - (define (make-event-buffer encryptor max-size) - ($make-event-buffer encryptor max-size '() 0 (make-mutex))) - - (define $make-event-buffer - (record-constructor - (make-record-constructor-descriptor - (record-type-descriptor event-buffer) #f #f))) + (nongenerative event-buffer-type) + (protocol + (lambda (new) + (lambda (encryptor max-size) + (new encryptor max-size '() 0 (make-mutex)))))) (define (buffer-store! buf event) (let ([mtx (event-buffer-mutex buf)]) --- a/lib/secmon/config.sls +++ b/lib/secmon/config.sls @@ -8,7 +8,7 @@ load-key-from-env) (import (chezscheme) - (jerboa prelude) + (jerboa prelude clean) (std text hex)) (define-record-type agent-config --- a/lib/secmon/crypto/ecies.sls +++ b/lib/secmon/crypto/ecies.sls @@ -8,7 +8,7 @@ encrypted-payload->bytevector bytevector->encrypted-payload) (import (chezscheme) - (jerboa prelude) + (jerboa prelude clean) (std crypto native-rust) (std text hex)) --- a/lib/secmon/crypto/psk.sls +++ b/lib/secmon/crypto/psk.sls @@ -10,7 +10,7 @@ psk-response->bytevector bytevector->psk-response) (import (chezscheme) - (jerboa prelude) + (jerboa prelude clean) (std crypto native-rust) (std text hex)) @@ -45,21 +45,15 @@ (define-record-type psk-auth (fields psk auth-key transport-key) - (nongenerative psk-auth-type)) - - (define (make-psk-auth psk-bytes) - ;; Derive auth and transport keys via HKDF - (let ([auth-key (hkdf-sha256 psk-bytes #f - (string->utf8 "secmon-psk-auth-v1") 32)] - [transport-key (hkdf-sha256 psk-bytes #f - (string->utf8 "secmon-psk-transport-v1") 32)]) - ($make-psk-auth psk-bytes auth-key transport-key))) - - ;; Use the auto-generated constructor name for direct creation - (define $make-psk-auth - (record-constructor - (make-record-constructor-descriptor - (record-type-descriptor psk-auth) #f #f))) + (nongenerative psk-auth-type) + (protocol + (lambda (new) + (lambda (psk-bytes) + (let ([auth-key (hkdf-sha256 psk-bytes #f + (string->utf8 "secmon-psk-auth-v1") 32)] + [transport-key (hkdf-sha256 psk-bytes #f + (string->utf8 "secmon-psk-transport-v1") 32)]) + (new psk-bytes auth-key transport-key)))))) ;; --- Challenge/Response Protocol --- --- a/lib/secmon/monitor/auth.sls +++ b/lib/secmon/monitor/auth.sls @@ -2,8 +2,9 @@ (export spawn-auth-monitor) (import (chezscheme) - (jerboa prelude) - (secmon monitor events)) + (jerboa prelude clean) + (secmon monitor events) + (std os file-info)) ;; Auth monitor: detects SSH logins, sudo, su, user changes (define (spawn-auth-monitor emit! poll-ms hostname) @@ -25,7 +26,7 @@ (tail-auth-log! log-path log-pos emit! hostname)) ;; Check utmp for login changes