Skip to main content

Overview

Binary Recombination is the process of preparing a Proone executable for a target host after successful Break and Enter (BNE). This enables decentralized propagation - each instance can infect hosts of different architectures without requiring binary distribution servers.

Executable Structure

A Proone executable consists of three parts:
┌────────────────────────────────┐
│ ELF Executable                 │  Platform-specific binary
│ (linux/armv4t, linux/sh4, etc) │
├────────────────────────────────┤
│ Data Vault (DVault)            │  Masked sensitive data
│ - Alignment padding            │  (shared across all archs)
│ - Appendix (8 bytes)           │
│   [dv_len:2][reserved:6]       │
├────────────────────────────────┤
│ Binary Archive (BA)            │  Compressed executables
│ - Alignment padding            │  for other architectures
│ - BA header & index            │
│ - Compressed ELF stream        │
└────────────────────────────────┘

Alignment

All sections are aligned to PRNE_BIN_ALIGNMENT boundaries for performance and parsing consistency.

Recombination Process

The diagram below illustrates recombination when a Linux/ARMv4T host infects a Linux/SH4 target:
                     linux armv4t host targetting linux sh4

 ┌──────────────┐                                              ┌──────────────┐
 │              │                         ┌──── decompress ──> │              │
 │    E L F     │                         ╵                    │    E L F     │
 │ linux armv4t │ ────────────────────────────┐                │  linux sh4   │
 │              │                         │   │                │              │
 ├──────────────┤                         │   │                ├──────────────┤
 │    DVault    │ ───────────────────────────╴│╶───── copy ──> │    DVault    │
 ├──────────────┤                         │   │                ├──────────────┤
 │   BA index   │ ───────────────────────────╴│╶─── update ──> │   BA index   │
 ├--------------┤                         │   │                ├--------------┤
 │ linux sh4    │ ────────────────────────┘   └── compress ──> │ linux armv4t │
 │ linux i686   │ ───────────────────────────── recompress ──> │ linux i686   │
 │ linux mips   │ ───────────────────────────── recompress ──> │ linux mips   │
 │ linux mpsl   │ ───────────────────────────── recompress ──> │ linux mpsl   │
 │ linux ppc    │ ───────────────────────────── recompress ──> │ linux ppc    │
 │ linux m68k   │ ───────────────────────────── recompress ──> │ linux m68k   │
 └──────────────┘                                              └──────────────┘

Process Steps

  1. Extract target ELF - Decompress the entire BA stream, extract the target architecture’s ELF
  2. Copy DVault - DVault is identical across all architectures, copy as-is
  3. Update BA index - Remove target arch entry, add host arch entry
  4. Rebuild BA stream - Recompress all executables into new BA
The entire compressed stream is decompressed and recompressed for each recombination. This maximizes compression efficiency but adds CPU overhead during infection.

Recombination API

Context Structure

typedef struct {
    prne_bin_rcb_read_f read_f;       // Read function pointer
    prne_bin_rcb_ctx_free_f ctx_free_f; // Cleanup function
    void *o_ctx;                       // Implementation-specific context
} prne_bin_rcb_ctx_t;

Starting Recombination

// From src/pack.c:466-556
prne_pack_rc_t prne_start_bin_rcb (
    prne_bin_rcb_ctx_t *ctx,
    const prne_bin_host_t target,      // Target OS/arch
    const prne_bin_host_t *self,       // Host OS/arch (NULL if unknown)
    const uint8_t *m_self,             // Host's complete executable
    const size_t self_len,             // Host executable size
    const size_t exec_len,             // Host ELF-only size
    const uint8_t *m_dvault,           // DVault data
    const size_t dvault_len,           // DVault size
    const prne_bin_archive_t *ba)      // Binary Archive
