malware-triage · git:20260905.e1d9a1a · 2026-09-05 · sha256 0b17fefe250112c8

malware-triage git:20260905.e1d9a1aA

Immutable. This exact content is served forever at /api/v1/blob/0b17fefe250112c8.

---
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.
- **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`.
- **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