PackDetect — Packer Detection & Unpacking Tool
Packers are used by malware authors to compress and obfuscate PE binaries, making static analysis harder and evading signature-based AV detection. PackDetect is a command-line tool that identifies packed Windows executables using three layered detection techniques — entropy analysis, signature scanning, and structural heuristics — and attempts automatic unpacking for supported packers.
The tool was tested against a real UPX-packed malware sample obtained from MalwareBazaar, successfully detecting the packer, reporting per-section entropy, and unpacking the binary automatically.
Prerequisites
- Python 3.12+
- Windows / Linux / macOS
upxon PATH (optional — only needed for auto-unpack)rich >= 13.0(optional — for coloured terminal output)
Installation
git clone https://github.com/you/packdetect
cd packdetect
python -m venv .venv
.venv\Scripts\activate # Windows
source .venv/bin/activate # Mac / Linux
pip install -e .
Verify installation:
packdetect --help
How It Works
Detection runs in three independent layers, each contributing to a weighted confidence score:
┌─────────────────┐ ┌──────────────────┐ ┌──────────────────────┐
│ Layer 1 │ │ Layer 2 │ │ Layer 3 │
│ Entropy │──▶│ Signature scan │──▶│ Heuristics │
│ Shannon H(x) │ │ magic bytes + │ │ EP location, import │
│ per section │ │ section names │ │ count, size ratios │
└─────────────────┘ └──────────────────┘ └──────────────────────┘
│
Verdict + confidence%
Layer 1 — Shannon entropy measures randomness per PE section (0–8 bits/byte). Normal compiled code sits between 4.5–6.5. Packed or encrypted sections exceed 7.0.
Layer 2 — Signature scan searches for packer magic bytes (e.g. UPX!) and known section names (e.g. .MPRESS1). Covers UPX, MPRESS, ASPack, PECompact, Themida, VMProtect, FSG, Petite, and NSIS.
Layer 3 — Heuristics fires on structural anomalies regardless of signature presence — catching custom and unknown packers:
| Heuristic | What it detects |
|---|---|
| Virtual-only section | raw_size ≈ 0, virtual_size >> 0 — decompression placeholder |
EP outside .text |
Entry point landing in an unexpected section |
| No import table | Packed binaries rebuild their IAT dynamically at runtime |
| Low raw/virtual ratio | Large runtime expansion = decompression |
| Non-standard section names | Cleared or renamed sections |
Step 1: Scan a Binary
packdetect scan malware.exe
Output includes the file info panel, verdict with confidence %, per-section entropy bars, signature hits, and heuristic findings.
For plain ASCII output without Rich:
packdetect scan malware.exe --plain
Step 2: Test Against the Included Malware Sample
A real UPX-packed malware sample is included in malware-lab/ for immediate testing.
[!WARNING]
Do not execute the sample. Keep it in an isolated folder or VM. It was obtained from MalwareBazaar for static analysis purposes only.
packdetect scan ..\..\malware-lab\7d7655e9446fd41dc1ae859435f39c250964532bc604c9bf6d737992430d645e.exe
Expected verdict:
╭──────────────────────── Verdict ──────────────────────────╮
│ ⚠ PACKED │
│ │
│ Confidence : ██████████ 97% │
│ Risk level : HIGH │
│ Packer : UPX │
╰─────────────────────────────────────────────────────────────╯
Section Entropy Bar Flag
───────── ───────── ───────────────────────── ──────────
UPX0 0.0000 ░░░░░░░░░░░░░░░░░░░░░░░░ NORMAL
UPX1 7.88xx ████████████████████████ HIGH
.rsrc 3.4xxx ██████████░░░░░░░░░░░░░░ NORMAL
[HIT] UPX — magic bytes "UPX!" found in stub header
Tip: The
UPX0section has near-zero entropy because it is empty on disk — it is the virtual placeholder that gets populated with decompressed code at runtime.UPX1holds the compressed original binary with entropy ~7.88.
Step 3: Export a JSON Report
packdetect scan malware.exe --json
packdetect scan malware.exe --save-json
--json prints to stdout (pipe-friendly). --save-json writes malware.packdetect.json alongside the input file.
Sample output:
{
"file": {
"path": "malware.exe",
"size": 45056,
"md5": "...",
"sha256": "...",
"arch": "x86",
"entry_point": "0x00001000",
"entry_point_section": "UPX1"
},
"verdict": {
"verdict": "packed",
"packer": "UPX",
"confidence": 97,
"risk": "HIGH",
"unpack_supported": true,
"unpack_command": "upx -d malware.exe -o malware_unpacked.exe"
}
}
Step 4: Auto-Unpack
For UPX and MPRESS samples, PackDetect can call the unpacker automatically.
Install UPX first (upx.github.io), then:
packdetect unpack ..\..\malware-lab\7d7655e9446fd41dc1ae859435f39c250964532bc604c9bf6d737992430d645e.exe
This calls upx -d under the hood and writes a _unpacked.exe next to the original. Re-scan to confirm entropy dropped back to normal:
packdetect scan ..\..\malware-lab\7d7655e9446fd41dc1ae859435f39c250964532bc604c9bf6d737992430d645e_unpacked.exe
The unpacked binary should show .text entropy in the 5.x range — confirming a clean decompression.
Tip: If the unpacker is not on PATH, PackDetect reports the missing tool gracefully without crashing, and suggests the manual alternative using x64dbg + Scylla for unsupported packers.
Step 5: Batch Scan a Directory
packdetect batch ./samples/
packdetect batch ./samples/ --all # include non-PE extensions
packdetect batch ./samples/ --save-json # saves packdetect_batch.json
Produces a summary table showing verdict, packer, entropy, confidence, and risk level for every file scanned.
Step 6: Run the Test Suite
The project includes 26 unit tests covering the full engine. They use only synthetic in-memory PE binaries built with struct — no real malware required.
pip install pytest
pytest tests/ -v
Expected output:
tests/test_engine.py::TestShannonEntropy::test_all_zeros PASSED
tests/test_engine.py::TestShannonEntropy::test_uniform_distribution PASSED
tests/test_engine.py::TestPEParser::test_parses_minimal_pe PASSED
tests/test_engine.py::TestPEParser::test_rejects_non_pe PASSED
tests/test_engine.py::TestSignatureScanner::test_upx_magic_detected PASSED
tests/test_engine.py::TestSignatureScanner::test_mpress_section_name_detected PASSED
tests/test_engine.py::TestHeuristics::test_virtual_only_section_flagged PASSED
tests/test_engine.py::TestHeuristics::test_high_entropy_exec_section_flagged PASSED
tests/test_engine.py::TestVerdictComputation::test_signature_hit_gives_packed PASSED
tests/test_engine.py::TestVerdictComputation::test_no_sig_high_heuristic_gives_unknown PASSED
tests/test_engine.py::TestAnalyseIntegration::test_upx_magic_in_file PASSED
...
26 passed in 0.22s
With coverage:
pip install pytest-cov
pytest tests/ -v --cov=packdetect --cov-report=term-missing
Exit Codes
Designed for scripting integration:
| Code | Meaning |
|---|---|
| 0 | Clean / success |
| 2 | Packed binary detected |
| 3 | Suspicious (inconclusive) |
| 4 | Unpack not supported |
| 5 | Unpack attempted but failed |
packdetect scan "$file" --plain
if [ $? -eq 2 ]; then
packdetect unpack "$file"
fi
Project Structure
packdetect/
├── packdetect/
│ ├── __init__.py version string
│ ├── __main__.py CLI — argparse, scan / unpack / batch commands
│ ├── engine.py PE parser, entropy, signatures, heuristics, unpacker
│ └── output.py Rich renderer, plain renderer, JSON serialiser
├── malware-lab/
│ └── 7d7655e9...exe real UPX-packed sample (MalwareBazaar)
├── tests/
│ └── test_engine.py 26 unit tests
├── pyproject.toml
└── requirements.txt
🛠️ Tools & Techniques
- PE format analysis: Pure stdlib
struct— no pefile dependency - Entropy: Shannon H(x) computed per section
- Signature DB: 9 packers — UPX, MPRESS, ASPack, PECompact, Themida, VMProtect, FSG, Petite, NSIS
- Unknown packer heuristics: EP anomaly, import table absence, size ratio analysis
- CLI: Python
argparse, exit codes for scripting - Display:
richpanels and tables with--plainfallback - Testing:
pytest, synthetic PE binaries viastruct - Sample source: MalwareBazaar — abuse.ch public repository