reverse-skills-max: initial public release (restored, redacted, hardened)

This commit is contained in:
baradok609hecl1pq6qi
2026-10-20 02:17:15 +03:00
commit da24c2bd20
2227 changed files with 443931 additions and 0 deletions

View File

@@ -0,0 +1,324 @@
---
name: malware-dynamic-analysis
description: Behavioral analysis of a sample executed in an isolated VM. Use after triage when runtime behavior, C2 traffic, dropped files, persistence, or injection must be observed. Claude produces a tailored VM runbook from triage predictions, then parses the exported text evidence (Procmon CSV, Sysmon JSON/CSV, tshark output, autoruns, strings) on the host to reconstruct behavior and extract IOCs. The analyst runs the VM; Claude never executes the sample.
---
# Malware Dynamic Analysis
Two halves. **Part A** is a runbook the analyst follows inside the isolated VM (REMnux / FlareVM); you tailor it from triage predictions and it ends with text-format evidence exports. **Part B** is your work: parse that evidence on the host, reconstruct what the sample did, extract behavioral IOCs, and write findings to `analysis_state.md`.
## Execution Model
- **You never execute the sample.** The VM is network-isolated and has no Claude in it. Everything you run is on the host against exported evidence files.
- **Locate skill files.** Scripts and reference files ship in this skill's directory. Set `R="${CLAUDE_PLUGIN_ROOT:-<dir containing this SKILL.md>}"` once (when installed as a plugin `$CLAUDE_PLUGIN_ROOT` is set; otherwise it is this skill folder). Your working directory is the user's analysis workspace, so prefix every script path below with `$R`, e.g. `python3 "$R"/scripts/ioc_extract.py`.
- **Before the VM run:** read the triage findings in `analysis_state.md` and emit a runbook tailored to the predictions (which process names to filter, which Sysmon event IDs matter, expected protocol, expected persistence, expected evasion). Skip the tailoring only if triage is missing — then use the default runbook as-is.
- **After the VM run:** the user hands you an evidence directory. Inventory it, reject binary formats with the conversion command (see **Converting Binary Evidence**), and then work through Part B with Bash and the bundled scripts. Do not ask the user to summarize the evidence for you.
- **Scripts** (relative to this skill dir): `scripts/procmon_summary.py`, `scripts/sysmon_summary.py`; plus `../scripts/ioc_extract.py` at repo root. All stdlib Python 3.
- **Sandbox reports count as evidence.** ANY.RUN / Joe Sandbox / Hybrid Analysis / Threat.Zone (MCP) JSON or text reports go through Part B Step 6.
- **Large files:** `wc -l` first. Use the summary scripts and `grep`, never `cat` a 200k-line Procmon CSV.
---
## Part A — VM Runbook (analyst executes this)
Emit this to the user, filled in with the sample name and triage predictions. Keep the safety checklist verbatim.
### A1. Safety checklist — all boxes or do not execute
- [ ] Clean VM snapshot taken
- [ ] Network: host-only / internal, with INetSim or FakeNet-NG answering (never NAT to the real internet unless the engagement explicitly allows it)
- [ ] No shared folders, no clipboard sharing, no drag-and-drop
- [ ] Guest time sync disabled
- [ ] Monitoring tools started **before** execution (order below)
- [ ] Analysis persona active — no real credentials, names, or corporate artifacts on the VM
- [ ] Evidence transfer path decided (USB / one-way share) and a `C:\evidence\` directory created
Full VM hardening and anti-VM-detection countermeasures: `references/sandbox_setup.md`, `references/anti_analysis_bypass.md`.
### A2. Start monitoring (in this order)
1. INetSim / FakeNet-NG (network simulation)
2. Sysmon — already installed with `olafhartong/sysmon-modular` or SwiftOnSecurity config
3. Procmon — clear capture (Ctrl+X), apply filters (below)
4. Wireshark — capture on the VM adapter
5. System Informer
6. Regshot 1st shot (optional) / `autorunsc` baseline (below)
Tool configuration details: `references/tool_setup.md`.
**Procmon filters** (tailor from triage — add predicted child processes and LOLBins):
```
Process Name is <sample.exe> Include
Process Name is <predicted child, e.g. powershell.exe, rundll32.exe> Include
Operation contains Process Include (keeps Process Create for the tree)
Process Name is procmon.exe / SystemInformer.exe / wireshark.exe Exclude
```
Do **not** filter to the sample alone if injection is predicted — include the predicted target (`explorer.exe`, `svchost.exe`, `RegAsm.exe`, …).
**Baseline persistence snapshot** (Sysinternals Autoruns, run as admin):
```powershell
autorunsc64.exe -accepteula -a * -c -h -s -nobanner | Out-File -Encoding utf8 C:\evidence\autoruns_before.csv
```
### A3. Execute
```powershell
# EXE
.\sample.exe # add args if triage found a required switch, e.g. /install /silent
# DLL — triage lists the exports; try DllMain first, then each named export
rundll32.exe sample.dll,DllMain
rundll32.exe sample.dll,<Export>
# Scripts
powershell.exe -ExecutionPolicy Bypass -File sample.ps1
cscript.exe //NoLogo sample.vbs
wscript.exe sample.js
mshta.exe sample.hta
```
Observe **15 minutes minimum**; 60+ if triage found sleep/time checks. Interact where the sample expects it (click the decoy, dismiss dialogs). Note the wall-clock time of execution — it anchors every timeline.
Watch for: new processes, network attempts (INetSim log), files in `%TEMP%`, `%APPDATA%`, `C:\ProgramData`, `C:\Users\Public`, registry Run keys, new services/tasks, UI changes (ransom note, fake error), CPU spikes.
If nothing happens: see `references/anti_analysis_bypass.md` (VM checks, sleep patching, required arguments, missing parent process, locale/keyboard checks).
### A4. Export evidence — text formats only
Run these before reverting. Every file lands in `C:\evidence\` (or `/evidence` on REMnux) and must be text.
| Source | Command / action | File |
|--------|------------------|------|
| Procmon | File → Save → **All events** → **CSV** (or `procmon.exe /OpenLog cap.pml /SaveAs procmon.csv`) | `procmon.csv` |
| Sysmon | `Get-WinEvent -FilterHashtable @{LogName='Microsoft-Windows-Sysmon/Operational'} \| Select-Object Id,TimeCreated,Message \| ConvertTo-Json -Depth 3 \| Out-File -Encoding utf8 C:\evidence\sysmon.json` | `sysmon.json` |
| Wireshark → tshark | see block below | `dns.txt`, `http.txt`, `conns.txt`, `tls.txt`, `syn.txt` |
| Wireshark objects | File → Export Objects → HTTP → save all | `http_objects/` |
| INetSim | copy `/var/log/inetsim/service.log` and `report/` | `inetsim_service.log` |
| System Informer | File → Save → `processes.txt`; for suspicious process: right-click → Create dump file | `processes.txt`, `<proc>.dmp` |
| Memory dump strings | `strings -a -n 8 -e l proc.dmp > proc_strings_wide.txt; strings -a -n 8 proc.dmp > proc_strings.txt` (REMnux `strings`, or FLOSS) | `*_strings*.txt` |
| Persistence | `autorunsc64.exe -accepteula -a * -c -h -s -nobanner \| Out-File -Encoding utf8 C:\evidence\autoruns_after.csv` | `autoruns_after.csv` |
| Scheduled tasks / services / WMI | PowerShell block below | `persistence.txt` |
| Regshot | Compare → output **TXT** | `regshot.txt` |
| Dropped files | PowerShell block below; copy the files themselves into `dropped/` (they are new samples) | `dropped_hashes.csv`, `dropped/` |
| Screenshots | ransom notes, dialogs, anything visual | `screenshots/*.png` |
**tshark exports** (run on REMnux against the saved `capture.pcapng`, or in the FlareVM Wireshark install dir):
```bash
P=capture.pcapng
tshark -r $P -Y "dns.flags.response==0" -T fields -e frame.time -e ip.src -e dns.qry.name > dns.txt
tshark -r $P -Y "http.request" -T fields -e frame.time -e ip.dst -e http.host -e http.request.method -e http.request.uri -e http.user_agent > http.txt
tshark -r $P -Y "tls.handshake.type==1" -T fields -e frame.time -e ip.dst -e tcp.dstport -e tls.handshake.extensions_server_name -e tls.handshake.ja3 > tls.txt
tshark -r $P -q -z conv,tcp -z conv,udp > conns.txt
tshark -r $P -Y "tcp.flags.syn==1 && tcp.flags.ack==0" -T fields -e frame.time_epoch -e ip.dst -e tcp.dstport > syn.txt
tshark -r $P -Y "http.request.method==POST" -T fields -e ip.dst -e http.request.uri -e http.content_length -e http.file_data > http_post.txt
```
**Persistence and dropped files** (PowerShell, after execution):
```powershell
$E = "C:\evidence"
Get-ScheduledTask | ? {$_.TaskPath -notlike "\Microsoft*"} | Select TaskName,TaskPath,State,@{n='Action';e={$_.Actions.Execute + ' ' + $_.Actions.Arguments}} | Format-List | Out-File -Encoding utf8 "$E\persistence.txt"
Get-CimInstance Win32_Service | ? {$_.PathName -notlike "C:\Windows\*"} | Select Name,PathName,StartMode,State | Format-List | Out-File -Encoding utf8 "$E\persistence.txt" -Append
Get-CimInstance -Namespace root\Subscription -Class __FilterToConsumerBinding | Format-List | Out-File -Encoding utf8 "$E\persistence.txt" -Append
foreach ($k in 'HKCU','HKLM') { Get-ItemProperty "${k}:\Software\Microsoft\Windows\CurrentVersion\Run*" | Out-File -Encoding utf8 "$E\persistence.txt" -Append }
$t = (Get-Date).AddMinutes(-90) # adjust to execution time
mkdir "$E\dropped" -Force | Out-Null
Get-ChildItem -Path $env:TEMP,$env:APPDATA,$env:LOCALAPPDATA,C:\ProgramData,C:\Users\Public,C:\Windows\Temp -Recurse -File -Force -ErrorAction SilentlyContinue |
? {$_.CreationTime -gt $t} | % { Copy-Item $_.FullName "$E\dropped\" -Force -ErrorAction SilentlyContinue; $_ } |
Get-FileHash -Algorithm SHA256 | Export-Csv "$E\dropped_hashes.csv" -NoTypeInformation
```
Then: transfer `C:\evidence\` to the host, **revert the snapshot**, verify the revert.
### Converting Binary Evidence
If the user brings PML / PCAP(NG) / EVTX instead, reply with the conversion command and wait:
| Got | Convert with |
|-----|--------------|
| `.pml` | Windows VM: `procmon.exe /OpenLog x.pml /SaveAs x.csv` |
| `.pcap` / `.pcapng` | The tshark block in A4 (tshark exists on REMnux and on any host with Wireshark) — you can run this yourself on the host if tshark is installed: `command -v tshark` |
| `.evtx` | Windows: `Get-WinEvent -Path sysmon.evtx \| Select Id,TimeCreated,Message \| ConvertTo-Json -Depth 3 \| Out-File -Encoding utf8 sysmon.json`. Host/REMnux: `evtx_dump --format jsonl sysmon.evtx > sysmon.jsonl` (`pip install evtx`) — `sysmon_summary.py` reads both |
| `.dmp` | `strings -a -n 8 x.dmp > x_strings.txt; strings -a -n 8 -e l x.dmp > x_strings_wide.txt` — run on the host, it is not executable |
PCAP and memory dumps are safe to handle on the host; the PE files in `dropped/` are new samples and go back through triage.
---
## Part B — Evidence Analysis (you do this)
### B0. Inventory
```bash
E=/path/to/evidence
ls -la "$E"; file "$E"/* | grep -v ':.*text' ; wc -l "$E"/*.csv "$E"/*.txt "$E"/*.json 2>/dev/null
head -2 "$E/procmon.csv" # confirm columns: Time of Day, Process Name, PID, Operation, Path, Result, Detail
```
Anything that is not text → **Converting Binary Evidence**. Note the sample's process name(s) from triage/state; you will scope everything on them.
### B1. Process behavior — Procmon
```bash
python3 malware-dynamic-analysis/scripts/procmon_summary.py "$E/procmon.csv" --process sample.exe > "$E/procmon_summary.txt"
wc -l "$E/procmon_summary.txt"; sed -n 1,80p "$E/procmon_summary.txt"
```
The summary gives: process tree with command lines, files created/written/renamed/deleted, registry writes with persistence keys flagged, network endpoints, DLLs loaded from outside `C:\Windows`, event counts. Read it fully (page with `sed -n`). Then drill into specifics straight from the CSV:
```bash
# everything one child did
grep -F '"powershell.exe"' "$E/procmon.csv" | cut -d, -f2,4,5 | sort | uniq -c | sort -rn | head -40
# a specific path or key
grep -iF 'CurrentVersion\Run' "$E/procmon.csv" | head
# timeline of process starts (first-seen order)
grep -F '"Process Start"' "$E/procmon.csv" | cut -d, -f1,2,3,7 | head -40
```
Interpret against the triage predictions: confirmed, refuted, or unexpected. Injection shows up as the sample writing to another process's address space only indirectly in Procmon — cross-check with Sysmon EID 8/10 in B2. Self-deletion (`SetDispositionInformationFile` on its own path, or `cmd /c del`), decoy launches, and LOLBin chains (`rundll32`, `regsvr32`, `mshta`, `wscript`, `msiexec`, `certutil`, `bitsadmin`) are the usual tells.
### B2. Process behavior — Sysmon
```bash
python3 malware-dynamic-analysis/scripts/sysmon_summary.py "$E/sysmon.json" --process sample.exe > "$E/sysmon_summary.txt"
sed -n 1,120p "$E/sysmon_summary.txt"
```
Sections and what they prove:
| Section (EID) | Read it for |
|---------------|-------------|
| Process tree (1) | Authoritative parent/child + full command lines + hashes of every executed image |
| Network (3) | Per-process destinations; `BEACON-LIKE` tag = regular intervals (coefficient of variation < 0.3 over ≥5 connections) |
| DNS (22) | Queried names and resolved IPs, even when the connection failed |
| Files (11) | Dropped files by process — the source of truth for `dropped/` |
| Registry (12/13/14) | `PERSISTENCE` flags: Run/RunOnce, Services, Winlogon, IFEO, TaskCache, AppInit |
| Remote threads (8) | `CreateRemoteThread` injection: source → target, start address |
| Process access (10) | `OpenProcess` with high rights (`0x1F0FFF`, `0x1FFFFF`, `0x143A`) → injection prep, LSASS access → credential theft |
| Image loads (7) | Unsigned DLLs from user-writable paths, sideloading |
| Pipes (17/18) | Named pipes = Cobalt Strike / Meterpreter / custom IPC IOCs |
| WMI (19/20/21) | WMI event subscription persistence |
| Tampering (25) | Process hollowing / herpaderping |
Correlate B1 and B2: Procmon has more file/registry granularity, Sysmon has hashes, injection, DNS, and command lines. Where they disagree, trust Sysmon for process identity and Procmon for file operations.
### B3. Network
```bash
cat "$E/dns.txt" | awk '{print $NF}' | sort | uniq -c | sort -rn | head -30
cat "$E/http.txt" | head -40
cat "$E/tls.txt" | awk -F'\t' '{print $2":"$3, $4, $5}' | sort -u
sed -n 1,60p "$E/conns.txt"
grep -v '^$' "$E/http_post.txt" | head -20
grep -iE 'connect|request|GET|POST' "$E/inetsim_service.log" | head -40
```
Beacon detection from `syn.txt` (interval regularity per destination):
```bash
python3 - "$E/syn.txt" <<'EOF'
import sys, statistics, collections
by = collections.defaultdict(list)
for line in open(sys.argv[1]):
p = line.split()
if len(p) == 3: by[(p[1], p[2])].append(float(p[0]))
for (ip, port), ts in by.items():
ts.sort(); gaps = [b-a for a, b in zip(ts, ts[1:])]
if len(gaps) >= 4:
m = statistics.mean(gaps); cv = statistics.pstdev(gaps)/m if m else 9
print(f"{ip}:{port} n={len(ts)} mean={m:.1f}s cv={cv:.2f} {'BEACON' if cv < 0.3 else ''}")
EOF
```
What to establish: C2 hosts (DNS name → IP → port → protocol), beacon interval and jitter, User-Agent and URI pattern (Suricata material), TLS SNI/JA3, download URLs and what came back (`http_objects/` — `file` them; PE payloads go back to triage), exfiltration (POST sizes, encoding), DGA signs (many NXDOMAIN, random-looking labels), and whether traffic went to INetSim or the sample gave up (matters for what you can claim).
### B4. Persistence and system changes
```bash
cat "$E/persistence.txt"
# autoruns diff: entries present after but not before
python3 - "$E/autoruns_before.csv" "$E/autoruns_after.csv" <<'EOF'
import csv, sys
key = lambda r: (r.get('Entry Location',''), r.get('Entry',''), r.get('Image Path',''))
before = {key(r) for r in csv.DictReader(open(sys.argv[1], encoding='utf-8-sig', errors='replace'))}
for r in csv.DictReader(open(sys.argv[2], encoding='utf-8-sig', errors='replace')):
if key(r) not in before: print(r.get('Entry Location'), '|', r.get('Entry'), '|', r.get('Image Path'), '|', r.get('Signer',''))
EOF
grep -iE 'Run|Services|TaskCache|Winlogon' "$E/regshot.txt" | head -40
```
Map each mechanism to ATT&CK (T1547.001 Run keys, T1053.005 scheduled task, T1543.003 service, T1546.003 WMI, T1574.002 sideloading, …) — the detection phase needs the technique IDs.
### B5. Dropped files and memory strings
```bash
cat "$E/dropped_hashes.csv"
file "$E"/dropped/*
python3 scripts/ioc_extract.py "$E"/*_strings*.txt # C2, keys, paths decrypted only at runtime
grep -iE 'user-agent|/gate|/panel|\.php|password|token|Bearer' "$E"/*_strings*.txt | sort -u | head -40
```
Every PE in `dropped/` is a new sample: add it to `analysis_state.md` and route back through `malware-triage`. Runtime strings that were absent from the static strings dump are the decrypted config — call that out explicitly, it is the strongest YARA/Suricata material.
### B6. Sandbox reports (if provided)
ANY.RUN / Joe / Hybrid Analysis / Threat.Zone JSON: `jq` the process tree, network, dropped files, and MITRE sections; treat them as a fourth evidence source and reconcile with B1-B5. Sandbox results are weaker than your own VM run when the sample is VM-aware — say so if behaviors differ.
```bash
jq -r '.. | .processes? // empty' report.json | head; jq -r '.. | .network? // empty' report.json | head
```
### B7. Consolidate
```bash
cat "$E/procmon_summary.txt" "$E/sysmon_summary.txt" "$E"/dns.txt "$E"/http.txt "$E"/tls.txt "$E"/persistence.txt 2>/dev/null | python3 scripts/ioc_extract.py > "$E/iocs_defanged.txt"
```
Remove your own lab artifacts (VM hostname, INetSim IPs, analyst username) and Windows-internal noise from the IOC list before recording it.
## Output
Append to `analysis_state.md` under the sample's **Analysis Findings** and **IOCs Identified**, then print the findings summary:
```markdown
## Dynamic Analysis — [sample] (executed [UTC time], observed [n] min, VM: [FlareVM/REMnux], network: [INetSim/FakeNet/none])
### Execution chain
[process tree with command lines; injection targets; LOLBins; self-deletion]
### Predictions vs observed
| Triage prediction | Observed | Evidence |
### File system
Created / modified / deleted, with hashes for dropped PE files
### Registry & persistence
Mechanism → key/task/service → payload path → ATT&CK ID
### Network
C2: host → IP:port, protocol, beacon interval/jitter, UA, URI pattern, TLS SNI/JA3 · Downloads · Exfil · DGA
### Evasion observed
Sleep, VM checks, debugger checks, tampering (EID 25), what was bypassed and how
### Behavioral IOCs (defanged)
Process (names, cmdlines, mutexes, pipes) · File (paths, hashes) · Registry · Network
### MITRE ATT&CK
Technique IDs with the observation that supports each
### Not observed / limitations
[network to real C2 blocked; sample exited after VM check; only 15 min observed …]
```
Recommend `detection-engineer` next (behaviors + network IOCs are ready) unless dropped payloads need triage first.
## Quality Gate
Evidence inventoried and all text · process tree with command lines · every prediction marked confirmed/refuted/unexpected · dropped files hashed and routed · persistence mapped to ATT&CK · C2 characterized (or explicitly "no network observed") · IOCs defanged and lab noise removed · limitations stated · state file updated.
## References
- `references/tool_setup.md` — Procmon filters/columns, Wireshark filters, System Informer, Sysmon install, Regshot, TCPView, Noriben, INetSim, FakeNet-NG
- `references/sandbox_setup.md` — REMnux/FlareVM build, isolation, snapshots, CAPE/Cuckoo
- `references/anti_analysis_bypass.md` — VM/debugger/sleep evasion and how to defeat it
- `scripts/procmon_summary.py`, `scripts/sysmon_summary.py` — evidence condensers (`--self-test` to verify)
- `../scripts/ioc_extract.py` — IOC extraction and defanging

View File

@@ -0,0 +1,667 @@
# Anti-Analysis Bypass Guide
Reference for identifying and bypassing malware evasion techniques during dynamic analysis.
---
## Overview
Anti-analysis techniques fall into four categories:
| Category | Goal | MITRE ATT&CK |
|----------|------|--------------|
| VM/sandbox detection | Avoid executing in automated analysis environments | T1497 |
| Debugger detection | Prevent live debugging and breakpoint analysis | T1622 |
| Timing-based evasion | Delay execution past sandbox timeout windows | T1497.003 |
| Environmental keying | Execute only when specific conditions are met | T1480 |
---
## VM and Sandbox Detection
### How Malware Detects VMs
**Registry artifact checks (MITRE T1497.001):**
```
HKLM\SOFTWARE\Oracle\VirtualBox Guest Additions
HKLM\SOFTWARE\VMware, Inc.\VMware Tools
HKLM\SYSTEM\CurrentControlSet\Services\VBoxGuest
HKLM\SYSTEM\CurrentControlSet\Services\vmhgfs
HKLM\HARDWARE\DEVICEMAP\Scsi\Scsi Port 0\Scsi Bus 0\Target Id 0\Logical Unit Id 0\Identifier
→ "VBOX HARDDISK", "VMWARE VIRTUAL IDE HARD DRIVE"
```
**Process enumeration:**
```
vboxservice.exe VirtualBox guest service
vboxtray.exe VirtualBox system tray
vmtoolsd.exe VMware Tools daemon
vmwaretray.exe VMware system tray
vmacthlp.exe VMware activation helper
vmsrvc.exe VirtualPC service
df5serv.exe Parallels service
prl_tools.exe Parallels Tools
```
**Driver/device checks:**
```
\\.\VBoxMiniRdrDN
\\.\VBoxGuest
\\.\vmci
\\.\HGFS
\Device\VBoxGuest
\Device\vmhgfs
```
**CPUID hypervisor bit (EAX=1, ECX bit 31):**
Malware executes CPUID and checks bit 31 of ECX. If set, a hypervisor is present. Subsequent CPUID with EAX=0x40000000 returns a vendor string: "VMwareVMware", "KVMKVMKVM", "VBoxVBoxVBox", "Microsoft Hv".
**MAC address OUI checks:**
```
08:00:27 VirtualBox
00:0C:29 VMware
00:50:56 VMware (vSphere)
00:1C:14 VMware
00:05:69 VMware
00:03:FF Microsoft Hyper-V
```
**Screen resolution / user interaction:**
```
Screen size exactly 1024x768 → sandbox default
No mouse movement history
No browser history / recent files
Uptime < 10 minutes
Single CPU core
Total RAM < 2 GB
```
**Identifying VM detection in static analysis:**
Look for these API calls in import tables or dynamic calls:
```
GetSystemInfo → CPU count check
GlobalMemoryStatusEx → RAM check
EnumProcesses → process list scan
CreateFile(\\.\<device>) → device existence check
RegOpenKeyEx / RegQueryValueEx → registry artifact checks
GetAdaptersInfo → MAC address check
GetTickCount / GetSystemTime → uptime / time checks
```
### Bypass Strategies
**Remove VirtualBox Guest Additions:**
```powershell
# Uninstall via Control Panel or:
Start-Process "C:\Program Files\Oracle\VirtualBox Guest Additions\uninstall.exe" -Wait
# Remove leftover registry keys
Remove-Item "HKLM:\SOFTWARE\Oracle\VirtualBox Guest Additions" -Recurse -ErrorAction SilentlyContinue
Remove-Item "HKLM:\SYSTEM\CurrentControlSet\Services\VBoxGuest" -Recurse -ErrorAction SilentlyContinue
Remove-Item "HKLM:\SYSTEM\CurrentControlSet\Services\VBoxSF" -Recurse -ErrorAction SilentlyContinue
Remove-Item "HKLM:\SYSTEM\CurrentControlSet\Services\VBoxMouse" -Recurse -ErrorAction SilentlyContinue
# Stop and delete VBox services
sc.exe stop VBoxGuest
sc.exe delete VBoxGuest
```
**Spoof BIOS/DMI strings (VirtualBox, run on host):**
```bash
VBoxManage setextradata "VM-Name" \
"VBoxInternal/Devices/pcbios/0/Config/DmiBIOSVendor" "American Megatrends Inc."
VBoxManage setextradata "VM-Name" \
"VBoxInternal/Devices/pcbios/0/Config/DmiBIOSVersion" "F10"
VBoxManage setextradata "VM-Name" \
"VBoxInternal/Devices/pcbios/0/Config/DmiSystemProduct" "MS-7B86"
VBoxManage setextradata "VM-Name" \
"VBoxInternal/Devices/pcbios/0/Config/DmiSystemVendor" "MSI"
VBoxManage setextradata "VM-Name" \
"VBoxInternal/Devices/pcbios/0/Config/DmiBoardProduct" "MAG Z390 TOMAHAWK"
VBoxManage setextradata "VM-Name" \
"VBoxInternal/Devices/pcbios/0/Config/DmiChassisVendor" "MSI"
```
**Suppress CPUID hypervisor bit (VirtualBox):**
```bash
VBoxManage modifyvm "VM-Name" --paravirtprovider none
```
**VMware: suppress CPUID and hypervisor detection:**
Add to the `.vmx` file:
```
hypervisor.cpuid.v0 = "FALSE"
SMBIOS.reflectHost = "TRUE"
board.vendor = "MSI"
board.product = "MAG Z390 TOMAHAWK"
```
**Spoof MAC address:**
```bash
# VirtualBox (use a real vendor OUI)
VBoxManage modifyvm "VM-Name" --macaddress1 "8C8D28AABBCC"
# Intel OUIs: 8C:8D:28, 00:1B:21, F4:4D:30
# Realtek OUI: 00:E0:4C
```
**Add realistic user artifacts:**
```powershell
# Create browser history (requires Chrome installed)
# Open URLs from PowerShell
Start-Process "chrome.exe" "https://www.gmail.com"
Start-Process "chrome.exe" "https://www.stackoverflow.com"
# Create recent documents
$docs = @("budget_2024.xlsx","notes.txt","presentation.pptx")
foreach ($d in $docs) {
New-Item -Path "$env:USERPROFILE\Documents\$d" -ItemType File -Force
}
# Set realistic uptime by running VM for 30+ minutes before analysis
```
**Use VBoxHardenedLoader (advanced):**
VBoxHardenedLoader patches the VirtualBox kernel driver to pass CPUID and other checks transparently without removing guest additions.
```
# https://github.com/hfiref0x/VBoxHardenedLoader
# Load driver: VBoxHLinstall.cmd
# Provides transparent VM hiding while keeping shared folders functional
```
---
## Debugger Detection
### How Malware Detects Debuggers (MITRE T1622)
**IsDebuggerPresent / CheckRemoteDebuggerPresent:**
Most common check. Reads the PEB (Process Environment Block) `BeingDebugged` flag directly.
```
PEB offset 0x2 (32-bit) / 0x2 (64-bit): BeingDebugged byte
PEB offset 0x68 (32-bit) / 0xBC (64-bit): NtGlobalFlag (set to 0x70 under debugger)
```
**NtQueryInformationProcess:**
```
ProcessDebugPort (class 7) → non-zero if debugged
ProcessDebugObjectHandle (class 30) → valid handle if debugged
ProcessDebugFlags (class 31) → 0 if debugged
```
**Heap flags (PEB.ProcessHeap):**
```
Heap.Flags: normally 0x2, under debugger 0x50000062
Heap.ForceFlags: normally 0x0, under debugger 0x40000060
```
**Hardware breakpoint detection:**
Malware reads the debug registers DR0–DR3 via `GetThreadContext`. If any are non-zero, a hardware breakpoint is set.
**Timing-based debugger detection:**
```c
DWORD t1 = GetTickCount();
// execute a few instructions
DWORD t2 = GetTickCount();
if ((t2 - t1) > threshold) { /* debugger detected */ }
```
When single-stepping in a debugger, even a few instructions take milliseconds vs. nanoseconds normally.
**Exception-based detection:**
Malware raises an exception (INT3, divide-by-zero) and checks if the debugger swallows it instead of passing it to the exception handler.
**Window/process name enumeration:**
```
FindWindow("OLLYDBG", NULL)
FindWindow("WinDbgFrameClass", NULL)
FindWindow("ID", NULL) → Immunity Debugger
FindWindow("Qt5QWindowIcon", NULL) → x64dbg
EnumProcesses → scan for:
ollydbg.exe, x32dbg.exe, x64dbg.exe, windbg.exe,
ida.exe, ida64.exe, idag.exe, idaq.exe,
immunitydebugger.exe, dnspy.exe, cheatengine.exe
```
### Bypass Strategies
**ScyllaHide (primary tool):**
ScyllaHide is an anti-anti-debug plugin for x64dbg, OllyDbg, and IDA Pro. It patches all common detection methods transparently.
```
# Download: https://github.com/x64dbg/ScyllaHide
# Install in x64dbg:
# Copy ScyllaHide.dp32 / ScyllaHide.dp64 to x64dbg\plugins\
# Restart x64dbg
# Plugins → ScyllaHide → Options
# Enable all protections for evasive samples:
[x] PEB BeingDebugged
[x] PEB NtGlobalFlag
[x] PEB HeapFlags
[x] NtQueryInformationProcess
[x] Unhandled Exception Handler
[x] Hardware Breakpoint Protection
[x] GetTickCount Hook
[x] NtSetInformationThread
```
**TitanHide (kernel-level, for advanced evasion):**
TitanHide is a kernel driver that hides debuggers at the OS level, defeating NtQueryInformationProcess and similar kernel-query-based checks.
```
# Download: https://github.com/mrexodia/TitanHide
# Requires: Windows with test signing enabled or TESTSIGNING boot flag
# Load driver: TitanHide.sys
# Use x64dbg plugin interface for configuration
```
**Manual PEB patching in x64dbg:**
```
# In x64dbg, after attaching:
# Open Memory Map → find PEB
# Manually zero BeingDebugged byte:
# 32-bit:
# FS:[0x30] → PEB base
# PEB+0x2 → BeingDebugged (set to 0x00)
# PEB+0x68 → NtGlobalFlag (set to 0x00)
# Use x64dbg script:
mov byte ptr [peb()]:2, 0 # Clear BeingDebugged
mov dword ptr [peb()]:0x68, 0 # Clear NtGlobalFlag
```
**Handle timing checks — NOP the sleep/delay:**
```
# In x64dbg, find GetTickCount/QueryPerformanceCounter calls
# Identify the comparison instruction after the delta calculation
# NOP the conditional jump (JNE, JA, JBE, etc.)
# Or patch the threshold to an impossibly large value
```
**Handle exception-based detection:**
```
# In x64dbg: Options → Preferences → Exceptions
# Add exception codes to "pass to program":
# 0x80000003 (INT3 / STATUS_BREAKPOINT)
# 0xC0000094 (divide by zero)
# 0xC0000005 (access violation, if used as detection)
```
---
## Timing-Based Evasion
### How Malware Uses Timing (MITRE T1497.003)
**Sleep-based evasion:**
Malware calls `Sleep(600000)` (10 minutes) or longer. Automated sandboxes that have a 2-5 minute timeout never observe the payload.
```
Sleep(DWORD dwMilliseconds)
SleepEx(DWORD dwMilliseconds, BOOL bAlertable)
WaitForSingleObject(HANDLE, INFINITE)
NtDelayExecution(BOOLEAN Alertable, PLARGE_INTEGER DelayInterval)
```
**Date/time checks:**
Malware compares the current date against a hardcoded activation date. If before that date, it does nothing (making sandbox captures useless). If after an expiry date, it also does nothing (anti-forensics).
**Idle/user activity checks:**
```
GetLastInputInfo() → returns milliseconds since last user input
GetCursorPos() → called twice, checks if cursor moved
GetForegroundWindow() → checks for active user window
```
If no mouse movement or keyboard input for N seconds, the malware assumes it is in a sandbox.
### Bypass Strategies
**Patch Sleep calls in x64dbg:**
```
# Find calls to Sleep / SleepEx / NtDelayExecution
# Option 1: NOP the entire Sleep call
# Option 2: Patch the millisecond argument to 0 or 1
# In x64dbg, set a breakpoint on Sleep:
# Breakpoints → Add → "Sleep"
# When hit, change RCX (x64) or stack arg (x86) to 0 before continuing
# x64dbg script to auto-patch:
bp Sleep
loopSleep:
erun
mov rcx, 0 // Zero the sleep duration (x64 first arg)
erun
goto loopSleep
```
**Accelerate sandbox clock (Cuckoo/CAPE):**
```yaml
# In cuckoo.conf:
[timeouts]
critical = 60
vm_state = 60
# Enable clock skipping in analysis options:
options: "clock=20200101120000"
```
**Manipulate system time on Windows VM:**
```powershell
# Set date forward to trigger time-gated payloads
Set-Date -Date "2025-06-15 12:00:00"
# Use faketime (Linux / Wine)
faketime '2025-06-15 12:00:00' wine sample.exe
```
**Simulate user activity:**
```powershell
# Move mouse programmatically
Add-Type -AssemblyName System.Windows.Forms
while ($true) {
$x = Get-Random -Minimum 100 -Maximum 1800
$y = Get-Random -Minimum 100 -Maximum 900
[System.Windows.Forms.Cursor]::Position = New-Object System.Drawing.Point($x, $y)
Start-Sleep -Milliseconds 500
}
```
**Extend sandbox timeout:**
For CAPE/Cuckoo, increase the analysis timeout:
```bash
python3 utils/submit.py --file sample.exe --timeout 300
```
For manual analysis, simply wait — set observation window to 60+ minutes for suspected sleep-evasive samples.
---
## Environmental Keying
### How Malware Implements Environmental Checks (MITRE T1480)
Environmental keying means the malware will only execute its payload if specific environmental conditions are met. This is distinct from VM detection — the malware is looking for a specific victim configuration.
**Domain/hostname checks:**
```
GetComputerNameEx(ComputerNameDnsDomain, ...)
NetGetJoinInformation()
LookupAccountSid()
→ Malware may require a specific AD domain name or NETBIOS name
```
**Username checks:**
```
GetUserName() → compare against hardcoded victim username
WNetGetUser()
```
**Installed software checks:**
```
HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\ → enumerate installed apps
HKLM\SOFTWARE\ → check for specific vendor keys
```
**Language/locale checks:**
```
GetSystemDefaultLangID()
GetUserDefaultLCID()
→ Some malware won't run if locale is English or matches researcher's profile
→ Others skip execution if locale is Russian/Ukrainian (avoiding domestic prosecution)
```
**Network environment checks:**
```
GetAdaptersInfo() → check for specific IP ranges
DnsQuery() → verify domain membership via DNS
NetServerGetInfo() → check for domain controller presence
```
### Bypass Strategies
**Domain/hostname spoofing:**
```powershell
# Change computer name (requires restart)
Rename-Computer -NewName "WORKSTATION-01" -Force
# Join a fake workgroup matching expected domain name
# (Does not require actual domain controller)
Add-Computer -WorkgroupName "TARGETCORP"
```
**Username spoofing:**
```powershell
# Create a user matching expected victim name
New-LocalUser -Name "jsmith" -NoPassword
Add-LocalGroupMember -Group "Administrators" -Member "jsmith"
# Log in as that user, then execute sample
```
**Locale patching:**
```
# In x64dbg, break on GetSystemDefaultLangID / GetUserDefaultLCID
# Patch EAX return value to match expected locale
# Russian: 0x0419, Romanian: 0x0418, Ukrainian: 0x0422
```
**Registry keying bypass:**
```
# If malware checks for a specific product registry key:
# Identify the expected key via static analysis (strings, IDA)
# Create a fake registry entry before execution:
New-Item -Path "HKLM:\SOFTWARE\TargetVendor\TargetProduct" -Force
New-ItemProperty -Path "HKLM:\SOFTWARE\TargetVendor\TargetProduct" `
-Name "Version" -Value "1.0" -PropertyType String
```
---
## Tools for Anti-Analysis Bypass
### ScyllaHide
Purpose: Plugin for x64dbg, OllyDbg, IDA Pro, TitanHide. Patches all common anti-debug checks transparently.
```
Download: https://github.com/x64dbg/ScyllaHide/releases
Platforms: x32dbg, x64dbg, OllyDbg 1.10, OllyDbg 2.01, IDA Pro
Install: Copy DLL to debugger plugins folder
Key options:
- PEB patch (BeingDebugged, NtGlobalFlag, HeapFlags)
- NtQueryInformationProcess hooks
- Timing hooks (GetTickCount, QueryPerformanceCounter)
- NtSetInformationThread (hide from debugger)
- Unhandled exception fix
```
### TitanHide
Purpose: Kernel-mode driver that hides debugger presence from NtQueryInformationProcess and related syscalls.
```
Download: https://github.com/mrexodia/TitanHide
Requires: Windows with test signing or driver certificate
Use case: When ScyllaHide user-mode hooks are insufficient
(malware uses direct syscalls to bypass user-mode hooks)
```
### al-khaser
Purpose: Open-source tool that tests over 170 anti-analysis checks. Use to validate VM hardening before analyzing evasive samples.
```
Download: https://github.com/LordNoteworthy/al-khaser
Build: Visual Studio solution (.sln)
Run: al-khaser.exe
Output: Pass/fail for each check category:
- Anti-VM
- Anti-debug
- Anti-sandbox
- Anti-disassembly
- Timing
```
### pafish
Purpose: Lightweight VM/sandbox detection tester. Faster than al-khaser for quick pre-analysis validation.
```
Download: https://github.com/a0rtega/pafish/releases
Run: pafish.exe
Green: Check passed (not detected)
Red: Check failed (artifact detected — fix before analyzing evasive samples)
```
### hollows_hunter / pe-sieve
Purpose: Scan a running process for injected code, hollowed sections, and unpacked payloads in memory. Useful after malware defeats initial analysis controls.
```
Download: https://github.com/hasherezade/hollows_hunter
Run: hollows_hunter.exe /pid <malware_pid>
Output: Dumps hollowed/injected regions to disk for static analysis
```
### x64dbg Plugins for Anti-Analysis
```
ScyllaHide Anti-debug bypass (essential)
xAnalyzer Extended analysis and comment generation
xHotConvert Quick conversion utilities
OllyDumpEx Process dumper for unpacking
```
---
## Identifying Anti-Analysis at a Glance
During static analysis, flag these patterns for targeted bypass:
**String indicators:**
```
"IsDebuggerPresent" → debugger detection
"CheckRemoteDebugger" → remote debugger detection
"NtQueryInformationProcess" → advanced debugger/VM detection
"VBOX", "VMWARE", "QEMU", "VIRTUAL" → VM string checks
"GetTickCount" → timing-based detection
"GetLastInputInfo" → user interaction check
"GetSystemDefaultLangID" → locale-based keying
"GetComputerName" → hostname keying
"EnumProcesses" → process-list scanning
```
**Import table red flags:**
```
kernel32.dll: IsDebuggerPresent, CheckRemoteDebuggerPresent,
GetTickCount, QueryPerformanceCounter,
GetLastInputInfo, GetCursorPos
ntdll.dll: NtQueryInformationProcess, NtSetInformationThread,
NtDelayExecution, RtlQueryProcessHeapInformation
advapi32.dll: RegOpenKeyEx (combined with SYSTEM\CurrentControlSet\Services)
iphlpapi.dll: GetAdaptersInfo (MAC address check)
```
**Behavioral indicators during dynamic analysis:**
```
Process exits immediately (< 2 seconds) → VM or debugger detected
No network activity despite expected C2 → sandbox/network detection
Sleep calls with long durations (> 30s) → timing evasion
Repeated GetTickCount/RDTSC in tight loop → timing check
CreateFile on \\.\VBoxGuest or \\.\vmci → device probing
```
---
## MITRE ATT&CK Reference
| Technique | ID | Description |
|-----------|-----|-------------|
| Virtualization/Sandbox Evasion | T1497 | Parent technique |
| System Checks | T1497.001 | Registry, filesystem, hardware artifacts |
| User Activity Based Checks | T1497.002 | Mouse movement, input, idle time |
| Time Based Evasion | T1497.003 | Sleep calls, date checks, RDTSC |
| Debugger Evasion | T1622 | IsDebuggerPresent, NtQueryInformationProcess |
| Execution Guardrails | T1480 | Environmental keying, domain/locale checks |
| Obfuscated Files or Information | T1027 | Packed/encrypted payloads that unpack in memory |
---
## Analysis Decision Tree
```
Sample exits immediately?
├─ Yes → Check: is VM artifact present? → Fix VM hardening, retry
│ Check: debugger detected? → Enable ScyllaHide, retry
│ Check: locale/domain check? → Spoof environment, retry
└─ No → Sample is executing
No network activity?
├─ Check: INetSim running and configured?
├─ Check: DNS resolving to INetSim IP?
├─ Check: malware performing HTTPS certificate pinning?
│ → Use Proxifier + mitmproxy to intercept
└─ Check: malware waiting for sleep delay?
→ Patch Sleep calls, extend timeout
Behavior stops after initial activity?
├─ Check: C2 response expected? → Configure INetSim response templates
├─ Check: time-gated payload? → Advance system clock
└─ Check: requires second-stage download? → Serve fake C2 response via INetSim
```

