Capture
Tenet consumes traces that conform to the binary trace format. Any tool that emits a compliant .bin file can feed the analysis pipeline.
Official capture: QBDITrace
Section titled “Official capture: QBDITrace”QBDITrace is the reference capture engine for iOS and Android AArch64. It runs inside the target process and writes a compact binary stream while expensive disassembly, symbol work, and analysis remain offline.
bash tools/qbditrace/build-qbditrace-ios.shldid -S tools/qbditrace/build-ios/libqbditrace.dylibscp -P 2222 tools/qbditrace/build-ios/libqbditrace.dylib root@localhost:/var/jb/usr/lib/Android
Section titled “Android”export NDK_PATH=/path/to/android-ndkbash tools/qbditrace/build-qbditrace-android.shadb push tools/qbditrace/build-android/libqbditrace.so /data/local/tmp/Hook and Invoke modes
Section titled “Hook and Invoke modes”Hook mode waits for the application to invoke the target naturally—use it for paths that depend on application state. Invoke mode constructs typed arguments and calls the target through qbditrace_run_call—use it for repeatable isolated calls.
What QBDITrace records
Section titled “What QBDITrace records”| Category | Details |
|---|---|
| Instructions | Per-instruction PC, NZCV flags, and written-register diffs (absolute values) |
| Memory | Load/store addresses, sizes, and values—including 9–16 byte wide stores |
| Embedded code | Raw bytes of each distinct executed instruction (deduplicated), so offline disassembly needs no external image |
| ObjC dispatch | objc_msgSend family: receiver, selector, and runtime class name captured at the call site |
| System calls | svc instruction sites with syscall number and arguments |
| C API calls | Interception of memcpy, strcmp, strlen, CommonCrypto, BoringSSL, JNI, etc. with argument snapshots and string captures |
| Cross-boundary transfers | Call-site PC and target address when execution leaves or re-enters the instrumented module |
| FPR / NEON (v7) | 128-bit vector register diffs and anchor snapshots (opt-in) |
Design highlights
Section titled “Design highlights”- Module-level tracing: the entire executable segment of the target module is instrumented; code outside the module runs natively and generates only transfer events.
- Anchor + diff reconstruction: periodic full-register anchors let the decoder rebuild exact state at any instruction; the rest of the stream stores only what changed.
- Fault-tolerant memory reads:
vm_read_overwriteis used to safely probe store values, ObjC class names, and embedded code bytes—bad addresses return errors instead of crashing. - arm64e pointer authentication: QBDI bypasses PAC so
braa/blraaindirect jumps (includingobjc_msgSendtail calls) are traced correctly. - ARC-safe return address: a dedicated RX page replaces QBDI’s default fake return value so
objc_autoreleaseReturnValueprobes do not fault. - 1 MiB block compression with a tail index for random access; PC-delta encoding (v5) shrinks INST headers from 16 to 6 bytes.
- Crash PC ring: a 2048-entry ring buffer always captures the last executed PCs; on SIGSEGV/SIGBUS the fault handler dumps them automatically, or call
qbditrace_dump_pcringfrom a Frida exception handler.
Threading and GCD
Section titled “Threading and GCD”A QBDI VM controls only the calling thread. Work dispatched to other threads via dispatch_async is not included. The optional --trace-gcd mode hooks the GCD dispatcher and synchronously re-invokes discovered block invoke pointers—each in its own trace file. This is not a true multi-thread trace: blocks execute an extra time on the calling thread, so side-effectful or queue-dependent blocks may behave differently. Prefer serializing capture whenever possible.
Custom collectors
Section titled “Custom collectors”Tenet does not require QBDITrace. Any runtime that can emit the binary trace format can serve as a capture source. Common alternatives:
- Frida Stalker: instrument a thread with
Stalker.followand writeREC_INST+REC_ANCHOR+REC_CODErecords. - Other DBI frameworks: DynamoRIO, Pin, or custom engines that produce compliant
.binfiles. - Emulators: Unicorn or QEMU instrumented to log per-instruction state into the format.
- Static analysis output: synthetic traces for test and validation purposes.
The minimum requirement for Tenet to open a trace is: valid QBDT magic, version ≥ 4, and a complete per-instruction PC stream. See the compatibility tiers for how additional record types unlock richer analysis.
Choosing what to emit
Section titled “Choosing what to emit”| Tier | Required records | Unlocked analysis |
|---|---|---|
| Minimum | REC_INST (PC only), REC_ANCHOR |
Basic instruction listing, address normalization |
| Structure | + REC_CODE, branch hints, REC_EVENT (XFER_CALL/RETURN) |
CFG recovery, function boundaries, call graphs, loop detection |
| State | + GPR diffs, REC_INST NZCV, memory accesses |
Register reconstruction, memory model, taint analysis, data-flow slices |
| Platform | + REC_SVC, REC_OBJC, REC_CAPICALL |
Syscall naming, ObjC message resolution, C API argument capture |
| Vector | + REC_INST FPR diffs, REC_ANCHOR FPR snapshots (v7) |
NEON/vector register reconstruction, crypto algorithm identification |
Missing records degrade gracefully: Tenet opens the trace and runs the passes whose inputs are present.