Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion examples/c/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Empty file added examples/c/Test.txt
Empty file.
75 changes: 75 additions & 0 deletions examples/c/brain.py
Original file line number Diff line number Diff line change
@@ -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)
15 changes: 15 additions & 0 deletions examples/c/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<body class="bg-black text-green-500 font-mono p-8">
<h1 class="text-2xl mb-4 border-b border-green-900">🛡️ KERNEL-TRACE AI ENGINE</h1>
<div id="log" class="space-y-1 h-[80vh] overflow-y-auto"></div>

<script>
const ws = new WebSocket(`ws://${location.host}`);
const log = document.getElementById('log');
ws.onmessage = (e) => {
const entry = document.createElement('div');
entry.className = e.data.includes('ALERT') ? 'text-red-400 animate-pulse' : '';
entry.textContent = `[${new Date().toLocaleTimeString()}] ${e.data}`;
log.prepend(entry);
};
</script>
</body>
21 changes: 21 additions & 0 deletions examples/c/server.ts
Original file line number Diff line number Diff line change
@@ -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);
}
Binary file added examples/c/spy
Binary file not shown.
37 changes: 37 additions & 0 deletions examples/c/spy.bpf.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
#include "vmlinux.h"
#include <bpf/bpf_helpers.h>
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";
61 changes: 61 additions & 0 deletions examples/c/spy.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
// SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause)
/* Copyright (c) 2020 Facebook */
#include <stdio.h>
#include <unistd.h>
#include <sys/resource.h>
#include <bpf/libbpf.h>
#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;
}