Return codes:
  • PRNE_PACK_RC_OK - Recombination initialized successfully
  • PRNE_PACK_RC_INVAL - Invalid parameters
  • PRNE_PACK_RC_NO_ARCH - Target architecture not in archive
  • PRNE_PACK_RC_ERRNO - Memory allocation failure
  • PRNE_PACK_RC_Z_ERR - zlib initialization error

Reading Recombined Binary

ssize_t prne_bin_rcb_read (
    prne_bin_rcb_ctx_t *ctx,
    uint8_t *buf,
    size_t len,
    prne_pack_rc_t *prc,
    int *err)
Reads the recombined executable in chunks, suitable for streaming to network socket.

Recombination Scenarios

Scenario 1: Same Architecture

When host and target architectures match:
// From src/pack.c:483-497
if (self != NULL && prne_eq_bin_host(self, &target)) {
    pack_rcb_pt_octx_t *ny_ctx =
        (pack_rcb_pt_octx_t*)prne_malloc(sizeof(pack_rcb_pt_octx_t), 1);

    ny_ctx->data = m_self;
    ny_ctx->rem = self_len;

    ctx->read_f = pack_rcb_ptread_f;  // Pass-through read
}
Optimization: Simply copy the host’s complete executable to target. No decompression or recombination needed.

Scenario 2: Different Architecture

When architectures differ:
  1. Locate target binary in BA index
  2. Initialize decompressor (inflate) for BA stream
  3. Initialize compressor (deflate) for new BA stream
  4. Stream transformation:
    • Read target ELF from decompressor
    • Write target ELF to output
    • Copy DVault with updated appendix
    • Build new BA index (replace target with host)
    • Recompress remaining binaries

Scenario 3: Architecture Compatibility Fallback

// From src/pack.c:558-614
prne_pack_rc_t prne_start_bin_rcb_compat (
    prne_bin_rcb_ctx_t *ctx,
    const prne_bin_host_t target,
    const prne_bin_host_t *self,
    const uint8_t *m_self,
    const size_t self_len,
    const size_t exec_len,
    const uint8_t *m_dvault,
    const size_t dvault_len,
    const prne_bin_archive_t *ba,
    prne_bin_host_t *actual)          // Output: actual arch used
If exact architecture not found, try compatible alternatives:
// Example: ARMv7 target
// Try in order:
const prne_arch_t F_ARM[] = {
    PRNE_ARCH_AARCH64,   // Prefer 64-bit if available
    PRNE_ARCH_ARMV7,     // Exact match
    PRNE_ARCH_ARMV4T,    // Backward compatible
    PRNE_ARCH_NONE
};

State Machine

The recombination reader uses a state machine with multiple read functions:

State: pack_rcb_eeread_f (Extract ELF)

Decompress and extract target ELF from BA stream.
// From src/pack.c:381-464
static ssize_t pack_rcb_eeread_f (
    prne_bin_rcb_ctx_t *ctx_p,
    uint8_t *buf,
    size_t len,
    prne_pack_rc_t *out_prc,
    int *out_err)
Actions:
  • Seek to target binary offset (discard bytes)
  • Extract target binary (output bytes)
  • Transition to pack_rcb_dvread_f when complete

State: pack_rcb_dvread_f (DVault + Index)

Write DVault and generate new BA index.
// From src/pack.c:287-379
static ssize_t pack_rcb_dvread_f (
    prne_bin_rcb_ctx_t *ctx_p,
    uint8_t *buf,
    size_t len,
    prne_pack_rc_t *out_prc,
    int *out_err)
Actions:
  • Output DVault bytes
  • Generate alignment padding
  • Build new BA header
  • Add host’s ELF to index (if self provided)
  • Add all other binaries except target to index
  • Transition to pack_rcb_rpread_f

State: pack_rcb_rpread_f (Recompress)

Recompress remaining binaries into new BA stream.
// From src/pack.c:193-285
static ssize_t pack_rcb_rpread_f (
    prne_bin_rcb_ctx_t *ctx_p,
    uint8_t *buf,
    size_t len,
    prne_pack_rc_t *out_prc,
    int *out_err)
