Packer Detection & Unpacking
Overview
Packers compress or encrypt executable code to reduce file size, obfuscate functionality, and evade signature-based detection. Packed malware must unpack itself at runtime before executing its real payload. Detecting packers and unpacking samples is often required before meaningful static analysis can proceed.
How Packers Work
Original binary
│
▼
┌──────────────────┐
│ Packer Tool │ Compresses/encrypts original code + data
└──────────────────┘
│
▼
┌──────────────────────────────────┐
│ Packed Binary │
│ ┌───────────────────────────┐ │
│ │ Unpacking stub │ │ ← Entry point runs this first
│ ├───────────────────────────┤ │
│ │ Compressed/encrypted │ │ ← Original code (packed)
│ │ original code + data │ │
│ └───────────────────────────┘ │
└──────────────────────────────────┘
│
▼ (at runtime)
┌──────────────────────────────────┐
│ 1. Stub allocates memory │
│ 2. Stub decompresses/decrypts │
│ 3. Stub fixes imports │
│ 4. Stub jumps to OEP │ (Original Entry Point)
└──────────────────────────────────┘
Packer Detection
Detect It Easy (DIE)
# Detect It Easy
# https://github.com/horsicq/Detect-It-Easy
# Standard scan
diec sample.exe
# Deep scan (more thorough detection)
diec -d sample.exe
# Heuristic scan (detect unknown packers)
diec -u sample.exe
# Aggressive scan
diec -g sample.exe
# Show entropy (high entropy = likely packed)
diec -e sample.exe
# JSON output for scripting
diec -j sample.exe
# Recursive scan (archives)
diec -r archive.zip
DIE identifies specific packers (UPX, Themida, VMProtect, ASPack, etc.), compilers (MSVC, GCC, Delphi, Go), and linkers.
Entropy Analysis
Entropy measures the randomness of data. Compressed or encrypted data has high entropy (close to 8.0 for byte-level), while normal code and data typically range between 4.0 and 6.5.
# radare2
# https://github.com/radareorg/radare2
# Show section layout (size, virtual size, permissions, type, name)
rabin2 -S sample.exe
# rabin2 -S does not display per-section entropy; use the pefile Python snippet below
# pefile
# https://github.com/erocarrera/pefile
import pefile
import math
pe = pefile.PE('sample.exe')
for section in pe.sections:
name = section.Name.decode().rstrip('\x00')
entropy = section.get_entropy()
status = "SUSPICIOUS" if entropy > 7.0 else "normal"
print(f"{name:10s} entropy: {entropy:.2f} [{status}]")
| Entropy Range | Interpretation |
|---|---|
| 0.0 - 1.0 | Very uniform data (null padding, repeated patterns) |
| 4.0 - 6.5 | Normal compiled code and data |
| 6.5 - 7.0 | Possibly compressed or encoded |
| 7.0 - 8.0 | Almost certainly compressed or encrypted (packed) |
Section-Based Indicators
# pefile
# https://github.com/erocarrera/pefile
import pefile
pe = pefile.PE('sample.exe')
for section in pe.sections:
name = section.Name.decode().rstrip('\x00')
vsize = section.Misc_VirtualSize
rsize = section.SizeOfRawData
entropy = section.get_entropy()
chars = section.Characteristics
flags = []
if entropy > 7.0:
flags.append("HIGH_ENTROPY")
if rsize == 0 and vsize > 0:
flags.append("EMPTY_RAW")
if vsize > rsize * 5 and rsize > 0:
flags.append("VSIZE>>RSIZE")
if chars & 0x80000000 and chars & 0x20000000:
flags.append("WRITE+EXEC")
print(f"{name:10s} VSize: {vsize:8d} RSize: {rsize:8d} "
f"Entropy: {entropy:.2f} {' '.join(flags)}")
| Indicator | Meaning |
|---|---|
| High entropy section | Compressed or encrypted content |
| Raw size = 0, virtual size > 0 | Section populated at runtime (unpacking buffer) |
| Virtual size >> raw size | Section will expand at runtime |
| Write + execute section | Self-modifying code (unpacking stub) |
| Very few imports | Imports resolved dynamically after unpacking |
| Entry point in non-.text section | Entry in packer stub section |
Common Packer Section Names
| Section Name | Packer |
|---|---|
| UPX0, UPX1, UPX2 | UPX |
| .themida | Themida |
| .vmp0, .vmp1 | VMProtect |
| .aspack, .adata | ASPack |
| .petite | Petite |
| .nsp0, .nsp1 | NSPack |
| .enigma1, .enigma2 | Enigma Protector |
| .MPRESS1, .MPRESS2 | MPRESS |
Import Table Indicators
# pefile
# https://github.com/erocarrera/pefile
import pefile
pe = pefile.PE('sample.exe')
total_imports = 0
dll_count = 0
has_loadlib = False
has_getproc = False
if hasattr(pe, 'DIRECTORY_ENTRY_IMPORT'):
for entry in pe.DIRECTORY_ENTRY_IMPORT:
dll_count += 1
for imp in entry.imports:
total_imports += 1
if imp.name:
name = imp.name.decode()
if 'LoadLibrary' in name:
has_loadlib = True
if 'GetProcAddress' in name:
has_getproc = True
print(f"DLLs: {dll_count}, Total imports: {total_imports}")
print(f"LoadLibrary: {has_loadlib}, GetProcAddress: {has_getproc}")
if total_imports < 10 and has_loadlib and has_getproc:
print("WARNING: Likely packed — minimal imports with dynamic resolution")
Unpacking Techniques
UPX Unpacking
UPX is the most common packer. It has a built-in decompression flag.
# UPX
# https://github.com/upx/upx
# Check if file is UPX-packed
upx -t sample.exe
# List compression info
upx -l sample.exe
# Decompress (unpack)
upx -d sample.exe
# Decompress to a different file
upx -d sample.exe -o unpacked.exe
# Force decompression (for modified UPX headers)
upx -d -f sample.exe -o unpacked.exe
Some malware modifies UPX headers (e.g., changes section names from UPX0/UPX1)
to prevent upx -d from working. In that case, restore the original section
names or use dynamic unpacking.
Manual Unpacking (General Approach)
When automated tools fail, manual unpacking with a debugger follows this general process:
1. Load packed binary in debugger
2. Set breakpoints on:
- VirtualAlloc / VirtualProtect (memory allocation for unpacked code)
- Tail jumps (JMP to distant address = jump to OEP)
3. Run until unpacking stub finishes
4. Identify the Original Entry Point (OEP)
5. Dump the process memory
6. Fix the import table (IAT)
Dynamic Unpacking with radare2
# radare2
# https://github.com/radareorg/radare2
# Open in debug mode
r2 -d packed_sample.exe
# Analyze
aa
# Set breakpoint on VirtualProtect
db sym.imp.kernel32.dll_VirtualProtect
# Continue execution
dc
# After break, examine registers and step
dr
ds
# When OEP is reached, dump memory
# Seek to image base, then write memory to file
s 0x400000
wtf unpacked_dump.bin 0x10000
Dumping with GDB (Linux)
# GDB
# https://www.sourceware.org/gdb/
# Debug the packed binary
gdb ./packed_sample
# Set breakpoint on mprotect (used to make unpacked code executable)
b mprotect
# Run
r
# After breaking, examine memory maps
info proc mappings
# When OEP is reached, dump memory
dump binary memory unpacked.bin 0x400000 0x410000
Fixing Imports After Dumping
After dumping an unpacked binary from memory, the import table (IAT) usually needs to be reconstructed because it contains runtime addresses rather than the original import descriptors. Tools for IAT reconstruction include:
- Scylla (Windows) — GUI tool that attaches to a running process, locates the IAT, and rebuilds import tables
- Impscan (Volatility plugin) — reconstructs imports from memory dumps
- PE-bear (Windows) — PE editor with import table repair capabilities
Identifying Unknown Packers
When DIE does not identify the packer:
- Check section names — custom or unusual names suggest a packer
- Check entropy — high entropy across multiple sections indicates packing
- Check imports — very few imports with
LoadLibrary/GetProcAddress - Check entry point — entry in a non-.text section
- Look for unpacking patterns in the entry point code:
- Loops with XOR operations (decryption)
- Calls to
VirtualAlloc/VirtualProtect - Large
JMPto a distant address (jump to OEP)
# radare2
# https://github.com/radareorg/radare2
# Disassemble entry point to look for unpacking patterns
r2 -A sample.exe -c 'pdf @ entry0' -q
Common Packers and Protectors
| Packer | Type | Notes |
|---|---|---|
| UPX | Compressor | Most common, easily unpacked with upx -d |
| MPRESS | Compressor | Similar to UPX, less common |
| ASPack | Compressor | Older packer, still seen in legacy malware |
| Themida | Protector | Anti-debug, anti-VM, code virtualization |
| VMProtect | Protector | Converts code to virtual machine bytecode |
| Enigma Protector | Protector | Licensing + anti-analysis |
| .NET Reactor | .NET Protector | Obfuscates .NET assemblies |
| ConfuserEx | .NET Obfuscator | Open-source .NET obfuscation |