Binary Ninja plugin that rebuilds a plausible source tree from the __FILE__
path strings passed to a logging or assert function. Spiritual sibling of
the logrn binaryninja plugin: logrn gives functions their names,
arborist gives them their place.
- Find a logging/assert function that takes a source path (
__FILE__) as an argument, e.g.log(const char *file, int line, ...). - Right-click it and run
export caller tree. - Pick which argument holds the file path.
- Choose an export mode:
- tree only recreates the directory structure with empty files.
- tree + Pseudo C also fills each file with the decompiled Pseudo C of the callers attributed to it (falls back to HLIL if Pseudo C is unavailable).
- Pick an output directory (prefilled with the last one used this session). Boom, a browsable tree.
All callers of the function are walked, each one placed by the path it passes in the chosen argument. Runs as a background task so it won't freeze binja.
Runs are additive: existing files are never truncated, and a function is
never written twice for the same format (tracked in .arborist.json). So you
can point several log functions at one directory and everything merges. The
final log line reports how many new files and functions were written. Delete
.arborist.json (and the files) to start fresh.
- Attribution is first-hit per caller, like logrn. A function inlined from
another translation unit can carry a foreign
__FILE__; a few misplaced functions are the price of coverage. - Partial by design. Functions that never pass a usable path argument are skipped, the tree reflects what the binary leaks, not the whole project.
- Reconstruction, not source. The written Pseudo C is decompiler output, not the original source, and is not compilable.
- Only literal "pointer to string" arguments are resolved. Computed or obfuscated paths are ignored.
- Pseudo C needs a Binary Ninja recent enough to expose the linear "Pseudo C" representation, otherwise it silently falls back to HLIL.
The paths come from the binary and are untrusted — a hostile sample could ship
crafted __FILE__ strings. arborist defends on three fronts before writing:
.,.., leading slashes and drive letters are stripped, so a path can never climb above the chosen output directory.- Only strings ending in a known source extension (
.c,.cpp,.h, …, see_SRC_EXT) are treated as paths; anything else the logger was fed is ignored. - Each resolved target is checked (via
realpath) to be inside the output directory before any write, catching symlinks or edge cases the first two miss.
Still, point it at a throwaway output directory when analysing untrusted binaries — never your home or a sensitive tree.
- Export format choice: HLIL (
.hlil) and disassembly (.asm). - Order functions within a file using
__LINE__when available. - Majority-vote attribution instead of first-hit.
- Coverage report + "unknown/" bucket for unattributed functions.