Automating UPX Unpacking: Scripts and Best Practices
Overview
Automating UPX unpacking speeds analysis and triage of many UPX-packed binaries. Typical automation detects UPX-packed files, runs an appropriate unpacking command, verifies success, logs results, and handles failures for manual follow-up.
Recommended tools
- upx (official) — packer/unpacker; supports –decompress (-d).
- Detect It Easy (diec) or binwalk — detection of packers.
- pefile (Python) — inspect PE headers to confirm unpacking.
- sigscan/clamscan — optional signature checks.
- Shell, Python, or PowerShell — scripting environments.
Example workflow (reasonable defaults)
- Scan files for UPX packing.
- Run upx -d or the appropriate decompression for the architecture.
- Verify unpacked binary (check PE entry point, import table).
- If verification fails, try alternative strategies (force unpack, run in sandbox to capture unpacked memory).
- Record results and move successes/failures to separate folders.
Bash script (Linux/macOS) — batch unpack and verify
#!/usr/bin/env bash
INPUT_DIR=”./samples”
OK_DIR=”./unpacked”
FAIL_DIR=”./failed”
mkdir -p “\(OK_DIR</span><span class="token" style="color: rgb(163, 21, 21);">"</span><span> </span><span class="token" style="color: rgb(163, 21, 21);">"</span><span class="token" style="color: rgb(54, 172, 170);">\)FAIL_DIR”
for f in “\(INPUT_DIR</span><span class="token" style="color: rgb(163, 21, 21);">"</span><span>/*</span><span class="token" style="color: rgb(57, 58, 52);">;</span><span> </span><span class="token" style="color: rgb(0, 0, 255);">do</span><span> </span><span></span><span class="token" style="color: rgb(57, 58, 52);">file</span><span> </span><span class="token" style="color: rgb(163, 21, 21);">"</span><span class="token" style="color: rgb(54, 172, 170);">\)f” | grep -qi “executable” || continue
# detect UPX
if strings “\(f</span><span class="token" style="color: rgb(163, 21, 21);">"</span><span> </span><span class="token" style="color: rgb(57, 58, 52);">|</span><span> </span><span class="token" style="color: rgb(57, 58, 52);">grep</span><span> -qi </span><span class="token" style="color: rgb(163, 21, 21);">"UPX"</span><span class="token" style="color: rgb(57, 58, 52);">;</span><span> </span><span class="token" style="color: rgb(0, 0, 255);">then</span><span> </span><span> </span><span class="token" style="color: rgb(57, 58, 52);">cp</span><span> </span><span class="token" style="color: rgb(163, 21, 21);">"</span><span class="token" style="color: rgb(54, 172, 170);">\)f” “\(f</span><span class="token" style="color: rgb(163, 21, 21);">.orig"</span><span> </span><span> </span><span class="token" style="color: rgb(0, 0, 255);">if</span><span> upx -d -o </span><span class="token" style="color: rgb(163, 21, 21);">"</span><span class="token" style="color: rgb(54, 172, 170);">\){OK_DIR}/\((</span><span class="token" style="color: rgb(57, 58, 52);">basename</span><span class="token" style="color: rgb(54, 172, 170);"> </span><span class="token" style="color: rgb(54, 172, 170);">"</span><span class="token" style="color: rgb(54, 172, 170);">\)f”)“ ”\(f</span><span class="token" style="color: rgb(163, 21, 21);">"</span><span> </span><span class="token file-descriptor" style="color: rgb(238, 153, 0); font-weight: bold;">2</span><span class="token" style="color: rgb(57, 58, 52);">></span><span>/tmp/upx.err</span><span class="token" style="color: rgb(57, 58, 52);">;</span><span> </span><span class="token" style="color: rgb(0, 0, 255);">then</span><span> </span><span> </span><span class="token" style="color: rgb(0, 128, 0); font-style: italic;"># quick verification: check for imports (PE)</span><span> </span><span> python3 - </span><span class="token" style="color: rgb(57, 58, 52);"><<</span><span class="token" style="color: rgb(163, 21, 21);">PY </span><span class="token" style="color: rgb(163, 21, 21);">import sys, pefile </span><span class="token" style="color: rgb(163, 21, 21);">try: </span><span class="token" style="color: rgb(163, 21, 21);"> p=pefile.PE(sys.argv[1]) </span><span class="token" style="color: rgb(163, 21, 21);"> print("OK") </span><span class="token" style="color: rgb(163, 21, 21);">except Exception as e: </span><span class="token" style="color: rgb(163, 21, 21);"> print("FAIL", e) </span><span class="token" style="color: rgb(163, 21, 21);">PY</span><span> </span><span> </span><span class="token" style="color: rgb(0, 0, 255);">if</span><span> </span><span class="token" style="color: rgb(57, 58, 52);">[</span><span> </span><span class="token" style="color: rgb(163, 21, 21);">"\)(python3 - “\(OK_DIR</span><span class="token" style="color: rgb(163, 21, 21);">/</span><span class="token" style="color: rgb(54, 172, 170);">\)(basename ”\(f</span><span class="token" style="color: rgb(54, 172, 170);">"</span><span class="token" style="color: rgb(54, 172, 170);">)</span><span class="token" style="color: rgb(163, 21, 21);">"</span><span> </span><span class="token file-descriptor" style="color: rgb(238, 153, 0); font-weight: bold;">2</span><span class="token" style="color: rgb(57, 58, 52);">></span><span>/dev/null </span><span class="token" style="color: rgb(57, 58, 52);">|</span><span> </span><span class="token" style="color: rgb(57, 58, 52);">head</span><span> -n1</span><span class="token" style="color: rgb(57, 58, 52);">)</span><span class="token" style="color: rgb(163, 21, 21);">" = "</span><span>OK</span><span class="token" style="color: rgb(163, 21, 21);">" ]; then </span><span class="token" style="color: rgb(163, 21, 21);"> echo "</span><span class="token" style="color: rgb(54, 172, 170);">\)(basename “\(f</span><span class="token" style="color: rgb(163, 21, 21);">"</span><span class="token" style="color: rgb(54, 172, 170);">)</span><span class="token builtin" style="color: rgb(43, 145, 175);">:</span><span> unpacked</span><span class="token" style="color: rgb(163, 21, 21);">" >> unpack.log </span><span class="token" style="color: rgb(163, 21, 21);"> else </span><span class="token" style="color: rgb(163, 21, 21);"> mv "</span><span class="token" style="color: rgb(54, 172, 170);">\){OK_DIR}/\((</span><span class="token" style="color: rgb(57, 58, 52);">basename</span><span class="token" style="color: rgb(54, 172, 170);"> </span><span class="token" style="color: rgb(163, 21, 21);">"</span><span class="token" style="color: rgb(163, 21, 21);">\)f”)” “\(FAIL_DIR</span><span>/</span><span class="token" style="color: rgb(163, 21, 21);">" </span><span class="token" style="color: rgb(163, 21, 21);"> echo "</span><span class="token" style="color: rgb(54, 172, 170);">\)(basename ”\(f</span><span class="token" style="color: rgb(163, 21, 21);">"</span><span class="token" style="color: rgb(54, 172, 170);">)</span><span class="token builtin" style="color: rgb(43, 145, 175);">:</span><span> unpacked but verification failed</span><span class="token" style="color: rgb(163, 21, 21);">" >> unpack.log </span><span class="token" style="color: rgb(163, 21, 21);"> fi </span><span class="token" style="color: rgb(163, 21, 21);"> else </span><span class="token" style="color: rgb(163, 21, 21);"> echo "</span><span class="token" style="color: rgb(54, 172, 170);">\)(basename “\(f</span><span class="token" style="color: rgb(163, 21, 21);">"</span><span class="token" style="color: rgb(54, 172, 170);">)</span><span class="token builtin" style="color: rgb(43, 145, 175);">:</span><span> upx -d failed</span><span class="token" style="color: rgb(163, 21, 21);">" >> unpack.log </span><span class="token" style="color: rgb(163, 21, 21);"> mv "</span><span class="token" style="color: rgb(54, 172, 170);">\)f” “$FAIL_DIR/” fi
fi
done
Python script (cross-platform) — detect + call UPX, log results
- Use subprocess to invoke upx.
- Use pefile to validate imports/entry point.
- Use concurrent.futures for parallelism.
Best practices
- Verify results programmatically (PE imports, entry point, entropy drop).
- Keep originals (store .orig copies) for forensic integrity.
- Rate-limit/parallelize to avoid resource exhaustion; use a small worker pool.
- Handle different architectures (32 vs 64-bit) and non-PE formats.
- Fallback strategies: try running in a sandboxed VM or emulator and dump memory after execution if static unpack fails.
- Logging & metrics: record filename, original hash, result, error output, timestamp.
- Security: run unpacking in isolated environment (VM, container) and avoid executing untrusted binaries on host.
- Tool versions: pin UPX and supporting libraries; UPX formats evolve.
- Avoid over-automation: flag ambiguous cases for manual review.
Common pitfalls
- UPX-packed stubs altered or non-standard UPX variants — upx -d may fail.
- False positives from strings matching “UPX”.
- High-entropy packed data may need additional unpacking passes.
- Packed loaders that decrypt at runtime — require dynamic unpacking.
Quick verification heuristics
- PE import table populated (pefile shows imports).
- Entry point located in “.text” and not a tiny stub.
- Entropy decrease after unpacking (use binwalk/entropy tools).
- File type still an executable.