name: packer-unpacking description: Identify and unpack common binary packers — UPX, ASPack, Themida, VMProtect, MPRESS, PECompact, Enigma. metadata: subdomain: reverse-engineering when_to_use: "packer unpack upx aspack themida vmprotect mpress pecompact enigma unpacking" mitre_attack: - T1027.002
Packer Unpacking Playbook
Packers compress and/or obfuscate binaries to defeat static analysis. The first job: identify which packer, then dispatch the right unpacker (or manual unpacking strategy if no automated tool exists).
1. Detect packing
# Entropy quick-check (>7.0 across the binary = likely packed)
ent /tmp/sample
# or
python3 -c "
import sys, math
d = open('/tmp/sample','rb').read()
f = [0]*256
for b in d: f[b] += 1
h = -sum((c/len(d))*math.log2(c/len(d)) for c in f if c)
print(f'entropy={h:.3f}')
"
# Section-level entropy via radare2
r2 -qc "iSj" /tmp/sample | jq '.[] | "\(.name): \(.entropy)"'
# Tool-based detection
detect-it-easy /tmp/sample # most reliable, GUI + CLI
diec /tmp/sample # CLI for DIE
yara -r /opt/yara-rules/packers/ /tmp/sample
Decepticon helper:
bin_packer("/tmp/sample")
2. Common packer signatures
| Packer | Signature |
|---|---|
| UPX | UPX! magic at section header, sections named UPX0, UPX1 |
| ASPack | .aspack section, jump after entry to packed code |
| Themida | .themida section, anti-debug, anti-VM heavy |
| VMProtect | .vmp0, .vmp1 sections; obfuscated EP w/ virtualized handlers |
| MPRESS | .MPRESS1, .MPRESS2 sections |
| PECompact | pec1 section, encrypted sections |
| Enigma | .enigma1, .enigma2 sections |
| Petite | small overlay, .petite section |
| FSG | tiny imports, packed sections |
| MEW | MEW magic in section name |
| Armadillo | runtime decryption, anti-debug (older) |
3. Automated unpacking
UPX (easy)
upx -d /tmp/sample -o /tmp/unpacked
file /tmp/unpacked
If upx -d fails with "not packed by UPX", the version field has been
tampered with (anti-unpack trick). Fix:
# Patch the version byte back
python3 -c "
d = bytearray(open('/tmp/sample','rb').read())
# Find UPX! magic, fix version
import re
for m in re.finditer(b'UPX!', d):
d[m.end()] = 0x0d # set version field
open('/tmp/patched','wb').write(d)
"
upx -d /tmp/patched -o /tmp/unpacked
ASPack
unaspack /tmp/sample # or use ASPackDie / ASPack Stripper
Manual: ASPack's OEP jump is JMP <reg> at the end of unpack stub.
Set breakpoint there in x64dbg, dump from Scylla (PE only).
MPRESS
quickunpack /tmp/sample
# Or load in x64dbg, set BP on tail jump (E9 to OEP), dump w/ Scylla
PECompact / FSG
Use unpacme (uploads to UnpacMe service if engagement permits cloud
processing), or run in monitored sandbox + memory-dump strategy.
4. Manual unpacking strategy (Themida / VMProtect / Enigma)
These are commercial-grade and don't have reliable auto-unpackers. Approach:
Themida
- Static: identify anti-debug checks, patch them or rewrite
- Dynamic: x64dbg +
ScyllaHideplugin → bypass anti-debug - Set hardware breakpoint on
VirtualProtect(Themida unpacks via this) - When hit, walk back to find decrypted code regions
- Dump w/
Scyllaafter OEP is reached - Themida often has multiple layers — repeat per layer
VMProtect
VMProtect translates code into bytecode for a custom VM. No simple "unpack" — you must either:
- Devirtualize (extract VM handlers + write a translator). Tools:
VTIL(Vladimir's tools),vmpfix, manual w/ IDA + bytecode trace - Trace + symbolic execute via
Tritonorangr - Skip RE and treat as black-box (fuzz the interfaces)
Enigma Protector
Similar to Themida. ScyllaHide handles many checks. The license / virt machine layer is hardest. Some Enigma variants:
- v3-v5: scriptable unpack via
Enigma Static Unpacker - v6+: manual w/ x64dbg + Scylla, multiple decryption passes
5. Manual unpack technique (universal)
For any packer:
- Disable ASLR / DEP if needed (ScyllaHide /
setdllchar) - Set BP on entry point
- Step through unpack stub; watch for:
- Large
VirtualAlloc(decryption region) memsetfollowed by decrypted code being written- Tail jump to OEP (often
JMP <reg>orRETafter PUSHAD/POPAD)
- Large
- At suspected OEP, dump process w/
Scylla(PE) orr2 -d - Fix imports (Scylla auto-rebuild IAT), save dumped PE
- Re-run static analysis on the dumped file
6. Anti-anti-unpacking tricks
| Anti-unpack | Counter |
|---|---|
IsDebuggerPresent |
ScyllaHide, or patch w/ NOP |
NtQueryInformationProcess(ProcessDebugPort) |
ScyllaHide |
Timing checks (rdtsc measure) |
x64dbg "timing" plugin or patch |
| INT3 detection (BP byte scan) | hardware BPs only |
| Self-checksum | identify check loop, patch comparison |
| TLS callbacks (run before main entry) | BP in TLS callback list (IDA: View → Open Subviews → TLS) |
| Anti-VM (CPUID hypervisor bit) | Run on bare metal or KVM w/ CPUID masking |
7. Promote
kg_add_node(kind="observation", label="packed: <packer-name>",
props={"sample":"<sha256>","entropy":<float>,"packer":"<name>"})
kg_add_edge(src=<sample>, dst=<observation>, kind="exhibits")
# After unpack, re-run triage on the dumped file
kg_add_node(kind="artifact", label="unpacked: <sha256>",
props={"original":"<orig-sha256>","unpacker":"<tool>"})
Severity (not a vuln, but a triage gate)
| Outcome | Implication for engagement |
|---|---|
| Automated unpack succeeded → static analysis viable | Normal triage path |
| Only partial unpack (multi-layer) | Use dynamic analysis as primary |
| VMProtect / Themida heavy | Likely commercial protection — escalate effort, schedule realistically |
| Cannot unpack | Black-box fuzz + dynamic only; document static-blind constraint |
Known exemplars
- Stuxnet: multi-layer packing including custom routines
- WannaCry: UPX + custom obfuscation
- Most commodity malware: UPX (because it's free + easy)
- Banking trojans: Themida or VMProtect common
- Cobalt Strike beacons: encrypted shellcode + reflective loader, "packer-like"