Skip to content
Merged
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
120 changes: 82 additions & 38 deletions core/ssh_session_agent.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,86 +7,130 @@ import (
"log"
"os"
"os/signal"
"os/user"
"strconv"
"time"

"github.com/cilium/ebpf"
"github.com/cilium/ebpf/link"
"github.com/cilium/ebpf/ringbuf"
"github.com/cilium/ebpf/perf"
"github.com/cilium/ebpf/rlimit"
)

type SshSessionEvent struct {
Pid uint32
Uid uint32
StartTimeNs uint64
DurationNs uint64
Comm [16]byte
type ShellEvent struct {
Pid uint32
Uid uint32
EventType uint8
_ [3]byte // padding
Comm [16]byte
}

func uidToUsername(uid uint32) string {
uidStr := strconv.Itoa(int(uid))
u, err := user.LookupId(uidStr)
if err != nil {
return "unknown"
}
return u.Username
}

func RunSSHSessionMonitor() error {
if err := rlimit.RemoveMemlock(); err != nil {
return fmt.Errorf("remove memlock: %w", err)
}

spec, err := ebpf.LoadCollectionSpec("bin/ssh_session_monitor.o")
if err != nil {
return fmt.Errorf("load spec: %w", err)
}

coll, err := ebpf.NewCollectionWithOptions(spec, ebpf.CollectionOptions{})
if err != nil {
return fmt.Errorf("create collection: %w", err)
}
defer coll.Close()

ringbufMap := coll.Maps["SSH_SESSION_RINGBUF"]
if ringbufMap == nil {
return fmt.Errorf("map SSH_SESSION_RINGBUF not found")
objs := struct {
TrackShellStart *ebpf.Program `ebpf:"track_shell_start"`
TrackShellExit *ebpf.Program `ebpf:"track_shell_exit"`
EVENTS *ebpf.Map `ebpf:"EVENTS"`
Sessions *ebpf.Map `ebpf:"sessions"`
}{}

if err := spec.LoadAndAssign(&objs, nil); err != nil {
return fmt.Errorf("load & assign: %w", err)
}
defer objs.TrackShellStart.Close()
defer objs.TrackShellExit.Close()
defer objs.EVENTS.Close()
defer objs.Sessions.Close()

kp, err := link.Kprobe("do_execveat_common", coll.Programs["track_shell_start"], nil)
tpStart, err := link.Tracepoint("sched", "sched_process_exec", objs.TrackShellStart, nil)
if err != nil {
return fmt.Errorf("attach kprobe: %w", err)
return fmt.Errorf("attach exec tracepoint: %w", err)
}
defer kp.Close()
defer tpStart.Close()

tp, err := link.Tracepoint("sched", "sched_process_exit", coll.Programs["sched_process_exit"], nil)
tpExit, err := link.Tracepoint("sched", "sched_process_exit", objs.TrackShellExit, nil)
if err != nil {
return fmt.Errorf("attach tracepoint: %w", err)
return fmt.Errorf("attach exit tracepoint: %w", err)
}
defer tp.Close()
defer tpExit.Close()

log.Println("SSH Session Monitor started. Waiting for events...")
reader, err := ringbuf.NewReader(ringbufMap)
rd, err := perf.NewReader(objs.EVENTS, os.Getpagesize())
if err != nil {
return fmt.Errorf("open ringbuf: %w", err)
return fmt.Errorf("open perf reader: %w", err)
}
defer reader.Close()
defer rd.Close()

log.Println("Monitoring shell sessions. Press Ctrl+C to exit.")

sig := make(chan os.Signal, 1)
signal.Notify(sig, os.Interrupt)

active := make(map[uint32]ShellEvent)

loop:
for {
select {
case <-sig:
log.Println("Exiting SSH Session Monitor...")
log.Println("Exiting.")
break loop

default:
record, err := reader.Read()
record, err := rd.Read()
if err != nil {
if err != ringbuf.ErrClosed {
log.Printf("read ringbuf: %v", err)
if err == perf.ErrClosed {
break loop
}
log.Printf("perf read error: %v", err)
continue
}

var event SshSessionEvent
if err := binary.Read(bytes.NewBuffer(record.RawSample), binary.LittleEndian, &event); err != nil {
log.Printf("decode event: %v", err)
var ev ShellEvent
if err := binary.Read(bytes.NewReader(record.RawSample), binary.LittleEndian, &ev); err != nil {
log.Printf("decode error: %v", err)
continue
}

start := time.Unix(0, int64(event.StartTimeNs))
duration := time.Duration(event.DurationNs)
comm := string(bytes.TrimRight(event.Comm[:], "\x00"))
username := uidToUsername(ev.Uid)
action := "START"
if ev.EventType == 1 {
action = "EXIT"
delete(active, ev.Pid)
} else {
active[ev.Pid] = ev
}

fmt.Printf("[%s] %s PID=%d UID=%d USER=%s COMM=%s\n",
time.Now().Format("15:04:05"),
action,
ev.Pid,
ev.Uid,
username,
bytes.TrimRight(ev.Comm[:], "\x00"))

fmt.Printf("[ssh_session] PID=%d UID=%d Comm=%s Started=%s Duration=%s\n",
event.Pid, event.Uid, comm, start.Format(time.RFC3339), duration)
fmt.Println("Active Shell Sessions:")
for _, s := range active {
userStr := uidToUsername(s.Uid)
fmt.Printf("- PID %d, UID %d, USER=%s, COMM=%s\n",
s.Pid, s.Uid, userStr, bytes.TrimRight(s.Comm[:], "\x00"))
}
fmt.Println()
}
}

Expand Down
6 changes: 0 additions & 6 deletions ebpf-programs/ssh_session_monitor/Cargo.toml

This file was deleted.

14 changes: 14 additions & 0 deletions ebpf-programs/ssh_session_monitor/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# ebpf-programs/ssh_session_monitor/Makefile

CLANG ?= clang
TARGET = ssh_session_monitor
SRC = ssh_session_monitor.c
OBJ = ../../bin/$(TARGET).o

all: $(OBJ)

$(OBJ): $(SRC)
$(CLANG) -O2 -g -target bpf -D__TARGET_ARCH_x86 -I. -c $(SRC) -o $(OBJ)

clean:
rm -f $(OBJ)
11 changes: 0 additions & 11 deletions ebpf-programs/ssh_session_monitor/ssh-session-ebpf/Cargo.toml

This file was deleted.

25 changes: 0 additions & 25 deletions ebpf-programs/ssh_session_monitor/ssh-session-ebpf/src/lib.rs

This file was deleted.

66 changes: 66 additions & 0 deletions ebpf-programs/ssh_session_monitor/ssh_session_monitor.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
// ebpf-programs/ssh_session_monitor/ssh_session_monitor.c

#include "vmlinux.h"
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_tracing.h>

char LICENSE[] SEC("license") = "GPL";

#define TASK_COMM_LEN 16

struct shell_event {
u32 pid;
u32 uid;
u8 event_type; // 0 = start, 1 = exit
char comm[TASK_COMM_LEN];
};

struct {
__uint(type, BPF_MAP_TYPE_HASH);
__type(key, u32);
__type(value, struct shell_event);
__uint(max_entries, 1024);
} sessions SEC(".maps");

struct {
__uint(type, BPF_MAP_TYPE_PERF_EVENT_ARRAY);
__type(key, int);
__type(value, __u32);
} EVENTS SEC(".maps");

// Tracepoint for shell start
SEC("tracepoint/sched/sched_process_exec")
int track_shell_start(struct trace_event_raw_sched_process_exec *ctx) {
struct shell_event event = {};
u32 pid = bpf_get_current_pid_tgid() >> 32;
u32 uid = bpf_get_current_uid_gid() & 0xFFFFFFFF;
bpf_get_current_comm(&event.comm, sizeof(event.comm));

// Shell detection
if ((event.comm[0] == 'b' && event.comm[1] == 'a' && event.comm[2] == 's' && event.comm[3] == 'h') ||
(event.comm[0] == 'z' && event.comm[1] == 's' && event.comm[2] == 'h') ||
(event.comm[0] == 's' && event.comm[1] == 'h')) {

event.pid = pid;
event.uid = uid;
event.event_type = 0;

bpf_map_update_elem(&sessions, &pid, &event, BPF_ANY);
bpf_perf_event_output(ctx, &EVENTS, BPF_F_CURRENT_CPU, &event, sizeof(event));
}

return 0;
}

// Tracepoint for shell exit
SEC("tracepoint/sched/sched_process_exit")
int track_shell_exit(struct trace_event_raw_sched_process_template *ctx) {
u32 pid = bpf_get_current_pid_tgid() >> 32;
struct shell_event *event = bpf_map_lookup_elem(&sessions, &pid);
if (event) {
event->event_type = 1;
bpf_perf_event_output(ctx, &EVENTS, BPF_F_CURRENT_CPU, event, sizeof(*event));
bpf_map_delete_elem(&sessions, &pid);
}
return 0;
}
Loading