LAB-22Hard
Manual Unpacking Challenge
Go past packer identification: reason about what the compressed payload region implies, and what it takes to actually recover the unpacked code.
30 min6 prerequisites7 tools
Investigate
Hints
Hint 1
0x5200 - 0x200 is the region's length, not either endpoint's value.
Hint 2
Look at the Optional Header field that tells the loader how many bytes of virtual address space to reserve — packers keep this large even though the file itself is small.
Hint 3
You don't need to decompress the payload by hand — you just need to catch it after the packer decompresses it for you, at the moment it hands off to OEP.
Submit Findings
Solution
Manual Unpacking Challenge — Solution
- 20480 bytes (
0x5200 - 0x200) — the compressed payload region, genuinely high-entropy data (not a placeholder). On disk this stays a fraction of the size UPX0's virtual size reserves for it; UPX's whole premise is that a short decompression stub can inflate it back to full size at runtime. - SizeOfImage — the Optional Header field the loader uses to reserve virtual address space for the unpacked image. A packed PE's
SizeOfImage(here,0xC000— far more than the ~21KB UPX1 actually occupies on disk) is typically far larger than its on-disk file size — a strong static signal that something gets decompressed after load, before you even look at section entropy. - Dump (a memory/process dump) — set a breakpoint at the tail of the unpacking stub (right before it jumps to OEP), let it run until the payload is decompressed in memory, then dump that memory region to disk. The dumped image now has real, readable code and strings — the packer's obfuscation only ever protected the file on disk, never the process once it's actually running.
Prerequisites
- ar1-packers-and-unpacking
- m4-obfuscation-packing
- w1-pe-deep-dive
- PE structure (SizeOfImage, sections, entry point)
- x64dbg + Scylla dump workflow
- Hardware breakpoints (DR0-DR3)
Methodology
Investigation Methodology
1. Static Confirmation of Packing
| Signal | This Sample |
|---|---|
UPX! signature at 0x20 |
Present |
| Sections UPX0 (empty) / UPX1 (payload) | Present |
| High entropy in UPX1 | Present (compressed data) |
| SizeOfImage >> file size | Present |
| Entry point in UPX1 (stub) | Present |
2. UPX Section Layout
UPX0: Empty in file, R-X in memory (target for decompressed code)
UPX1: Compressed payload in file, R-X in memory (decompression stub + data)
- UPX0 has
SizeOfRawData= 0, butVirtualSize> 0 - At runtime, UPX0 fills with decompressed code
- Decompression writes into UPX0 (from UPX1)
3. Unpacking Workflow (Dynamic)
1. Load packed.exe in x64dbg
2. Set hardware breakpoint on WRITE to UPX0 region
(or breakpoint at tail of stub — the jump to OEP)
3. Run — decompression writes to UPX0
4. Break when write to UPX0 occurs (stub is filling .text)
5. Find the final jump to OEP (e.g., jmp [orig_entry])
6. Break at OEP — code is fully decompressed
7. Dump memory (Scylla / OllyDump / x64dbg)
8. Fix imports (IAT is rebuilt — use Scylla)
9. Analyze dumped binary — strings, imports now visible
4. Finding OEP
Common UPX stub patterns:
; ... decompression loop ...
popad ; restore registers
jmp original_entry_point ; jump to OEP
Look for:
popad/popfdthen unconditionaljmpjmp [address](indirect) = jump to OEPpush OEP/ret= jump via ret
5. Dump Techniques
| Tool | Method |
|---|---|
| Scylla | x64dbg plugin, dump + IAT fix |
| OllyDump | OllyDbg plugin |
| LordPE | Process dump |
| Process Hacker | Memory dump |
| procdump | Sysinternals |
| gdb (Linux) | dump memory |
6. Import Reconstruction
- IAT is rebuilt by the stub at runtime
- After dump, imports point to correct addresses
- Scylla scans for IAT, rebuilds import table
- Alternative: use the packed binary's original imports (if not obfuscated)
7. Post-Unpack Verification
- Strings now visible (C2, registry keys, mutexes)
- Imports full (network, injection, persistence APIs)
- CFG reconstructable
- Compare hash of unpacked vs packed
Common Pitfalls
- Dumping too early (before full decompression) — incomplete code
- Dumping too late (after stub cleans up) — missing imports
- Not fixing imports — dumped binary won't run/analyze correctly
- Missing the OEP — jumping to wrong address
- Breaking on READ instead of WRITE to UPX0
- Not setting breakpoint at correct point (before stub finishes)
Tools
- x64dbg + Scylla (dump + IAT fix)
- OllyDbg + OllyDump
- PE-bear / CFF Explorer (SizeOfImage, sections)
- Detect It Easy (packer ID)
- upx -d (for UPX only)
- LordPE (dump)
- Procmon (dynamic observation)
References
- Lesson AR1: Packers & Unpacking
- Lesson M4: Obfuscation & Packing
- UPX Documentation (decompression stub analysis)
- Scylla x64dbg plugin guide
- Manual unpacking tutorials (x64dbg)
Extensions & Challenges
- Unpack a real UPX sample with x64dbg + Scylla, compare to `upx -d` output
- Analyze the UPX decompression stub — how does it work?
- Research: How to unpack a modified UPX (corrupted headers)?
- Build a Ghidra script to auto-detect UPX stub patterns
- Practice: Unpack ASPack/Themida/VMProtect samples
- Write a YARA rule for UPX stub + compressed payload signature