View File

@@ -0,0 +1,465 @@
# Sandbox Setup Guide
Quick reference for building and maintaining isolated malware analysis environments.
---
## REMnux - Linux Analysis VM
REMnux is a curated Ubuntu-based distro pre-loaded with reverse engineering and network simulation tools.
### Installation
```bash
# Option 1: Download pre-built OVA (recommended)
# https://remnux.org/#distro
# Import via VirtualBox/VMware: File → Import Appliance → remnux-vX.X.ova
# Option 2: Add REMnux to existing Ubuntu 22.04 LTS
wget https://REMnux.org/remnux-cli
mv remnux-cli /usr/local/bin/remnux
chmod +x /usr/local/bin/remnux
sudo remnux install
```
### Key Pre-Installed Tools
| Category | Tools |
|----------|-------|
| Network simulation | INetSim, FakeNet-NG |
| Traffic analysis | Wireshark, tshark, tcpdump, NetworkMiner |
| Static analysis | FLOSS, strings, binwalk, file, ssdeep |
| PDF analysis | pdf-parser, pdfid, peepdf |
| Office analysis | olevba, mraptor, msodde |
| Disassembly | Ghidra, Binary Ninja (trial) |
| Scripting | Python 3, Ruby, Node.js |
| Memory forensics | Volatility 3, bulk_extractor |
### Initial Configuration
```bash
# Update REMnux tools
sudo remnux upgrade
# Set timezone to UTC for consistent timestamps
sudo timedatectl set-timezone UTC
# Disable automatic updates (prevent unintended network activity)
sudo systemctl disable apt-daily.timer
sudo systemctl disable apt-daily-upgrade.timer
# Verify INetSim is ready
sudo systemctl status inetsim
```
### INetSim Configuration on REMnux
```bash
sudo nano /etc/inetsim/inetsim.conf
```
Key settings to configure:
```
# Bind to interface facing Windows analysis VM
service_bind_address 192.168.56.1
# DNS: respond to all queries with INetSim's own IP
dns_default_ip 192.168.56.1
dns_version "Microsoft DNS 6.1.7601"
# HTTP/HTTPS
http_bind_port 80
https_bind_port 443
http_fakemode yes
# SMTP
smtp_bind_port 25
smtp_banner "220 mail.example.com ESMTP"
```
```bash
# Start INetSim
sudo inetsim
# View INetSim logs
tail -f /var/log/inetsim/service.log
```
---
## FlareVM - Windows Analysis VM
FlareVM is a Chocolatey-based installer that transforms Windows into a malware analysis workstation.
### Prerequisites
- Windows 10 (version 1903 or later) — fresh install strongly preferred
- At minimum 60 GB disk, 4 GB RAM (8 GB recommended)
- PowerShell 5+ with execution policy set to unrestricted
- .NET 4+ installed
- Disable Windows Defender and Windows Update before installing
### Disable Defender Before Installation
```powershell
# Run PowerShell as Administrator
# Disable real-time protection (temporary, for installation)
Set-MpPreference -DisableRealtimeMonitoring $true
# Disable all Defender components
Set-MpPreference -DisableBehaviorMonitoring $true
Set-MpPreference -DisableIOAVProtection $true
Set-MpPreference -DisableScriptScanning $true
# Disable Windows Update
sc.exe config wuauserv start=disabled
sc.exe stop wuauserv
```
### Installation
```powershell
# Run PowerShell as Administrator
Set-ExecutionPolicy Unrestricted -Force
# Download and run FlareVM installer
(New-Object net.webclient).DownloadFile(
'https://raw.githubusercontent.com/mandiant/flare-vm/main/install.ps1',
"$env:temp\install.ps1"
)
Unblock-File "$env:temp\install.ps1"
& "$env:temp\install.ps1"
```
The installer opens a GUI for package selection. Installation takes 1-2 hours.
### Key Installed Tools
| Category | Tools |
|----------|-------|
| Debuggers | x64dbg, WinDbg, OllyDbg |
| Disassemblers | IDA Free, Ghidra, Binary Ninja (trial) |
| Process analysis | System Informer (formerly Process Hacker), ProcDOT, Process Monitor |
| Network | Wireshark, FakeNet-NG, TCPView |
| Static analysis | PE-bear, CFF Explorer, PEiD, Detect-It-Easy |
| String extraction | FLOSS, strings |
| .NET | dnSpy, de4dot |
| Office | olevba, oletools |
| Hex editors | HxD, 010 Editor |
| Scripting | Python 3, Ruby |
### Post-Installation Hardening
```powershell
# Disable Windows Firewall (analysis VM only — never production)
netsh advfirewall set allprofiles state off
# Disable UAC prompts that interrupt malware execution
Set-ItemProperty -Path REGISTRY::HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Policies\System -Name ConsentPromptBehaviorAdmin -Value 0
# Show hidden files and extensions
Set-ItemProperty -Path 'HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced' -Name Hidden -Value 1
Set-ItemProperty -Path 'HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced' -Name HideFileExt -Value 0
```
---
## Network Isolation Configuration
### VirtualBox Host-Only Network Setup
Host-only networking creates a private network between the host and VMs only — no internet access.
**Create Host-Only Adapter (VirtualBox):**
```
File → Host Network Manager → Create
IPv4 Address: 192.168.56.1
IPv4 Network Mask: 255.255.255.0
DHCP Server: Disabled (assign static IPs manually)
```
**Configure Windows Analysis VM:**
```
VM Settings → Network
Adapter 1: Host-Only Adapter → vboxnet0
```
**Set static IP on Windows VM:**
```
Network and Sharing Center → Change adapter settings
→ Right-click adapter → Properties → IPv4 → Manual:
IP Address: 192.168.56.10
Subnet Mask: 255.255.255.0
Gateway: 192.168.56.1 (REMnux INetSim)
DNS Server: 192.168.56.1 (REMnux INetSim)
```
**Configure REMnux VM:**
```
VM Settings → Network
Adapter 1: Host-Only Adapter → vboxnet0
(Static IP: 192.168.56.1 — set during OS install or via netplan)
```
### VMware Host-Only Network Setup
```
Edit → Virtual Network Editor → Add Network → VMnet1 (Host-only)
Subnet IP: 192.168.56.0
Subnet mask: 255.255.255.0
Disable: Use local DHCP service
```
**Assign to analysis VM:**
```
VM Settings → Network Adapter → Custom: VMnet1
```
### Verifying Isolation
From the Windows analysis VM, confirm no external connectivity:
```cmd
# Should resolve (INetSim responds)
nslookup google.com
# Should fail (no external routing)
ping 8.8.8.8
# Verify gateway is INetSim, not external router
tracert google.com
```
From REMnux, confirm INetSim is intercepting:
```bash
# Watch live connections from Windows VM
tail -f /var/log/inetsim/service.log
```
---
## Snapshot Management
Snapshots are the single most important operational practice. A corrupted or infected baseline wastes hours.
### Snapshot Strategy
**Baseline snapshot** — taken once after clean VM build, before any analysis:
- OS fully patched (final time)
- All analysis tools installed and tested
- INetSim/FakeNet configured
- Sysmon installed with SwiftOnSecurity config
- Defender disabled
- Named: `BASELINE-CLEAN-YYYY-MM-DD`
**Pre-execution snapshot** — taken at the start of each analysis session:
- All monitoring tools open and ready (Procmon capturing, Wireshark running)
- INetSim running on REMnux
- Sample copied to VM but not yet executed
- Named: `PRE-EXEC-<sample-hash>-YYYY-MM-DD`
**Mid-analysis snapshots** — optional, taken at significant milestones:
- After installation phase completes
- Before triggering a specific behavior
- Named: `MID-<phase>-<sample-hash>`
### VirtualBox Snapshot Commands
```bash
# List snapshots
VBoxManage snapshot "Windows10-Analysis" list
# Take snapshot
VBoxManage snapshot "Windows10-Analysis" take "PRE-EXEC-abc123-2024-01-15" \
--description "Sample abc123 loaded, tools running"
# Restore snapshot
VBoxManage snapshot "Windows10-Analysis" restore "BASELINE-CLEAN-2024-01-01"
# Delete old snapshots (free disk space)
VBoxManage snapshot "Windows10-Analysis" delete "MID-install-abc123"
```
### VMware Snapshot Commands
```bash
# Take snapshot (VMware CLI)
vmrun snapshot /path/to/vm.vmx "PRE-EXEC-abc123-2024-01-15"
# Revert to snapshot
vmrun revertToSnapshot /path/to/vm.vmx "BASELINE-CLEAN-2024-01-01"
# List snapshots
vmrun listSnapshots /path/to/vm.vmx
```
### Discipline Rules
- **Always revert to baseline after each sample** — never analyze two samples in the same session without reverting
- **Never take a snapshot while malware is running** — the snapshot captures infected state
- **Store baseline OVA/OVF off the analysis machine** — backup in case of host compromise
- **Label snapshots with dates** — baselines age; tools need updates
---
## VM Detection Countermeasures
Malware commonly fingerprints VMs to avoid analysis. See `references/anti_analysis_bypass.md` for full bypass techniques. Common baseline hardening steps:
### Hardware Fingerprint Spoofing
**VirtualBox — spoof BIOS/hardware strings:**
```bash
# Run on host before starting VM
VBoxManage setextradata "Windows10-Analysis" \
"VBoxInternal/Devices/pcbios/0/Config/DmiBIOSVendor" "American Megatrends Inc."
VBoxManage setextradata "Windows10-Analysis" \
"VBoxInternal/Devices/pcbios/0/Config/DmiSystemProduct" "MS-7B86"
VBoxManage setextradata "Windows10-Analysis" \
"VBoxInternal/Devices/pcbios/0/Config/DmiSystemVendor" "MSI"
VBoxManage setextradata "Windows10-Analysis" \
"VBoxInternal/Devices/pcbios/0/Config/DmiChassisVendor" "MSI"
```
**Spoof MAC address (remove VirtualBox OUI 08:00:27:xx:xx:xx):**
```
VM Settings → Network → Advanced → MAC Address → Generate new
```
Or set manually to a real vendor OUI (e.g., Intel: `8C:8D:28`):
```bash
VBoxManage modifyvm "Windows10-Analysis" --macaddress1 "8C8D28AABBCC"
```
### Remove VM Artifacts on Windows Guest
```powershell
# Remove VirtualBox Guest Additions (if installed)
# Control Panel → Programs → Uninstall VirtualBox Guest Additions
# Remove VMware Tools
# Control Panel → Programs → Uninstall VMware Tools
# Remove obvious registry keys
Remove-Item -Path "HKLM:\SOFTWARE\Oracle\VirtualBox Guest Additions" -Recurse -ErrorAction SilentlyContinue
Remove-Item -Path "HKLM:\SOFTWARE\VMware, Inc." -Recurse -ErrorAction SilentlyContinue
# Remove VBox/VMware processes from autostart
# Check: HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Run
```
### Screen Resolution and User Activity
```
Set VM resolution to common values: 1920x1080 or 1366x768
Create desktop shortcuts, browser history, recent documents
Place realistic-looking files in Downloads, Documents, Desktop
Add a few installed applications (Chrome, 7-Zip, Notepad++)
```
### Testing VM Hardening
Use pafish (Paranoid Fish) to verify detection resistance:
```
# Download: https://github.com/a0rtega/pafish
# Run pafish.exe in the VM
# Each red "FAIL" is a detectable artifact
# Aim for all green before analyzing evasive samples
```
---
## CAPE Sandbox - Local Automated Sandbox
CAPE (Config And Payload Extraction) automates dynamic analysis at scale.
### Quick Install (Docker)
```bash
# Clone CAPE
git clone https://github.com/kevoreilly/CAPEv2.git
cd CAPEv2
# Use Docker Compose (simplest setup)
sudo docker-compose up -d
# Web interface available at:
# http://localhost:8000
```
### Submit Sample via CLI
```bash
# Submit file
python3 utils/submit.py --file /path/to/sample.exe
# Submit with options
python3 utils/submit.py \
--file /path/to/sample.exe \
--timeout 120 \
--options "procmemdump=yes,extraction=yes"
```
### Submit via Web UI
```
http://localhost:8000/submit
→ Upload file
→ Set timeout (60-300 seconds)
→ Select package (exe, dll, doc, pdf, etc.)
→ Submit
```
### Retrieve Report
```bash
# List tasks
python3 utils/process.py -r <task_id>
# Reports stored at:
# /opt/CAPEv2/storage/analyses/<task_id>/reports/report.json
```
---
## Troubleshooting
**Windows VM has internet access (should not):**
- Verify adapter is Host-Only, not NAT or Bridged
- Check default gateway points to INetSim IP, not router IP
- Disable any secondary adapters
**INetSim not responding to DNS queries:**
- Confirm `service_bind_address` matches REMnux's host-only IP
- Check INetSim is running: `sudo systemctl status inetsim`
- Verify no firewall blocking port 53 on REMnux: `sudo ufw status`
**Snapshot restore fails (VirtualBox):**
- Ensure VM is fully powered off before restoring
- Verify sufficient disk space on host
- Try: `VBoxManage snapshot <vm> restorecurrent`
**FlareVM installation fails partway through:**
- Re-run installer — Chocolatey packages are idempotent
- Check Windows Update is fully disabled
- Verify Defender exclusions cover `C:\ProgramData\chocolatey`
**pafish detects VM even after hardening:**
- Check CPUID hypervisor bit: requires CPU passthrough or nested virt disabled
- VMware: add `hypervisor.cpuid.v0 = FALSE` to .vmx file
- VirtualBox: `VBoxManage modifyvm <vm> --paravirtprovider none`

