| 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
ent /tmp/sample
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}')
"
r2 -qc "iSj" /tmp/sample | jq '.[] | "\(.name): \(.entropy)"'
detect-it-easy /tmp/sample
diec /tmp/sample
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:
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
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
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 +
ScyllaHide plugin → bypass anti-debug
- Set hardware breakpoint on
VirtualProtect (Themida unpacks via this)
- When hit, walk back to find decrypted code regions
- Dump w/
Scylla after 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
Triton or angr
- 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)
memset followed by decrypted code being written
- Tail jump to OEP (often
JMP <reg> or RET after PUSHAD/POPAD)
- At suspected OEP, dump process w/
Scylla (PE) or r2 -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"