dUP2 Project Unpacker and Decompiler
UNdUP2 is a command-line tool developed in Python that allows you to unpack and decompile executables generated with dUP2 (dUP - Patcher Creator 2), a popular utility for creating binary patches.
Executables created with dUP2 package an encrypted internal DLL containing patching modules (byte search and replace, among others). UNdUP2 automates the process of:
- Extracting the encrypted DLL from the executable's RCDATA resources.
- Decrypting the DLL using the rotative XOR algorithm with the key
0xDEADBEEF. - Reconstructing the original
.dUP2project file, allowing analysis or modification of patching modules.
- Forensic analysis of existing binary patches.
- Reverse engineering of patchers to understand what modifications they perform.
- Recovery of dUP2 projects when the original source file has been lost.
Author: Bakasura
License: Unlicense (Public Domain)
UNdUP2 implements a monolithic CLI script architecture with well-defined auxiliary functions. The design is intentionally minimalist and straightforward, optimized for a specific task without unnecessary abstractions.
┌─────────────────┐
│ dUP2 │
│ Executable │
│ (.exe) │
└────────┬────────┘
│
▼
┌─────────────────┐
│ lief.parse() │ ← PE format parsing
└────────┬────────┘
│
▼
┌─────────────────┐
│ RCDATA │ ← Recursive navigation of RCDATA resources
│ Extraction │
└────────┬────────┘
│
▼
┌─────────────────┐
│ decrypt_bytes │ ← Rotative XOR decryption (key: 0xDEADBEEF)
└────────┬────────┘
│
├──────────────────────┐
▼ ▼
┌─────────────────┐ ┌─────────────────┐
│ .dumped.dll │ │ make_dup2_file │
│ (extracted DLL)│ │ (reconstruction)│
└─────────────────┘ └────────┬────────┘
│
▼
┌─────────────────┐
│ .dUP2 File │
│ (project) │
└─────────────────┘
| Component | Responsibility |
|---|---|
main() |
CLI entry point, main flow orchestration |
resource_data_bytes() |
Recursive navigation of PE resource tree to extract data |
decrypt_bytes() |
Implementation of rotative XOR decryption algorithm |
make_dup2_file() |
Reconstruction of dUP2 project format |
align_16() |
Data alignment to 16 bytes (format requirement) |
padded_4_bytes_length() |
4-byte padding calculation |
padded_16_bytes_length() |
16-byte padding calculation |
- Simple monolith: The limited scope of the problem (unpacking a specific format) doesn't justify additional abstraction layers.
- Pure functions: Auxiliary functions (
decrypt_bytes,align_16) are deterministic and side-effect free, facilitating testing. - Single dependency: LIEF handles all PE parsing complexity, allowing the code to focus on business logic.
- LIEF: Library for parsing and manipulating executable formats (PE, ELF, Mach-O).
The project follows a procedural programming approach with static typing, prioritizing:
- Clarity over brevity.
- Small functions with single responsibility.
- Complete type hints for documentation and static validation.
- Indentation: 4 spaces (Python standard).
- Quotes: Single (
') for strings, configured in Ruff. - Imports: Sorted by isort (stdlib → third-party → local), with two blank lines after the import block.
- Line limit: Implicit in Ruff configuration (88 characters by default).
The project uses explicit exceptions with descriptive messages:
if binary is None:
raise Exception(f'{exe_path.name} is not a valid PE file')
if not binary.has_resources:
raise Exception(f'{exe_path.name} not have resources')Validations are performed at critical points in the flow, failing early with contextual messages that include the processed file name.
- Guard clauses: Validations at the beginning of functions that return early.
- Recursion for tree navigation:
resource_data_bytes()recursively navigates the PE resource structure. - Bytearray for incremental construction: The
.dUP2file is built by progressively adding bytes.
The code is organized in implicit sections:
- Utility functions (padding, alignment).
- Decryption algorithm.
- Resource extraction.
- Entry point and main logic.
- Project file generation.
| Type | Convention | Examples |
|---|---|---|
| Local variables | snake_case |
dll_bytes, exe_path, dup2_content |
| Constants | Implicit in code | 0xDEADBEEF (encryption key) |
| Counters | Descriptive | modules, modules_search_and_replace |
| Convention | Examples |
|---|---|
Descriptive snake_case |
decrypt_bytes(), resource_data_bytes() |
make_ prefix for constructors |
make_dup2_file() |
padded_ prefix for padding calculations |
padded_4_bytes_length() |
align_ prefix for alignment operations |
align_16() |
No custom classes are defined. Type hints from libraries are used:
res_manager: lief.PE.ResourcesManager
node: ResourceNode| Pattern | Example |
|---|---|
| Main code | main.py |
| Tests | tests/test_*.py |
| Configuration | pyproject.toml |
| Type | Convention |
|---|---|
| Extracted DLL | {original_name}.dumped.dll |
| Reconstructed project | {original_name}.dUP2 |
| Component | Version | Notes |
|---|---|---|
| Python | >= 3.14 |
Requires modern language features |
| Package manager | uv |
Modern dependency management tool |
| Package | Version | Purpose |
|---|---|---|
| LIEF | >= 1.0.0 |
PE executable format parsing and manipulation |
| Package | Version | Purpose |
|---|---|---|
| pytest | >= 9.1.1 |
Testing framework |
| Ruff | (implicit) | Linter and formatter |
[build-system]
requires = ["uv_build>=0.12.13,<0.13.0"]
build-backend = "uv_build"UNdUP2/
├── .git/ # Git repository
├── .gitignore # Git exclusions (Python template)
├── .python-version # Python version for pyenv/uv (3.14)
├── .venv/ # Virtual environment
├── LICENSE # Unlicense license (public domain)
├── README.md # This file
├── main.py # Main source code
├── pyproject.toml # Project configuration and dependencies
├── tests/ # Tests directory
│ └── test_resource_data.py # Unit tests
└── uv.lock # Dependency lock file (uv)
| File | Description |
|---|---|
main.py |
Main script with all unpacking logic |
pyproject.toml |
Project metadata, dependencies, and Ruff configuration |
uv.lock |
Exact dependency versions for reproducibility |
tests/test_resource_data.py |
Unit tests with LIEF structure mocks |
The project is a local CLI tool that doesn't distinguish between environments. It runs directly on the user's machine.
There are no environment variables or external configuration files. All parameters are passed via CLI.
| Parameter | Type | Description | Required |
|---|---|---|---|
file_path |
str |
Path to the dUP2 executable to process | Yes |
[tool.ruff.lint]
select = [
"E", # pycodestyle (style)
"W", # pycodestyle (warnings)
"F", # Pyflakes (logic errors)
"I", # isort (import ordering)
"B", # flake8-bugbear (common errors)
"C4", # flake8-comprehensions
"UP", # pyupgrade (modern syntax)
"N", # pep8-naming
"SIM", # flake8-simplify
"ANN", # flake8-annotations (types)
]The project does not have a configured CI/CD pipeline. It's a local tool distributed as source code.
-
GitHub Actions for:
- Test execution with
pytest. - Linting validation with
ruff check. - Type verification (optional, with
mypy).
- Test execution with
-
PyPI Publishing (if distribution is desired):
- Build with
uv build. - Upload with
uv publishortwine.
- Build with
- The project does not handle credentials or secrets.
- It does not make network connections.
- It operates exclusively on local files.
| Aspect | Status |
|---|---|
| User input | Validates that the file is a valid PE |
| File writing | Writes to the same directory as the input file |
| Code execution | Does not execute code from the processed file |
- Malicious files: LIEF could be vulnerable to malformed PE files designed to exploit parser bugs. Keep LIEF updated.
- File overwriting: Output files (
.dumped.dll,.dUP2) overwrite without confirmation if they already exist.
- Linux, macOS, or Windows with Python 3.14+.
| Tool | Minimum Version | Installation |
|---|---|---|
| Python | 3.14 | python.org |
| uv | Latest | pip install uv or docs.astral.sh/uv |
- Basic familiarity with the command line.
- Understanding of PE (Portable Executable) format is helpful but not required.
git clone <repository-url>
cd UNdUP2# Create virtual environment and install dependencies
uv syncOr alternatively with pip:
# Create virtual environment
python -m venv .venv
source .venv/bin/activate # Linux/macOS
# .venv\Scripts\activate # Windows
# Install dependencies
pip install lief>=1.0.0# With uv
uv run python main.py /path/to/patcher.exe
# With activated virtual environment
python main.py /path/to/patcher.exe$ uv run python main.py ~/Downloads/MyPatcher.exe
UNdUP2: dUP2 Unpacker / Decompiler | Bakasura - 2024
Unpacked!After successful execution, two files are generated in the same directory as the input executable:
| File | Description |
|---|---|
MyPatcher.dumped.dll |
Decrypted DLL extracted from the executable |
MyPatcher.dUP2 |
Reconstructed dUP2 project (importable in dUP2) |
# With uv
uv run pytest
# With pytest directly
pytest tests/# Check style
uv run ruff check .
# Format code
uv run ruff format .dUP2 uses a rotative XOR encryption with the fixed key 0xDEADBEEF. The algorithm:
- Iterates over each byte of the encrypted content.
- Applies XOR with the least significant byte of the key.
- Rotates the key 1 bit to the right (ROR).
- XORs the key with the original byte.
- Adds the remaining counter to the key.
This encryption is symmetric: the same algorithm works for both encryption and decryption.
┌────────────────────────────────────────┐
│ Header (16 bytes) │
│ [0-3] Number of modules (uint32 LE) │
│ [4-7] Comments offset │
│ [8-15] Reserved (zeros) │
├────────────────────────────────────────┤
│ Modules │
│ For each module: │
│ [4 bytes] Module length │
│ [N bytes] Module data │
├────────────────────────────────────────┤
│ Padding (16-byte alignment) │
├────────────────────────────────────────┤
│ Comments │
│ For each type 4 module: │
│ [4 bytes] Length (0x30) │
│ [48 bytes] Empty comment │
└────────────────────────────────────────┘
Documentation automatically generated based on source code analysis.