Skip to content

About

Automated static triage for malware analysis — hashes, strings, IOC extraction, PE parsing and YARA scanning. Static only; never executes samples.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

malware-triage-toolkit

A small, defensive static triage helper for malware analysis. It reads a suspicious file's bytes and produces a Markdown, HTML, or JSON report with hashes, strings, extracted IOCs, PE structure (including deep details such as Rich header, TLS callbacks, delay imports, .NET detection, and the resource tree), YARA matches, YAML capability signatures with MITRE ATT&CK mapping, packer identification, single-byte-XOR hidden-string mining, and static indicators for analyst review. It can also triage a whole directory in batch mode. Optional extras carve embedded PEs out of files and memory dumps, unpack UPX, query capa/FLOSS, and export annotation scripts for IDA Pro and Ghidra.

It never executes, loads, or emulates the sample. Static analysis only.

What it does

  • Hashes -- MD5, SHA1, SHA256, SHA512
  • File identification -- by magic bytes, plus overall Shannon entropy
  • Strings -- ASCII and UTF-16 extraction
  • IOC extraction from strings: URLs, IPv4 and IPv6 addresses, domains, e-mail addresses, cryptocurrency wallet addresses, registry Run/RunOnce key references, file paths. IPv6 candidates are validated with the standard ipaddress parser and reported in canonical form, so clock times, MAC addresses, and colon-joined fragments such as C++ std::filesystem::path are not reported as addresses. Bitcoin candidates are likewise only reported after checksum validation (legacy 1.../3... addresses via Base58Check, SegWit bc1... addresses via the BIP-173 Bech32 / BIP-350 Bech32m checksum plus witness-program shape checks), alongside Ethereum (0x + 40 hex) and Monero (95-character) address shapes. Litecoin addresses are validated the same way as Bitcoin's: legacy L.../M... candidates via their Base58Check checksum (version bytes 0x30/0x32, which also keeps them distinct from Bitcoin's 0x00/0x05) and SegWit ltc1... candidates via the Bech32/Bech32m checksum under the ltc HRP. Dogecoin addresses are validated the same way: legacy D... (P2PKH) and 9.../A... (P2SH) candidates via their Base58Check checksum (version bytes 0x1E/0x16, which also keeps them distinct from Bitcoin's and Litecoin's version bytes). Tron addresses are validated the same way too: fixed-length T... candidates via their Base58Check checksum (version byte 0x41) -- relevant because USDT-TRC20 is a frequent payment rail in ransom notes and stealer configs. XRP classic r... addresses are validated the same way, except under Ripple's own Base58 alphabet (not Bitcoin's) with version byte 0x00, so a Bitcoin 1... address never validates as XRP and vice versa. Cardano mainnet addr1... payment addresses are validated the same way, via their Bech32 checksum plus header and payload-length checks (base addresses, 57 bytes; enterprise addresses, 29 bytes); testnet addr_test1... addresses are not reported. Stellar G... account addresses are validated the same way, via their StrKey encoding (Base32 decoding, version byte 48, and the CRC16-XModem checksum over version + key); S... secret seeds are deliberately never extracted. Polkadot / Kusama / generic Substrate SS58 account addresses are validated the same way, via their Blake2b checksum (first 2 bytes of Blake2b-512 over SS58PRE + prefix + key) with a 1-byte network prefix (Polkadot 0, Kusama 2, generic Substrate 42) and a 32-byte key; other prefixes and key lengths are not reported. Algorand account addresses are validated the same way, via their Base32 encoding (a 32-byte public key plus a 4-byte checksum, the last 4 bytes of the SHA-512/256 hash of the key), 58 characters in total. Cosmos Hub cosmos1... account addresses are validated the same way, via their Bech32 checksum plus a 20-byte payload-length check (45 characters in total); other Cosmos-SDK chains with a different HRP are not reported
  • Suspicious API references -- grouped by behaviour (process injection, persistence, network, crypto, anti-analysis, dynamic resolution, execution)
  • PE parsing (via pefile): architecture, compile timestamp, entry point, sections with per-section entropy and flags, import table, imphash, overlay size
  • Deep PE details: Authenticode certificate-table presence and size (existence/size only, no chain validation), Rich header presence with checksum validation and compid entries, TLS callbacks, delay-load imports, .NET (CLR) detection -- managed assemblies are flagged with a pointer to decompilers instead of native analysis -- a resource-tree inventory (type, language, size, per-resource SHA256 prefix), and version information (CompanyName, FileVersion, ...) when present
  • Hidden-string mining: every single-byte XOR key (0x01-0xFF) is tried and decoded strings that yield an IOC or a suspicious keyword are reported with their key and file offset (noise-filtered, capped list)
  • Batch mode -- point the tool at a directory: files are collected recursively, de-duplicated by SHA256, triaged individually, and an index report summarises everything (per-sample links, aggregated IOC totals, signature statistics, ATT&CK totals). Files over a size limit (default 200 MiB, --max-file-size) are skipped and listed
  • HTML reports (--format html) -- a self-contained single file with inline CSS and no external resources, with the same content as the Markdown report (which remains the default)
  • YARA scanning (via yara-python) against the bundled generic rules in yara_rules/, or your own rule directory
  • YAML signature matching (signatures.py + signatures/) -- human- readable capability signatures (imports, API references, substrings, regexes, section names/entropy, file type) with per-match evidence
  • Packer identification -- known packer section names (UPX, ASPack, VMProtect, Themida, ...), entry-point placement, and section entropy
  • Static unpacking helpers (opt-in, --unpack-out): overlay extraction, embedded-PE carving, and UPX decompression via the external upx binary followed by a before/after re-triage
  • Memory-dump mode (--from-dump) -- carve every PE out of a dump, triage each one, and merge the results plus global IOCs into one report
  • Tool integrations (integrations.py) -- optional capa capability scans and FLOSS string deobfuscation merged into the report, plus annotation-script export for IDA Pro and Ghidra
  • Static triage indicators -- observations such as high-entropy sections, RWX sections, overlay data, forged/zero PE timestamps, persistence strings, YARA and signature matches

