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_ROOTis 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.mdand 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.pyat 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 -lfirst. Use the summary scripts andgrep, nevercata 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)
- INetSim / FakeNet-NG (network simulation)
- Sysmon — already installed with
olafhartong/sysmon-modularor SwiftOnSecurity config - Procmon — clear capture (Ctrl+X), apply filters (below)
- Wireshark — capture on the VM adapter
- System Informer
- Regshot 1st shot (optional) /
autorunscbaseline (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):
autorunsc64.exe -accepteula -a * -c -h -s -nobanner | Out-File -Encoding utf8 C:\evidence\autoruns_before.csv
A3. Execute
# 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):
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):
$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
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
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:
# 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
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
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):
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
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
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.
jq -r '.. | .processes? // empty' report.json | head; jq -r '.. | .network? // empty' report.json | head
B7. Consolidate
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:
## 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-NGreferences/sandbox_setup.md— REMnux/FlareVM build, isolation, snapshots, CAPE/Cuckooreferences/anti_analysis_bypass.md— VM/debugger/sleep evasion and how to defeat itscripts/procmon_summary.py,scripts/sysmon_summary.py— evidence condensers (--self-testto verify)../scripts/ioc_extract.py— IOC extraction and defanging