malware-dynamic-analysis · diff
git:20260905.fd80b2a to git:20260905.e1d9a1a
1 added, 0 removed. Audit A to A.
---
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