diff --git a/examples/c/Makefile b/examples/c/Makefile index 912b4e5e..6a10680f 100644 --- a/examples/c/Makefile +++ b/examples/c/Makefile @@ -24,7 +24,7 @@ INCLUDES := -I$(OUTPUT) -I../../libbpf/include/uapi -I$(dir $(VMLINUX)) -I$(LIBB CFLAGS := -g -Wall ALL_LDFLAGS := $(LDFLAGS) $(EXTRA_LDFLAGS) -APPS = minimal minimal_legacy minimal_ns bootstrap bootstrap_legacy uprobe kprobe fentry \ +APPS = minimal spy minimal_legacy minimal_ns bootstrap bootstrap_legacy uprobe kprobe fentry \ usdt sockfilter tc ksyscall task_iter lsm CARGO ?= $(shell which cargo) diff --git a/examples/c/Test.txt b/examples/c/Test.txt new file mode 100644 index 00000000..e69de29b diff --git a/examples/c/brain.py b/examples/c/brain.py new file mode 100644 index 00000000..5cfba8c2 --- /dev/null +++ b/examples/c/brain.py @@ -0,0 +1,75 @@ +import sys +import re +from collections import deque +from sklearn.ensemble import IsolationForest + +# --- TUNING PARAMETERS --- +WINDOW_SIZE = 2000 # The AI remembers the last 2000 system events +RETRAIN_EVERY = 300 # Refresh the model every 300 new events +CONTAMINATION = 0.001 # Sensitivity (0.1% of events flagged as weird) + +# --- THE ENGINE --- +window = deque(maxlen=WINDOW_SIZE) +model = None +events_since_train = 0 + +def normalize_path(path): + """ + Strips random hashes/hex from filenames (Spotify/Zen noise). + Example: 'data_A53F00B8.bin' -> 'data_HASH.bin' + """ + return re.sub(r'[a-fA-F0-9]{8,}', 'HASH', path) + +def get_features(pid, comm, filename): + """Turns raw kernel data into a numeric vector.""" + norm_path = normalize_path(filename) + return [ + float(pid) / 100000, # Normalized PID + len(comm), # Process name length + len(norm_path), # Normalized path length + norm_path.count('/'), # Folder depth + 1 if ".cache" in filename else 0 # Cache flag + ] + +print(" KernelTrace AI: Adaptive Engine Starting...") + +try: + for line in sys.stdin: + try: + # Parse the CSV data from the C loader + parts = line.strip().split(',', 2) + if len(parts) < 3: continue + pid, comm, filename = parts + + # 1. Feature Engineering + features = get_features(pid, comm, filename) + window.append(features) + events_since_train += 1 + + # 2. Initial Training + if model is None and len(window) == WINDOW_SIZE: + print(" Baseline captured. Protection Active.") + model = IsolationForest(contamination=CONTAMINATION, n_jobs=-1) + model.fit(list(window)) + + # 3. Sliding Window Retrain + if model and events_since_train >= RETRAIN_EVERY: + model.fit(list(window)) + events_since_train = 0 + # print("AI Brain updated with recent system patterns.") + + # 4. Anomaly Detection + if model: + prediction = model.predict([features])[0] + if prediction == -1: + score = model.decision_function([features])[0] + # Print formatted for the Bun server pipe + print(f" ALERT | Score: {score:.3f} | Proc: {comm} | Path: {filename}") + sys.stdout.flush() # Ensure the pipe sees this immediately + + except ValueError: + continue + +except KeyboardInterrupt: + print("\n\n Brain shutting down safely. Great hunt!") + sys.exit(0) diff --git a/examples/c/index.html b/examples/c/index.html new file mode 100644 index 00000000..31142864 --- /dev/null +++ b/examples/c/index.html @@ -0,0 +1,15 @@ + +

🛡️ KERNEL-TRACE AI ENGINE

+
+ + + diff --git a/examples/c/server.ts b/examples/c/server.ts new file mode 100644 index 00000000..89453e83 --- /dev/null +++ b/examples/c/server.ts @@ -0,0 +1,21 @@ +// server.ts +const server = Bun.serve({ + port: 3000, + fetch(req, server) { + if (server.upgrade(req)) return; + return new Response(Bun.file("index.html")); + }, + websocket: { + open(ws) { ws.subscribe("alerts"); }, + message(ws, msg) {}, + }, +}); + +console.log("🚀 Dashboard: http://localhost:3000"); + +// PIPE: Read Python alerts from stdin and push to browser +const decoder = new TextDecoder(); +for await (const chunk of Bun.stdin.stream()) { + const text = decoder.decode(chunk); + server.publish("alerts", text); +} diff --git a/examples/c/spy b/examples/c/spy new file mode 100755 index 00000000..ebf7c96d Binary files /dev/null and b/examples/c/spy differ diff --git a/examples/c/spy.bpf.c b/examples/c/spy.bpf.c new file mode 100644 index 00000000..5518170d --- /dev/null +++ b/examples/c/spy.bpf.c @@ -0,0 +1,37 @@ +#include "vmlinux.h" + #include +struct event { + int pid; + char comm[16]; + char filename[256]; +}; + +// This defines the Ring Buffer Map +struct { + __uint(type, BPF_MAP_TYPE_RINGBUF); + __uint(max_entries, 256 * 1024); // 256KB of memory +} rb SEC(".maps"); + +SEC("tp/syscalls/sys_enter_openat") +int handle_tp(struct trace_event_raw_sys_enter *ctx) { + struct event *e; + + // 1. Reserve space in the ring buffer for one event + e = bpf_ringbuf_reserve(&rb, sizeof(*e), 0); + if (!e) + return 0; // If buffer is full, just skip (don't crash the kernel!) + + // 2. Fill the data + e->pid = bpf_get_current_pid_tgid() >> 32; // Get the actual PID + bpf_get_current_comm(&e->comm, sizeof(e->comm)); + + const char *user_ptr = (const char *)ctx->args[1]; + bpf_probe_read_user_str(&e->filename, sizeof(e->filename), user_ptr); + + // 3. Submit to the ring buffer (Python can now see it!) + bpf_ringbuf_submit(e, 0); + + return 0; +} + +char LICENSE[] SEC("license") = "Dual BSD/GPL"; diff --git a/examples/c/spy.c b/examples/c/spy.c new file mode 100644 index 00000000..b316af56 --- /dev/null +++ b/examples/c/spy.c @@ -0,0 +1,61 @@ +// SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) +/* Copyright (c) 2020 Facebook */ +#include +#include +#include +#include +#include "minimal.skel.h" +#include "spy.skel.h" + +static int libbpf_print_fn(enum libbpf_print_level level, const char *format, va_list args) +{ + return vfprintf(stderr, format, args); +} +/* This struct must match exactly what is in your spy.bpf.c */ +struct event { + int pid; + char comm[16]; + char filename[256]; +}; + +/* The Callback: This is called every time a new event arrives */ +static int handle_event(void *ctx, void *data, size_t data_sz) { + const struct event *e = data; + + /* Clean output for the Python Brain to read */ + printf("%d,%s,%s\n", e->pid, e->comm, e->filename); + fflush(stdout); + return 0; +} +int main(int argc, char **argv) { + struct spy_bpf *skel; + struct ring_buffer *rb = NULL; + int err; + + skel = spy_bpf__open_and_load(); + if (!skel) return 1; + + err = spy_bpf__attach(skel); + if (err) goto cleanup; + + /* Set up the Ring Buffer manager to use our handle_event function */ + rb = ring_buffer__new(bpf_map__fd(skel->maps.rb), handle_event, NULL, NULL); + if (!rb) { + fprintf(stderr, "Failed to create ring buffer\n"); + goto cleanup; + } + + printf("KernelSpy Active. Streaming to Brain...\n"); + + /* Poll the buffer infinitely */ + while (true) { + err = ring_buffer__poll(rb, 100 /* timeout in ms */); + if (err == -EINTR) continue; + if (err < 0) break; + } + +cleanup: + ring_buffer__free(rb); + spy_bpf__destroy(skel); + return 0; +}