# How LiteBox Syscall Rewriting Works: Static ELF Binary Transformation Explained

> Explore how LiteBox syscall rewriting statically transforms ELF binaries. Learn how it redirects syscalls to user-space callbacks without kernel changes. Discover this powerful technique.

- Repository: [Microsoft/litebox](https://github.com/microsoft/litebox)
- Tags: internals
- Published: 2026-02-16

---

**LiteBox syscall rewriting works by statically patching every `syscall` instruction in an ELF binary to jump to a trampoline that redirects execution to a user-space callback, enabling interception without kernel modifications.**

LiteBox syscall rewriting is a static binary instrumentation technique implemented in the `microsoft/litebox` repository that transforms unmodified Linux ELF executables at build time. The rewriter replaces privileged instructions with jumps to generated trampoline code, allowing a user-space loader to intercept system calls at runtime without dynamic instrumentation overhead.

## The Three Phases of LiteBox Syscall Rewriting

The rewriter operates in three distinct phases, implemented primarily in [`litebox_syscall_rewriter/src/lib.rs`](https://github.com/microsoft/litebox/blob/main/litebox_syscall_rewriter/src/lib.rs).

### Phase 1: Analyzing the Input ELF

The process begins by parsing the input binary using `object::File::parse` to extract all executable `.text` sections (`text_sections`). The analyzer records every possible **control-transfer target**—including branch, call, and jump destinations—to ensure the rewriter never overwrites an instruction that other code may jump to.

This safety analysis prevents corrupting the binary's control flow graph when later patching instructions.

### Phase 2: Locating and Creating the Trampoline

The rewriter reserves a page-aligned memory region immediately after the last `PT_LOAD` segment using `find_addr_for_trampoline_code`. This region contains the **trampoline code** that will handle intercepted syscalls.

The trampoline starts with an 8-byte placeholder for the real entry address, which remains unfilled during the static rewrite. This placeholder is later populated by the loader at runtime with the actual address of `syscall_callback`, enabling the same rewritten binary to work across different platforms and address spaces.

### Phase 3: Patching Syscall Instructions

For each `.text` section, the rewriter disassembles bytes using the **iced-x86** library. When it encounters a `syscall` instruction (or `int 0x80` / `call gs:0x10` on 32-bit x86), it determines the **maximum safe region** that can be overwritten—up to 5 bytes—while avoiding any recorded control-transfer targets.

The rewriter then emits the following sequence into the trampoline:

1. **Store the original return address** in `RCX` (x86-64) or `EAX` (x86) using `LEA`
2. **Jump to the global `syscall_callback`** via an indirect jump through the placeholder filled by the loader
3. **Return path** that jumps back to the original code after the callback completes

Finally, the rewriter replaces the original syscall bytes with a 5-byte `jmp rel32` instruction pointing to the newly generated trampoline code.

## Why LiteBox Uses a Trampoline Architecture

The trampoline design provides three critical advantages for LiteBox syscall rewriting:

* **Zero-overhead entry:** The jump to the trampoline is a single 5-byte relative `jmp`, so the original instruction stream incurs virtually no extra latency compared to dynamic instrumentation.
* **Page-aligned isolation:** The trampoline lives on its own page, making it easy for the loader to apply the correct protection (read + execute) after filling the entry point.
* **Runtime flexibility:** The placeholder at the start of the trampoline is overwritten by the loader with the actual address of `syscall_callback` for the current process, allowing the same rewritten binary to be used on many platforms without recompilation.

## Runtime Integration: How the Loader Activates the Trampoline

When the rewritten binary executes, the LiteBox ELF loader (`litebox_common_linux::loader`) detects the trampoline by reading the magic bytes `"LITEBOX0"` at the end of the file. This validation occurs in `parse_trampoline` ([`litebox_common_linux/src/loader.rs`](https://github.com/microsoft/litebox/blob/main/litebox_common_linux/src/loader.rs)).

The loader then:
1. Validates the trampoline header and ensures page alignment
2. Maps the trampoline page into memory using `load_trampoline` ([`litebox_common_linux/src/loader.rs`](https://github.com/microsoft/litebox/blob/main/litebox_common_linux/src/loader.rs))
3. Writes the **real entry point** (the address of `syscall_callback` from `litebox_platform_linux_userland::syscall_intercept::SYSTRAP_ENTRY`) into the first 8 bytes of the trampoline
4. Protects the page as read-execute only

This is where the static rewrite meets the dynamic runtime, transforming the placeholder jumps into active syscall interceptions.

## Practical Examples of LiteBox Syscall Rewriting

### Rewriting a Binary with the CLI

The `litebox_syscall_rewriter` crate provides a command-line interface defined in [`litebox_syscall_rewriter/src/main.rs`](https://github.com/microsoft/litebox/blob/main/litebox_syscall_rewriter/src/main.rs):

```bash

# Rewrite a native Linux binary (e.g. /usr/bin/ls)

litebox_syscall_rewriter \
    /usr/bin/ls \
    --output /tmp/ls.hooked \
    --trampoline-addr 0x0   # use placeholder; loader will fill it later

```

The resulting file (`ls.hooked`) contains the trampoline code and the `TRAMPOLINE_MAGIC` marker, ready for the LiteBox loader.

### Loading the Rewritten Binary in Rust

The loader implementation in [`litebox_common_linux/src/loader.rs`](https://github.com/microsoft/litebox/blob/main/litebox_common_linux/src/loader.rs) handles runtime activation:

```rust
use litebox_common_linux::loader::{ElfParsedFile, ElfParseError};

fn load_and_run(path: &std::path::Path) -> Result<(), ElfParseError<std::io::Error>> {
    // Open the file and parse ELF headers
    let mut file = std::fs::File::open(path)?;
    let mut parsed = ElfParsedFile::parse(&mut file)?;

    // Tell the loader where the syscall entry point lives (provided by the shim)
    let syscall_entry = litebox_platform_linux_userland::syscall_intercept::SYSTRAP_ENTRY;
    parsed.parse_trampoline(&mut file, syscall_entry)?;

    // Map the binary into memory (mapper / mem are platform‑specific)
    // let mut mapper = ...;
    // let mut mem = ...;
    // let mapping_info = parsed.load(&mut mapper, &mut mem)?;
    // unsafe { std::mem::transmute::<usize, fn()>(mapping_info.entry_point)() };

    Ok(())
}

```

### Examining the Generated Trampoline Code

The trampoline generated by `hook_syscalls_in_section` in [`litebox_syscall_rewriter/src/lib.rs`](https://github.com/microsoft/litebox/blob/main/litebox_syscall_rewriter/src/lib.rs) follows this x86-64 structure:

```text
00 00 00 00 00 00 00 00   ; placeholder for entry point (filled by loader)
48 8D 0D <disp32>          ; LEA RCX, [RIP+disp32]  ← store return address
FF 25 <disp32>             ; JMP [RIP+disp32]        ← indirect jump to entry point

```

The first 8 bytes are overwritten at load time with the actual `syscall_callback` address. The `LEA RCX` instruction satisfies the calling convention expected by the platform shim, storing the original return address for the callback to use.

## Key Source Files in the LiteBox Repository

Understanding LiteBox syscall rewriting requires familiarity with these specific files:

- **[`litebox_syscall_rewriter/src/lib.rs`](https://github.com/microsoft/litebox/blob/main/litebox_syscall_rewriter/src/lib.rs)** – Core rewrite engine that parses ELF files, locates syscalls using iced-x86, constructs trampolines, and patches binary code.

- **[`litebox_syscall_rewriter/src/main.rs`](https://github.com/microsoft/litebox/blob/main/litebox_syscall_rewriter/src/main.rs)** – CLI wrapper using `clap` that reads input binaries and outputs hooked versions with embedded trampolines.

- **[`litebox_common_linux/src/loader.rs`](https://github.com/microsoft/litebox/blob/main/litebox_common_linux/src/loader.rs)** – ELF loader that detects the `TRAMPOLINE_MAGIC` marker, validates trampoline headers, and maps the code into memory with proper permissions.

- **[`litebox_platform_linux_userland/src/syscall_intercept/systrap.rs`](https://github.com/microsoft/litebox/blob/main/litebox_platform_linux_userland/src/syscall_intercept/systrap.rs)** – Platform-specific implementation of `syscall_callback` that handles the intercepted calls on Linux by storing the return address and dispatching to the kernel.

- **[`litebox_platform_windows_userland/src/lib.rs`](https://github.com/microsoft/litebox/blob/main/litebox_platform_windows_userland/src/lib.rs)** – Windows counterpart defining the same `syscall_callback` entry point for cross-platform trampoline compatibility.

## Summary

LiteBox syscall rewriting transforms unmodified ELF binaries through a three-phase static instrumentation process:

- **Analysis phase** parses the binary with `object::File::parse` and maps all control-transfer targets to identify safe patching locations.
- **Trampoline generation** reserves page-aligned memory after the last `PT_LOAD` segment and creates a placeholder for the runtime callback address.
- **Binary patching** uses the iced-x86 disassembler to locate `syscall`, `int 0x80`, and `call gs:0x10` instructions, replacing them with 5-byte `jmp rel32` instructions targeting the trampoline.

The runtime loader in [`litebox_common_linux/src/loader.rs`](https://github.com/microsoft/litebox/blob/main/litebox_common_linux/src/loader.rs) activates the instrumentation by detecting the `LITEBOX0` magic bytes, writing the actual `syscall_callback` address into the trampoline placeholder, and protecting the page as executable.

## Frequently Asked Questions

### What is LiteBox syscall rewriting?

LiteBox syscall rewriting is a static binary instrumentation technique that modifies ELF executables at build time to intercept system calls. It replaces native `syscall` instructions with jumps to generated trampoline code, which then redirects execution to a user-space callback function provided by the LiteBox runtime, enabling syscall monitoring and virtualization without kernel modules or dynamic instrumentation overhead.

### How does LiteBox avoid corrupting branch targets when patching?

During the analysis phase in [`litebox_syscall_rewriter/src/lib.rs`](https://github.com/microsoft/litebox/blob/main/litebox_syscall_rewriter/src/lib.rs), the rewriter records every control-transfer target—including branch, call, and jump destinations—within the `.text` sections. When patching syscalls, it calculates the maximum safe overwrite region (up to 5 bytes) and ensures this region does not overlap with any recorded target address, preventing corruption of the binary's control flow graph.

### Can LiteBox rewrite 32-bit binaries or only 64-bit?

LiteBox supports both 64-bit (x86-64) and 32-bit (x86) binaries. The rewriter detects multiple syscall mechanisms: the `syscall` instruction for 64-bit, and `int 0x80` or `call gs:0x10` for 32-bit systems. The trampoline generation adjusts register usage accordingly (using `RCX` for return addresses on x86-64 versus `EAX` on x86) to maintain correct calling conventions across architectures.

### What happens if the loader cannot find the TRAMPOLINE_MAGIC marker?

If the LiteBox loader in [`litebox_common_linux/src/loader.rs`](https://github.com/microsoft/litebox/blob/main/litebox_common_linux/src/loader.rs) fails to locate the `LITEBOX0` magic bytes at the end of the file during `parse_trampoline`, it treats the binary as a standard ELF without syscall interception support. The loader will skip trampoline initialization and proceed with normal ELF loading, meaning the binary will execute with original `syscall` instructions unmodified, effectively bypassing LiteBox virtualization for that execution.