_____ __ __ ____ __
/ ___// /_____ _/ /_/ __ \____ ______/ /__
\__ \/ __/ __ `/ __/ /_/ / __ `/ ___/ //_/
___/ / /_/ /_/ / /_/ ____/ /_/ / /__/ ,<
/____/\__/\__,_/\__/_/ \__,_/\___/_/|_|
---experimental x64 ELF packer---A proof-of-concept ELF binary packer for x86-64 static executables, written in C.
Compresses, encrypts, and wraps a target binary inside a self-contained packed ELF that transparently decompresses and executes the original at runtime. No shared libraries, no loader, no external dependencies.
┌─────────────────────────────────────────────────────────┐
│ targets.packed (ET_EXEC) │
│ │
│ 0x40800000 ┌──────────────────────┐ │
│ │ stub.bin │ <- custom loader │
│ │ (naked _start, │ │
│ │ aplib decompressor)│ │
│ ├──────────────────────┤ │
│ │ encrypted payload │ <- XOR ciphered │
│ │ (aplib-compressed │ │
│ │ original ELF) │ │
│ └──────────────────────┘ │
└─────────────────────────────────────────────────────────┘
At execution time the stub:
1. XOR-decrypts the payload in place
2. Decompresses it into a fresh anonymous mmap
3. Maps each PT_LOAD segment of the original ELF (MAP_FIXED)
4. Restores the kernel stack frame (rsp, rdx) untouched
5. Jumps to the original entry point
The packed binary is a valid ELF — file, readelf, and the kernel all accept it normally.
Important
I used XOR encryption for the sake of simplicity & debug but RC4 is well implemented and usable. => more efficient more some real testing.
statpack/
├── packer/
│ ├── packer.c # main packer: reads target, compresses, encrypts, builds output ELF
│ └── Makefile
├── stub/
│ ├── stub.c # runtime stub: decrypt -> decompress -> load -> jump
│ ├── stub.ld # linker script (loads stub at 0x40800000)
│ └── Makefile
├── compress/
│ ├── compress.c/h # aplib wrapper
│ └── aplib_elf64/ # aPLib compression library (asm, x86-64 ELF)
├── crypto/
│ ├── xor.h # XOR stream cipher (inline, key up to 128 bytes)
│ └── rc4.h # RC4 (implemented, swappable)
├── include/
│ ├── stub_header.h # StubHeader struct shared between packer and stub
│ ├── elf-common.h # ELF parsing helpers
│ ├── types.h # BYTE typedef
│ └── utils.h # formatting helpers
├── targets/
│ └── targets.c # sample static binary used for testing
└── bug-logs/
└── README.md # full post-mortem of the 6 bugs fixed during development
- Reads the target ELF and validates the header.
- Compresses it with aPLib — a high-ratio LZ77-family compressor.
- XOR-encrypts the compressed blob with the user-supplied key.
- Reads
stub.bin, scans it forSTUB_MAGIC(0xDEADBEEF) to locate theStubHeader, and patches it with metadata:
typedef struct {
uint32_t magic; // sentinel — packer finds this
uint32_t payload_size; // compressed + encrypted size
uint32_t orig_size; // decompressed size (for mmap)
uint32_t stub_size; // size of stub.bin (payload offset)
uint64_t orig_entry; // original e_entry
uint8_t key[256]; // XOR key
uint8_t key_len; // actual key length
} StubHeader;- Builds the output ELF with a single
PT_LOAD RWXsegment:[stub.bin | encrypted payload], loaded at0x40800000.
The stub is compiled as a flat binary (objcopy -O binary) linked at 0x40800000 with a custom linker script. _start is declared __attribute__((naked)) to prevent GCC from emitting any prologue, preserving the kernel's argc/argv/envp/auxv stack frame intact for the target binary.
_start (naked)
| save rsp -> rbx (callee-saved, survives the C call)
| align rsp for C ABI
| call stub_run <- all real work here
| restore rsp <- rbx
| xor edx, edx <- clear rtld_fini (required by glibc ABI)
+- jmp *rax <- original entry point
For each PT_LOAD segment of the decompressed ELF:
mmap(MAP_FIXED | MAP_ANONYMOUS)at the segment'sp_vaddr(page-aligned)- Copy
p_fileszbytes from the decompressed buffer - Zero-fill
p_memsz - p_fileszbytes (BSS) mprotectwith the segment's original flags (PF_R/W/X->PROT_READ/WRITE/EXEC)
All syscalls are made directly via inline assembly — no libc involved in the stub.
Requirements: gcc, make, objcopy (binutils).
# Build packer + stub + test target
cd packer
make
# Pack the test binary with a key
./build/packer -k mysecretkey ../targets/targets ./build/stub.bin
# Run it
./targets.packedOptions:
Usage: packer [OPTIONS] <elf_file> <stub_path>
-k <key> XOR encryption key (max 128 bytes)
-v Verbose — display ELF segment information
-h Help
Example output:
=== FILE INFORMATIONS ===
Filename : targets
Original size : 744.52 KB (762392 B)
Entry point : 0x4014f0
=== COMPRESSION INFORMATIONS ===
Original size : 744.52 KB
Compressed size : 315.48 KB (323048 B)
Ratio : 42.4%
=== ENCRYPTION ===
Algorithm : XOR
Key length : 7 bytes
=== OUTPUT ===
File : targets.packed
Stub size : 10467 bytes
Payload size : 323048 bytes
Total : 337611 bytes
Two ciphers are implemented and available in crypto/:
| Cipher | File | Status | Notes |
|---|---|---|---|
| XOR | xor.h |
Active | Inline, rolling key, up to 128 bytes |
| RC4 | rc4.h |
Implemented | Drop-in replacement |
Swapping to RC4 requires changing one call in packer.c and one in stub.c.
This is a proof of concept, not a production tool.
- Targets must be statically linked x86-64 ELF executables (
ET_EXEC). - PIE binaries (
ET_DYN) are not supported — no relocation handling. - The stub address (
0x40800000) must not conflict with the target's segments. - No support for
PT_INTERP,PT_TLS, orPT_GNU_RELRObeyondPT_LOAD. - XOR encryption is not cryptographically strong — use RC4 for better obfuscation.
Six non-trivial bugs were found and fixed during development, covering ELF memory permissions, self-overwriting stubs, GCC prologue corruption of the kernel stack frame, and glibc's rtld_fini ABI contract.
Full post-mortem with root-cause analysis and fixes: bug-logs/README.md
- aPLib — compression library used for the payload
- System V AMD64 ABI — process entry state, calling convention
- Linux
binfmt_elf.c— how the kernel loads ELF executables - glibc
sysdeps/x86_64/start.S—_startinternals andrtld_finiconvention