A reverse-engineering debugger and userspace CPU emulator for Linux.
If you do any reverse engineering you've probably used a debugger like x64dbg or Ghidra. I wanted something like that on Linux with a twist, so I built Aracari. It has the usual multi pane setup with disassembly, registers, breakpoints, and a memory view, plus a built in CPU emulator that can run a binary with no real process behind it.
The emulator is the heart of it. It loads an ELF and runs it on a virtual CPU (Unicorn), handling the program's Linux syscalls itself, so a normal dynamically-linked glibc binary just runs inside it with no kernel and no real process. On top of that it does real memory protection, delivers CPU faults to the program's own signal handlers, runs multiple threads with working futexes, and can snapshot and restore the entire machine state.
It also embeds a Lua engine for scripting and hooks, so you can automate analysis, patch code on the fly, spoof return addresses, and build ROP chains right from the debugger. See LUA_API.md for the full API.
- Two execution backends, one UI: debug a real process via
ptrace, or run it under full instruction-level emulation with Unicorn. Pick per launch. - Syscall-level emulation: 50+ Linux syscalls serviced directly; file I/O is
proxied to the host and the real
ld.so/glibc run, so dynamically-linked binaries Just Work, no API reimplementation. - Advanced memory management: real
mmap/mprotect/munmapwith reserve → commit semantics (PROT_NONEreserves,mprotectcommits) and W^X enforcement. A strict mode faults on wild pointers instead of hiding them. - Exception handling: synchronous CPU faults are dispatched into the guest's
own signal handlers (the SEH analog):
SIGSEGV/SIGBUS/SIGILL, realrt_sigaction/sigaltstack, properucontextframes andrt_sigreturnso a handler can fix the fault and resume. - Threading: a round-robin scheduler over a shared address space with real
futexwait/wake (clone/clone3, full pthreads). - State management: fast in-memory snapshots and on-disk serialization of the entire emulator: registers, every mapped page, allocator state, signal handlers, and the thread table.
- Anti-analysis evasion: RDTSC spoofing, CPUID masking, hidden hypervisor
bit, and deterministic
getrandom/clocks for reproducible runs. - Analysis & ROP: function discovery (named symbols +
sub_XXXXvia code analysis) and a searchable ROP gadget scanner. - Lua scripting & hooks: an interactive REPL plus per-address Lua hooks with
regs/mem/stack/patch/spoofAPIs, a ROP-chain builder, and module dumping. - MCP server: drive the emulator from AI tools over the Model Context Protocol: load, step, breakpoints, memory map, threads, snapshots, and the syscall log.
A C++20 toolchain, CMake, and Ninja. Zydis, Unicorn, GLFW, Dear ImGui, Lua, and fmt are fetched and built automatically. On Linux, GLFW needs the X11 / OpenGL development headers:
sudo apt install build-essential cmake ninja-build pkgconf \
libx11-dev libxrandr-dev libxinerama-dev libxcursor-dev libxi-dev libgl1-mesa-devgit clone https://github.com/whosstyler/Aracari.git
cd Aracari
cmake -S . -B build -G Ninja
cmake --build buildThe Unicorn emulator is built by default. Disable it with -DARACARI_BUILD_EMU=OFF,
or build headless (no GUI) with -DARACARI_BUILD_GUI=OFF.
Launch the GUI on a target:
./build/aracari ./path/to/binaryUse File → Launch (or the command bar) to open the launch dialog, where the
Use Emulator (Unicorn) checkbox switches the backend and toggles RDTSC /
instruction / syscall tracing. The title bar shows the active backend
(ptrace / Zydis or Unicorn / Zydis).
run <path> launch a target si / step step into
emu <path> launch under emulation so step over
attach <pid> attach to a process c / continue resume
bp / hwbp <addr> toggle breakpoint set <reg> <v> write a register
bpcond <addr> <expr> conditional breakpoint write <addr> <bytes> patch memory
hook <addr> <lua> install a Lua hook lua <code> run a Lua chunk
goto <addr> move the disassembly stop / q kill / quit
The Script Console is an interactive Lua REPL against the stopped target, and
any address can carry a hook script that runs on each hit (cont() to resume
silently, pause() to break into the UI):
-- log every malloc with its caller, break on big allocations
log(string.format("malloc(%d) <- %s", arg(1), sym.resolve(ret())))
if arg(1) > 0x10000 then pause() else cont() endNamespaces: regs, mem, sym, mod, stack, patch, bp, file, plus
globals log / arg / ret / cont / pause. Highlights, stack.spoof(addr)
to redirect a return, stack.chain({...}) to build a ROP chain, patch.bytes(addr, "EB 10") to patch, and mod.dump(name, path, true) to dump a module image. See
LUA_API.md for the full reference.
aracari-mcp exposes the emulator over the Model Context Protocol so an AI agent
can drive it, load_binary, step, continue_execution, breakpoints,
read_memory/read_registers, get_memory_map, get_threads, snapshot /
restore, save_state / load_state, and get_syscall_log.
Inspired by x64dbg, the original.
Licensed under the GNU General Public License v3.0. Contributions are welcome, open an issue or PR.