--- 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:-}"` 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 Include Process Name is 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, # 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`, `.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` (use an already-installed `evtx_dump`; otherwise convert on Windows) — `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 "$R"/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 "$R"/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 "$R"/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 through a PE static-triage workflow. 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 "$R"/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 a detection-engineering phase 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