Modular network beacon emulator for EDR/NDR detection lab validation — written in Rust.
Echos replays realistic C2-style beacon traffic so defenders can verify that their detection stack — EDR, NDR, IDS/IPS, SIEM — fires the right alerts under controlled, repeatable conditions. It is not a post-exploitation framework. It sends no payloads, executes no code on remote systems, and requires no implant. It is purely a traffic generator designed for authorized lab use.
Detection engineering requires repeatable, controllable stimulus. Running a real implant in a lab introduces risk, licensing complexity, and noise. Echos gives you a single binary that generates accurately structured beacon traffic — correct headers, correct timing distributions, correct protocol shapes — without any of that baggage.
Use it to answer questions like:
- Does my NDR alert on Cobalt Strike-style HTTP headers hitting an internal listener?
- Does my SIEM correctly correlate 10 DNS beacon queries from the same host in under 5 minutes?
- Does my detection rule fire on SMTP EHLO probes from workstation endpoints?
- Does exponential backoff behavior in a failing beacon evade my anomaly model?
- 7 protocols — HTTP, HTTPS, DNS, ICMP, SMB, WebSocket, SMTP
- 10 built-in profiles — Cobalt Strike, APT28, Lazarus, APT29, Emotet, FIN7, ICMP, SMB, WebSocket, SMTP
- 3 jitter algorithms — Uniform, Gaussian (Box-Muller), Sinusoidal (business-hours modulation)
- Bounded execution —
--count Nor--duration SECS, stop on whichever hits first - Runtime target override —
--targetpoints any profile at your listener without recompiling - External TOML config — define custom profiles without touching source code
- Profile sequencing —
--sequence "Cobalt,APT28"or named sequences from config - Sigma rule export —
--export-sigma --profile <name>prints a ready-to-tune Sigma YAML rule - Suricata & Snort rule export —
--export-suricata/--export-snortgenerate IDS rules from profile definitions - Structured logging — human-readable or JSON, with optional file output
- Exponential backoff — kicks in after 3 consecutive beacon failures, caps at 300 s
- Rotating target pools — simulate DGA/fast-flux by specifying multiple targets per profile
- Safe HTTPS defaults — certificate validation on by default;
--insecure-tlsonly when explicitly passed - Dry-run mode — preview exactly what would happen without sending any traffic
# Build
git clone https://github.com/xdrew87/echos
cd echos
cargo build --release
# List all profiles
./target/release/echos --list
# Run a single beacon iteration
./target/release/echos --profile Cobalt --count 1 --target http://127.0.0.1:8080
# Preview without sending traffic
./target/release/echos --profile APT29 --dry-runRequires Rust 1.70+.
git clone https://github.com/xdrew87/echos
cd echos
cargo build --release
./target/release/echos --listRequires Rust 1.70+.
Available Profiles (18 total — 18 built-in, 0 from config)
NAME PROTOCOL DELAY JITTER ALGORITHM ROTATING HEADERS SOURCE
─────────────────────────────────────────────────────────────────────────────────────────────────────
Cobalt HTTP 10s 20% Uniform No 5 Built-in
APT28 DNS 30s 10% Uniform No 0 Built-in
ICMP Beacon ICMP 60s 5% Uniform No 0 Built-in
Lazarus HTTPS 300s 15% Gaussian No 5 Built-in
APT29 HTTPS 600s 10% Sinusoidal No 5 Built-in
Emotet HTTP 60s 25% Gaussian Yes 3 Built-in
FIN7 HTTPS 30s 10% Uniform No 6 Built-in
SMB Beacon SMB 120s 10% Uniform No 0 Built-in
WebSocket Beacon WebSocket 15s 15% Uniform No 0 Built-in
SMTP Beacon SMTP 90s 10% Uniform No 0 Built-in
Sandworm ICMP 180s 20% Sinusoidal No 0 Built-in
Turla HTTP 45s 15% Gaussian No 6 Built-in
Carbanak HTTPS 120s 10% Gaussian No 6 Built-in
CS DNS Beacon DNS 20s 30% Uniform Yes 0 Built-in
Meterpreter HTTP 5s 25% Gaussian No 5 Built-in
FTP Beacon FTP 90s 10% Uniform No 0 Built-in
LDAP Beacon LDAP 60s 10% Uniform No 0 Built-in
RDP Beacon RDP 30s 10% Uniform No 0 Built-in
Target: use --target to override any profile's destination at runtime.
[DRY RUN] Would run profile "Cobalt" (HTTP → http://127.0.0.1:8080)
Jitter : 20% Uniform
Timeout : 10s
Insecure TLS : No
Count limit : none
Duration : none
2026-04-15T04:02:11Z INFO echos started profile=Cobalt protocol=Http target=http://10.0.0.1:8080
2026-04-15T04:02:11Z INFO beacon sent profile=Cobalt protocol=Http target=http://10.0.0.1:8080 attempt=1
2026-04-15T04:02:19Z INFO beacon sent profile=Cobalt protocol=Http target=http://10.0.0.1:8080 attempt=2
2026-04-15T04:02:30Z INFO beacon sent profile=Cobalt protocol=Http target=http://10.0.0.1:8080 attempt=3
─────────────────────────────────────
Run Summary
─────────────────────────────────────
Profile : Cobalt
Protocol : HTTP
Target : http://10.0.0.1:8080
Attempts : 3
Successes : 3
Failures : 0
Failure Rate : 0.0%
Avg Delay : 9.84s
Start : 2026-04-15 04:02:11
End : 2026-04-15 04:02:41
Runtime : 30.2s
Dry Run : No
Insecure TLS : No
─────────────────────────────────────
{
"timestamp": "2026-04-15T04:02:11.112Z",
"level": "INFO",
"fields": {
"message": "beacon sent",
"profile": "Cobalt",
"protocol": "Http",
"target": "http://10.0.0.1:8080",
"attempt": 1
},
"target": "echos"
}JSON summary (end of run):
{
"profile": "Cobalt",
"protocol": "HTTP",
"target": "http://10.0.0.1:8080",
"attempts": 3,
"successes": 3,
"failures": 0,
"failure_rate_pct": 0.0,
"avg_delay_secs": 9.84,
"start": "2026-04-15 04:02:11",
"end": "2026-04-15 04:02:41",
"runtime_secs": 30.2,
"dry_run": false,
"insecure_tls": false
}| Flag | Default | Description |
|---|---|---|
-p, --profile <NAME> |
Cobalt |
Beacon profile to use |
--list |
— | Print available profiles and exit |
--target <URL> |
— | Override the profile destination at runtime |
--count <N> |
— | Send exactly N beacon iterations then exit |
--duration <SECS> |
— | Run for this many seconds then exit |
--config <FILE> |
— | Load additional profiles from a TOML file |
--config-dir <DIR> |
— | Load profiles from all .toml files in a directory |
--sequence <NAMES> |
— | Run profiles in sequence: "Cobalt,APT28" or a named sequence from config |
--export-sigma |
— | Print a Sigma YAML detection rule for the selected profile and exit |
--export-suricata |
— | Print a Suricata .rules detection rule for the selected profile and exit |
--export-snort |
— | Print a Snort 3 detection rule for the selected profile and exit |
--json |
— | Emit structured JSON logs and output summary as JSON |
--verbose |
— | Show debug-level details |
--quiet |
— | Show only warnings, errors, and the final summary |
--log-file <FILE> |
— | Write logs to a file (always JSON format) |
--timeout <SECS> |
10 |
Per-connection/request timeout |
--insecure-tls |
— | Accept invalid/self-signed TLS certificates (HTTPS only) |
--dry-run |
— | Print what would happen and exit without sending traffic |
When both --count and --duration are given, echos stops when either limit is first reached.
Run multiple profiles back-to-back in a defined order using --sequence.
Inline (comma-separated):
echos --sequence "Cobalt,APT28" --count 3Each profile fires 3 beacons in order: Cobalt first, then APT28.
Named sequence from config:
# examples/echos.toml
[[sequences]]
name = "recon-chain"
profiles = ["APT28", "LDAP Beacon", "RDP Beacon", "SMB Beacon"]echos --config examples/echos.toml --sequence recon-chainWith a global time budget:
echos --config examples/echos.toml --sequence recon-chain --duration 120The 120 s budget is shared across all profiles in the sequence; execution stops when time runs out.
In sequence mode, --count sets the per-profile iteration count (default: 1 per profile).
Generate an experimental Sigma YAML detection rule from any profile definition:
echos --export-sigma --profile APT28Example output:
title: Echos - APT28
id: echos-apt28
status: experimental
description: "Detects beacon traffic matching the Echos 'APT28' profile (DNS protocol)."
author: Echos (generated)
date: 2025-01-15
references:
- https://github.com/xdrew87/Echos
tags:
- attack.command_and_control
- attack.t1071.004
logsource:
category: dns
detection:
selection:
QueryName|contains: "example.com"
QueryType: 'A'
condition: selection
fields:
- QueryName
- QueryType
- record_type
- answers
falsepositives:
- Legitimate traffic using the same user-agent or ports
- Security scanning tools
level: mediumRules are experimental — review and tune before deploying in production. Use with --config to export rules for custom profiles too:
echos --config examples/echos.toml --export-sigma --profile "My Custom Profile"Generate experimental Suricata or Snort 3 IDS rules from any profile definition:
echos --export-suricata --profile Cobalt > cobalt.rules
echos --export-snort --profile APT28Example Suricata output (--export-suricata --profile Cobalt):
# Echos - Suricata Rules for profile: Cobalt
# Protocol: HTTP
# Generated: 2025-01-15
# Status: experimental - review and tune before production use
# https://github.com/xdrew87/Echos
alert http $HOME_NET any -> $EXTERNAL_NET any (msg:"Echos - Cobalt"; flow:established,to_server; http.user_agent; content:"Mozilla/5.0 (Windows NT 10.0; Win64; x64)"; nocase; classtype:trojan-activity; sid:9000001; rev:1; metadata:profile cobalt, tool Echos, protocol http;)
Example Snort 3 output (--export-snort --profile APT28):
# Echos - Snort 3 Rules for profile: APT28
# Protocol: DNS
# Generated: 2025-01-15
# Status: experimental - review and tune before production use
# https://github.com/xdrew87/Echos
alert udp $HOME_NET any -> any 53 (msg:"Echos - APT28 DNS Beacon"; content:"example.com"; nocase; classtype:trojan-activity; sid:9100101; rev:1; metadata:profile apt28, tool Echos, protocol dns;)
Suricata rules use sticky buffer keywords (dns.query, http.user_agent, http.content_type) for accurate protocol-layer matching. Snort 3 rules use http_header for UA matching and raw UDP payload matching for DNS (tune before production). SID ranges: Suricata 9_000_000+, Snort 9_100_000+.
Validate that your C2 listener traffic pattern is indistinguishable from the real threat actor profile before running an engagement:
# Confirm Cobalt Strike HTTP profile reaches your team server
echos --profile Cobalt --count 5 --target http://teamserver.lab.internal:8080
# Test APT29-style slow HTTPS beaconing over 10 minutes
echos --profile APT29 --duration 600 --insecure-tls --target https://192.168.10.5:443Trigger specific detection rules on demand, repeatably:
# Fire Cobalt Strike HTTP User-Agent signature once
echos --profile Cobalt --count 1 --target http://sensor.lab.internal:8080
# Generate 10 DNS beacon queries for correlation testing
echos --profile APT28 --count 10
# Trigger SMB negotiate probe detection
echos --profile "SMB Beacon" --count 3 --target 192.168.1.50Use structured JSON output to feed results into your validation pipeline:
# Machine-readable output for CI/CD detection validation
echos --profile Cobalt --count 10 --json --quiet 2>/dev/null
# Write a timestamped run log to disk
echos --profile FIN7 --count 20 --json --log-file /tmp/fin7-run.json
# Validate multiple profiles in a shell loop
for profile in Cobalt APT28 Lazarus; do
echos --profile "$profile" --count 1 --json --quiet
doneEchos is designed exclusively for authorized security testing and detection validation in environments you own or have explicit written permission to test.
- It sends no payloads and executes no code on remote systems
- All traffic is directed at targets you specify via
--target - Default targets point to
127.0.0.1— no external traffic by default - TLS certificates are validated by default
- Generating unsolicited beacon traffic on networks you do not control may violate computer-fraud laws
See SECURITY.md for the full responsible-use policy.
See docs/lab-guide.md for a step-by-step walkthrough of setting up a detection validation lab with Echos.
See ROADMAP.md for planned features and future direction.
# List all available profiles
echos --list
# Dry-run to preview behaviour without sending traffic
echos --profile Cobalt --dry-run
# Override target at runtime
echos --profile Cobalt --target http://10.0.0.1:8080 --count 5
# Run Lazarus profile for 60 s with self-signed cert support
echos --profile Lazarus --duration 60 --insecure-tls
# Emit structured JSON output and save logs to file
echos --profile APT29 --count 3 --json --log-file run.json
# Load a custom profile from TOML, run quietly
echos --config examples/echos.toml --profile "My Custom Profile" --count 10 --quiet
# Load all TOML files from a directory
echos --config-dir ./profiles.d --listBy default, echos validates TLS certificates normally. This is the safe, secure default.
Pass --insecure-tls only when your lab server uses a self-signed certificate and you understand the implications:
echos --profile Lazarus --insecure-tls --count 1This flag affects only HTTPS profiles. HTTP, DNS, ICMP, SMB, WebSocket, and SMTP profiles are unaffected.
Define additional profiles in TOML. Load them at runtime with --config or --config-dir.
# examples/echos.toml
[[profiles]]
name = "My Custom Profile"
protocol = "https"
target = "https://192.168.1.100:8443"
base_delay_secs = 45
jitter_percent = 15.0
jitter_algorithm = "gaussian" # uniform | gaussian | sinusoidal (default: uniform)
[profiles.headers]
User-Agent = "Mozilla/5.0 (compatible; LabScanner/1.0)"
X-Lab-ID = "detection-lab-01"Required fields: name, protocol, target, base_delay_secs, jitter_percent
Optional fields: targets (rotating list), headers, jitter_algorithm
External profiles with the same name as a built-in profile silently override the built-in.
| Profile | Protocol | Delay | Jitter | Algorithm | Notes |
|---|---|---|---|---|---|
| Cobalt | HTTP | 10 s | 20% | Uniform | Cobalt Strike-style headers |
| APT28 | DNS | 30 s | 10% | Uniform | DNS lookup beacon |
| ICMP Beacon | ICMP | 60 s | 5% | Uniform | Ping-based probe |
| Lazarus | HTTPS | 300 s | 15% | Gaussian | Korean-language UA, slow C2 |
| APT29 | HTTPS | 600 s | 10% | Sinusoidal | Office 365 UA, business-hours blend |
| Emotet | HTTP | 60 s | 25% | Gaussian | Rotating 4-target pool, DGA simulation |
| FIN7 | HTTPS | 30 s | 10% | Uniform | CDN-masquerading headers |
| SMB Beacon | SMB | 120 s | 10% | Uniform | SMB negotiate probe on port 445 |
| WebSocket Beacon | WebSocket | 15 s | 15% | Uniform | Single text frame over WS |
| SMTP Beacon | SMTP | 90 s | 10% | Uniform | EHLO probe without sending mail |
| Sandworm | ICMP | 180 s | 20% | Sinusoidal | GRU-style slow ICMP, time-of-day jitter |
| Turla | HTTP | 45 s | 15% | Gaussian | FSB-style obfuscation headers |
| Carbanak | HTTPS | 120 s | 10% | Gaussian | Banking-industry UA strings |
| CS DNS Beacon | DNS | 20 s | 30% | Uniform | Cobalt Strike DNS, rotating subdomains |
| Meterpreter | HTTP | 5 s | 25% | Gaussian | Meterpreter reverse_http POST pattern |
| FTP Beacon | FTP | 90 s | 10% | Uniform | Outbound FTP banner probe, port 21 |
| LDAP Beacon | LDAP | 60 s | 10% | Uniform | Anonymous LDAP bind probe, port 389 |
| RDP Beacon | RDP | 30 s | 10% | Uniform | RDP connection request probe, port 3389 |
| Algorithm | Behaviour |
|---|---|
uniform |
Random delay uniformly distributed in [base × (1−jitter%), base × (1+jitter%)] |
gaussian |
Box-Muller normal distribution centred at base, σ = jitter_amount; clamped to > 0.1 s |
sinusoidal |
Time-of-day modulation: 3× base at 01:00, 1× base at 13:00; mimics business-hours blending |
Pre-built binaries for Linux, Windows, and macOS are published on the Releases page for every tagged version.
| Platform | File |
|---|---|
| Linux x86-64 | echos-linux-x86_64 |
| Windows x86-64 | echos-windows-x86_64.exe |
| macOS x86-64 | echos-macos-x86_64 |
See CONTRIBUTING.md for how to add profiles, run tests, and open pull requests.
Echos is intended exclusively for authorized security testing and defensive research in environments you own or have explicit written permission to test. Generating unsolicited beacon traffic on networks you do not control may violate computer-fraud laws. The authors assume no liability for misuse.