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,9 @@
Apache License 2.0 — see the repository root LICENSE file for the full text.
Copyright the AnalystAIPack contributors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0

View File

@@ -0,0 +1,84 @@
---
name: diffing-malware-samples-to-find-changes
description: 'Compares two related malware samples to surface what changed between variants using
section-level hashing, import-set differences, and fuzzy similarity scoring. Activates for
requests to diff two samples, compare malware variants, or measure similarity between two binaries
for variant tracking.'
domain: cybersecurity
subdomain: reverse-engineering
tags:
- reverse-engineering
- bindiff
- variant-analysis
- similarity
- triage
version: 1.0.0
author: analyst-ai-pack
license: Apache-2.0
mitre_attack:
- T1587.001
- T1027
- T1588.001
d3fend:
- D3-FA
- D3-DA
references:
- 'PE Format — https://learn.microsoft.com/windows/win32/debug/pe-format'
- 'ssdeep fuzzy hashing (CTPH) — https://ssdeep-project.github.io/ssdeep/'
---
# Diffing Malware Samples to Find Changes
## When to Use
- You have two related samples (suspected variants of one family) and want a fast, structural diff
before a full BinDiff.
- You need to quantify similarity and pinpoint changed sections/imports for variant tracking.
**Do not use** this as authoritative function-level diffing — that requires BinDiff/Diaphora on
disassembly. This skill does structural/statistical diffing and executes nothing.
## Prerequisites
- Two sample files (read inertly). Optional: `ssdeep`/`tlsh` for fuzzy scores (degrade gracefully).
## Workflow
### Step 1: Structural diff
```bash
python scripts/analyst.py diff a.bin b.bin
```
Compares file size, per-section SHA-256 and entropy, and the import name sets, reporting added/
removed/changed sections and imports.
### Step 2: Similarity score
Reports a byte-level similarity ratio and, if available, ssdeep/tlsh fuzzy-hash comparison scores.
### Step 3: Prioritize changed regions
Changed sections (same name, different hash) and new imports are the high-value targets for deeper
disassembly diffing.
### Step 4: Document
Record what changed and the similarity score to support variant/lineage tracking.
## Validation
- Section comparison keys on section name; identical sections report equal hashes.
- Import diff lists are accurate (added vs removed vs common).
- Fuzzy-hash scores are reported only when the library is available, else clearly omitted.
## Pitfalls
- Recompilation shifting addresses makes raw byte diff noisy — rely on section/import structure.
- Packers making both samples look similar (packed) while the payloads differ — unpack first.
- Treating a high byte-similarity as proof of same author without corroboration.
## References
- See [`references/api-reference.md`](references/api-reference.md) for the differ.
- PE format and ssdeep references (linked in frontmatter).

View File

@@ -0,0 +1,40 @@
# API Reference: Diffing Malware Samples to Find Changes
## Overview
`scripts/analyst.py` structurally diffs two PE samples: per-section SHA-256 and entropy, import name
set differences, and a byte-level similarity ratio, optionally adding an ssdeep comparison score.
It reads bytes statically and executes nothing. Function-level diffing (BinDiff/Diaphora) is out of
scope.
## Dependencies
| Package | Version | Purpose |
|---------|---------|---------|
| pefile | latest | import table parsing (optional) |
| ssdeep | latest | fuzzy-hash similarity (optional) |
| (stdlib) | — | difflib, hashlib, struct, math, JSON |
## Core functions
### `diff(path_a, path_b)`
Returns added/removed/changed sections, added/removed imports, byte similarity ratio, and (if
available) ssdeep score.
### `_sections(data)`
Per-section SHA-256 and entropy keyed by section name.
### `_imports(data)`
Imported symbol name set via pefile (empty if unavailable).
## Usage
```bash
python scripts/analyst.py diff a.bin b.bin
```
## Sources
- PE Format (section table, import directory) — https://learn.microsoft.com/windows/win32/debug/pe-format
- ssdeep context-triggered piecewise hashing — https://ssdeep-project.github.io/ssdeep/
- Structural variant-diffing approach (section/import deltas) from public triage practice

