Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Detect lateral movement in network traffic using Zeek (formerly Bro) log analysis. Parses conn.log, smb_mapping.log, smb_files.log, dce_rpc.log, kerberos.log, and ntlm.log to identify SMB file transfers, NTLM account spray activity, remote service execution, and anomalous internal connections.
.claude/skills/detecting-lateral-movement-with-zeek/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-01 | ✗→✓ | ▲ Improved | 68% | 0% |
| case-02 | ✗→✓ | ▲ Improved | 18% | 0% |
| case-03 | ✗→✓ | ▲ Improved | 66% | 0% |
| case-07 | ✗→✓ | ▲ Improved | 154% | 0% |
| case-08 | ✗→✓ | ▲ Improved | 43% | 0% |
Analyze Zeek network logs to identify lateral movement techniques including SMB admin share access, DCE/RPC remote service creation, NTLM account spray, Kerberos ticket anomalies, and large internal data transfers indicative of staging or exfiltration between hosts.
Do not use as a standalone detection mechanism. Zeek sees network traffic only; combine with endpoint telemetry (Sysmon, EDR) for full visibility. Encrypted SMB3 traffic may limit Zeek's visibility into file-level details.
@load base/protocols/smb)@load base/protocols/dce-rpc)@load base/protocols/krb)/opt/zeek/logs/current/)\t, header lines prefixed with #)Confirm that Zeek is producing the required log files for lateral movement detection:
bash# Check that all required analyzers are producing logs ls -la /opt/zeek/logs/current/conn.log ls -la /opt/zeek/logs/current/smb_mapping.log ls -la /opt/zeek/logs/current/smb_files.log ls -la /opt/zeek/logs/current/dce_rpc.log ls -la /opt/zeek/logs/current/kerberos.log ls -la /opt/zeek/logs/current/ntlm.log # Quick field check on conn.log zeek-cut id.orig_h id.resp_h id.resp_p proto service < /opt/zeek/logs/current/conn.log | head -20
Identify connections between internal hosts on lateral-movement-associated ports:
bash# Extract SMB connections (port 445) between internal hosts zeek-cut ts id.orig_h id.orig_p id.resp_h id.resp_p proto service duration orig_bytes resp_bytes \ < /opt/zeek/logs/current/conn.log \ | awk '$5 == 445 && $7 == "smb"' # Extract DCE/RPC connections (port 135) zeek-cut ts id.orig_h id.resp_h id.resp_p service \ < /opt/zeek/logs/current/conn.log \ | awk '$4 == 135' # Extract WinRM connections (port 5985/5986) zeek-cut ts id.orig_h id.resp_h id.resp_p service \ < /opt/zeek/logs/current/conn.log \ | awk '$4 == 5985 || $4 == 5986'
Detect access to administrative shares (C$, ADMIN$, IPC$) which is the primary vector for tools like PsExec:
bash# Check smb_mapping.log for admin share access zeek-cut ts id.orig_h id.resp_h path share_type \ < /opt/zeek/logs/current/smb_mapping.log \ | grep -iE '(C\$|ADMIN\$|IPC\$)' # Check smb_files.log for file writes to admin shares zeek-cut ts id.orig_h id.resp_h action path name size \ < /opt/zeek/logs/current/smb_files.log \ | grep -i 'SMB::FILE_WRITE'
Deploy the following Zeek script to generate notice.log alerts on admin share access:
zeek@load base/protocols/smb @load base/frameworks/notice redef enum Notice::Type += { Admin_Share_Access }; event smb1_tree_connect_andx_request(c: connection, hdr: SMB1::Header, path: string, service: string) { if ( /\$/ in path ) NOTICE([$note=Admin_Share_Access, $msg=fmt("Admin share access: %s -> %s (%s)", c$id$orig_h, c$id$resp_h, path), $conn=c]); }
Monitor for remote service creation and scheduled task registration via DCE/RPC:
bash# Look for service control manager operations (PsExec pattern) zeek-cut ts id.orig_h id.resp_h endpoint operation \ < /opt/zeek/logs/current/dce_rpc.log \ | grep -iE '(svcctl|atsvc|ITaskSchedulerService)'
Analyze ntlm.log for authentication anomalies indicating credential reuse. Zeek's ntlm.log does not expose password hashes, so this detection identifies a single account authenticating to many hosts in a short window — the network signature of credential spraying tools like CrackMapExec:
bash# Extract NTLM authentications zeek-cut ts id.orig_h id.resp_h username domainname server_nb_computer_name success \ < /opt/zeek/logs/current/ntlm.log # Failed NTLM authentications (brute force or credential testing) zeek-cut ts id.orig_h id.resp_h username success \ < /opt/zeek/logs/current/ntlm.log \ | awk '$5 == "F"' # Sort by timestamp for timeline analysis zeek-cut ts id.orig_h id.resp_h username success \ < /opt/zeek/logs/current/ntlm.log \ | sort -k1,1
Deploy the following Zeek script to generate notice.log alerts when a single account touches more hosts than the threshold in a rolling window:
zeek@load base/protocols/ntlm @load base/frameworks/notice redef enum Notice::Type += { NTLM_Account_Spray }; global ntlm_tracker: table[string] of set[addr] &create_expire=5min; const spray_threshold = 3 &redef; event ntlm_log(rec: NTLM::Info) { if ( ! rec?$username || rec$username == "-" ) return; if ( rec$username !in ntlm_tracker ) ntlm_tracker[rec$username] = set(); add ntlm_tracker[rec$username][rec$id$resp_h]; if ( |ntlm_tracker[rec$username]| >= spray_threshold ) NOTICE([$note=NTLM_Account_Spray, $msg=fmt("NTLM account spray: %s -> %d hosts", rec$username, |ntlm_tracker[rec$username]|), $sub=rec$username, $conn=rec$id]); }
Use the provided agent.py for comprehensive lateral movement detection:
bashpython3 agent.py /opt/zeek/logs/current/ python3 agent.py /opt/zeek/logs/2026-03-18/ # Analyze a specific date
notice.log entry when the spray threshold is exceeded| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-01 | fail→pass | 30,782 | 15,505 | -50% | 1 | 1 | 0% | 3,356 | 5,636 | +68% | 0 | 0 | — |
case-02 | fail→pass | 21,000 | 13,169 | -37% | 1 | 1 | 0% | 4,026 | 4,757 | +18% | 0 | 0 | — |
case-03 | fail→pass | 22,231 | 24,135 | +9% | 1 | 1 | 0% | 4,542 | 7,548 | +66% | 0 | 0 | — |
case-04 | fail→fail | 4,453 | 5,010 | +13% | 1 | 1 | 0% | 656 | 3,003 | +358% | 0 | 0 | — |
case-05 | fail→fail | 3,113 | 3,013 | -3% | 1 | 1 | 0% | 568 | 2,684 | +373% | 0 | 0 | — |
case-06 | pass→pass | 14,421 | 5,849 | -59% | 1 | 1 | 0% | 2,397 | 3,135 | +31% | 0 | 0 | — |
case-07 | fail→pass | 23,047 | 2,856 | -88% | 1 | 1 | 0% | 1,024 | 2,601 | +154% | 0 | 0 | — |
case-08 | fail→pass | 15,970 | 13,297 | -17% | 1 | 1 | 0% | 2,843 | 4,059 | +43% | 0 | 0 | — |
case-09 | fail→pass | 5,160 | 9,528 | +85% | 1 | 1 | 0% | 1,126 | 4,099 | +264% | 0 | 0 | — |
case-10 | pass→fail | 12,013 | 7,302 | -39% | 1 | 1 | 0% | 2,126 | 2,904 | +37% | 0 | 0 | — |
case-11 | fail→pass | 8,625 | 6,845 | -21% | 1 | 1 | 0% | 1,690 | 3,461 | +105% | 0 | 0 | — |
case-12 | fail→fail | 8,980 | 2,923 | -67% | 1 | 1 | 0% | 1,604 | 2,643 | +65% | 0 | 0 | — |
case-13 | fail→pass | 12,488 | 6,032 | -52% | 1 | 1 | 0% | 2,464 | 3,243 | +32% | 0 | 0 | — |
case-14 | fail→fail | 12,690 | 6,645 | -48% | 1 | 1 | 0% | 2,194 | 3,240 | +48% | 0 | 0 | — |
case-15 | fail→pass | 7,399 | 3,557 | -52% | 1 | 1 | 0% | 1,428 | 2,681 | +88% | 0 | 0 | — |
case-16 | fail→fail | 6,398 | 2,536 | -60% | 1 | 1 | 0% | 1,182 | 2,505 | +112% | 0 | 0 | — |
case-17 | fail→pass | 8,022 | 1,792 | -78% | 1 | 1 | 0% | 1,365 | 2,398 | +76% | 0 | 0 | — |
case-18 | fail→pass | 10,670 | 2,559 | -76% | 1 | 1 | 0% | 1,907 | 2,524 | +32% | 0 | 0 | — |
case-19 | fail→fail | 3,386 | 2,419 | -29% | 1 | 1 | 0% | 668 | 2,434 | +264% | 0 | 0 | — |
case-20 | fail→fail | 15,966 | 12,057 | -24% | 1 | 1 | 0% | 2,542 | 4,357 | +71% | 0 | 0 | — |
case-21 | fail→fail | 12,046 | 8,027 | -33% | 1 | 1 | 0% | 1,971 | 3,470 | +76% | 0 | 0 | — |
case-22 | fail→fail | 16,434 | 8,853 | -46% | 1 | 1 | 0% | 2,660 | 3,481 | +31% | 0 | 0 | — |
case-23 | fail→fail | 8,040 | 6,536 | -19% | 1 | 1 | 0% | 1,343 | 3,110 | +132% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 23 cases were attempted, and 22 counted toward the lift figure. The other 1 produced results that are not comparable between the two arms, so they are excluded from the headline rather than averaged into it. The headline lift of +43 percentage points is the difference between those two pass rates over the 22 comparable cases. 1 case got worse with the skill loaded, and it is included in that figure.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.