The Quiet Signal — Hunt Forward Lab #001: C2 Beaconing Detection
嗯,用户让我帮忙总结一篇文章,控制在一百个字以内,而且不需要特定的开头。首先,我需要理解文章的内容。看起来这篇文章是一个实验室的指南,关于检测C2信标活动的。 文章讲述了Alex Chen作为初级SOC分析师,如何通过日志分析发现可疑活动。主要涉及三个信号:定时连接、一致的字节大小和未知外部目标。然后用户需要系统地使用KQL查询来识别信标主机、确认信标、发现第二个感染主机以及追踪横向移动。 我需要将这些关键点浓缩到100字以内。可能需要提到检测C2信标,使用KQL查询分析网络日志,识别异常模式,如定时连接和一致的数据包大小,以及追踪横向移动。这样用户就能快速了解文章的核心内容了。 </think> 这篇文章描述了一个实验室场景,通过模拟网络攻击事件教授如何检测C2信标活动。参与者使用Elastic SIEM分析日志数据,识别异常网络行为(如定时连接、一致字节大小和未知外部目标),并利用KQL查询系统地追踪恶意活动,最终确认信标主机、发现第二个感染设备及追踪横向移动行为。 2026-4-27 05:2:23 Author: infosecwriteups.com(查看原文) 阅读量:21 收藏

Hunt Forward

🔬 Lab Difficulty: Beginner — Estimated Time: 60–90 minutes

🗂️ MITRE ATT&CK: T1071.001 — Application Layer Protocol: Web Protocols

What is Hunt Forward ?

Elastic SIEM Access request — https://hunt-forward.com

Press enter or click to view image in full size

Image generated by chatgpt for hunt-forward.com

How to use this lab: Read the story to understand the attack. Then follow the Hunt section to find it yourself in Elastic SIEM.

Document your findings in your Hunt Notebook as you go — you’ll use them to build your GitHub portfolio at the end.

📖 Part 1: The Scenario

Monday, 8:03 AM, San Francisco.

Alex Chen, junior SOC analyst, first week on the job. His team lead Dana drops a sticky note on his keyboard before vanishing into a meeting: “Routine log review. Network flows, last 24 hours. Probably nothing.”

Alex opens Kibana. Starts scrolling. AWS. Slack. Google. Normal, normal, normal — then one entry makes him pause.

192.168.1.100  →  203.0.113.42:8080  |  TCP  |  150 bytes out  |  75 bytes in
192.168.1.100 → 203.0.113.42:8080 | TCP | 153 bytes out | 78 bytes in
192.168.1.100 → 203.0.113.42:8080 | TCP | 149 bytes out | 74 bytes in

One minute apart. Every time. He switches to histogram view. Six hours. Clockwork.

He looks up the source IP — a developer workstation belonging to Priya, who has access to the production credentials vault. He pastes 203.0.113.42 into VirusTotal.

12 vendors flag it. Category: C2 Server.

He calls Dana. “I think I found something.” / “How bad?” / “Bad enough.”

What happened behind the scenes: Three days earlier, Priya clicked a phishing link disguised as a Slack security notice. A Remote Access Trojan (RAT) installed silently. Its only job: check in with the attacker’s Command and Control (C2) server every 60 seconds — a tiny “I’m alive, any orders?” — and wait. The attacker was patient. They hadn’t moved yet. But that quiet, clockwork heartbeat is called a beacon, and it left tracks in the logs.

Now it’s your turn to find it.

The Three Signals That Give Beaconing Away

┌─────────────────────────────────────────────────────────────┐
│ 🚨 SIGNAL 1: Clockwork Timing │
│ Normal app: connects when user does something │
│ Malware: connects on a timer. Every. Single. Minute. │
├─────────────────────────────────────────────────────────────┤
│ 🚨 SIGNAL 2: Identical Byte Counts │
│ Normal app: transfers vary wildly (KB to MB) │
│ Malware: same tiny payload every time (~150 bytes) │
├─────────────────────────────────────────────────────────────┤
│ 🚨 SIGNAL 3: Unknown External Destination │
│ Normal app: talks to known CDNs, cloud providers │
│ Malware: a bare IP address, no legitimate business use │
└─────────────────────────────────────────────────────────────┘