Important: the indicators are prompts for analyst review, not a detection verdict. Packed installers, EDR products, debuggers, and other legitimate software routinely trigger several of them.

Bundled YARA rules (yara_rules/)

Rule Idea
Process_Injection_API_Combination PE imports VirtualAllocEx + WriteProcessMemory + a remote-thread API
High_Entropy_PE_Section A section with raw-data entropy > 7.2 (packing/encryption hint)
Persistence_Run_Key_Strings Strings referencing the registry Run/RunOnce autostart keys
AntiVM_Environment_Strings Multiple VM/sandbox artifact strings (VMware, VirtualBox, QEMU, ...)

All rules are generic and defensive in intent: they surface behaviours worth a second look, in samples and in legitimate software alike.

Bundled YAML signatures (signatures/)

Signatures describe capabilities in plain YAML -- no code -- and every match comes with the concrete evidence that triggered it:

- name: process_injection_capability
  severity: high
  category: capability
  attack_id: T1055          # MITRE ATT&CK Enterprise technique
  attack_name: Process Injection
  conditions:
    logic: all            # or "any"; items may nest
    items:
      - api_refs:         # import table OR string tokens
          any: [VirtualAllocEx, WriteProcessMemory, CreateRemoteThread]
          min_count: 2    # at least 2 of the listed names

Supported predicates: imports (import table only), api_refs (imports or string tokens), strings (substrings), strings_regex, section_name, min_section_entropy, and file_type. Name-like specs accept a string, a list (any-of), or {any: [...], min_count: N} / {all: [...]} / {contains: X}.

The bundled library (signatures/capabilities.yaml, 9 signatures) covers process-injection capability, Run-key persistence, anti-VM artifacts, anti-debug APIs, downloader behaviour, crypto-API usage, keylogging capability, credential-store strings, and high-entropy (packed) sections.

Every bundled signature also carries an attack_id / attack_name mapping to the closest MITRE ATT&CK Enterprise technique (e.g. process injection -> T1055, Run keys -> T1547.001, anti-VM -> T1497.001, debugger evasion -> T1622). Reports aggregate matches into an ATT&CK summary table, and batch index reports total techniques across all samples. A mapping is analyst guidance -- the closest technique for the capability -- not proof the technique was exercised.

Where YAML signatures fit next to YARA and capa: YARA is byte-pattern matching, capa is deep semantic capability analysis over a disassembly. These signatures sit in between -- cheap, readable, string/import-level capability hints that run anywhere Python runs.

Packing, carving, and dump mode

Packer identification runs automatically for PE files (section-name fingerprints such as UPX0/UPX1, .aspack, .vmp0, plus entry-point and entropy analysis, reported with a confidence level and reasons).

Extraction actions are opt-in via --unpack-out DIR:

  • Overlay extraction -- overlay bytes are written to overlay.bin with size, entropy, and SHA256.
  • Embedded-PE carving -- every valid PE embedded after offset 0 is validated with pefile and written to DIR/carved/ with its offset, size, and SHA256.
  • UPX unpacking -- if the fingerprint says UPX and the upx binary is on PATH, a copy is decompressed into DIR and immediately re-triaged; the report compares size/entropy/packer before and after and links the full unpacked_report.md. Without upx, this step is skipped with a note. (Other packers are identified, not automatically unpacked -- use the dump mode below or a dedicated unpacker for those.)

