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
23 changes: 23 additions & 0 deletions cmd/ssh_session.go
Original file line number Diff line number Diff line change
@@ -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)
}
103 changes: 103 additions & 0 deletions core/ssh_session_agent.go
Original file line number Diff line number Diff line change
@@ -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
}
14 changes: 14 additions & 0 deletions ebpf-programs/ssh_session_monitor/Cargo.toml
Original file line number Diff line number Diff line change
@@ -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
4 changes: 4 additions & 0 deletions ebpf-programs/ssh_session_monitor/rust-toolchain.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
[toolchain]
channel = "nightly"
components = ["rust-src"]
targets = ["bpfel-unknown-none"]
115 changes: 115 additions & 0 deletions ebpf-programs/ssh_session_monitor/src/lib.rs
Original file line number Diff line number Diff line change
@@ -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<u32, SshSessionEvent> =
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::<SshSessionEvent>() 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";