View File

@@ -0,0 +1,114 @@
#!/usr/bin/env python3
"""Structurally diff two PE samples: section hashes/entropy, import sets, similarity.
Parses both PEs' section tables and import name sets, reports added/removed/changed sections and
imports, and computes a byte-level similarity ratio (plus ssdeep/tlsh if installed). Uses pefile
for imports when available; otherwise imports are best-effort. Reads bytes statically; executes
nothing.
"""
from __future__ import annotations
import argparse
import difflib
import hashlib
import json
import math
import struct
import sys
from collections import Counter
from pathlib import Path
try:
import pefile # type: ignore
HAVE_PEFILE = True
except ImportError:
HAVE_PEFILE = False
try:
import ssdeep # type: ignore
HAVE_SSDEEP = True
except ImportError:
HAVE_SSDEEP = False
def _entropy(chunk: bytes) -> float:
if not chunk:
return 0.0
counts = Counter(chunk)
total = len(chunk)
return round(-sum((c / total) * math.log2(c / total) for c in counts.values()), 3)
def _sections(data: bytes) -> dict[str, dict]:
out: dict[str, dict] = {}
if data[:2] != b"MZ":
return out
e_lfanew = struct.unpack_from("<I", data, 0x3c)[0]
if data[e_lfanew:e_lfanew + 4] != b"PE\x00\x00":
return out
num = struct.unpack_from("<H", data, e_lfanew + 6)[0]
opt = struct.unpack_from("<H", data, e_lfanew + 20)[0]
sect_off = e_lfanew + 24 + opt
for i in range(num):
base = sect_off + i * 40
raw = data[base:base + 40]
if len(raw) < 40:
break
name = raw[:8].rstrip(b"\x00").decode("latin-1", "ignore")
_, _, rawsize, rawptr = struct.unpack_from("<IIII", raw, 8)
body = data[rawptr:rawptr + rawsize]
out[name] = {"sha256": hashlib.sha256(body).hexdigest(), "entropy": _entropy(body)}
return out
def _imports(data: bytes) -> set[str]:
if not HAVE_PEFILE:
return set()
try:
pe = pefile.PE(data=data, fast_load=True)
pe.parse_data_directories(
directories=[pefile.DIRECTORY_ENTRY["IMAGE_DIRECTORY_ENTRY_IMPORT"]])
except Exception:
return set()
names = set()
for entry in getattr(pe, "DIRECTORY_ENTRY_IMPORT", []) or []:
for imp in entry.imports:
if imp.name:
names.add(imp.name.decode("latin-1", "ignore"))
return names
def diff(path_a: str, path_b: str) -> dict:
a = Path(path_a).read_bytes()
b = Path(path_b).read_bytes()
sa, sb = _sections(a), _sections(b)
changed = sorted(n for n in sa.keys() & sb.keys() if sa[n]["sha256"] != sb[n]["sha256"])
ia, ib = _imports(a), _imports(b)
result = {
"size_a": len(a), "size_b": len(b),
"sections_added": sorted(sb.keys() - sa.keys()),
"sections_removed": sorted(sa.keys() - sb.keys()),
"sections_changed": changed,
"imports_added": sorted(ib - ia)[:100],
"imports_removed": sorted(ia - ib)[:100],
"byte_similarity_ratio": round(difflib.SequenceMatcher(None, a, b).quick_ratio(), 4),
"import_diff_available": HAVE_PEFILE,
}
if HAVE_SSDEEP:
result["ssdeep_score"] = ssdeep.compare(ssdeep.hash(a), ssdeep.hash(b))
return result
def main(argv: list[str] | None = None) -> int:
parser = argparse.ArgumentParser(description="Diff two PE samples structurally")
sub = parser.add_subparsers(dest="cmd", required=True)
p = sub.add_parser("diff")
p.add_argument("file_a")
p.add_argument("file_b")
args = parser.parse_args(argv)
print(json.dumps(diff(args.file_a, args.file_b), indent=2))
return 0
if __name__ == "__main__":
sys.exit(main())