malware-triage · git:20260905.fd80b2a · 2026-09-05 · sha256 2a9f99b856a01ad5
malware-triage git:20260905.fd80b2aA
Immutable. This exact content is served forever at /api/v1/blob/2a9f99b856a01ad5.
---
name: malware-triage
description: Rapid static assessment, classification, and prioritization of malware samples. Use for the first look at any unknown file — hashes, file type, packing, imports, strings, initial IOCs, threat level, and the decision on which deep-analysis phase comes next. Claude runs the static tooling on the host itself; the sample is never executed.
---
# Malware Triage
First phase for every sample. You run static tools on the host, interpret the output, classify the sample, and decide the next phase. Target: 5-15 minutes per sample.
## Execution Model
- **You run the commands.** You have Bash. Execute every command in this skill yourself and interpret the output. Do not ask the user to run tools or paste results unless a tool is missing and cannot be installed.
- **Never execute the sample.** Only static tools touch it: `file`, `strings`, `pe_info.py`, `upx -d`, `7z l`, hashing. No `chmod +x`, no `wine`, no `./sample`.
- **Never upload the sample** to any service without explicit consent. Hash lookups are fine; file uploads are not.
- **Script paths** are relative to this skill directory. From the repo/skill root: `malware-triage/scripts/pe_info.py`, `malware-triage/scripts/hash_calculator.py`, `scripts/ioc_extract.py`. Resolve them from wherever this SKILL.md lives.
- **Tool check first.** Run once per session; note what is missing and degrade rather than stop:
```bash
command -v file strings upx 7z yara; python3 -c "import pefile" 2>&1 | tail -1
# pefile missing -> pip install pefile (needed by pe_info.py)
```
- **Big outputs:** never `cat` a strings dump. Save to a file, `wc -l`, then `grep`/`head`.
- **Ask the user only for:** the sample path, context (where it came from, incident ID, anything already known), and reputation results if no MCP server is available.
## Workflow
### Step 1 — Identify and hash
```bash
S=/path/to/sample
file "$S"; ls -l "$S"
python3 malware-triage/scripts/hash_calculator.py "$S"
```
Record MD5/SHA1/SHA256, size, `file` output. Then route on the `file` output:
| `file` says | Action |
|-------------|--------|
| `Mono/.Net assembly` | Record hashes, hand back to orchestrator → `specialized-file-analyzer` (check this **before** PE) |
| `Microsoft Office`, `PDF document`, `ELF`, `MS Windows shortcut`, `Zip/RAR/7-zip`, `ISO 9660`, `Microsoft Disk Image`, `HTML document`, script/text | Record hashes, hand back to orchestrator → `specialized-file-analyzer` |
| `PE32` / `PE32+` (not .NET) | Continue below |
| `data` / unrecognized | Continue at **Unknown Files** |
### Step 2 — Reputation
- **MCP available** (VirusTotal, Threat Intel / MalwareBazaar): look up the SHA256. Record detection ratio, family labels, first-seen date, tags. Look up any imphash and section hashes too.
- **No MCP:** give the user the SHA256 and ask them to check VirusTotal / MalwareBazaar while you continue. Do not block on it. Zero detections on a fresh hash means nothing; 0 detections on an old hash is informative.
- Reputation is a hint, not the verdict. Static findings override AV labels.
### Step 3 — PE static summary
```bash
python3 malware-triage/scripts/pe_info.py "$S" # add --json for structured output
```
Read every section of the output. What matters:
| Output | Interpretation |
|--------|----------------|
| `.NET: YES` | Stop, route to specialized-file-analyzer |
| Compile timestamp in the future / before 2000 | Forged; ignore for dating. Rich header linker info is harder to fake |
| Overall entropy > 7.0, or executable section > 6.8, or `!` markers | Packed/encrypted — see **Packed Samples** |
| Entry point outside `.text`/`CODE`, or in last section | Packer stub or appended code |
| Import count < 10, or only `LoadLibrary`/`GetProcAddress`/`VirtualAlloc` | Runtime API resolution — packed or deliberately hiding imports |
| Suspicious import categories (injection, keylogging, network, anti-debug, crypto, persistence) | Map each to a capability; cross-check with `references/indicators.md` |
| Overlay present | Appended data: config, second-stage payload, or installer archive. Carve it: `python3 -c "..."` using the reported offset, then `file` the result |
| Signature present | Verify claimed signer; revoked/self-signed/mismatched = red flag. Unsigned is neutral |
| PDB path | Project/user names, build environment — high-value attribution string |
| Resources with high entropy or large size | Embedded payload. Note type/size; extract in deep analysis |
| TLS callbacks | Code runs before entry point — anti-debug or early unpacking |
| Version info mimicking Microsoft/Adobe/etc. while unsigned | Masquerading |
### Step 4 — Strings and IOCs
```bash
W=triage_$(basename "$S") # work dir
mkdir -p "$W"
strings -a -n 6 "$S" > "$W/strings_ascii.txt"
strings -a -n 6 -e l "$S" > "$W/strings_wide.txt" # UTF-16LE, essential for Windows binaries
wc -l "$W"/strings_*.txt
python3 scripts/ioc_extract.py "$W"/strings_*.txt # defanged IPs, domains, URLs, emails, hashes, reg keys, paths
```
Then targeted greps (adjust after reading the ioc_extract output):
```bash
grep -ihE 'HKEY_|HKLM|HKCU|CurrentVersion\\Run|schtasks|sc create|netsh|vssadmin|bcdedit|wmic' "$W"/strings_*.txt | sort -u | head -40
grep -ihE 'user-agent|mozilla/|POST |GET |Content-Type|\.php|\.onion|/gate|/panel' "$W"/strings_*.txt | sort -u | head -40
grep -ihE 'mutex|Global\\|Local\\|\\pipe\\' "$W"/strings_*.txt | sort -u | head -20
grep -ihE 'IsDebuggerPresent|VMware|VBox|vbox|QEMU|Xen|sandbox|SbieDll|wireshark|procmon|ollydbg|x64dbg' "$W"/strings_*.txt | sort -u
grep -ihE 'bitcoin|monero|\.onion|decrypt|ransom|your files|README' "$W"/strings_*.txt | sort -u | head -20
grep -ihE '\.exe|\.dll|\.bat|\.ps1|\.vbs|\.tmp' "$W"/strings_*.txt | grep -viE 'kernel32|ntdll|user32|advapi32|msvcr|api-ms-win' | sort -u | head -40
```
Note what is **absent** too: a binary with network imports but no URL/IP strings is decrypting its config at runtime.
Suspicious-API and string categories: `references/indicators.md`.
### Packed Samples
Triggered by Step 3 entropy/import findings.
| Packer | Indicators | Unpack on host? |
|--------|------------|-----------------|
| UPX | `UPX0`/`UPX1` sections, `UPX!` in overlay | Yes: `upx -d "$S" -o "$W/unpacked.exe"` |
| Themida / WinLicense | `.themida`, `.winlicense` sections, huge protected section | No — dynamic analysis |
| VMProtect | `.vmp0`/`.vmp1` | No — dynamic analysis |
| ASPack / MPRESS / Petite / Enigma / Upack | named sections (`.aspack`, `.MPRESS1`, `.petite`, `.enigma1`, `.Upack`) | Rarely — dynamic analysis |
| Custom / unknown | high entropy, few imports, no known section names | No — dynamic analysis |
If UPX unpacking succeeds: hash the unpacked file, re-run Steps 3-4 on it, record both hashes (packed = delivery artifact, unpacked = what matches families). If `upx -d` fails, the UPX header was tampered; treat as custom.
Set sophistication: known packer → at least Moderate; commercial protector → Advanced; custom packer → Advanced/APT. Add "requires dynamic analysis to reveal imports/strings" to next steps.
### Step 5 — Classify
Combine imports, strings, reputation, and context. Consult `references/indicators.md` for the patterns.
| Type | Typical evidence |
|------|------------------|
| Trojan / RAT / Backdoor | socket or WinHTTP + command dispatch strings, screen/keyboard APIs, persistence |
| Ransomware | Crypt* APIs, file enumeration, `vssadmin delete shadows`, ransom-note strings, wallet addresses |
| Infostealer | browser profile paths (`Login Data`, `cookies.sqlite`), wallet dirs, `CryptUnprotectData`, exfil URL |
| Dropper / Loader | small binary, high-entropy resource or overlay, `URLDownloadToFile`, `WriteFile` + `CreateProcess`, few other capabilities |
| Cryptominer | pool URLs (`stratum+tcp://`), `xmrig`, high CPU-related config strings |
| Worm | SMB/RPC APIs, network share enumeration, removable-drive checks |
**Threat level:** Critical (destructive, ransomware, APT) / High (data theft, full compromise) / Medium (limited capability, targeted) / Low (adware, PUP, commodity).
**Sophistication:** Simple / Moderate / Advanced / APT-level, driven by protection, evasion, and custom code.
**Verdict:** Malicious / Suspicious / Benign. Benign requires positive evidence (valid signature from a plausible vendor, expected imports for its stated purpose, reputation clean over time), not just absence of red flags.
### Step 6 — Predict behavior
Turn static evidence into a watch list for the dynamic phase. Each prediction names the evidence:
- **Process:** child processes or injection expected? Which targets (`CreateRemoteThread` + `OpenProcess` → injection; `CreateProcess` + `cmd`/`powershell` strings → LOLBin chain)?
- **Files:** paths from strings; temp/AppData drops if `GetTempPath`/`SHGetFolderPath` imported.
- **Registry:** Run keys, service creation, IFEO from strings/imports.
- **Network:** protocol (WinHTTP/WinINet → HTTP(S); `socket`/`connect` → raw TCP; `DnsQuery` only → DNS tunnelling/DGA), hard-coded C2 vs runtime-decrypted, likely beacon.
- **Persistence:** mechanism and location.
- **Evasion:** anti-VM/anti-debug strings → plan for bypass (`malware-dynamic-analysis/references/anti_analysis_bypass.md`).
These predictions become the Procmon/Sysmon filters and the observation plan for `malware-dynamic-analysis`.
### Step 7 — Priority and next phase
| Priority | When |
|----------|------|
| **Immediate** | Unknown/undetected, incident-related, destructive capability, APT indicators, matches current threat intel |
| **Standard** | Known family variant, commodity malware with existing coverage |
| **Low** | Well-documented sample, adware/PUP, old, likely false positive |
Next phase: `malware-dynamic-analysis` for PE files that are packed, have runtime-resolved config, or need behavioral confirmation; `detection-engineer` + `malware-report-writer` directly when the sample is a known variant and static IOCs suffice; `Benign` closes the sample.
## Unknown Files
`file` returned `data` or something unhelpful:
```bash
xxd "$S" | head -20 # magic bytes, structure
strings -a -n 8 "$S" | head -50
python3 - "$S" <<'EOF' # single-byte XOR brute force for a hidden PE
import sys; b=open(sys.argv[1],'rb').read()
for k in range(1,256):
d=bytes(x^k for x in b[:4096])
if d[:2]==b'MZ' or b'This program cannot' in d: print(f"XOR key 0x{k:02x} reveals PE header")
EOF
```
Also check: base64 blob (`[A-Za-z0-9+/]{100,}`), gzip/zlib magic (`1f 8b`, `78 9c`), reversed PE (`ZM` at end), shellcode (starts with `fc e8` / `e8 00 00 00 00` / `55 8b ec`). Decode what you find, `file` the result, and re-route.
## Batch Triage
For N samples, do a quick pass on all before deep-diving any:
```bash
for f in samples/*; do
echo "== $f"; file "$f"; sha256sum "$f"
python3 malware-triage/scripts/pe_info.py "$f" 2>/dev/null | grep -E '^\.NET:|packed|imported functions'
done
```
Produce a ranking table (sample, type, packed, detections, priority, one-line reason), then run the full workflow on Immediate samples first.
## Output
1. **Print the triage report** (template below, filled in — no placeholders left).
2. **Append to `analysis_state.md`** in the user's working directory using the orchestrator's structure: hashes, type, priority, classification, threat level, triage findings, IOCs (defanged), next-phase recommendation.
3. **Recommend the next phase** with reasoning, then wait for the user.
```markdown
## Triage Report — [filename]
**Hashes:** MD5 … / SHA1 … / SHA256 … **Size:** … bytes **Type:** … **Packed:** No | Yes (packer)
**Reputation:** VT x/y, family label(s), first seen … | not checked
### Static Findings
- Compile time / linker / signature / PDB
- Sections + entropy (flag anomalies)
- Suspicious imports by category → capability
- Notable strings: URLs, IPs, paths, registry, mutex, UA, commands
- Resources / overlay / TLS callbacks
### Classification
**Verdict:** Malicious | Suspicious | Benign **Type:** … **Threat:** Critical | High | Medium | Low **Sophistication:** …
**Capabilities:** …
### Predicted Behavior
Process / Files / Registry / Network / Persistence / Evasion — each with the evidence behind it
### Initial IOCs (defanged)
File: hashes, imphash, dropped names · Network: … · Host: registry, paths, mutex
### Recommendation
**Priority:** … **Next phase:** … **Why:** …
```
## Quality Gate
Before handing back: all three hashes; `file` type; packing status with evidence; imports categorized; strings run in both ASCII and UTF-16; IOCs extracted and defanged; classification with reasoning; predictions tied to evidence; state file updated.
## References
- `references/indicators.md` — suspicious API imports, string patterns, behavioral indicators, packer signatures, classification patterns
- `references/triage_checklist.md` — full checklist, decision tree, dynamic go/no-go criteria, tool reference
- `scripts/pe_info.py` — PE header/section/import/resource/overlay/signature summary (needs `pefile`)
- `scripts/hash_calculator.py` — MD5/SHA1/SHA256
- `../scripts/ioc_extract.py` — IOC extraction and defanging from any text