Skip to content

Commit 2fbbbfb

Browse files
DavidJBiancoclaude
andcommitted
Phase 6.0: Expert-identified realism fixes + tracking
5 P0 fixes from blind expert panel review (4 domain experts): 1. LogonType distribution: weighted by system type (workstation vs server), Types 2-11 including unlock(7), cached(11), batch(4), service(5). Type 3/4/5/8/9 are standalone events without session creation. 2. Realistic PIDs: Windows multiples of 4 starting 2000-6000 with irregular gaps, Linux sequential from 500-2000. Dynamic ParentProcessName lookup from StateManager. Added winlogon->userinit->explorer.exe process chain. lsass PID from seeded process tree instead of hardcoded 0x2e0. 3. Protocol-aware Zeek: UDP uses Dd/DdDd/D history and SF/S0/OTH conn_state (never TCP-only states like REJ/RSTO/S1). ICMP uses OTH with type/code as ports. Packet counts enforce consistency with history strings. 4. DNS NXDOMAIN: ~20% of lookups emit additional NXDOMAIN for suffix search failures (wpad.corp.local, isatap, etc.). Varied TTLs including cached decremented values. AA=True for internal .corp.local domains. 5. Syslog diversity: 12-80 events/hr per Linux server (role-dependent). 10 programs: systemd(PID 1), CRON(uppercase), kernel(no PID brackets, uptime counter, UFW BLOCK), sshd, snapd, systemd-logind, timesyncd, sudo. Fixed facility=1->3/9 for system processes. Kernel-aware template. Also adds Phase 6 to TODO.md tracking all 44 expert findings (P0/P1/P2/P3) with cross-references to REALISM_ASSESSMENT-2.md. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent d99bbe3 commit 2fbbbfb

9 files changed

Lines changed: 558 additions & 64 deletions

File tree

TODO.md

Lines changed: 188 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
# EvidenceForge Implementation Plan
22

3-
**Status:** Phase 4 - Data Quality Evaluation ✅ SCORING COMPLETE (5/5 dimensions). LLM spot-check deferred.
3+
**Status:** Phase 6 - Expert-Identified Realism Fixes (44 findings from blind expert panel, 6 resolved)
44
**Started:** 2026-03-11
5-
**Last Updated:** 2026-03-16 (Phase 4 eval framework complete: 5 dimensions, 23 sub-scores, 653+ tests)
5+
**Last Updated:** 2026-03-18 (Phase 6 added: 44 expert findings tracked as P0/P1/P2/P3)
66
**Target MVP Completion:** 7-10 weeks from start
77

88
**Recent Completions:**
@@ -685,6 +685,192 @@
685685

686686
---
687687