Actions:
  • Inflate old BA stream
  • Deflate into new BA stream
  • Skip target binary (already extracted)
  • Include host binary (from memory)
  • Include all other binaries

State: pack_rcb_nullread_f (EOF)

Recombination complete, return EOF.

Memory and CPU Considerations

Memory Usage

Recombination requires:
  • Host executable in memory (~500KB - 2MB)
  • DVault in memory (~10KB - 100KB)
  • Decompression state (z_stream): ~300KB
  • Compression state (z_stream): ~300KB
  • Working buffers: ~8KB
Total: ~1-3 MB peak memory usage

CPU Usage

Compression at level 9 is CPU-intensive:
  • Deflate: ~5-10 MB/s on embedded ARM (200-500 MHz)
  • Inflate: ~10-20 MB/s on same hardware
Typical recombination time:
  • Same arch (pass-through): < 1 second
  • Different arch (recompress 10MB BA): 10-30 seconds on embedded device
Binary recombination is the most resource-intensive operation in Proone. BNE workers have lowest priority to prevent starving other subsystems during multiple concurrent infections.

Error Handling

Pack Result Codes

// From src/pack.c:691-704
const char *prne_pack_rc_tostr (const prne_pack_rc_t prc) {
    switch (prc) {
    case PRNE_PACK_RC_OK: return "ok";
    case PRNE_PACK_RC_EOF: return "eof";
    case PRNE_PACK_RC_INVAL: return "invalid";
    case PRNE_PACK_RC_FMT_ERR: return "format error";
    case PRNE_PACK_RC_ERRNO: return "errno";
    case PRNE_PACK_RC_Z_ERR: return "zlib error";
    case PRNE_PACK_RC_NO_ARCH: return "no arch";
    case PRNE_PACK_RC_UNIMPL_REV: return "unimpl rev";
    }
}

Common Failures

PRNE_PACK_RC_NO_ARCH: Target architecture not in Binary Archive
  • Resolution: Try compatibility fallback with prne_start_bin_rcb_compat()
  • If still fails, target cannot be infected
PRNE_PACK_RC_FMT_ERR: Binary Archive corrupted
  • Indicates memory corruption or incomplete BA
  • Fatal - instance may need restart
PRNE_PACK_RC_Z_ERR: zlib error during compression/decompression
  • Check return from BNE worker
  • May indicate memory pressure

Nybin Format

The complete appended binary data (DVault + BA) is called “nybin”:
// From src/pack.c:626-660
bool prne_index_nybin (
    const uint8_t *m_nybin,
    const size_t nybin_len,
    const uint8_t **m_dv,    // Output: DVault pointer
    size_t *dv_len,          // Output: DVault size
    const uint8_t **m_ba,    // Output: BA pointer
    size_t *ba_len)          // Output: BA size
Nybin structure:
Offset  Size  Field
0-1     2     DVault length (big-endian)
2-5     4     Magic: "nybin" identifier
6       1     Reserved
7       1     Revision (must be 0x00)
8+      N     DVault data
        pad   Alignment padding
        M     Binary Archive

Design Philosophy

From the spec (lines 217-232):
“Proone parses its own executable in order to locate the appended data during the initialisation process. Having located the data, Proone then proceeds to load the DVault and the binary archive (BA).”
“If the target host is a different platform and the BA contains the executable for that platform, a process is initiated for creation of the executable for the target host.”
This self-contained design enables:
  • No central infrastructure - No binary distribution servers to take down
  • Resilient propagation - Each instance is a complete infection source
  • Cross-platform infection - ARM bot can infect x86 targets and vice versa

Source Files

  • src/pack.h - Recombination API definitions
  • src/pack.c:466 - Recombination implementation
  • src/bne.c - Break and Enter worker (uses recombination)

Build docs developers (and LLMs) love