Now it’s your turn to find it.

🎯 Part 2: Your Mission

Alex found this manually by instinct and a sharp eye. You’re going to find it systematically — using KQL queries that will work against any environment, not just this one.

By the end of this lab you will have:

  • ✅ Identified the beaconing host using connection frequency analysis
  • ✅ Confirmed the beacon using byte consistency analysis
  • ✅ Found a second infected host using DNS anomaly detection
  • ✅ Traced lateral movement from the initial compromise
  • ✅ Documented your full investigation in your Hunt Notebook

🔧 Part 3: Lab Setup

You’ll need a Hunt Forward account for this lab. Hunt Forward gives you a live Elastic SIEM environment with this exact dataset pre-loaded — no installation, no configuration.

👉 hunt-forward.com — 7-day free trial, then $5/month

Click : Discover Session

OR

You can follow the below steps:

  1. Open Kibana → click the hamburger menu (top left) → Discover
  2. Select the index c2-lab-logs
  3. Set the time range to April 24, 2024 (custom: 2024-04-24T00:00:002024-04-24T23:59:59)

You should see 739 log entries. That’s your crime scene. Let’s work it.

🔍 Part 4: The Hunt

Hunt 1 — Find the Machine Talking Too Much

Alex’s first instinct was right: something was making way too many connections to the same place. Let’s quantify that with a single ES|QL query.

Switch to ES|QL in Discover by clicking </>ES|QL on the top right corner of the discover.

Press enter or click to view image in full size

Screenshot taken by author
FROM c2-lab-logs
| WHERE event.dataset == "zeek.conn"
AND NOT destination.ip == "8.8.8.8"
| STATS connection_count = COUNT()
BY source.ip, destination.ip, destination.port
| SORT connection_count DESC
| LIMIT 20

What each line does:

  • FROM c2-lab-logs — query this index
  • WHERE event.dataset == "zeek.conn" — only network connection logs, not DNS
  • AND NOT destination.ip == "8.8.8.8" — drop Google DNS (background noise)
  • STATS connection_count = COUNT() BY ... — count connections grouped by source IP, destination IP, and port
  • SORT connection_count DESC — highest count first
  • LIMIT 20 — show top 20 results

What you’re looking for: One row with a connection count dramatically higher than everything else — we’re talking 10x or more. Every legitimate application in this dataset connects to many different destinations. Malware talks to one destination, over and over.

📝 Hunt Notebook checkpoint: Write down the source IP, destination IP, destination port, and connection count you find. This is your first IOC (Indicator of Compromise). Notebook can be accessed using this icon on the blog within your dashboard.

Press enter or click to view image in full size

Screenshot taken from huntfroward.com

🏁 Milestone 1 of 4 — Suspicious Host Identified Open your Hunt Notebook in the Hunt Forward dashboard and paste this template. Fill in your findings from the query above.

## 🔍 Milestone 1: Suspicious Host Identified