688+
## Phase 6: Expert-Identified Realism Fixes
689+
690+
**Goal:** Address all findings from blind expert panel review (REALISM_ASSESSMENT-2.md). Four domain experts (threat hunter, detection engineer, Windows sysadmin, Linux/network admin) identified the data as synthetic within seconds. This phase fixes the specific tells, organized by severity. Cross-references are to finding numbers in the assessment.
691+
692+
**Source:** `../DataGenTest/scenarios/insider-exfiltration/REALISM_ASSESSMENT-2.md`
693+
**Baseline:** Run 8 (overall eval score 82, 6 of 44 original findings resolved)
694+
695+
### 6.0 Fixes Already Implemented (Run 8)
696+
697+
- [x] **LogonType diversity** — Types 2,3,4,5,7,8,9,10,11 with weighted distribution by system type (#1 original, N1 new)
698+
- [x] **PID multiples of 4** — OS-aware allocation: Windows multiples of 4 from realistic range, Linux sequential (#2 partial)
699+
- [x] **UDP/TCP history separation** — UDP uses Dd/D/DdDd, ICMP uses OTH, protocol-aware conn_state (#3)
700+
- [x] **NXDOMAIN responses**~20% of DNS lookups emit NXDOMAIN for suffix search failures, WPAD probes (#4)
701+
- [x] **Syslog volume and diversity** — 12-80 events/hr per Linux server, 10 programs, kernel format, systemd PID 1 (#5)
702+
- [x] **SYSTEM domain** — NT AUTHORITY for SYSTEM account (#14 original)
703+
- [x] **explorer.exe in process tree** — winlogon → userinit → explorer.exe chain seeded (#2 partial)
704+
- [x] **Dynamic ParentProcessName** — looked up from StateManager instead of hardcoded (#2 partial)
705+
706+
### 6.1 P0: Critical (Instant Giveaways)
707+
708+
- [ ] **Fix DNS query type semantics** (Assessment #1)
709+
- AAAA queries must return IPv6 addresses (e.g., `2607:f8b0:4004:800::201b`), not IPv4
710+
- PTR queries must use `in-addr.arpa` format (e.g., `40.246.107.13.in-addr.arpa` → hostname)
711+
- Remove CNAME as explicit qtype (CNAMEs are returned in answer chains, not queried directly)
712+
- Add SRV queries (qtype 33) for AD: `_ldap._tcp.dc._msdcs.corp.local`, `_kerberos._tcp.corp.local`
713+
- Files: `activity.py` (`_emit_dns_lookup`)
714+
- [ ] **Fix parent PID still always 0x4** (Assessment #2)
715+
- ParentProcessName says explorer.exe but numeric ProcessId (parent) says 0x4 — contradictory
716+
- Baseline activity must pass `parent_pid=system_pids['explorer']` to `generate_process()`
717+
- eCAR ppid field also always 4 — must use actual parent PID
718+
- Files: `activity.py` (`execute_baseline_activity`, `generate_process`), `engine.py`
719+
- [ ] **Fix duplicate fields in 4624 XML template** (Assessment #3)
720+
- TargetUserName, TargetDomainName, TargetLogonId, LogonType, LogonGuid appear twice in EventData
721+
- Template concatenation bug in `windows_event_security.yaml`
722+
- Files: `formats/definitions/windows_event_security.yaml`
723+
- [ ] **Add Kerberos/LDAP/SQL traffic to Zeek** (Assessment #4)
724+
- Zero connections to port 88 (Kerberos), 389 (LDAP), 1433 (SQL Server)
725+
- Database traffic incorrectly goes to MySQL port 3306 instead of scenario-documented SQL Servers
726+
- Generate Kerberos/LDAP as system traffic from domain-joined machines to DCs
727+
- Files: `engine.py` (`_generate_system_traffic`), `activity.py`
728+
- [ ] **Correlate Zeek DNS and conn UIDs** (Assessment #5)
729+
- DNS conn.log UID does not appear in dns.log and vice versa
730+
- `_emit_dns_lookup` must share the same UID between conn.log and dns.log emissions
731+
- Files: `activity.py` (`_emit_dns_lookup`, `generate_connection`)
732+
733+
### 6.2 P1: Major (Would Fool Casual Observers, Not Experts)
734+
735+
- [ ] **Add missing Windows Event IDs** (Assessment #6, #8)
736+
- 4768/4769 (Kerberos TGT/service tickets), 4776 (NTLM validation)
737+
- 5156 (WFP connection allowed), 4648 (explicit creds)
738+
- 5140/5145 (share access), 4720-4740 (account management)
739+
- 4103/4104 (PowerShell logging)
740+
- DCs should have massive Kerberos event volume
741+
- Files: `windows_event_security.yaml`, `activity.py`, `engine.py`
742+
- [ ] **Add machine account ($) activity** (Assessment #7)
743+
- Zero COMPUTERNAME$ events; real AD has constant machine account auth (GPO, Kerberos, LDAP)
744+
- 118 domain-joined systems should generate significant machine account volume
745+
- Files: `engine.py` (`_generate_system_traffic`), `activity.py`
746+
- [ ] **Set `local_resp: true` for internal servers** (Assessment #9)
747+
- All 122K Zeek records show `local_resp: false`, even for connections to internal 10.10.100.x servers
748+
- Should be `true` when responder IP is in `Site::local_nets`
749+
- Files: `activity.py` (`generate_connection`)
750+
- [ ] **Fix Zeek packet/byte IP+TCP overhead** (Assessment #10)
751+
- Consistent 40-byte overhead per packet; real TCP needs 52-72 bytes (IP + TCP + options)
752+
- Files: `activity.py` (packet count calculation)
753+
- [ ] **Per-computer EventRecordIDs** (Assessment #11)
754+
- Global counter 10001→185448 across all computers; real Windows logs have per-machine sequences
755+
- Files: `activity.py` (`_get_next_event_record_id`), `engine.py`
756+
- [ ] **Fix LogonType distribution** (Assessment #12, N1)
757+
- Type 7 (unlock) too high at 43K; Type 3 (network) should dominate (60-80%)
758+
- Type 5 (service) incorrectly assigned to regular user accounts — restrict to service accounts
759+
- Recalibrate weights: servers need Type 3 at 60-80%, workstations need Type 3 higher than current
760+
- Files: `activity.py` (`execute_baseline_activity`)
761+
- [ ] **Massively increase DC event volume** (Assessment #13, N2)
762+
- Only 147 events from both DCs over 72 hours; real DCs generate tens of thousands per hour
763+
- DCs contribute <0.1% of total events — should be among noisiest machines
764+
- Files: `engine.py` (`_generate_system_traffic` or new DC-specific generator)
765+
- [ ] **Break mechanical traffic pattern** (Assessment #14, N3)
766+
- Every workstation generates rigid DNS→web→DNS→SMTP 4-tuple pattern
767+
- Real traffic is bursty with persistent connections, keep-alives, concurrent connections
768+
- Files: `activity.py` (`execute_baseline_activity`), `engine.py`
769+
- [ ] **Route SMTP through internal Exchange** (Assessment #15)
770+
- Workstations connect directly to Gmail/O365 on port 587; should route through SRV-EXCH-01
771+
- Files: `activity.py` (connection templates), `engine.py`
772+
- [ ] **Route DNS through documented DCs** (Assessment #16)
773+
- DNS queries go to 10.0.0.1 (undocumented); should use DC-01/DC-02 from scenario
774+
- Files: `engine.py` (`_detect_infrastructure_ips`), `activity.py` (`_dns_server_ip`)
775+
776+
### 6.3 P2: Moderate (Polish & Realism)
777+
778+
- [ ] **Add jitter to storyline timestamps** (Assessment #17)
779+
- Attack timestamps are exact multiples of 900s/3600s with .000 microseconds
780+
- Background events have realistic jitter; the contrast is obvious
781+
- Files: `engine.py` (`_execute_storyline_event`)
782+
- [ ] **Realistic LogonIDs** (Assessment #18)
783+
- TargetLogonId values 0x3e7, 0x3e8, 0x3e9... incrementing by 1
784+
- Real LSASS generates high-entropy 64-bit values like 0x1A2B3C4D
785+
- 0x3e7 is SYSTEM's well-known LogonID but assigned to regular users
786+
- Files: `state_manager.py` (logon ID generation)
787+
- [ ] **Populate real base64 in encoded commands** (Assessment #19)
788+
- Literal `<base64_encoded_command>` placeholder never replaced with actual base64
789+
- Files: `engine.py` (storyline execution)
790+
- [ ] **Use realistic public IPs for exfiltration** (Assessment #20)
791+
- RFC 5737 documentation IPs (203.0.113.x, 198.51.100.x) are not internet-routable
792+
- Files: scenario YAML, `engine.py`
793+
- [ ] **Add Kerberos auth package + LogonGuids** (Assessment #21)
794+
- LogonGuid always null; Kerberos-enabled domains populate this for cross-machine correlation
795+
- AuthenticationPackageName never "Kerberos" — should dominate in AD
796+
- Files: `activity.py` (`generate_logon`)
797+
- [ ] **Fix internal DNS IPs and DB ports** (Assessment #22)
798+
- db-primary.corp.local resolves to 10.0.100.x but docs say 10.10.100.x; MySQL 3306 instead of SQL 1433
799+
- Files: `activity.py` (REVERSE_DNS, EXTERNAL_IPS), `engine.py`
800+
- [ ] **Fix Zeek `ts` type consistency** (Assessment #23)
801+
- String in zeek_conn.json, bare number in zeek_dns.json; should be consistent
802+
- Files: `emitters/zeek.py`, `emitters/zeek_dns.py`
803+
- [ ] **Interleave scenario events chronologically** (Assessment #24)
804+
- Attack events appended as separate block at end of ecar.json/zeek_conn.json
805+
- Files: `emitters/base.py` or `engine.py` (output ordering)
806+
- [ ] **Add FQDN to Computer names** (Assessment #25)
807+
- Short names like EXEC-WS-04 instead of EXEC-WS-04.corp.meridiancapital.com
808+
- Files: `activity.py` (Computer field in Windows events)
809+
- [ ] **Add RID gaps and computer account SIDs** (Assessment #26)
810+
- RIDs 1001-1105 with zero gaps; no computer account RIDs or deleted-object gaps
811+
- Files: `engine.py` (SID registry)
812+
- [ ] **Use SSH source IPs from documented subnets** (Assessment #27, N4)
813+
- SSH from 10.0.x.x instead of documented 10.10.x.x topology
814+
- Files: `engine.py` (syslog generation), `activity.py`
815+
- [ ] **Sort syslog chronologically** (Assessment #28)
816+
- Within each hour, timestamps jump randomly; real syslog is append-only/ordered
817+
- Files: `engine.py` (`_generate_system_traffic` syslog section), `emitters/syslog.py`
818+
- [ ] **Limit systemd-timesyncd message** (Assessment #29, N5)
819+
- "Synchronized for the first time" repeats dozens of times; should appear once per boot
820+
- Files: `engine.py` (syslog templates)
821+
- [ ] **Diversify Zeek history strings** (Assessment #30)
822+
- Only ~9 patterns; real Zeek has dozens including retransmission markers (T/t), varied FIN ordering
823+
- Files: `activity.py` (TCP_CONN_STATE_DISTRIBUTION)
824+
- [ ] **Correlate eCAR/Zeek source ports** (Assessment #31)
825+
- eCAR FLOW and Zeek conn at same timestamp show different source ports
826+
- Files: `activity.py` (`generate_connection`, `_emit_ecar_flow_event`)
827+
- [ ] **Add SERVFAIL responses** (Assessment #32)
828+
- Zero SERVFAIL in 39K DNS records; real environments have ~0.1-0.5%
829+
- Files: `activity.py` (`_emit_dns_lookup`)
830+
831+
### 6.4 P3: Minor (Nice-to-Have Improvements)
832+
833+
- [ ] **Fix eCAR DNS FLOW protocol to UDP** (Assessment #33)
834+
- eCAR shows `protocol: "tcp"` for DNS while Zeek correctly shows UDP
835+
- Files: `activity.py` (`_emit_ecar_flow_event`)
836+
- [ ] **Add process attribution to eCAR FLOWs** (Assessment #34)
837+
- `pid: -1` on all FLOW events; real EDR tracks socket-to-process mapping
838+
- Files: `activity.py` (`_emit_ecar_flow_event`)
839+
- [ ] **Reduce eCAR LOGIN frequency** (Assessment #35)
840+
- LOGIN events paired with every activity burst; users generate new LOGIN every 1-2 seconds
841+
- Files: `activity.py` (`execute_baseline_activity`)
842+
- [ ] **Vary filenames in eCAR file operations** (Assessment #36)
843+
- Cycles through 5 generic filenames: spreadsheet.xlsx, presentation.pptx, notes.txt, etc.
844+
- Files: `activity.py` (_ECAR_FILE_PATHS_WIN, _ECAR_FILE_PATHS_LINUX)
845+
- [ ] **Decrement DNS TTLs for cached responses** (Assessment #37)
846+
- Always round values; cached responses should show decremented TTLs
847+
- Files: `activity.py` (`_emit_dns_lookup`)
848+
- [ ] **Fix 4625 event Version to 0** (Assessment #38)
849+
- Uses Version 2 instead of standard Version 0 for 4625
850+
- Files: `formats/definitions/windows_event_security.yaml`
851+
- [ ] **Vary command-line arguments** (Assessment #39)
852+
- Same exact PowerShell/sqlcmd commands repeated dozens of times
853+
- Files: `activity.py` (PROCESS_TEMPLATES)
854+
- [ ] **Add PID interleaving for CRON** (Assessment #40, N6)
855+
- CRON PIDs too sequential (1660, 1661, 1662...); needs interleaving from other processes
856+
- Files: `engine.py` (syslog CRON generation)
857+
- [ ] **Include resp_ip_bytes: 0 on zero-packet records** (Assessment #41)
858+
- Some records missing resp_ip_bytes instead of having it set to 0
859+
- Files: `activity.py` (`generate_connection`)
860+
- [ ] **Multiple answers for popular DNS domains** (Assessment #42)
861+
- Every query returns single answer; CDN domains return multiple A records
862+
- Files: `activity.py` (`_emit_dns_lookup`)
863+
- [ ] **Add `<Events>` root XML wrapper** (Assessment #43)
864+
- Standalone `<Event>` elements without containing root element
865+
- Files: `emitters/windows.py`
866+
- [ ] **Set AA flag for internal DNS** (Assessment #44)
867+
- Internal zone queries (corp.local) show AA: false; should be authoritative
868+
- Files: `activity.py` (`_emit_dns_lookup`)
869+
870+
**Phase 6 Milestone:** Expert panel re-review finds no P0 instant giveaways. P1 findings reduced by 50%+. Eval score ≥ 90.
871+
872+
---
873+
688874
## Post-MVP Enhancements (Future)
689875

690876
**Not part of MVP, but tracked here for future reference.**

src/evidenceforge/formats/definitions/syslog.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,4 +50,4 @@ output:
5050
file_extension: ".log"
5151
encoding: utf-8
5252
template: |
53-
{{ timestamp.strftime('%b %d %H:%M:%S') }} {{ hostname }} {{ app_name }}[{{ pid if pid else '-' }}]: {{ message }}
53+
{% if app_name == 'kernel' %}{{ timestamp.strftime('%b %d %H:%M:%S') }} {{ hostname }} kernel: {{ message }}{% else %}{{ timestamp.strftime('%b %d %H:%M:%S') }} {{ hostname }} {{ app_name }}[{{ pid }}]: {{ message }}{% endif %}

0 commit comments

Comments
 (0)