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
50 changes: 41 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

Aurora Linux is a real-time Linux EDR agent.

It attaches eBPF programs to kernel tracepoints (process exec, file open, network state changes, and bpf syscalls), enriches the captured telemetry in user space, and evaluates each event against Sigma rules and IOC feeds to emit high-signal alerts in text or JSON. The goal is practical host detection with low overhead and clear, actionable output.
It attaches eBPF programs to kernel tracepoints (process exec, file open, network state changes, bpf syscalls, and file timestamp changes) and ingests Linux audit logs, enriches the captured telemetry in user space, and evaluates each event against Sigma rules and IOC feeds to emit high-signal alerts in text or JSON. The goal is practical host detection with low overhead and clear, actionable output.

```mermaid
flowchart LR
Expand All @@ -13,10 +13,12 @@ flowchart LR
E2["sys_enter/sys_exit_openat"]
E3["inet_sock_set_state"]
E4["sys_enter/sys_exit_bpf"]
E5["sys_enter/sys_exit_utimensat"]
end

subgraph USER["User Space"]
L["eBPF Listener"]
AU["Audit Provider"]
C["Enrichment + Correlation"]
S["Sigma Engine"]
end
Expand All @@ -25,21 +27,34 @@ flowchart LR
E2 --> L
E3 --> L
E4 --> L
E5 --> L
L -->|ring buffers| C
AU -->|audit.log tail| C
C -->|LRU parent cache| S
S -->|JSON/text alerts| A["Alert Output"]
```

## What It Detects