**Date of Hunt:** [today's date]
**Lab:** Hunt Forward #001 — C2 Beaconing Detection
**Analyst:** [your name]

### Finding
Identified a host making an abnormally high number of connections to a
single external destination.

| Field | Value |
|------------------|----------------|
| Source IP | [your finding] |
| Destination IP | [your finding] |
| Destination Port | [your finding] |
| Connection Count | [your finding] |
| Time Window | [your finding] |

### ES|QL Query Used
```esql
FROM c2-lab-logs
| WHERE event.dataset == "zeek.conn"
AND NOT destination.ip == "8.8.8.8"
| STATS connection_count = COUNT()
BY source.ip, destination.ip, destination.port
| SORT connection_count DESC
| LIMIT 20

Notes:

Connection count is significantly higher than any other source/destination pair in the dataset, indicating automated or scripted behavior.

Hunt 2 — Confirm It’s a Beacon (The Clockwork Test)

High connection count could be a chatty but legitimate app. The real test is regularity and consistency. Run this ES|QL query to calculate the byte statistics across every connection in the suspicious pair:

FROM c2-lab-logs
| WHERE event.dataset == "zeek.conn"
AND source.ip == "192.168.1.100"
AND destination.ip == "203.0.113.42"
| STATS
total_connections = COUNT(),
avg_bytes_out = AVG(network.bytes_out),
min_bytes_out = MIN(network.bytes_out),
max_bytes_out = MAX(network.bytes_out),
avg_bytes_in = AVG(network.bytes_in),
min_bytes_in = MIN(network.bytes_in),
max_bytes_in = MAX(network.bytes_in),
first_seen = MIN(@timestamp),
last_seen = MAX(@timestamp)

What each line does:

  • WHERE source.ip == ... AND destination.ip == ... — scope to just the suspicious pair
  • STATS COUNT() — total number of connections
  • AVG / MIN / MAX on network.bytes_out and network.bytes_in — calculate the spread of byte transfers
  • MIN / MAX @timestamp — shows the full time range of the activity

What you’re looking for: Two things that confirm a beacon:

  1. Clockwork timingtotal_connections should be ~360 over the 6-hour window between first_seen and last_seen. Divide the duration by the count to get your approximate beacon interval.
  2. Byte consistency — the gap between min_bytes_out and max_bytes_out should be tiny relative to the average. Less than 10% variance is a strong beacon indicator. Legitimate app traffic varies wildly.

A browser session might transfer anywhere from 5KB to 5MB per connection. Malware sends the same 150-byte “I’m alive” message every single time.

📝 Hunt Notebook checkpoint: Record total_connections, all six byte stats (avg/min/max for in and out), first_seen, and last_seen. Calculate the approximate beacon interval and byte variance percentage.

Beacon confirmed. 192.168.1.100 is infected and calling home every ~60 seconds.

🏁 Milestone 2 of 4 — Beacon Confirmed Add this block to your Hunt Notebook below Milestone 1.

## ✅ Milestone 2: Beacon Confirmed

### Timing Analysis
| Field | Value |
|--------------------|----------------|
| Beacon interval | ~[X] seconds |
| First beacon seen | [timestamp] |
| Last beacon seen | [timestamp] |
| Total beacons | [count] |
| Duration | [X] hours |

### Byte Consistency Analysis
| Field | Value |
|--------------------|----------------|
| bytes_out min | [your finding] |
| bytes_out max | [your finding] |
| bytes_out avg | [your finding] |
| bytes_in min | [your finding] |
| bytes_in max | [your finding] |

### ES|QL Query Used
```esql
FROM c2-lab-logs
| WHERE event.dataset == "zeek.conn"
AND source.ip == "192.168.1.100"
AND destination.ip == "203.0.113.42"
| STATS
total_connections = COUNT(),
avg_bytes_out = AVG(network.bytes_out),
min_bytes_out = MIN(network.bytes_out),
max_bytes_out = MAX(network.bytes_out),
avg_bytes_in = AVG(network.bytes_in),
min_bytes_in = MIN(network.bytes_in),
max_bytes_in = MAX(network.bytes_in),
first_seen = MIN(@timestamp),
last_seen = MAX(@timestamp)

Conclusion

Byte variance is under [X]% across all connections. Combined with clockwork timing (~[X]s interval over [X] hours), this confirms automated beacon behavior — not legitimate application traffic.

Severity: High Confidence: High

Hunt 3 — Find the Second Infected Host (DGA Hunt)

Priya’s machine isn’t the only one. There’s a second infected host using a more sophisticated technique called Domain Generation Algorithms (DGA).

Get Hunt Forward’s stories in your inbox

Join Medium for free to get updates from this writer.

Remember me for faster sign in

Instead of beaconing to a hardcoded IP, this malware generates hundreds of random-looking domain names and tries each one until it finds an active C2 server. The symptom this leaves in DNS logs is called an NXDOMAIN storm — a flood of “domain not found” failures from one host in a short time window.

Run this ES|QL query to rank every host by how many NXDOMAIN responses they generated:

FROM c2-lab-logs
| WHERE event.dataset == "zeek.dns"
AND dns.response_code == "NXDOMAIN"
| STATS nxdomain_count = COUNT()
BY source.ip
| SORT nxdomain_count DESC

What each line does:

  • WHERE event.dataset == "zeek.dns" — DNS logs only
  • AND dns.response_code == "NXDOMAIN" — only failed lookups
  • STATS COUNT() BY source.ip — count failures per host
  • SORT nxdomain_count DESC — worst offender first

What you’re looking for: One IP generating dramatically more NXDOMAIN failures than anyone else. Normal hosts occasionally hit a bad domain. A DGA host hits dozens or hundreds in a short window.

Now follow the trail — find the domain the malware eventually resolved successfully:

FROM c2-lab-logs
| WHERE event.dataset == "zeek.dns"
AND source.ip == "192.168.1.105"
AND dns.response_code == "NOERROR"
| KEEP @timestamp, dns.question.name, dns.answers.data
| SORT @timestamp ASC

What each line does:

  • AND source.ip == "192.168.1.105" — scope to the DGA host you just found
  • AND dns.response_code == "NOERROR" — only successful resolutions
  • KEEP — show only these three fields so results are readable
  • SORT @timestamp ASC — earliest resolution first

You’ll see the domain the malware found — disguised to look like a software update service. That’s the active C2 domain.

📝 Hunt Notebook checkpoint: Record the second infected host IP, the number of NXDOMAIN responses, and the C2 domain it eventually resolved. Note the timestamp when the DGA storm started and when it resolved.

🏁 Milestone 3 of 4 — Second Host & DGA Identified Add this block to your Hunt Notebook below Milestone 2.

## 🌐 Milestone 3: Second Infected Host — DGA Behavior

### DGA Storm
| Field | Value |
|------------------------|----------------|
| Source IP | [your finding] |
| NXDOMAIN count | [your finding] |
| Storm start time | [timestamp] |
| Storm end time | [timestamp] |
| Sample DGA domains | [domain 1] |
| | [domain 2] |
| | [domain 3] |

### C2 Domain Resolved
| Field | Value |
|------------------------|-------------------------|
| Active C2 domain | [your finding] |
| Resolved IP | [your finding] |
| First successful query | [timestamp] |

### ES|QL Queries Used

FROM c2-lab-logs
| WHERE event.dataset == "zeek.dns"
AND dns.response_code == "NXDOMAIN"
| STATS nxdomain_count = COUNT()
BY source.ip
| SORT nxdomain_count DESC

FROM c2-lab-logs
| WHERE event.dataset == "zeek.dns"
AND source.ip == "192.168.1.105"
AND dns.response_code == "NOERROR"
| KEEP @timestamp, dns.question.name, dns.answers.data
| SORT @timestamp ASC

Conclusion

Host [IP] exhibited DGA behavior — [N] NXDOMAIN failures before resolving [domain]. The use of a dynamic DNS provider and update-themed domain name are common evasion techniques.

Severity: High Confidence: High

Hunt 4 — Follow the Attacker (Lateral Movement)

Here’s where the story gets urgent.

Once the attacker had a foothold on Priya’s machine, they didn’t stop there. After four hours of maintaining their beacon, they started exploring — reaching out to other machines on the internal network, looking for servers with valuable data.

This is called lateral movement, and it’s a critical escalation in any incident.

Run this ES|QL query to find every internal host the compromised machine touched, and how much data moved in each connection:

esql

FROM c2-lab-logs
| WHERE network.direction == "internal"
AND destination.port == 445
| STATS
connection_count = COUNT(),
total_bytes_out = SUM(network.bytes_out),
total_bytes_in = SUM(network.bytes_in)
BY source.ip, destination.ip
| SORT total_bytes_out DESC

What each line does:

  • WHERE network.direction == "internal" — only traffic between internal hosts
  • AND destination.port == 445 — SMB port, the most common lateral movement channel
  • STATS COUNT(), SUM(bytes_out), SUM(bytes_in) BY source/dest — count connections and total data per pair
  • SORT total_bytes_out DESC — the pair with the most data transferred appears first

What you’re looking for: The compromised host (192.168.1.100) reaching multiple different internal machines. Most entries will show tiny byte counts — those are probes. One entry will show a dramatically larger transfer. That's not a probe — that's a successful connection where the attacker got in.

📝 Hunt Notebook checkpoint: Record the list of internal IPs that were probed, the timestamp range of the scanning activity, and which machine received the largest data transfer. This is your lateral movement evidence.

🏁 Milestone 4 of 4 — Lateral Movement Traced Add this final block to your Hunt Notebook below Milestone 3. This completes your investigation.

## 🔀 Milestone 4: Lateral Movement

### SMB Scan Evidence
| Field | Value |
|------------------------|----------------|
| Source (attacker host) | [your finding] |
| Scan start time | [timestamp] |
| Scan end time | [timestamp] |
| Unique hosts probed | [count] |
| Port targeted | 445 (SMB) |

### Internal Hosts Probed
| Target IP | Bytes Out | Bytes In | Result |
|----------------|-----------|----------|---------------|
| [IP 1] | [bytes] | [bytes] | Probe only |
| [IP 2] | [bytes] | [bytes] | Probe only |
| [IP 3] | [bytes] | [bytes] | ⚠️ Large transfer — possible access |

### ES|QL Query Used

FROM c2-lab-logs
| WHERE network.direction == "internal"
AND destination.port == 445
| STATS
connection_count = COUNT(),
total_bytes_out = SUM(network.bytes_out),
total_bytes_in = SUM(network.bytes_in)
BY source.ip, destination.ip
| SORT total_bytes_out DESC

Conclusion

Compromised host [IP] scanned [N] internal targets over [duration]. One host ([IP]) received a significantly larger data transfer ([bytes]), indicating a successful SMB connection. That host may be compromised.

Severity: Critical — active lateral movement in progress Confidence: High

Recommended Immediate Actions

  1. Isolate 192.168.1.100 (primary beacon host)

2. Isolate 192.168.1.105 (DGA/secondary beacon host)

3. Isolate [IP of successful SMB target] pending investigation

4. Block 203.0.113.42 and [C2 domain] at perimeter firewall

5. Preserve memory image of 192.168.1.100 before shutdown

6. Escalate to IR team with this document as initial findings report

📋 Part 5: Building Your Timeline

You’ve found everything Alex found — and you did it systematically with repeatable queries. Now pull it together.

┌────────────────────────────────────────────────────────────────────────┐
│ INCIDENT TIMELINE — NovaPay Network Compromise │
├──────────┬─────────────────────────────────────────────────────────────┤
│ 08:00 │ 192.168.1.100 begins beaconing to 203.0.113.42:8080 │
│ │ → Every ~60 seconds, ~150 bytes out / ~75 bytes in │
├──────────┼─────────────────────────────────────────────────────────────┤
│ 08:00 │ 192.168.1.105 begins DGA domain polling │
│ │ → 100 NXDOMAIN responses to random-string domains │
├──────────┼─────────────────────────────────────────────────────────────┤
│ 10:00 │ 192.168.1.105 resolves update-svc.ddns.net │
│ │ → DGA succeeded; second host now under attacker control │
├──────────┼─────────────────────────────────────────────────────────────┤
│ 12:00 │ 192.168.1.100 begins internal SMB scanning │
│ │ → 8 internal hosts probed on port 445 in rapid succession │
├──────────┼─────────────────────────────────────────────────────────────┤
│ 12:06 │ Large data transfer to 192.168.1.20 via SMB │
│ │ → Possible successful lateral movement to file server │
└──────────┴─────────────────────────────────────────────────────────────┘

📝 Part 6: Export Your Hunt Notebook → GitHub Portfolio

If you followed the milestone blocks throughout this lab, your Hunt Notebook already contains four completed sections. You have two options for turning them into a GitHub portfolio piece:

Option A — Merge your milestones. Combine all four milestone blocks into a single document, add a cover section with your name and an executive summary, and push it as hunt-001-c2-beaconing-detection.md. Everything you need is already written — just assemble it.

✅ Milestone 1 — Suspicious Host Identified
✅ Milestone 2 — Beacon Confirmed
✅ Milestone 3 — Second Host & DGA Identified
✅ Milestone 4 — Lateral Movement Traced

Option B — Use the Hunt Forward pre-written report. A completed reference report for this lab is available in your Hunt Forward dashboard. Download it, push it to GitHub, and you’re done.

That said — here’s a recommendation worth taking seriously.

Write your own.

You don’t have to do it from scratch. Use AI to handle the repetitive parts — formatting tables, structuring sections, cleaning up your notes. But keep yourself in the driver’s seat: decide how to structure the narrative, choose which findings to lead with, write the executive summary in your own voice. The goal isn’t a perfect document — it’s the practice of thinking like someone who has to communicate a threat investigation clearly to another human.

That skill compounds. Every report you write makes the next one faster and sharper. And when you’re sitting in an interview and a hiring manager asks you to walk them through an investigation you’ve done, the one you wrote yourself is the one you can actually talk about with confidence.

Export from the Hunt Notebook and push to GitHub as:

hunt-001-c2-beaconing-detection.md

That’s your first portfolio piece. Four milestones of real findings, your KQL queries, a timeline you built from raw logs, and your name on it.

🛡️ Part 7: What Alex Did Next

Dana’s meeting ended sixty seconds after Alex’s call. Both hosts were isolated, the C2 IP and domain blocked at the perimeter, and Priya’s machine forensically imaged before anyone touched it. The attacker had been patient — 72 hours of silent beaconing — but Alex caught the lateral movement before a single byte of payment data left the network.

First week. Real incident. He opened his Hunt Notebook and started writing.

🎓 The Takeaway

You just practiced three core threat hunting techniques that real SOC analysts use every day:

None of these required knowing the attacker’s IP in advance. You found them purely by looking for behavior that doesn’t match normal network activity.

That’s threat hunting.

🚀 Ready for the Next Lab?

This is Hunt Forward Lab #001. New labs publish 2–3 times per week.

Coming up:

  • Lab #002: LOLBAS — When the Attacker Uses Your Own Tools Against You
  • Lab #003: DNS Tunneling — Finding Data Exfiltration Hidden in Plain Sight
  • Lab #004: Credential Dumping — Hunting LSASS Access in Endpoint Logs

Each one has the same format: a story, a hunt, a Hunt Notebook template, and a portfolio piece waiting to be written.

👉 Access all labs at hunt-forward.com— 7-day free trial, then $5/month

And if you want to follow along as new labs drop — follow Hunt Forward on Medium. You’ll get notified every time a new scenario goes live.

Hunt Forward Lab #001 — C2 Beaconing Detection MITRE ATT&CK: T1071.001 | Dataset: c2-lab-logs | Difficulty: Beginner


文章来源: https://infosecwriteups.com/the-quiet-signal-hunt-forward-lab-001-c2-beaconing-detection-2ac84f976e8a?source=rss----7b722bfd1b8d---4
如有侵权请联系:admin#unsafe.sh