diff --git a/cmd/ssh_session.go b/cmd/ssh_session.go new file mode 100644 index 0000000..9696da3 --- /dev/null +++ b/cmd/ssh_session.go @@ -0,0 +1,23 @@ +package cmd + +import ( + "fmt" + "netbarrier/core" + + "github.com/spf13/cobra" +) + +var sshSessionCmd = &cobra.Command{ + Use: "ssh-session-monitor", + Short: "Monitor SSH sessions (start and duration) using eBPF", + Run: func(cmd *cobra.Command, args []string) { + fmt.Println("Running SSH Session Monitor") + if err := core.RunSSHSessionMonitor(); err != nil { + fmt.Printf("Error: %v\n", err) + } + }, +} + +func init() { + rootCmd.AddCommand(sshSessionCmd) +} diff --git a/core/ssh_session_agent.go b/core/ssh_session_agent.go new file mode 100644 index 0000000..a483370 --- /dev/null +++ b/core/ssh_session_agent.go @@ -0,0 +1,103 @@ +package core + +import ( + "bytes" + "encoding/binary" + "fmt" + "log" + "os" + "os/signal" + "time" + + "github.com/cilium/ebpf" + "github.com/cilium/ebpf/link" + "github.com/cilium/ebpf/ringbuf" +) + +// Match Rust layout (repr(C)) +type SshSessionEvent struct { + Pid uint32 + Uid uint32 + StartTimeNs uint64 + DurationNs uint64 + Comm [16]byte +} + +func RunSSHSessionMonitor() error { + spec, err := ebpf.LoadCollectionSpec("bin/ssh_session_monitor.o") + if err != nil { + return fmt.Errorf("load spec: %w", err) + } + + // Load with no pinning or options + coll, err := ebpf.NewCollectionWithOptions(spec, ebpf.CollectionOptions{}) + if err != nil { + return fmt.Errorf("create collection: %w", err) + } + defer coll.Close() + + // Get maps + ringbufMap := coll.Maps["SSH_SESSION_RINGBUF"] + if ringbufMap == nil { + return fmt.Errorf("map SSH_SESSION_RINGBUF not found") + } + + // Attach kprobe to track shell exec + kp, err := link.Kprobe("do_execveat_common", coll.Programs["track_shell_start"], nil) + if err != nil { + return fmt.Errorf("attach kprobe: %w", err) + } + defer kp.Close() + + // Attach tracepoint to track process exit + tp, err := link.Tracepoint("sched", "sched_process_exit", coll.Programs["sched_process_exit"], nil) + if err != nil { + return fmt.Errorf("attach tracepoint: %w", err) + } + defer tp.Close() + + log.Println("SSH Session Monitor started. Waiting for events...") + + // Set up ring buffer reader + reader, err := ringbuf.NewReader(ringbufMap) + if err != nil { + return fmt.Errorf("open ringbuf: %w", err) + } + defer reader.Close() + + // Graceful shutdown on Ctrl+C + sig := make(chan os.Signal, 1) + signal.Notify(sig, os.Interrupt) + +loop: + for { + select { + case <-sig: + log.Println("Exiting SSH Session Monitor...") + break loop + default: + record, err := reader.Read() + if err != nil { + if err != ringbuf.ErrClosed { + log.Printf("read ringbuf: %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) + continue + } + + start := time.Unix(0, int64(event.StartTimeNs)) + duration := time.Duration(event.DurationNs) + comm := string(bytes.TrimRight(event.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) + } + } + + return nil +} diff --git a/ebpf-programs/ssh_session_monitor/Cargo.toml b/ebpf-programs/ssh_session_monitor/Cargo.toml new file mode 100644 index 0000000..4b791eb --- /dev/null +++ b/ebpf-programs/ssh_session_monitor/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "ssh_session_monitor" +version = "0.1.0" +edition = "2021" + +[dependencies] +aya-ebpf = { version = "0.1.1" } + +[profile.release] +opt-level = "z" +lto = false +panic = "abort" +strip = "debuginfo" +codegen-units = 1 diff --git a/ebpf-programs/ssh_session_monitor/rust-toolchain.toml b/ebpf-programs/ssh_session_monitor/rust-toolchain.toml new file mode 100644 index 0000000..78474db --- /dev/null +++ b/ebpf-programs/ssh_session_monitor/rust-toolchain.toml @@ -0,0 +1,4 @@ +[toolchain] +channel = "nightly" +components = ["rust-src"] +targets = ["bpfel-unknown-none"] diff --git a/ebpf-programs/ssh_session_monitor/src/lib.rs b/ebpf-programs/ssh_session_monitor/src/lib.rs new file mode 100644 index 0000000..543a4eb --- /dev/null +++ b/ebpf-programs/ssh_session_monitor/src/lib.rs @@ -0,0 +1,115 @@ +#![no_std] +#![no_main] + +use core::mem; +use aya_ebpf::{ + macros::{kprobe, map, tracepoint}, + programs::{ProbeContext, TracePointContext}, + helpers::{ + bpf_get_current_comm, + bpf_get_current_pid_tgid, + bpf_get_current_uid_gid, + bpf_ktime_get_ns, + bpf_ringbuf_output, + }, + maps::{HashMap, RingBuf}, + EbpfContext, + bpf_printk, +}; + +#[repr(C)] +#[derive(Clone, Copy)] +pub struct SshSessionEvent { + pub pid: u32, + pub uid: u32, + pub start_time_ns: u64, + pub duration_ns: u64, + pub comm: [u8; 16], +} + +#[map(name = "SSH_SESSION_STARTS")] +static SSH_SESSION_STARTS: HashMap = + HashMap::with_max_entries(1024, 0); + +#[map(name = "SSH_SESSION_RINGBUF")] +static SSH_SESSION_RINGBUF: RingBuf = + RingBuf::with_byte_size(4096, 0); + +#[kprobe] +pub fn track_shell_start(_ctx: ProbeContext) -> u32 { + let pid = (bpf_get_current_pid_tgid() >> 32) as u32; + let uid = (bpf_get_current_uid_gid() >> 32) as u32; + let ts = unsafe { bpf_ktime_get_ns() }; + + let comm = bpf_get_current_comm().unwrap_or([0u8; 16]); + + if !comm.starts_with(b"bash") && !comm.starts_with(b"sh") && !comm.starts_with(b"zsh") { + return 0; + } + + let mut comm_fixed = [0u8; 16]; + let len = core::cmp::min(comm.len(), 16); + comm_fixed[..len].copy_from_slice(&comm[..len]); + + let event = SshSessionEvent { + pid, + uid, + start_time_ns: ts, + duration_ns: 0, + comm: comm_fixed, + }; + + unsafe { + let _ = SSH_SESSION_STARTS.insert(&pid, &event, 0); + bpf_printk!(b"[ssh_session] Start PID=%d UID=%d\n", pid, uid); + } + + 0 +} + +#[tracepoint] +pub fn sched_process_exit(ctx: TracePointContext) -> i32 { + let pid = unsafe { + let ptr = ctx.as_ptr().add(16); + *(ptr as *const u32) + }; + + let now = unsafe { bpf_ktime_get_ns() }; + + unsafe { + if let Some(event) = SSH_SESSION_STARTS.get(&pid) { + let mut out = *event; + out.duration_ns = now - event.start_time_ns; + + let data_ptr = &out as *const _ as *mut core::ffi::c_void; + let size = mem::size_of::() as u64; + + bpf_ringbuf_output( + &SSH_SESSION_RINGBUF as *const _ as *mut core::ffi::c_void, + data_ptr, + size, + 0, + ); + + let _ = SSH_SESSION_STARTS.remove(&pid); + + bpf_printk!( + b"[ssh_session] End PID=%d UID=%d duration_ns=%llu\n", + pid, + out.uid, + out.duration_ns + ); + } + } + + 0 +} + +#[panic_handler] +fn panic(_: &core::panic::PanicInfo) -> ! { + loop {} +} + +#[no_mangle] +#[link_section = "license"] +pub static LICENSE: [u8; 4] = *b"GPL\0"; \ No newline at end of file