View File

@@ -0,0 +1,397 @@
# Dynamic Analysis Tool Setup Guide
Quick reference for configuring essential dynamic analysis tools.
## Process Monitor (Procmon) - Sysinternals
### Initial Setup
1. Download from: https://docs.microsoft.com/en-us/sysinternals/downloads/procmon
2. Run as Administrator
3. Procmon starts capturing immediately
### Essential Filters (Reduce Noise)
**Filter by Process:**
```
Process Name → is → sample.exe → Include
Process Name → is → rundll32.exe → Include (if DLL execution)
Process Name → is → explorer.exe → Exclude
Process Name → is → svchost.exe → Exclude
```
**Filter by Operation Type:**
```
Operation → contains → Reg → Include (registry operations)
Operation → contains → File → Include (file operations)
Operation → begins with → Process → Include (process operations)
```
**Filter by Result:**
```
Result → is → SUCCESS → Include (focus on successful operations)
```
### Columns to Enable
- Time
- Process Name
- PID
- Operation
- Path
- Result
- Detail
### Export Options
```
File → Save → All Events → CSV
File → Save → Filtered Events → CSV (recommended)
```
### Pro Tips
- Clear capture (Ctrl+X) before malware execution for clean logs
- Use bookmarks (Ctrl+B) to mark important events
- Stack duplicate events: Tools → Count Occurrences
- Enable boot logging for persistence analysis: Options → Enable Boot Logging
---
## Wireshark - Network Analysis
### Capture Setup
1. Download from: https://www.wireshark.org
2. Start → Options → Select adapter connected to INetSim
3. Start capture before executing malware
### Display Filters (During Analysis)
**DNS Traffic:**
```
dns
dns.qry.name contains "malicious"
```
**HTTP Traffic:**
```
http
http.request.method == "POST"
http.request.uri contains "/api"
```
**HTTPS/TLS:**
```
tls
tls.handshake.type == 1
```
**Specific IP:**
```
ip.addr == 192.168.1.100
ip.dst == 192.168.1.100
```
**Specific Port:**
```
tcp.port == 443
udp.port == 53
```
### Export Options
```
File → Export Specified Packets → Save as PCAPNG
File → Export Objects → HTTP (extract downloaded files)
```
### Command-Line Analysis (tshark)
```bash
# Extract DNS queries
tshark -r capture.pcapng -Y dns.qry.name -T fields -e dns.qry.name | sort -u
# Extract HTTP hosts
tshark -r capture.pcapng -Y http.request -T fields -e http.host -e http.request.uri
# Extract all contacted IPs
tshark -r capture.pcapng -T fields -e ip.dst | sort -u
```
---
## System Informer (formerly Process Hacker) - Process/Memory Analysis
### Setup
1. Download from: https://systeminformer.sourceforge.io
2. Run as Administrator
3. Enable: View → Update Speed → Fast
### Key Features
**Process Tree View:**
```
View → Tree View
Expand malware process to see child processes
```
**Memory Inspection:**
```
Double-click process → Memory tab
Look for RWX regions (suspicious)
Right-click memory region → Read/Write → String scan
```
**Handles:**
```
Double-click process → Handles tab
Filter: Type → Mutant (mutexes)
Filter: Type → File (open files)
```
**Network Connections:**
```
Double-click process → Network tab
Shows all connections made by process
```
**Memory Dump:**
```
Right-click process → Create dump file
Save full memory for Volatility analysis
```
### Highlighting Rules
```
Options → Highlight → New
Condition: Process Name → contains → sample.exe
Color: Red
```
---
## Sysmon - Advanced Windows Logging
### Installation
```powershell
# Download Sysmon
# https://docs.microsoft.com/en-us/sysinternals/downloads/sysmon
# Install with a community config
sysmon64.exe -accepteula -i sysmonconfig.xml
# Config sources (pick one):
# https://github.com/olafhartong/sysmon-modular (actively maintained, MITRE-mapped; use sysmonconfig.xml)
# https://github.com/SwiftOnSecurity/sysmon-config (classic, less frequently updated)
```
### View Logs
```powershell
# Event Viewer
eventvwr.msc → Applications and Services Logs → Microsoft → Windows → Sysmon → Operational
# PowerShell
Get-WinEvent -LogName "Microsoft-Windows-Sysmon/Operational" -MaxEvents 100 | Format-List
# Export logs
wevtutil epl Microsoft-Windows-Sysmon/Operational C:\evidence\sysmon.evtx
```
### Key Event IDs
- **Event 1**: Process creation (command line, hashes, parent process)
- **Event 3**: Network connection (source/dest IP, port, process)
- **Event 5**: Process terminated
- **Event 7**: Image loaded (DLL loading)
- **Event 8**: CreateRemoteThread (injection)
- **Event 10**: ProcessAccess (process manipulation)
- **Event 11**: FileCreate (file creation time)
- **Event 12/13/14**: Registry operations
- **Event 22**: DNS query
---
## Regshot - Registry Comparison
### Workflow
1. Download from: https://sourceforge.net/projects/regshot/
2. Run Regshot
3. Click "1st shot" (before malware execution)
4. Execute malware
5. Wait for all malware activity to complete
6. Click "2nd shot" (after malware execution)
7. Click "Compare"
8. Output saved as HTML (registry_changes.html)
### Output Interpretation
- **Keys added**: New persistence mechanisms
- **Keys deleted**: Cleanup activities
- **Values modified**: Configuration changes
- **Values added**: New settings
### Pro Tips
- Scan selected folders only for faster scans
- Use during specific malware phases (install vs. runtime)
- Export both shots for archival
---
## TCPView - Real-Time Network Connections
### Setup
1. Download from: https://docs.microsoft.com/en-us/sysinternals/downloads/tcpview
2. Run as Administrator
3. Shows all active TCP/UDP connections
### Features
- Real-time connection monitoring
- Color-coded (green = new, red = closed)
- Right-click → Close Connection (for testing)
- Right-click → Whois (lookup IP)
### Save Output
```
File → Save → CSV
```
---
## Noriben - Automated Procmon Analysis
### Setup
```bash
# Download from: https://github.com/Rurik/Noriben
git clone https://github.com/Rurik/Noriben.git
cd Noriben
```
### Usage
```bash
# Automated execution with Procmon
python Noriben.py --cmd sample.exe --timeout 300
# Manual mode (execute malware yourself)
python Noriben.py --output noriben_report.txt
```
### Output
- Filtered Procmon events
- Suspicious behaviors highlighted
- Timeline of activities
- File, registry, network artifacts
---
## INetSim - Fake Internet Simulation
### Installation (Linux VM)
```bash
sudo apt update
sudo apt install inetsim
# Configure
sudo nano /etc/inetsim/inetsim.conf
```
### Configuration
```
service_bind_address 0.0.0.0
dns_default_ip 192.168.56.1 # INetSim host IP
https_bind_port 443
http_bind_port 80
smtp_bind_port 25
```
### Start INetSim
```bash
sudo inetsim
```
### Configure Windows VM
```
Set DNS server to INetSim IP (192.168.56.1)
Set gateway to INetSim IP (if using NAT)
```
### Verify
```cmd
# From Windows VM
ping google.com # Should resolve to INetSim IP
nslookup microsoft.com # Should resolve to INetSim IP
```
---
## FakeNet-NG - Windows Internet Simulation
### Installation
```bash
# Download from: https://github.com/mandiant/flare-fakenet-ng
pip install flare-fakenet-ng
```
### Usage
```cmd
# Run as Administrator
fakenet.exe
# Execute malware (FakeNet intercepts all network)
```
### Features
- Simulates HTTP, HTTPS, DNS, FTP, SMTP
- Captures all network traffic
- No external VM needed
- Logs all requests/responses
---
## Tool Startup Sequence
**Recommended Order:**
1. Take VM snapshot
2. Start INetSim (if using)
3. Start Sysmon (already installed)
4. Start Procmon (clear capture first)
5. Start Wireshark
6. Start System Informer
7. Start Regshot → 1st shot (if using)
8. Execute malware
9. Monitor for 15+ minutes
10. Regshot → 2nd shot (if using)
11. Stop all captures
12. Export all evidence
13. Revert to snapshot
---
## Quick Reference Card
| Tool | Purpose | When to Use |
|------|---------|-------------|
| **Procmon** | File/Registry/Process activity | Always |
| **Wireshark** | Network traffic | Always |
| **System Informer** | Process/Memory analysis | Always |
| **Sysmon** | Windows event logging | Always (background) |
| **Regshot** | Registry comparison | When need before/after snapshot |
| **TCPView** | Real-time connections | Quick network checks |
| **Noriben** | Automated Procmon | Time-constrained analysis |
| **INetSim** | Fake internet | Always (network isolation) |
| **FakeNet-NG** | Windows fake internet | Alternative to INetSim |
---
## Troubleshooting
**Procmon not capturing:**
- Run as Administrator
- Clear filters (Ctrl+R)
- Check "Capture Events" is enabled
**Wireshark shows no traffic:**
- Verify correct network adapter selected
- Check malware actually made network requests
- Verify VM network settings
**System Informer can't dump process:**
- Run as Administrator
- Process may be protected
- Try x64dbg or WinDbg instead
**Sysmon not logging:**
- Verify installation: `sc query sysmon64`
- Check config file is valid XML
- View logs: Event Viewer → Sysmon/Operational

