Skip to content

Yekuuun/statpack

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

9 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

          
                             _____ __        __  ____             __  
                            / ___// /_____ _/ /_/ __ \____ ______/ /__
                            \__ \/ __/ __ `/ __/ /_/ / __ `/ ___/ //_/
                           ___/ / /_/ /_/ / /_/ ____/ /_/ / /__/ ,<   
                          /____/\__/\__,_/\__/_/    \__,_/\___/_/|_|  
                          
                               ---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.


How it works

┌─────────────────────────────────────────────────────────┐
│                    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.


Project structure

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

Pipeline detail

1. Packer (packer/packer.c)

  1. Reads the target ELF and validates the header.
  2. Compresses it with aPLib — a high-ratio LZ77-family compressor.
  3. XOR-encrypts the compressed blob with the user-supplied key.
  4. Reads stub.bin, scans it for STUB_MAGIC (0xDEADBEEF) to locate the StubHeader, 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;
  1. Builds the output ELF with a single PT_LOAD RWX segment: [stub.bin | encrypted payload], loaded at 0x40800000.

2. Stub (stub/stub.c)

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

3. ELF loader (load_elf in stub.c)

For each PT_LOAD segment of the decompressed ELF:

  • mmap(MAP_FIXED | MAP_ANONYMOUS) at the segment's p_vaddr (page-aligned)
  • Copy p_filesz bytes from the decompressed buffer
  • Zero-fill p_memsz - p_filesz bytes (BSS)
  • mprotect with 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.


Build

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.packed

Options:

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

Crypto

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.


Limitations

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, or PT_GNU_RELRO beyond PT_LOAD.
  • XOR encryption is not cryptographically strong — use RC4 for better obfuscation.

Bug log

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


References

About

experimental static ELF binary packer for x64. Compresses and encrypts executables into a self-extracting stub.

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors