retrace is a userspace security and vulnerability discovery tool that
intercepts libc calls in dynamically-linked binaries. It works by
preloading a shared library into the target process
(LD_PRELOAD on ELF, DYLD_INSERT_LIBRARIES on Darwin, inline
hooking on Windows) and either logging or rewriting each intercepted
call’s arguments and return value.
Use cases: reverse engineering, debugging, fuzzing (malloc failure
injection, getenv buffer-overflow / format-string / garbage fuzzing,
incomplete I/O), redirecting network connect() calls, redirecting
file open() paths, faking OpenSSL verify results, and more.
| OS | Architecture | Interposition | Status |
|---|---|---|---|
Linux (glibc) |
x86_64, aarch64 |
|
Production |
Linux (musl / Alpine) |
x86_64, aarch64 |
|
Production |
Linux (musl / OHOS) |
aarch64 |
|
Cross-compile + signed |
macOS |
arm64 (Apple Silicon) |
|
Production (printf fixed in v2.1.0) |
macOS |
x86_64 (Intel) |
|
Production (init + FP varargs fixed in v2.1.0) |
Android |
arm64, x86_64 |
|
Cross-compile via NDK |
FreeBSD / OpenBSD / NetBSD |
x86_64 |
|
Production |
Windows (MSVC) |
x86_64, arm64 |
Inline hook (from scratch, ADR-0009) |
Production |
Windows (MinGW) |
x86_64 |
Inline hook |
Production |
Linux (statically linked) |
x86_64, aarch64 |
|
Production |
Pre-built binaries for every platform are attached to each release. Install with one command:
$ curl -sSL https://raw.githubusercontent.com/riboseinc/retrace/main/scripts/install.sh | sh# Install
$ curl -sSL https://raw.githubusercontent.com/riboseinc/retrace/main/scripts/install.sh | sh
# Trace libc calls (text output)
$ retrace trace malloc,free -- /bin/ls
# Trace libc calls (interactive HTML)
$ retrace trace malloc --html -- /bin/ls
# Fuzz malloc at 10% failure rate
$ retrace fuzz malloc --rate 0.1 -- ./your-program
# Mock a return value
$ retrace mock getuid 0 -- ./check-root
# Or use a JSON config for advanced scenarios
$ retrace run --config docs/cookbook/09-fuzz-malloc.json -- ./your-programSee the cookbook for 20+ recipes covering tracing, fuzzing, mocking, redirection, security auditing, and CI integration.
-
Native process attach —
retrace attach <pid>— attach to an already-running process via ptrace and trace its syscalls until it exits. NoLD_PRELOAD, no restart, no control of the launch required. This reaches the targets the preload backends structurally cannot: any running PID (and static binaries after they started). Output is the same JSON format and feeds the same downstream tools. New public API:retrace_attach_process(pid)andretrace_list_backends(). See the CLI reference. -
retrace backends— lists the interposition backends compiled into the library (preload-elf, preload-macho, preload-msvc, ptrace, …).
The v2.3.x series turns retrace from a single CLI into a tools ecosystem: one shared-library backend, plus standalone tools that all consume the same JSON log format.
-
Lock-free SPSC ring logger — per-thread ring with atomic head/tail; a single background flusher thread drains at 1ms cadence. Hot path is now a non-blocking push instead of a mutexed
fwrite. Env-gated viaRETRACE_LOGGER_RING(default on; disable for OHOS/QEMU). -
capture_bufferaction — post-call memory observation: reads N bytes from a pointer param and logs as hex or string. -
call_hash— per-thread FNV-1a rolling hash of intercepted libc calls; surfaces as coverage feedback for libFuzzer via a custom mutator. -
retrace-audit— compliance audit tool. Apply a policy file (baseline / PCI-DSS / HIPAA / ISO 27001 / custom) and emit findings as JSON, SARIF 2.1.0 (GitHub Code Scanning / Azure DevOps), or printable PDF. See cookbook 24. -
retrace-diff— differential trace analysis. Per-function count + duration diff with--threshold pct=Nfor CI gating, LCS-based call-order diff (--order), and statistical significance (--statsz-score against N baselines). See cookbook 25 and cookbook 26. -
retrace-replay— interactive TUI for traces. Step forward/backward, jump to any index, regex search. See cookbook 27. -
retrace-ws— WebSocket streamer for live traces with a built-in browser viewer athttp://localhost:8765/. See cookbook 28. -
Frida bridge (
frida-bridge/retrace-frida.js) — emits retrace-compatible JSON from inside Frida. The escape hatch whenLD_PRELOADcan’t reach the target: iOS apps, static binaries, attach to a running PID. See cookbook 29. -
eBPF bridge (
ebpf-bridge/retrace-ebpf.bpf.c) — kernel-level observation of everyopenat/closesyscall on the system. Observation only (eBPF cannot modify calls). See cookbook 30. -
VS Code extension — renders a retrace JSON log in a webview pane inside the editor; doubles as a
retrace-wsclient for live streams. See cookbook 31. -
Grafana data source plugin — loads a retrace JSON log (over HTTP) and exposes events as a Grafana frame. Cache TTL enables self-updating dashboards. See cookbook 32.
-
fuzz-replayCLI subcommand — replay a libFuzzer crash input through the matching harness for quick triage. -
Nightly fuzz workflow — matrix of 5 fuzzers, 5 minutes each, against the seed corpus. Crash artifacts uploaded for download.
-
Parson OOM hardening — allocation budget (
input_len * 1000) on the comment-stripping path; eliminates the OOM vector found by the nightly fuzz workflow. -
Website features — Decision Wizard, Recipe Builder, Cmd-K search palette, Glossary, Community section, Back-to-top.
See CHANGELOG.md for the full diff per release (v2.3.0 / v2.3.1 / v2.3.2 / v2.3.3).
-
Network function interception — 27 BSD-sockets functions now intercepted (
socket,connect,bind,listen,accept,send/recv/sendto/recvfrom,setsockopt/getsockopt,socketpair/accept4/shutdown/sendmsg/recvmsg, the resolver and inet families,getpeername/getsockname). -
addr_denyaction — network deny-list (the address-space counterpart ofsandbox). Specs support"host:port",":443","[::1]:443","/var/run/x.sock",""(deny all). -
Per-return-address routing — new
caller_matchesarray on eachintercept_script. Three match kinds:address,symbol(via dladdr),offset_in_module(ASLR-safe). OR-semantics; any match wins. -
Per-process dladdr cache — repeat-lookup cost ~10us → ~1us for symbol/module-offset matching.
-
Engine MECE refactor — engine.c split into five single- concern modules (
thread_context,reentrance_guard,cleanup,script_resolver,action_runner). Newdocs/engine-state-machine.mddocuments the 16-state per-call lifecycle. -
Property-based test suite — ~26 properties across parson,
sockaddr_inspect, actions,script_resolver,caller_match(~26,000 evaluations perctestrun). -
Action unit-test sweep — all 13 built-in actions have unit tests; the test pyramid now spans unit / property / stress / fuzz / perf.
-
libFuzzer harnesses —
fuzz_config_parse+fuzz_script_resolve. Opt in via-DRETRACE_BUILD_FUZZERS=ON(clang-only). Smoke runs: 369K and 1.99M iterations respectively, both clean. -
Stress test framework —
stress_threads: 8 threads x 100K iters x 4 calls/iter = 3.2M intercepted calls per family. -
Performance benchmark harness — 4 micro-benchmarks with percentile reporting (
script_resolve,caller_match,log_params,call_real). -
Cookbook recipe 17 — per-return-address routing with three working examples.
See CHANGELOG.md for the full diff.
-
Quick CLI subcommands —
retrace trace malloc — /bin/ls,retrace fuzz malloc --rate 0.1 — ./server,retrace mock getuid 0,retrace slow open --ms 100. No JSON needed for the 90% use case. Plusretrace pp(built-in text pretty-printer) andretrace html(interactive HTML trace viewer). -
Built-in HTML trace viewer —
retrace trace --html — /bin/lsgenerates a self-contained interactive HTML page. No Python, no git clone. Summary cards, category breakdown, filterable call table. -
Docker integration — one-line tracing/fuzzing for any container:
RUN curl … -o /usr/lib/libretrace.so. Pre-built image atghcr.io/riboseinc/retrace:latest. -
Binary releases — pre-built
.so/.dylib/.dllfor 8 platforms. Install viascripts/install.sh(detects OS + arch, downloads the right binary). Stable URLs for Dockerfiles. -
12 built-in actions — log_params, call_real, modify_in_param_*, modify_return_value_int, memory_fuzz, incomplete_io, fuzzing_seed, delay (latency injection), call_count_limit (resource exhaustion), sandbox (runtime path deny-list).
-
Android support — cross-compile via NDK for arm64-v8a and x86_64.
-
Engine MECE refactor — engine.c split into thread_context, script_resolver, action_runner (ADR-0013).
-
macOS Intel fixed — dlsym(RTLD_NEXT) fallback for ld64’s silent symbol-drop bug. Intel macOS is now fully production.
-
FP varargs on x86_64 — xmm0..7 saved in trampoline; printf("%f") works correctly on Linux, BSD, macOS.
-
Homebrew formula —
brew tap riboseinc/retrace && brew install retrace. -
Cookbook — 20+ recipes: tracing, fuzzing, mocking, redirection, security audit, CI integration, sandbox, enprot cross-link.
-
Tools — flamegraph (SVG), logpp (text), benchmark (overhead).
-
Per-platform binary artifacts in every release: 10 platforms
source tarball.
retrace uses CMake. vcpkg manifest mode pulls OpenSSL and cmocka on Windows automatically; system packages provide them on POSIX.
$ cmake -B build -G Ninja -DRETRACE_BUILD_TESTS=ON
$ cmake --build build
$ ctest --test-dir build --output-on-failure
$ sudo cmake --install build|
Tip
|
Quick smoke test without installing: # Linux / BSD
LD_PRELOAD=$PWD/build/src/v2/libretrace.so /bin/id
# macOS (Apple Silicon)
DYLD_INSERT_LIBRARIES=$PWD/build/src/v2/libretrace.dylib /bin/id
# Windows (PowerShell, after install)
$env:RETRACE_JSON_CONFIG = "config.json"
retrace-win-run myapp.exe # injects retrace.dll (hooks + boot in the child) |
Useful CMake options:
Option |
Default / purpose |
|
|
|
|
|
|
|
|
|
|
|
|
$ RETRACE_JSON_CONFIG=<config.json> LD_PRELOAD=build/src/v2/libretrace.so <binary>On macOS replace LD_PRELOAD with DYLD_INSERT_LIBRARIES:
$ RETRACE_JSON_CONFIG=<config.json> DYLD_INSERT_LIBRARIES=build/src/v2/libretrace.dylib <binary>Environment variables:
Variable |
Purpose |
|
Path to a JSON config file (see below). If unset, retrace uses a built-in default that activates |
|
|
|
|
|
Path to a log file (alternative to stderr). |
|
|
|
Per-thread ring capacity (power of 2 in 64..65536). Default 1024. |
|
|
Note
|
On macOS, System Integrity Protection strips |
retrace is driven by a JSON config file. The top-level shape:
{
"intercept_scripts": [
{
"func_name": "<glob or exact symbol>",
"actions": [
{ "action_name": "<action>", "action_params": { ... } },
...
]
},
...
]
}A func_name of "*" matches every intercepted symbol. Otherwise
the name must match a libc symbol retrace knows about (see
src/core/prototypes/ for the canonical list, grouped by header:
stdio.c, stdlib.c, unistd.c, dirent.c, uio.c, signal.c,
ctype.c, locale.c).
Actions run in the order listed. Each action either observes the call, mutates an argument, mutates the return value, or skips the real call entirely.
| Action | Effect | Required params |
|---|---|---|
|
Log the call (function name + arguments) to the configured logger. |
none |
|
Invoke the real libc implementation. Omit this action to skip the real call entirely (return zero / NULL). |
none |
|
Rewrite a string argument before the real call runs. |
|
|
Rewrite an integer argument before the real call runs. |
|
|
Rewrite an array argument before the real call runs. |
|
|
Override the integer return value (after |
|
|
Randomly fail |
|
New behaviors are added by registering a new action — no engine change
required (see src/core/actions/basic.c for the pattern).
retrace ships ~490 prototypes grouped by libc header. The canonical
list lives under src/core/prototypes/. Add a function by adding a
struct FuncPrototype entry to the right header file (and a
WRAPPER_ENTRY_* line in the matching funcs_symbols.S).
| Header | Count | Notable symbols |
|---|---|---|
|
28 |
|
|
13 |
|
|
4 |
|
|
5 |
|
|
165 |
|
|
73 |
|
|
8 |
|
|
198 |
|
\ Variadic.* printf/scanf-family prototypes are tagged FAT_PRINTF /
FAT_SCANF and go through the variadic-aware dispatcher so they work
on every platform’s ABI:
-
Apple AArch64: variadic args are pushed onto the caller’s stack (Apple’s ABI).
-
Linux/BSD AArch64 (AAPCS64): variadic args go in
x1..x7, then the stack. -
x86-64 (Sys V / Darwin): variadic args go in
rdi..r9, then the stack.
On glibc, modern gcc redirects scanf / fscanf / sscanf / vscanf
/ vsscanf / vfscanf to isoc99_* at the PLT. retrace intercepts
both the plain names (older binaries, musl, BSD) and the isoc99_*
names (modern glibc binaries) when the CMake link-check confirms the
symbol exists.
|
Tip
|
Float varargs ( |
{
"intercept_scripts": [
{
"func_name": "*",
"actions": [
{ "action_name": "log_params" },
{ "action_name": "call_real" }
]
}
]
}{
"intercept_scripts": [
{
"func_name": "getenv",
"actions": [
{ "action_name": "log_params" },
{
"action_name": "modify_in_param_str",
"action_params": { "param_name": "name", "match_str": "TEST", "new_str": "PATH" }
},
{ "action_name": "call_real" }
]
}
]
}{
"intercept_scripts": [
{
"func_name": "getuid",
"actions": [
{ "action_name": "call_real" },
{ "action_name": "modify_return_value_int", "action_params": { "retval_int": 42 } }
]
}
]
}{
"intercept_scripts": [
{
"func_name": "malloc",
"actions": [
{ "action_name": "call_real" },
{ "action_name": "memory_fuzz", "action_params": { "fail_rate": 0.1 } }
]
}
]
}When you’re running memory_fuzz over a large program, the default
log_params + call_real for every libc call produces gigabytes of
JSON you don’t care about. Two ways to suppress it:
1. Disable the logger entirely (zero JSON output, fuzz still runs):
$ RETRACE_LOGGER_DEF_ENA=0 \
RETRACE_JSON_CONFIG=fuzz.json \
LD_PRELOAD=build/src/v2/libretrace.so ./your-program2. Allowlist only the function you’re fuzzing (log only malloc):
{
"intercept_scripts": [
{
"func_name": "malloc",
"actions": [
{ "action_name": "log_params" },
{ "action_name": "call_real" },
{ "action_name": "memory_fuzz", "action_params": { "fail_rate": 0.1 } }
]
}
/* NOTE: no "*" wildcard script -- every other libc call goes
* through retrace's trampoline but the engine finds no matching
* script and just calls real. No log noise. */
]
}| Directory | What it demonstrates |
|---|---|
|
Fuzz DNS resolution paths |
|
Buffer-overflow and format-string fuzzing of |
|
HTTP server input fuzzing |
|
Redirect |
|
Network call fuzzing |
|
File/string injection helper (paired with |
|
Trace |
retrace ships one shared-library backend plus standalone tools that consume the same JSON log format. Each owns one job; mix and match freely.
| Tool | Job |
|---|---|
|
Apply a policy file (baseline / PCI-DSS / HIPAA / ISO 27001 / custom) and emit findings as JSON, SARIF 2.1.0, or PDF. |
|
Per-function count + duration diff between two traces, with
|
|
Interactive TUI: step / rewind / jump / regex-search through events. |
|
Tail a running trace and broadcast over WebSocket; built-in
browser viewer at |
|
Attach to a running process via ptrace and trace its syscalls until it exits — no preload, no restart (Linux). |
|
Convert a retrace JSON log to OTLP/JSON for Jaeger / Tempo / Honeycomb / Datadog. |
|
Frida script emitting retrace-compatible JSON. Use when
|
|
Linux kernel-level BPF program observing every |
|
VS Code extension: renders a trace in a webview pane, doubles
as a |
|
Grafana data source: load a trace over HTTP, expose events as a frame for time series / bar gauge / state timeline panels. |
For task-driven recipes covering every tool, see the tools overview and the cookbook (32 recipes and counting).
retrace is built around five clean concepts. Each is a MECE module extensible without modifying the others (Open/Closed Principle).
Per-arch trampoline |
One hand-written assembly trampoline per function. Pushes the SysV /
Microsoft x64 / AArch64 PCS register arguments into a frame, calls
|
Engine |
|
Action registry |
Built-in actions live in |
Backend plugin system |
Each (OS, arch) combo has its own backend ( |
Real-impl indirection |
All internal libc usage inside retrace goes through
|
Init order matters (see src/core/main.c constructor):
retrace_as_init → retrace_real_impls_init → retrace_logger_init
→ parson alloc hooks → retrace_conf_init →
retrace_loger_update_config → retrace_engine_init →
retrace_funcs_init → retrace_datatypes_init →
retrace_actions_init → retrace_as_init_late.
Architecture Decision Records under docs/adr/ capture the
load-bearing decisions:
ADR |
Topic |
|
Semantic versioning |
|
Opaque public types for ABI stability |
|
From-scratch Windows inline-hooking (no MinHook / Detours) |
|
AArch64 float params supported from day one |
|
v1 source removed at v2.1.0 (supersedes |
v1’s source code was removed at the v2.1.0 release (ADR-0011). If you were running v1, the table below maps every v1 concept to its v2 equivalent.
v1 |
v2 |
|
|
|
LD_PRELOAD / DYLD_INSERT_LIBRARIES directly. A native CLI is on the roadmap. |
|
|
|
|
|
|
|
Use |
|
|
|
|
|
Not yet ported to v2’s action system. Track via issue tracker if you need it. |
|
Not yet ported to v2. The |
|
|
|
Use multiple |
|
Not yet ported to v2. On the roadmap. |
Autotools build: |
CMake: |
|
Removed. Will be replaced by the native CLI when it lands. |
v1 examples under |
Still in the tree as v1-format text configs. They will be ported to v2 JSON in a follow-up. |
What was removed in v2.1.0:
-
The entire v1 source tree (
src/v1/). -
The Autotools build system (
configure.ac,Makefile.am,m4/,autogen.sh,configure, etc.). CMake is the only build system. -
The
retraceshell-script launcher. UseLD_PRELOADuntil the native CLI lands. -
The
RETRACE_CONFIGtext-format config file. UseRETRACE_JSON_CONFIG. -
The interactive pty CLI (
RETRACE_CLI=1).
What was renamed:
-
The installed library is
libretrace.so/libretrace.dylib/retrace.dll(waslibretrace_v2.*briefly during the transition; the_v2suffix was dropped at v2.1.0 because v1 no longer exists).
| Platform / feature | Status |
|---|---|
|
#450: malloc path inside libdl recurses / reenters retrace; skipped in CI. |
Float varargs ( |
Engine bails to asm Path A (correct output, no |
|
Added in v2.1.0 (PR #469). All printf-family + v*printf variants are now in the prototype registry. |
|
Added in v2.1.0 (PR #470). |
Incomplete-I/O action |
v1’s |
Explicit fuzzing seed |
v1’s |
We are Ribose, the secure sharing company. We
believe privacy and security form the foundation of liberty. We created
retrace to aid developers and security researchers in building
better, more defensible software.
-
Security issues, feature requests, and bug reports: GitHub Issues
-
General questions:
retrace@ribose.com