Cheat sheet — Unpacking & Obfuscation¶
Companion to Module 09 — Unpacking & Obfuscation · CC BY 4.0 — print it, pin it, share it.
Last reviewed: 2026-07
Only handle live malware in an isolated, disposable analysis VM with no bridged network. A static
upx -ddoesn't run the sample — but manual/dynamic unpacking (below) does.
Spot the packer — entropy + section names¶
diec sample # Detect It Easy: names UPX / MPRESS / Themida
python3 - <<'PY' # per-section entropy — the packer signal
import pefile
pe = pefile.PE("sample.exe")
for s in pe.sections:
print(s.Name.rstrip(b"\x00").decode(errors="replace"), f"{s.get_entropy():.2f}")
PY
- Benign
.textruns ~5–6 bits/byte; UPX pushes it past 7.5. That jump is the first signal. UPX also renames sections toUPX0/UPX1— trivial static detection.
UPX — the clean static unpack¶
upx -d sample -o sample.unpacked # decompress (-d); write to a new file
upx -l sample # list: is it UPX, and what compression level
upx -t sample # test integrity of a packed file
# Verify by hash equivalence — evidence, not a sanity check:
sha256sum original sample.unpacked # a UPX round-trip should restore identical bytes
- Workflow: identify packer → apply static unpacker if one exists → verify by hash. Matching a known-clean corpus sample means the payload isn't novel; matching nothing means a new artifact worth full analysis. The hash goes in your YARA
meta:and your case notes.
When upx -d fails — dump from memory¶
Most real packers have NO clean static unpacker. Next move:
let the sample unpack ITSELF in a controlled dynamic run, then dump the
reconstructed image from memory at the OEP (original entry point).
→ this is Module 10 territory; don't assume every sample yields to one command.
Decode obfuscated strings from a loader¶
The scripting-world cousin of packing: encoded blob → short decode loop → use (as a URL / registry key / filename). Recognise that shape and extract the IOC without reversing the rest.
# base64 layer
echo 'aHR0cDovL2V2aWwuZXhhbXBsZS9wYXk=' | base64 -d ; echo
# single-byte XOR — recover with the key you read from the loop:
python3 -c 'b=bytes.fromhex("2f2b3e..."); print(bytes(x^0x5a for x in b).decode())'
# nested (base64 of XOR of base64) — peel one layer, re-inspect, repeat
strings sample.exe | grep -iE 'frombase64|xor|-enc|char\[' # find the decode idiom
YARA — detect the packer¶
rule upx_packed {
meta:
attck = "T1027.002" // software packing
strings:
$u0 = "UPX0"
$u1 = "UPX1"
$sig = "UPX!" // stub magic
condition:
uint16(0) == 0x5A4D and 2 of them
}
yara rule.yar packed_sample # must fire on the PACKED binary
yara rule.yar sample.unpacked # must stay QUIET on the unpacked one
Gotchas worth remembering¶
- Hand a packed binary to a decompiler and you see the stub, not the payload — a few hundred instructions that decompress the real code and jump to it. Nothing meaningful until you defeat the packer.
- A packer changes on-disk bytes only; runtime behaviour is unchanged — that's why the entropy jump is on disk and the OEP still runs the same code.
- Hash equivalence is evidence, not a sanity check. Hash before packing and after unpacking; the match (or miss) is a finding that goes in the report.
> 7.0entropy can be a legit installer or compressed asset — check.textspecifically, and read it against the section that should hold code.- AI decodes a XOR/base64 blob fast but garbage-in/garbage-out — wrong key width or offset returns confident nonsense; sanity-check the output is a valid URL/filename before it becomes an IOC.
Comments
Sign in with GitHub to comment. Choose the type: Feedback (errors or suggestions on this page) · Hints (help for fellow learners — no spoilers) · General (anything else).