For memory dumps and process dumps there is --from-dump:

python triage.py mem.dmp --from-dump --unpack-out carved/ --out dump_report.md

Every PE is carved out of the dump, each is triaged individually (hashes, PE structure, YARA + YAML signatures, indicators), and the report merges a per-PE summary table with IOCs extracted from the whole dump's strings.

Integrations: IDA, Ghidra, capa, FLOSS

Export annotation scripts that carry the triage results into your disassembler:

python triage.py sample.exe --export-ida annotate_ida.py
python triage.py sample.exe --export-ghidra annotate_ghidra.py

Open the sample in IDA Pro, finish auto-analysis, then run the generated script via File -> Script file.... In Ghidra, run the generated script from the Script Manager or via analyzeHeadless ... -postScript annotate_ghidra.py. Both scripts only add comments (IOC strings, suspicious API references, and a signature summary at the entry point / image base); they never patch the sample.

Optional external tools, merged into the report when present on PATH:

python triage.py sample.exe --run-capa    # mandiant/capa capabilities
python triage.py sample.exe --run-floss   # FLOSS deobfuscated strings

capa and FLOSS are not dependencies: if the binary is missing, the report notes the step as skipped. FLOSS emulates string-decoding stubs internally, so treat it with the same care as the sample itself and run it only inside the isolated VM.

Installation

Requires Python 3.9+.

git clone <this-repo>
cd malware-triage-toolkit
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt

pefile, yara-python, and PyYAML are optional at runtime: if any is missing, triage.py still runs and notes in the report which step was skipped. The upx binary is only needed for the UPX unpacking action.

Usage

# Print a Markdown report to stdout (bundled YARA rules and YAML
# signatures are picked up automatically)
python triage.py /path/to/sample.exe

# Write the report to a file and also emit JSON
python triage.py /path/to/sample.exe --out report.md --json report.json

# Use your own YARA rules / YAML signatures
python triage.py /path/to/sample.exe --rules /path/to/rules \
    --signatures /path/to/signatures

# Extraction actions: overlay, embedded-PE carving, UPX unpack + re-triage
python triage.py /path/to/sample.exe --unpack-out unpacked/

# Memory-dump mode: carve + triage every embedded PE, one combined report
python triage.py /path/to/mem.dmp --from-dump --unpack-out carved/ \
    --out dump_report.md

# Hand results to IDA / Ghidra, and merge capa + FLOSS output
python triage.py /path/to/sample.exe --export-ida ida_notes.py \
    --export-ghidra ghidra_notes.py --run-capa --run-floss

# Self-contained HTML report instead of Markdown
python triage.py /path/to/sample.exe --format html --out report.html

# Batch mode: triage a whole directory (recursive, SHA256 de-duplicated,
# per-sample reports plus an index report in the output directory)
python triage.py /path/to/samples/ --out batch_reports/ --json

# Batch mode with a tighter size guard (files over the limit are skipped
# and listed in the index report)
python triage.py /path/to/samples/ --max-file-size 50000000

See examples/sample_report.md for a sample report (generated from the harmless EICAR antivirus test file).

Safety

  • Analyse samples only inside an isolated VM with no network access (or a tightly controlled fake network), no shared folders to the host, and no credentials you care about.
  • This tool only reads files, but treat every sample as hostile: other tools in your workflow may not be as careful.
  • Never run an unknown sample on your host or on a production machine "just to see what happens". That is what the isolated VM is for.

Tests

tests/make_samples.py builds three harmless test inputs (an EICAR test file, a minimal hand-crafted PE containing IOC strings, and a plain text file) and there is no real malware anywhere in this repository.

The full test suite (regression, YAML signature engine, packer detection, carving, dump mode, script export, batch mode with de-duplication and IOC aggregation, deep-PE fixtures -- hand-built .NET/certificate/TLS/delay-import/resource/Rich-header PEs -- XOR hidden-string recovery, ATT&CK mapping validation, HTML report checks, and -- when upx and a mingw cross-compiler are available -- a real UPX pack/unpack round-trip of a compiled "hello world" PE) runs with:

python tests/run_tests.py

License

MIT -- see LICENSE.

About

Automated static triage for malware analysis — hashes, strings, IOC extraction, PE parsing and YARA scanning. Static only; never executes samples.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages