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
126 changes: 126 additions & 0 deletions QUICK_START.md
Original file line number Diff line number Diff line change
@@ -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
```

4 changes: 2 additions & 2 deletions secrds-agent/internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
27 changes: 14 additions & 13 deletions secrds-agent/internal/kernel/loader.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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"]
Expand Down
147 changes: 98 additions & 49 deletions secrds-programs/ssh_kprobe.c
Original file line number Diff line number Diff line change
Expand Up @@ -9,32 +9,99 @@ 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);
__uint(key_size, sizeof(__u32));
__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)
{
Expand Down Expand Up @@ -63,30 +130,33 @@ 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;
}

// 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 = {};
Expand All @@ -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";

Loading