View File

@@ -0,0 +1,230 @@
#!/usr/bin/env python3
"""
procmon_summary.py - condense a Procmon CSV export into a behavior summary.
Runs on the host against evidence exported from the analysis VM
(Procmon: File -> Save -> All events -> CSV). Stdlib only.
Usage:
python3 procmon_summary.py procmon.csv --process sample.exe
python3 procmon_summary.py procmon.csv --all # no process filter
python3 procmon_summary.py --self-test
--process takes a comma list of image names (case-insensitive). The summary
covers those processes plus every descendant found via "Process Create".
Without --process (or with --all) every process in the log is included.
Output sections: process tree, files written/renamed/deleted, registry writes
(persistence keys flagged), network endpoints, non-system image loads.
Pipe the output through scripts/ioc_extract.py to get defanged IOCs.
"""
import argparse
import csv
import io
import re
import sys
from collections import defaultdict, OrderedDict
PERSISTENCE_RE = re.compile(
r"CurrentVersion\\(Run|RunOnce|RunServices|Explorer\\(Shell Folders|User Shell Folders))"
r"|CurrentControlSet\\Services\\"
r"|Winlogon\\(Shell|Userinit|Notify)"
r"|Image File Execution Options"
r"|Windows\\CurrentVersion\\Policies\\Explorer\\Run"
r"|Schedule\\TaskCache"
r"|Office\\.*\\Security"
r"|AppInit_DLLs"
r"|Start Menu\\Programs\\Startup",
re.IGNORECASE,
)
SYSTEM_DIR_RE = re.compile(r"^[A-Z]:\\Windows\\", re.IGNORECASE)
PID_RE = re.compile(r"PID:\s*(\d+)")
CMD_RE = re.compile(r"Command line:\s*(.*?)(?:,\s*Current directory:|$)", re.DOTALL)
NEWFILE_RE = re.compile(r"OpenResult:\s*(Created|Overwritten|Superseded)")
def read_text(path):
"""Read exported evidence regardless of PowerShell's default encoding (UTF-16LE with BOM)."""
raw = open(path, "rb").read()
if raw.startswith((b"\xff\xfe", b"\xfe\xff")):
return raw.decode("utf-16", errors="replace")
return raw.decode("utf-8-sig", errors="replace")
def load(path):
return list(csv.DictReader(io.StringIO(read_text(path))))
def field(row, *names):
for n in names:
if n in row and row[n] is not None:
return row[n]
return ""
def build_tree(rows):
"""Return (children: pid -> [pid], info: pid -> (name, cmdline))."""
children = defaultdict(list)
info = {}
for r in rows:
op = field(r, "Operation")
if op == "Process Create":
m = PID_RE.search(field(r, "Detail"))
if not m:
continue
child = m.group(1)
parent = field(r, "PID")
children[parent].append(child)
cmd = CMD_RE.search(field(r, "Detail"))
# name the child from the created image path (the actor row's name is the parent)
created = field(r, "Path")
child_name = created.rsplit("\\", 1)[-1] if created else "?"
info.setdefault(child, [child_name or "?", cmd.group(1).strip() if cmd else ""])
elif op == "Process Start":
pid = field(r, "PID")
cmd = CMD_RE.search(field(r, "Detail"))
info[pid] = [field(r, "Process Name"), cmd.group(1).strip() if cmd else ""]
for r in rows: # fill names for processes seen only as actors
pid = field(r, "PID")
if pid and pid not in info:
info[pid] = [field(r, "Process Name"), ""]
elif pid in info and info[pid][0] == "?":
info[pid][0] = field(r, "Process Name")
return children, info
def descendants(seeds, children):
seen, stack = set(), list(seeds)
while stack:
p = stack.pop()
if p in seen:
continue
seen.add(p)
stack.extend(children.get(p, []))
return seen
def print_tree(pid, children, info, depth, out):
name, cmd = info.get(pid, ("?", ""))
out.append(f"{' ' * depth}{name} (PID {pid})" + (f" {cmd}" if cmd else ""))
for c in children.get(pid, []):
print_tree(c, children, info, depth + 1, out)
def summarize(rows, process_names=None):
children, info = build_tree(rows)
if process_names:
wanted = {n.lower() for n in process_names}
seeds = [pid for pid, (name, _) in info.items() if name.lower() in wanted]
scope = descendants(seeds, children)
roots = seeds
else:
scope = set(info)
all_children = {c for cs in children.values() for c in cs}
roots = [p for p in info if p not in all_children]
files = OrderedDict()
renames, deletes = OrderedDict(), OrderedDict()
reg = OrderedDict()
net = OrderedDict()
loads = OrderedDict()
counts = defaultdict(int)
for r in rows:
pid = field(r, "PID")
if pid not in scope:
continue
op, path, detail = field(r, "Operation"), field(r, "Path"), field(r, "Detail")
who = f"{field(r, 'Process Name')}:{pid}"
counts[who] += 1
if op == "CreateFile" and NEWFILE_RE.search(detail):
files.setdefault(path, who)
elif op == "WriteFile":
files.setdefault(path, who)
elif op == "SetRenameInformationFile":
m = re.search(r"FileName:\s*(.*)$", detail)
renames.setdefault(f"{path} -> {m.group(1) if m else '?'}", who)
elif op in ("SetDispositionInformationFile", "SetDispositionInformationEx") and "True" in detail:
deletes.setdefault(path, who)
elif op in ("RegSetValue", "RegDeleteValue", "RegDeleteKey") or (
op == "RegCreateKey" and "REG_CREATED_NEW_KEY" in detail
):
data = ""
m = re.search(r"Data:\s*(.*)$", detail)
if m:
data = m.group(1)
reg.setdefault(f"{op} {path}" + (f" = {data}" if data else ""), who)
elif op.startswith(("TCP", "UDP")) and "->" in path:
dst = path.split("->", 1)[1].strip()
net.setdefault(f"{op.split()[0]} {dst}", who)
elif op == "Load Image" and not SYSTEM_DIR_RE.match(path):
loads.setdefault(path, who)
out = [f"# Procmon summary ({len(rows)} events, {len(scope)} processes in scope)", "", "## Process tree"]
for root in roots:
print_tree(root, children, info, 0, out)
def section(title, items, limit=200):
out.extend(["", f"## {title} ({len(items)})"])
for i, (k, who) in enumerate(items.items()):
if i >= limit:
out.append(f"... {len(items) - limit} more")
break
out.append(f"- {k} [{who}]")
section("Files created/written", files)
section("Files renamed", renames)
section("Files deleted", deletes)
out.extend(["", f"## Registry writes ({len(reg)})"])
for k, who in reg.items():
flag = " <-- PERSISTENCE" if PERSISTENCE_RE.search(k) else ""
out.append(f"- {k} [{who}]{flag}")
section("Network endpoints", net)
section("Image loads outside C:\\Windows", loads)
out.extend(["", "## Event count per process"])
for who, n in sorted(counts.items(), key=lambda x: -x[1])[:20]:
out.append(f"- {who}: {n}")
return "\n".join(out)
SELF_TEST_CSV = '''"Time of Day","Process Name","PID","Operation","Path","Result","Detail"
"1","explorer.exe","100","Process Create","C:\\Users\\Public\\sample.exe","SUCCESS","PID: 200, Command line: ""C:\\Users\\Public\\sample.exe"" /install"
"2","sample.exe","200","Process Start","","SUCCESS","Parent PID: 100, Command line: ""C:\\Users\\Public\\sample.exe"" /install, Current directory: C:\\Users\\Public\\"
"3","sample.exe","200","CreateFile","C:\\Users\\Bob\\AppData\\Roaming\\svc.exe","SUCCESS","Desired Access: Generic Write, OpenResult: Created"
"4","sample.exe","200","RegSetValue","HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Run\\svc","SUCCESS","Type: REG_SZ, Length: 60, Data: C:\\Users\\Bob\\AppData\\Roaming\\svc.exe"
"5","sample.exe","200","TCP Connect","host.local:49700 -> 203.0.113.5:443","SUCCESS","Length: 0"
"6","sample.exe","200","Process Create","C:\\Windows\\System32\\cmd.exe","SUCCESS","PID: 300, Command line: cmd.exe /c del sample.exe"
"7","cmd.exe","300","SetDispositionInformationFile","C:\\Users\\Public\\sample.exe","SUCCESS","Delete: True"
"8","chrome.exe","999","WriteFile","C:\\Users\\Bob\\cache.dat","SUCCESS","Offset: 0"
'''
def self_test():
rows = list(csv.DictReader(io.StringIO(SELF_TEST_CSV)))
out = summarize(rows, ["sample.exe"])
assert "cmd.exe (PID 300)" in out and " cmd.exe" in out, "child not nested under sample"
assert "svc.exe" in out and "PERSISTENCE" in out, "persistence run key not flagged"
assert "203.0.113.5:443" in out, "network endpoint missing"
assert "cache.dat" not in out, "unrelated process leaked into scope"
assert "Files deleted (1)" in out, "deletion missing"
print("self-test OK")
def main():
ap = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
ap.add_argument("csv", nargs="?")
ap.add_argument("--process", help="comma list of image names to scope on (plus descendants)")
ap.add_argument("--all", action="store_true", help="include every process")
ap.add_argument("--self-test", action="store_true")
a = ap.parse_args()
if a.self_test:
return self_test()
if not a.csv:
ap.error("csv path required")
names = None if a.all or not a.process else [n.strip() for n in a.process.split(",")]
print(summarize(load(a.csv), names))
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,235 @@
#!/usr/bin/env python3
"""
sysmon_summary.py - condense exported Sysmon events into a behavior summary.
Runs on the host against evidence exported from the analysis VM. Stdlib only.
Accepted inputs (auto-detected):
* PowerShell Get-WinEvent ... | Select Id,TimeCreated,Message | ConvertTo-Json (array or single object)
* PowerShell Get-WinEvent ... | Export-Csv (CSV with Id + Message)
* evtx_dump --format jsonl (one {"Event":{"System":..,"EventData":..}} per line)
Usage:
python3 sysmon_summary.py sysmon.json --process sample.exe
python3 sysmon_summary.py sysmon.csv --all
python3 sysmon_summary.py --self-test
Sections: process tree (EID 1), network (EID 3, beacon interval check), DNS (22),
files created (11), registry writes (12/13/14, persistence flagged), remote
threads (8), process access (10), non-Windows image loads (7), pipes (17/18),
WMI (19/20/21), tampering (25). Pipe output through scripts/ioc_extract.py.
"""
import argparse
import csv
import io
import json
import re
import statistics
import sys
from collections import defaultdict, OrderedDict
from datetime import datetime
PERSISTENCE_RE = re.compile(
r"CurrentVersion\\(Run|RunOnce|RunServices|Explorer\\(Shell Folders|User Shell Folders))"
r"|CurrentControlSet\\Services\\|Winlogon\\(Shell|Userinit|Notify)|Image File Execution Options"
r"|Schedule\\TaskCache|AppInit_DLLs|Start Menu\\Programs\\Startup",
re.IGNORECASE,
)
SYSTEM_DIR_RE = re.compile(r"^[A-Z]:\\Windows\\", re.IGNORECASE)
def parse_message(msg):
d = {}
for line in str(msg).replace("\r", "").split("\n"):
k, sep, v = line.partition(": ")
if sep:
d[k.strip()] = v.strip()
return d
def read_text(path):
"""Read exported evidence regardless of PowerShell's default encoding (UTF-16LE with BOM)."""
raw = open(path, "rb").read()
if raw.startswith((b"\xff\xfe", b"\xfe\xff")):
return raw.decode("utf-16", errors="replace")
return raw.decode("utf-8-sig", errors="replace")
def load(path):
"""Yield (event_id:int, data:dict) regardless of export format."""
text = read_text(path).lstrip("\ufeff")
stripped = text.lstrip()
if stripped.startswith(("[", "{")):
try:
obj = json.loads(text)
records = obj if isinstance(obj, list) else [obj]
except json.JSONDecodeError: # jsonl
records = [json.loads(l) for l in text.splitlines() if l.strip()]
for r in records:
if "Event" in r: # evtx_dump
sysm = r["Event"].get("System", {})
eid = sysm.get("EventID")
eid = eid.get("#text", eid) if isinstance(eid, dict) else eid
data = dict(r["Event"].get("EventData") or {})
yield int(eid), data
else:
yield int(r.get("Id", 0)), parse_message(r.get("Message", ""))
else:
for r in csv.DictReader(io.StringIO(text)):
yield int(r.get("Id") or 0), parse_message(r.get("Message", ""))
def beacon_stats(times):
ts = sorted(t for t in times if t)
if len(ts) < 5:
return ""
gaps = [(b - a).total_seconds() for a, b in zip(ts, ts[1:])]
mean = statistics.mean(gaps)
if mean <= 0:
return ""
cv = statistics.pstdev(gaps) / mean
tag = " <-- BEACON-LIKE" if cv < 0.3 else ""
return f" [{len(ts)} conns, mean gap {mean:.0f}s, cv {cv:.2f}]{tag}"
def parse_time(s):
try:
return datetime.strptime(s[:19], "%Y-%m-%d %H:%M:%S")
except (ValueError, TypeError):
return None
def summarize(events, process_names=None):
events = list(events)
children, info = defaultdict(list), {}
for eid, d in events:
if eid == 1:
pid, ppid = d.get("ProcessId", "?"), d.get("ParentProcessId", "?")
info[pid] = (d.get("Image", "?"), d.get("CommandLine", ""))
info.setdefault(ppid, (d.get("ParentImage", "?"), d.get("ParentCommandLine", "")))
children[ppid].append(pid)
if process_names:
wanted = {n.lower() for n in process_names}
seeds = [p for p, (img, _) in info.items() if img.split("\\")[-1].lower() in wanted]
scope, stack = set(), list(seeds)
while stack:
p = stack.pop()
if p not in scope:
scope.add(p)
stack.extend(children.get(p, []))
roots = seeds
else:
scope = None
all_children = {c for cs in children.values() for c in cs}
roots = [p for p in info if p not in all_children]
def in_scope(d):
return scope is None or d.get("ProcessId") in scope or d.get("SourceProcessId") in scope
net, net_times = OrderedDict(), defaultdict(list)
dns, files, reg, threads, access, loads, pipes, wmi, tamper = (OrderedDict() for _ in range(9))
for eid, d in events:
if not in_scope(d):
continue
img = d.get("Image", d.get("SourceImage", "?")).split("\\")[-1]
if eid == 3:
key = f"{d.get('Protocol', '?')} {d.get('DestinationIp', '?')}:{d.get('DestinationPort', '?')}" + (
f" ({d['DestinationHostname']})" if d.get("DestinationHostname") not in (None, "", "-") else ""
)
net.setdefault(key, img)
net_times[key].append(parse_time(d.get("UtcTime", "")))
elif eid == 22:
dns.setdefault(f"{d.get('QueryName', '?')} -> {d.get('QueryResults', '')}", img)
elif eid == 11:
files.setdefault(d.get("TargetFilename", "?"), img)
elif eid in (12, 13, 14):
key = f"{d.get('EventType', '')} {d.get('TargetObject', '?')}" + (
f" = {d['Details']}" if d.get("Details") else ""
)
reg.setdefault(key, img)
elif eid == 8:
threads.setdefault(f"{d.get('SourceImage')} -> {d.get('TargetImage')} @ {d.get('StartAddress')}", img)
elif eid == 10:
access.setdefault(f"{d.get('SourceImage')} -> {d.get('TargetImage')} access {d.get('GrantedAccess')}", img)
elif eid == 7 and not SYSTEM_DIR_RE.match(d.get("ImageLoaded", "")):
loads.setdefault(f"{d.get('ImageLoaded')} signed={d.get('Signed', '?')}", img)
elif eid in (17, 18):
pipes.setdefault(f"{'created' if eid == 17 else 'connected'} {d.get('PipeName')}", img)
elif eid in (19, 20, 21):
wmi.setdefault(f"EID{eid} {d.get('Name', '')} {d.get('Query', '')} {d.get('Destination', '')}".strip(), img)
elif eid == 25:
tamper.setdefault(f"{d.get('Image')} {d.get('Type', '')}", img)
out = [f"# Sysmon summary ({len(events)} events)", "", "## Process tree (EID 1)"]
def tree(pid, depth):
img, cmd = info.get(pid, ("?", ""))
out.append(f"{' ' * depth}{img} (PID {pid})" + (f" {cmd}" if cmd else ""))
for c in children.get(pid, []):
tree(c, depth + 1)
for r in roots:
tree(r, 0)
def section(title, items, extra=None):
out.extend(["", f"## {title} ({len(items)})"])
for k, who in items.items():
out.append(f"- {k} [{who}]" + (extra(k) if extra else ""))
section("Network connections (EID 3)", net, lambda k: beacon_stats(net_times[k]))
section("DNS queries (EID 22)", dns)
section("Files created (EID 11)", files)
section("Registry writes (EID 12/13/14)", reg, lambda k: " <-- PERSISTENCE" if PERSISTENCE_RE.search(k) else "")
section("Remote threads (EID 8)", threads)
section("Process access (EID 10)", access)
section("Image loads outside C:\\Windows (EID 7)", loads)
section("Named pipes (EID 17/18)", pipes)
section("WMI activity (EID 19/20/21)", wmi)
section("Process tampering (EID 25)", tamper)
return "\n".join(out)
SELF_TEST = [
{"Id": 1, "Message": "Process Create:\nUtcTime: 2026-01-01 10:00:00.000\nProcessId: 200\nImage: C:\\Users\\Public\\sample.exe\nCommandLine: sample.exe /install\nParentProcessId: 100\nParentImage: C:\\Windows\\explorer.exe\nParentCommandLine: explorer.exe"},
{"Id": 1, "Message": "Process Create:\nProcessId: 300\nImage: C:\\Windows\\System32\\cmd.exe\nCommandLine: cmd /c whoami\nParentProcessId: 200\nParentImage: C:\\Users\\Public\\sample.exe"},
{"Id": 13, "Message": "Registry value set:\nEventType: SetValue\nProcessId: 200\nImage: C:\\Users\\Public\\sample.exe\nTargetObject: HKU\\S-1\\Software\\Microsoft\\Windows\\CurrentVersion\\Run\\svc\nDetails: C:\\Users\\Bob\\AppData\\Roaming\\svc.exe"},
{"Id": 11, "Message": "File created:\nProcessId: 999\nImage: C:\\Program Files\\Chrome\\chrome.exe\nTargetFilename: C:\\Users\\Bob\\cache.dat"},
] + [
{"Id": 3, "Message": f"Network connection detected:\nUtcTime: 2026-01-01 10:0{i}:00.000\nProcessId: 200\nImage: C:\\Users\\Public\\sample.exe\nProtocol: tcp\nDestinationIp: 203.0.113.5\nDestinationPort: 443\nDestinationHostname: -"}
for i in range(6)
]
def self_test():
import tempfile, os
with tempfile.NamedTemporaryFile("w", suffix=".json", delete=False) as f:
json.dump(SELF_TEST, f)
try:
out = summarize(load(f.name), ["sample.exe"])
finally:
os.unlink(f.name)
assert " C:\\Windows\\System32\\cmd.exe (PID 300)" in out, "child not nested"
assert "PERSISTENCE" in out, "run key not flagged"
assert "BEACON-LIKE" in out, "regular 60s connections not flagged"
assert "cache.dat" not in out, "out-of-scope process leaked"
print("self-test OK")
def main():
ap = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
ap.add_argument("path", nargs="?")
ap.add_argument("--process", help="comma list of image names to scope on (plus descendants)")
ap.add_argument("--all", action="store_true")
ap.add_argument("--self-test", action="store_true")
a = ap.parse_args()
if a.self_test:
return self_test()
if not a.path:
ap.error("path required")
names = None if a.all or not a.process else [n.strip() for n in a.process.split(",")]
print(summarize(load(a.path), names))
if __name__ == "__main__":
main()