1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
//! 0.21 I.3: beacon/C2 detection via flowscope's `BeaconDetector`
//! + netring's `pattern_detector!` macro.
//!
//! Tracks per-flow inter-arrival times across a sliding window
//! and emits a `BeaconScore` when the variance is low enough to
//! look like a periodic check-in (C2-style traffic).
//!
//! Run:
//!
//! ```sh
//! cargo run --example monitor_beacon_detector \
//! --features "tokio,flow" -- eth0
//! ```
use std::time::Duration;
use flowscope::detect::patterns::{BeaconDetector, BeaconScore};
use flowscope::extract::FiveTupleKey;
use netring::prelude::*;
use netring::protocol::event_typed::FlowPacket;
/// Wraps the detector + the most recent score so the macro's
/// `verdict:` body can pick up what `feed:` produced.
struct Beacon {
detector: BeaconDetector<FiveTupleKey>,
last_score: Option<BeaconScore<FiveTupleKey>>,
}
impl Beacon {
fn new() -> Self {
Self {
detector: BeaconDetector::new(),
last_score: None,
}
}
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let iface = std::env::args().nth(1).unwrap_or_else(|| "lo".into());
let beacon = netring::pattern_detector! {
name: "BeaconCv",
// 0.22 R2: FlowPacket is flat (fires for every L4); scope to
// TCP in the feed via `evt.proto`.
event: FlowPacket,
detector: Beacon::new(),
feed: |evt, w| {
// Feed `(key, ts, bytes)` into the per-key
// inter-arrival window. The detector returns
// `Some(BeaconScore)` once the window has enough
// samples to score. Scope to TCP via `evt.proto`
// (0.22 R2: FlowPacket is flat).
if matches!(evt.proto, L4Proto::Tcp) {
w.last_score = w.detector.observe(evt.key, evt.ts, evt.len as u64);
}
},
verdict: |_evt, w| {
// Emit only above a confidence threshold; lower
// scores are noisy. The detector's default tuning
// produces high scores for well-spaced beacons.
w.last_score.as_ref().and_then(|s| {
if s.score >= 0.8 { Some(s.clone()) } else { None }
})
},
};
Monitor::builder()
.interface(&iface)
.name("beacon-c2")
.protocol::<Tcp>()
.detect(beacon)
.sink(StdoutSink::default())
.build()?
.run_for(Duration::from_secs(300))
.await?;
Ok(())
}