From f8c7b6ad65c2817c5232580d1ba2756bef72ed91 Mon Sep 17 00:00:00 2001 From: mahesh bhatiya Date: Fri, 7 Nov 2025 00:52:59 +0530 Subject: [PATCH] update phase 1 --- QUICK_START.md | 126 +++++++++++++++++++++ secrds-agent/internal/config/config.go | 4 +- secrds-agent/internal/kernel/loader.go | 27 ++--- secrds-programs/ssh_kprobe.c | 147 ++++++++++++++++--------- 4 files changed, 240 insertions(+), 64 deletions(-) create mode 100644 QUICK_START.md diff --git a/QUICK_START.md b/QUICK_START.md new file mode 100644 index 0000000..96d15ec --- /dev/null +++ b/QUICK_START.md @@ -0,0 +1,126 @@ +# Quick Start Guide - secrds Security Monitor + +## Issue: Agent Not Detecting SSH Attacks + +If you're seeing "No recent alerts" when SSH attacks occur, follow these steps: + +### Step 1: Make sure the service is running + +```bash +# Check if service is running +sudo systemctl status secrds + +# If not running, start it +sudo systemctl start secrds + +# Enable auto-start on boot +sudo systemctl enable secrds +``` + +### Step 2: Rebuild with updated eBPF program + +The eBPF program has been updated to better detect incoming connections: + +```bash +# Rebuild everything +make build + +# Reinstall +sudo ./install.sh +``` + +### Step 3: Test the detection + +From another server, try multiple SSH login attempts: + +```bash +# On attacker server (replace with your server IP) +for i in {1..10}; do + ssh root@YOUR_SERVER_IP "exit" 2>&1 | head -1 + sleep 0.5 +done +``` + +### Step 4: Check alerts + +```bash +# On the monitored server +secrds alerts + +# Or with more details +secrds alerts --limit 20 +``` + +### Step 5: View logs + +```bash +# View service logs +sudo journalctl -u secrds -f + +# Check if events are being received +sudo journalctl -u secrds | grep "SSH event" +``` + +## Troubleshooting + +### If still no alerts: + +1. **Check if eBPF program loaded correctly:** + ```bash + sudo journalctl -u secrds | grep "Attached kprobe" + ``` + +2. **Verify kernel supports eBPF:** + ```bash + uname -r # Should be 5.8+ + ls /sys/fs/bpf # Should exist + ``` + +3. **Check if connections are being tracked:** + ```bash + # Monitor in real-time + sudo journalctl -u secrds -f + # Then try SSH connections and see if events appear + ``` + +4. **Lower thresholds in config:** + ```bash + sudo nano /etc/secrds/config.yaml + # Set ssh_threshold: 2 + # Set ssh_window_seconds: 60 + sudo systemctl restart secrds + ``` + +5. **Test with manual run:** + ```bash + sudo /usr/local/bin/secrds-agent + # In another terminal, try SSH connections + # Press Ctrl+C to stop + ``` + +## Important Notes + +- The agent must be running **continuously** as a service to detect attacks +- Detection thresholds are now lower (3 attempts in 5 minutes) +- The eBPF program now hooks both incoming and outgoing connections +- Source IP detection has been improved + +## Service Management + +```bash +# Start service +sudo systemctl start secrds + +# Stop service +sudo systemctl stop secrds + +# Restart service +sudo systemctl restart secrds + +# Check status +secrds status + +# View alerts +secrds alerts +``` + diff --git a/secrds-agent/internal/config/config.go b/secrds-agent/internal/config/config.go index 86c16d7..f8e1365 100644 --- a/secrds-agent/internal/config/config.go +++ b/secrds-agent/internal/config/config.go @@ -29,9 +29,9 @@ type TelegramConfig struct { func Default() *Config { return &Config{ - SSHThreshold: 5, + SSHThreshold: 3, // Lowered for better detection (3 attempts in 5 minutes) SSHWindowSeconds: 300, - TCPThreshold: 10, + TCPThreshold: 5, // Lowered for better detection TCPWindowSeconds: 60, EnableIPBlocking: true, StoragePath: "/var/lib/secrds/events.json", diff --git a/secrds-agent/internal/kernel/loader.go b/secrds-agent/internal/kernel/loader.go index 9376f63..8cd41f5 100644 --- a/secrds-agent/internal/kernel/loader.go +++ b/secrds-agent/internal/kernel/loader.go @@ -80,7 +80,19 @@ func (l *Loader) LoadCPrograms() error { } l.collection = coll - // Attach kprobe for tcp_v4_connect + // Attach kprobe for inet_csk_accept (incoming connections on server) + if acceptProg := coll.Programs["ssh_kprobe_accept"]; acceptProg != nil { + kpAccept, err := link.Kprobe("inet_csk_accept", acceptProg, nil) + if err != nil { + fmt.Printf("Warning: failed to attach kprobe inet_csk_accept: %v\n", err) + fmt.Printf("This is normal if kernel doesn't export this symbol. Trying alternative...\n") + } else { + l.links = append(l.links, kpAccept) + fmt.Println("Attached kprobe to inet_csk_accept (incoming connections)") + } + } + + // Attach kprobe for tcp_v4_connect (outgoing connections) // Try different possible program names progNames := []string{"ssh_kprobe_tcp_connect", "ssh_kprobe_execve", "ssh_tracepoint_write"} var kprobeProg *ebpf.Program @@ -107,18 +119,7 @@ func (l *Loader) LoadCPrograms() error { return fmt.Errorf("failed to attach kprobe tcp_v4_connect: %w", err) } l.links = append(l.links, kp) - fmt.Println("Attached kprobe to tcp_v4_connect") - - // Attach kretprobe if available - if prog := coll.Programs["ssh_kretprobe_tcp_connect"]; prog != nil { - krp, err := link.Kretprobe("tcp_v4_connect", prog, nil) - if err != nil { - fmt.Printf("Warning: failed to attach kretprobe: %v\n", err) - } else { - l.links = append(l.links, krp) - fmt.Println("Attached kretprobe to tcp_v4_connect") - } - } + fmt.Println("Attached kprobe to tcp_v4_connect (outgoing connections)") // Open perf event array for SSH events sshMap := coll.Maps["ssh_events"] diff --git a/secrds-programs/ssh_kprobe.c b/secrds-programs/ssh_kprobe.c index c556d95..91d23d0 100644 --- a/secrds-programs/ssh_kprobe.c +++ b/secrds-programs/ssh_kprobe.c @@ -9,13 +9,6 @@ struct { __uint(value_size, sizeof(__u32)); } ssh_events SEC(".maps"); -struct { - __uint(type, BPF_MAP_TYPE_HASH); - __uint(max_entries, MAX_IP_ADDRESSES); - __uint(key_size, sizeof(__u32)); - __uint(value_size, sizeof(__u64)); -} ssh_failure_count SEC(".maps"); - struct { __uint(type, BPF_MAP_TYPE_HASH); __uint(max_entries, MAX_IP_ADDRESSES); @@ -23,18 +16,92 @@ struct { __uint(value_size, sizeof(__u64)); } ssh_attempts SEC(".maps"); -// sockaddr_in structure (simplified for IPv4) -struct sockaddr_in { - __u16 sin_family; // AF_INET = 2 - __be16 sin_port; // Port in network byte order - struct in_addr { - __be32 s_addr; // IP address in network byte order - } sin_addr; - __u8 sin_zero[8]; // Padding -}; +// Hook into inet_csk_accept to detect incoming SSH connections on the server side +// This is called when the server accepts a new connection +SEC("kprobe/inet_csk_accept") +int ssh_kprobe_accept(struct pt_regs *ctx) +{ + struct sock *sk = (struct sock *)PT_REGS_RC(ctx); + + if (!sk) return 0; + + __u64 pid_tgid = bpf_get_current_pid_tgid(); + __u32 pid = (__u32)(pid_tgid >> 32); + + // Try to read socket information + // For IPv4 sockets, we need to access inet_sock structure + // Common structure layout: + // struct inet_sock { + // struct sock sk; + // ... + // __be16 inet_sport; // source port + // __be16 inet_dport; // destination port + // __be32 inet_saddr; // source address + // __be32 inet_daddr; // destination address + // } + + __u32 src_ip = 0; + __u32 dst_ip = 0; + __u16 src_port = 0; + __u16 dst_port = 0; + + // Try to read destination port (offset varies by kernel, try common ones) + // inet_sock->inet_dport is typically around offset 72-80 from sock + bpf_probe_read_kernel(&dst_port, sizeof(dst_port), (char *)sk + 72); + dst_port = __builtin_bswap16(dst_port); + + // Only process SSH connections (port 22) + if (dst_port != 22) { + return 0; + } + + // Try to read source IP (inet_saddr, typically offset 64-68) + bpf_probe_read_kernel(&src_ip, sizeof(src_ip), (char *)sk + 64); + src_ip = __builtin_bswap32(src_ip); + + // Try alternative offsets if first attempt failed + if (src_ip == 0) { + bpf_probe_read_kernel(&src_ip, sizeof(src_ip), (char *)sk + 68); + src_ip = __builtin_bswap32(src_ip); + } + if (src_ip == 0) { + bpf_probe_read_kernel(&src_ip, sizeof(src_ip), (char *)sk + 60); + src_ip = __builtin_bswap32(src_ip); + } + + // Read destination IP + bpf_probe_read_kernel(&dst_ip, sizeof(dst_ip), (char *)sk + 56); + dst_ip = __builtin_bswap32(dst_ip); + if (dst_ip == 0) { + bpf_probe_read_kernel(&dst_ip, sizeof(dst_ip), (char *)sk + 52); + dst_ip = __builtin_bswap32(dst_ip); + } + + // If we still don't have source IP, skip (invalid socket) + if (src_ip == 0) { + return 0; + } + + // Track attempt + __u64 *count = bpf_map_lookup_elem(&ssh_attempts, &src_ip); + __u64 new_count = count ? *count + 1 : 1; + bpf_map_update_elem(&ssh_attempts, &src_ip, &new_count, BPF_F_CURRENT_CPU); + + // Initialize event structure + struct ssh_event event = {}; + event.ip = src_ip; + event.port = dst_port; + event.pid = pid; + event.event_type = SSH_ATTEMPT; + event.timestamp = bpf_ktime_get_ns(); + + bpf_perf_event_output(ctx, &ssh_events, BPF_F_CURRENT_CPU, &event, sizeof(event)); + + return 0; +} -// Hook into tcp_v4_connect to detect SSH connection attempts -// tcp_v4_connect signature: int tcp_v4_connect(struct sock *sk, struct sockaddr *uaddr, int addr_len) +// Also hook tcp_v4_connect for outgoing connections (in case server connects out) +// This helps track both directions SEC("kprobe/tcp_v4_connect") int ssh_kprobe_tcp_connect(struct pt_regs *ctx) { @@ -63,22 +130,25 @@ int ssh_kprobe_tcp_connect(struct pt_regs *ctx) return 0; } - // Get destination IP (we'll use this, but for source IP we need to read from socket) + // Get destination IP __u32 dst_ip = __builtin_bswap32(addr.sin_addr.s_addr); - // Try to get source IP from socket structure - // Common offsets for skc_rcv_saddr in sock_common (varies by kernel version) + // For outgoing connections, the "source" from our perspective is the destination + // We want to track who we're connecting TO (for server-initiated connections) + // But for incoming connections, we use inet_csk_accept + + // Try to get source IP from socket __u32 src_ip = 0; - // Try reading from common offsets (these are approximate and may need adjustment) - // Offset 12-16 are common locations for source address in sock_common - bpf_probe_read_kernel(&src_ip, sizeof(src_ip), (char *)sk + 12); + // Read from inet_sock structure (common offsets) + bpf_probe_read_kernel(&src_ip, sizeof(src_ip), (char *)sk + 64); + src_ip = __builtin_bswap32(src_ip); if (src_ip == 0) { - bpf_probe_read_kernel(&src_ip, sizeof(src_ip), (char *)sk + 16); + bpf_probe_read_kernel(&src_ip, sizeof(src_ip), (char *)sk + 68); + src_ip = __builtin_bswap32(src_ip); } - // If we still don't have source IP, use destination IP as fallback - // (This means we're tracking the connection target, not the source) + // Use destination IP if source IP not available (outgoing connection) if (src_ip == 0) { src_ip = dst_ip; } @@ -86,7 +156,7 @@ int ssh_kprobe_tcp_connect(struct pt_regs *ctx) // Track attempt __u64 *count = bpf_map_lookup_elem(&ssh_attempts, &src_ip); __u64 new_count = count ? *count + 1 : 1; - bpf_map_update_elem(&ssh_attempts, &src_ip, &new_count, BPF_ANY); + bpf_map_update_elem(&ssh_attempts, &src_ip, &new_count, BPF_F_CURRENT_CPU); // Initialize event structure struct ssh_event event = {}; @@ -101,25 +171,4 @@ int ssh_kprobe_tcp_connect(struct pt_regs *ctx) return 0; } -// Also hook IPv6 connections -SEC("kprobe/tcp_v6_connect") -int ssh_kprobe_tcp_v6_connect(struct pt_regs *ctx) -{ - // Similar to IPv4, but for IPv6 - // For now, we'll focus on IPv4, but this can be extended - return 0; -} - -// Track failed connections via return probe -SEC("kretprobe/tcp_v4_connect") -int ssh_kretprobe_tcp_connect(struct pt_regs *ctx) -{ - // If connection failed (negative return), we could track it here - // But we need the socket/IP info which is harder to get from kretprobe - // For now, we'll track all connection attempts and let user-space - // correlate with auth logs for actual failures - return 0; -} - char LICENSE[] SEC("license") = "Dual BSD/GPL"; -