Skip to content

This is an old project I was hacking on to learn about Linux binary emulation. Updated recently to use Unicorn 2. Won't be receiving updates for any real world use, it's intended to serve as a reference.

License

Notifications You must be signed in to change notification settings

xpcmdshell/celebi

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

3 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Celebi

A toy Linux x86_64 ELF binary emulator using Unicorn Engine.

Celebi loads statically-linked x86_64 ELF binaries and executes them by emulating the CPU and intercepting syscalls. It provides minimal Linux syscall emulation sufficient to run simple programs compiled with musl libc. This was written as part of a personal learning exercise around early Linux binary startup procedures and emulation requirements.

Features

  • ELF binary loading (PT_LOAD segments)
  • x86_64 CPU emulation via Unicorn Engine
  • Linux syscall emulation:
    • Process control: exit, exit_group
    • Memory: brk, mmap, mprotect
    • I/O: write, writev, ioctl, readlink
    • Identity: getuid, geteuid, getgid, getegid, getpid, gettid
    • System: uname, getrandom, arch_prctl
    • Signals: rt_sigprocmask
  • Thread Local Storage (TLS) setup via arch_prctl(ARCH_SET_FS)
  • Proper stack setup with auxiliary vectors (auxv)

Limitations

  • Only supports statically-linked ELF binaries (no dynamic linking)
  • Single-threaded only
  • No file I/O beyond stdout/stderr
  • Limited syscall support (just enough for basic programs)
  • No signal delivery

Building

cargo build --release

Usage

# Run a binary
./target/release/celebi <path-to-elf-binary>

# Or use cargo
cargo run --release -- <path-to-elf-binary>

Example Programs

The examples/ directory contains simple C programs for testing. Build them with Docker:

# Build all examples (requires Docker)
./scripts/build-examples.sh

# Build a specific example
./scripts/build-examples.sh hello.c

# Clean compiled binaries
./scripts/build-examples.sh clean

Run examples:

# Using the helper script
./scripts/run-example.sh hello

# Or directly
cargo run --release -- examples/bin/hello

Available Examples

Example Description Exit Code
hello printf test (writev, stdio, TLS) 42
loops Control flow and function calls 44
minimal Minimal program (just exit) 0

Example Output

$ ./scripts/run-example.sh hello
[*] Running: hello
========================================
[*] Loading: "examples/bin/hello"
[*] Loading segment: vaddr=0x400000, filesz=0x28c, memsz=0x28c
[*] Loading segment: vaddr=0x401000, filesz=0x39e7, memsz=0x39e7
[*] Loading segment: vaddr=0x405000, filesz=0xcdc, memsz=0xcdc
[*] Loading segment: vaddr=0x406fb8, filesz=0x158, memsz=0x838
[*] Entry point: 0x40105b
[*] Setting up stack at 0x7f0000000000
[*] Auxiliary vector written, rsp=0x7f00001fff90
[*] Stack setup complete, rsp=0x7f00001fff78
[*] Setting up heap at 0x600000
[*] Starting emulation...
----------------------------------------
arch_prctl(SetFs, 0x407698)
set_tid_address(0x4077d0)
ioctl(1, 0x5413)
Hello from celebi emulator!
Testing: 2 + 3 = 5
exit_group(42)

Project Structure

celebi/
├── src/
│   ├── main.rs           # Emulator core
│   └── syscall_linux.rs  # Syscall definitions
├── examples/
│   ├── hello.c           # printf test
│   ├── loops.c           # Control flow test
│   ├── minimal.c         # Minimal program
│   └── bin/              # Compiled binaries (gitignored)
├── scripts/
│   ├── build-examples.sh # Docker-based compiler
│   └── run-example.sh    # Example runner
├── Cargo.toml
└── README.md

How It Works

  1. ELF Loading: Parses the ELF header and loads PT_LOAD segments into emulated memory at their specified virtual addresses.

  2. Stack Setup: Creates a proper stack with argc, argv (empty), envp (empty), and auxiliary vectors. The auxv includes AT_RANDOM (for stack canaries), AT_PAGESZ, AT_ENTRY, and program header info.

  3. Heap Setup: Initializes the program break (brk) for heap allocation. The brk() syscall expands this region as needed.

  4. TLS Setup: When the C library calls arch_prctl(ARCH_SET_FS, addr), we write to the x86_64 FS_BASE register. This enables thread-local storage, which is required for errno and other libc internals.

  5. Syscall Interception: Hooks the SYSCALL instruction and dispatches to handlers based on the syscall number in RAX. Each handler reads arguments from registers and writes the return value to RAX.

  6. Execution: Starts emulation at the ELF entry point and runs until exit() or exit_group() is called.

License

MIT

About

This is an old project I was hacking on to learn about Linux binary emulation. Updated recently to use Unicorn 2. Won't be receiving updates for any real world use, it's intended to serve as a reference.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published