Aurora Linux loads standard [Sigma rules](https://github.com/SigmaHQ/sigma) for Linux and matches them in real time against four event types:
Aurora Linux loads standard [Sigma rules](https://github.com/SigmaHQ/sigma) for Linux and matches them in real time against eBPF events and auditd logs:

| Event Type | eBPF Hook | Example Detections |
### eBPF Events

| Event Type | Sysmon ID | eBPF Hook | Example Detections |
|---|---|---|---|
| **Process Creation** | 1 | `tracepoint/sched/sched_process_exec` | Reverse shells, base64 decode, webshell child processes, suspicious Java children |
| **File Create Time Change** | 2 | `tracepoint/syscalls/sys_{enter,exit}_utimensat` | Timestomping detection (attackers hiding file modification times) |
| **Network Connection** | 3 | `tracepoint/sock/inet_sock_set_state` | Bash reverse shells, malware callback ports, C2 on non-standard ports |
| **File Event** | 11 | `tracepoint/syscalls/sys_{enter,exit}_openat` | Cron persistence, sudoers modification, rootkit lock files, downloads to /tmp |
| **BPF Event** | 100 | `tracepoint/syscalls/sys_{enter,exit}_bpf` | Unauthorized BPF program loading, eBPF rootkit detection |

### Auditd Events

Aurora can also ingest Linux audit logs (`/var/log/audit/audit.log`) in real time and match them against [SigmaHQ linux/auditd rules](https://github.com/SigmaHQ/sigma/tree/master/rules/linux/auditd). Audit events are emitted with raw audit fields (`type`, `key`, `exe`, `comm`, `a0`, `a1`, `name`, `syscall`, ...) for direct compatibility with the upstream rule set.

| Source | Mode | Example Detections |
|---|---|---|
| **Process Creation** | `tracepoint/sched/sched_process_exec` | Reverse shells, base64 decode, webshell child processes, suspicious Java children |
| **File Creation** | `tracepoint/syscalls/sys_{enter,exit}_openat` | Cron persistence, sudoers modification, rootkit lock files, downloads to /tmp |
| **Network Connection** | `tracepoint/sock/inet_sock_set_state` | Bash reverse shells, malware callback ports, C2 on non-standard ports |
| **BPF Syscall** | `tracepoint/syscalls/sys_{enter,exit}_bpf` | Unauthorized BPF program loads, rootkit BPF attachment, suspicious BPF map operations |
| **audit.log** | Real-time tail or batch file | Suspicious C2 commands, password policy discovery, ASLR disable, audio capture, system info discovery |

## Requirements

Expand Down Expand Up @@ -88,6 +103,9 @@ Linux note:
```bash
# Point at the Linux Sigma root directory (subfolders are loaded recursively)
sudo ./aurora --rules /path/to/sigma/rules/linux --json

# With auditd log ingestion for real-time audit-based detection
sudo ./aurora --rules /path/to/sigma/rules/linux --audit-log /var/log/audit/audit.log --json
```

`--rules` is required. Aurora validates rule directories at startup and exits
Expand Down Expand Up @@ -256,6 +274,7 @@ When a Sigma rule matches, Aurora Linux emits a structured alert:
| `--min-level` | info | Load only rules at or above this Sigma level (`info`, `low`, `medium`, `high`, `critical`) |
| `--stats-interval` | 60 | Stats logging interval (seconds, 0=off) |
| `--sigma-no-collapse-ws` | on | Disable Sigma whitespace collapsing during matching (default, reduces allocation churn; stricter matching) |
| `--audit-log` | off | Paths to auditd log files (repeatable; enables audit provider with real-time tailing) |
| `--pprof-listen` | off | Enable local pprof endpoint on loopback `host:port` (for on-demand profiling) |
| `-v, --verbose` | off | Debug-level logging |

Expand All @@ -279,6 +298,8 @@ rules:
- /opt/sigma/rules/linux
filename-iocs: /opt/aurora-linux/resources/iocs/filename-iocs.txt
c2-iocs: /opt/aurora-linux/resources/iocs/c2-iocs.txt
audit-log:
- /var/log/audit/audit.log
logfile: /var/log/aurora-linux/aurora.log
logfile-format: syslog
tcp-target: myserver.local:514
Expand All @@ -291,20 +312,30 @@ pprof-listen: 127.0.0.1:6060

Aurora Linux follows a **provider → distributor → consumer** pipeline:

- **Provider** (`lib/provider/ebpf/`) -- eBPF programs attach to kernel tracepoints and deliver events via ring buffers. A userland listener reconstructs full fields from `/proc/PID/*`.
- **Provider: eBPF** (`lib/provider/ebpf/`) -- eBPF programs attach to kernel tracepoints and deliver events via ring buffers. A userland listener reconstructs full fields from `/proc/PID/*`.
- **Provider: Audit** (`lib/provider/audit/`) -- Reads Linux audit logs (e.g. `/var/log/audit/audit.log`), groups multi-line records by audit serial, and emits events with raw audit fields for direct SigmaHQ rule compatibility. Supports real-time tailing.
- **Distributor** (`lib/distributor/`) -- Applies enrichment functions (parent process correlation via LRU cache, UID→username resolution) and routes events to consumers.
- **Consumer** (`lib/consumer/sigma/`) -- Evaluates events against loaded Sigma rules using [go-sigma-rule-engine](https://github.com/markuskont/go-sigma-rule-engine). Includes per-rule throttling to suppress duplicate alerts.
- **Consumer** (`lib/consumer/ioc/`) -- Evaluates events against bundled IOC files (`filename-iocs.txt`, `c2-iocs.txt`) and emits IOC match alerts.

### Sigma Field Coverage

**eBPF provider** (Sysmon-compatible fields):

| Category | Sigma Fields Covered | Rule Coverage |
|---|---|---|
| `process_creation` | Image, CommandLine, ParentImage, ParentCommandLine, User, LogonId, CurrentDirectory | 119/119 rules (100%) |
| `file_event` | TargetFilename, Image | 8/8 rules (100%) |
| `file_event` | TargetFilename, Image, FileAction | 8/8 rules (100%) |
| `file_create_time` | TargetFilename, Image, NewAccessTime, NewModificationTime | timestomping detection |
| `network_connection` | Image, DestinationIp, DestinationPort, Initiated | 2/5 rules (40%) -- remaining 3 need DNS correlation |
| `bpf_event` | Image, User, ProcessId, BpfCommand, BpfProgramType, BpfProgramId, BpfProgramName, EventID | Sigma rules matching on `bpf()` syscall fields |

**Audit provider** (raw audit fields):

| Category | Sigma Fields Covered |
|---|---|
| `linux/auditd` | type, syscall, key, exe, comm, a0-aN, name, nametype, cwd, proctitle, pid, ppid, uid, auid, SYSCALL, UID, AUID, and all other raw audit fields |

## Project Structure

```
Expand All @@ -314,6 +345,7 @@ aurora-linux/
├── scripts/ Install + maintenance automation
├── lib/
│ ├── provider/ebpf/ eBPF listener + BPF C programs
│ ├── provider/audit/ Auditd log provider (real-time + batch)
│ ├── provider/replay/ JSONL replay provider (for CI)
│ ├── distributor/ Event routing + enrichment
│ ├── enrichment/ DataFieldsMap, correlator cache
Expand Down
39 changes: 35 additions & 4 deletions cmd/aurora/agent/agent.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,17 @@ import (
"github.com/Nextron-Labs/aurora-linux/lib/enrichment"
"github.com/Nextron-Labs/aurora-linux/lib/logging"
"github.com/Nextron-Labs/aurora-linux/lib/provider"
auditprovider "github.com/Nextron-Labs/aurora-linux/lib/provider/audit"
ebpfprovider "github.com/Nextron-Labs/aurora-linux/lib/provider/ebpf"
log "github.com/sirupsen/logrus"
)

// Agent orchestrates the lifecycle of the Aurora Linux EDR agent.
type Agent struct {
params Parameters
listener *ebpfprovider.Listener
dist *distributor.Distributor
params Parameters
listener *ebpfprovider.Listener
auditProvider *auditprovider.AuditProvider
dist *distributor.Distributor
consumer *sigma.SigmaConsumer
ioc *ioc.Consumer
correlator *enrichment.Correlator
Expand Down Expand Up @@ -155,6 +157,17 @@ func (a *Agent) Run() error {

log.Info("eBPF listener initialized, starting event collection")

// Create and initialize audit provider if audit log files are specified
if len(a.params.AuditLogFiles) > 0 {
a.auditProvider = auditprovider.New(a.params.AuditLogFiles...)
_ = a.auditProvider.AddSource(auditprovider.SourceAuditd)

if err := a.auditProvider.Initialize(); err != nil {
return fmt.Errorf("initializing audit provider: %w", err)
}
log.WithField("files", a.params.AuditLogFiles).Info("Audit log provider initialized")
}

// Start stats reporting
if a.params.StatsInterval > 0 {
a.statsStop = make(chan struct{})
Expand All @@ -167,7 +180,7 @@ func (a *Agent) Run() error {
signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM)
defer signal.Stop(sigCh)

// Start event collection in a goroutine
// Start event collection in goroutines
doneCh := make(chan struct{})
go func() {
a.listener.SendEvents(func(event provider.Event) {
Expand All @@ -182,6 +195,19 @@ func (a *Agent) Run() error {
close(doneCh)
}()

// Start audit provider event collection if enabled
if a.auditProvider != nil {
go a.auditProvider.SendEvents(func(event provider.Event) {
if a.params.Trace {
a.traceEvent(event)
}
if a.shouldExcludeEvent(event) {
return
}
a.dist.HandleEvent(event)
})
}

// Wait for signal
sig := <-sigCh
log.WithField("signal", sig).Info("Received shutdown signal")
Expand Down Expand Up @@ -213,6 +239,11 @@ func (a *Agent) shutdown() {
log.WithError(err).Warn("Failed to close eBPF listener cleanly")
}
}
if a.auditProvider != nil {
if err := a.auditProvider.Close(); err != nil {
log.WithError(err).Warn("Failed to close audit provider cleanly")
}
}
if a.consumer != nil {
if err := a.consumer.Close(); err != nil {
log.WithError(err).Warn("Failed to close Sigma consumer cleanly")
Expand Down
3 changes: 3 additions & 0 deletions cmd/aurora/agent/parameters.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,9 @@ type Parameters struct {
// PprofListen enables a local pprof HTTP endpoint on host:port.
// Empty disables runtime profiling endpoints.
PprofListen string

// AuditLogFiles contains paths to auditd log files for the audit provider.
AuditLogFiles []string
}

// DefaultParameters returns parameters with sensible defaults.
Expand Down
3 changes: 3 additions & 0 deletions cmd/aurora/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ func main() {
flags.IntVar(&params.StatsInterval, "stats-interval", params.StatsInterval, "Stats logging interval in seconds (0=disabled)")
flags.BoolVar(&params.SigmaNoCollapseWS, "sigma-no-collapse-ws", params.SigmaNoCollapseWS, "Disable sigma whitespace collapsing during pattern matching (default: true)")
flags.StringVar(&params.PprofListen, "pprof-listen", "", "Enable pprof HTTP endpoint on loopback host:port (example: 127.0.0.1:6060)")
flags.StringSliceVar(&params.AuditLogFiles, "audit-log", nil, "Paths to auditd log files to process (repeatable; enables audit provider)")

if err := rootCmd.Execute(); err != nil {
writeCLIError(err, params.JSONOutput, os.Stderr)
Expand Down Expand Up @@ -159,6 +160,8 @@ func applyCLIOverrides(set *pflag.FlagSet, dst *agent.Parameters, cli agent.Para
dst.SigmaNoCollapseWS = cli.SigmaNoCollapseWS
case "pprof-listen":
dst.PprofListen = cli.PprofListen
case "audit-log":
dst.AuditLogFiles = append([]string(nil), cli.AuditLogFiles...)
}
})
}
Expand Down
3 changes: 3 additions & 0 deletions lib/distributor/enrichments.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ func RegisterLinuxEnrichments(enricher *enrichment.EventEnricher, correlator *en
enricher.Register("LinuxEBPF:3", func(fields enrichment.DataFieldsMap) {
enrichImageFromCache(fields, correlator)
})

// Audit provider: raw audit fields, no Sysmon EventID enrichment needed.
// Sigma rules match on native audit fields (type, key, exe, a0, ...).
}

// enrichParentFields fills ParentImage and ParentCommandLine from the
Expand Down
Loading
Loading