❯ glaurung kickoff /bin/ls

reverse engineering for analysts and agents

Rust runs the expensive analysis. Python keeps it scriptable. Analysts, command-line tools, and optional LLM workflows share the same SQLite project, with provenance on every name and type.

The Glaurung mark: a gold dragon coiled into the letter G, breathing fire.
sign_bit_of_binary32 — clang -O2 x86-64
0x1190: 0f50c0    movmskps eax, xmm0
0x1193: 83e001    and      eax, 0x1
0x1196: c3        ret
glaurung decompile --func 0x1190 --style c
fn sign_bit_of_binary32 {
    ret = 0;
    ret = (ret | (var0 < 0));
    ret = (ret | ((var1 < 0) << 1));
    ret = (ret | ((var2 < 0) << 2));
    ret = (ret | ((var3 < 0) << 3));
    ret = (ret & 1);
    return (unsigned long)((unsigned int)(ret));
}

original C

int32_t sign_bit_of_binary32(float value) {
    return (int32_t)((fp174_float_bits(value) & FP174_SIGN_MASK) >> 31);
}

clang turned the sign-bit test into movmskps, which extracts four sign bits at once. The decompiler follows the three instructions and masks the result to one bit.

The recovered code looks different. The test only passes if it returns the same value.

Tests

recompile the output and run it

Text similarity cannot tell you whether recovered C still works. The fixture corpus calls both versions with the same inputs.

Each recovered function is compiled into a shared object. A subprocess loads it beside the original, calls both with seeded inputs, and compares the return value and every mutable buffer. One mismatch fails the lane.

213

fixture programs, from sorting and hashing to SHA-256 and Duff's device

tests/decompiler_fixtures/

3,449

function-lanes across 838 compiler and optimization cells

baseline.json

83.9%

behave the same as the original (2,895 of 3,449)

baseline.json @ 356be614

119

of 213 fixtures pass every lane they have

baseline.json

Counted from tests/decompiler_fixtures/baseline.json at commit 356be614, 2026-08-27. Toolchain pinned in the file: gcc 11.4.0, clang 14.0.0, glibc 2.35. By lane, over pass-or-fail verdicts: clang -O0 89.6%, gcc -O0 89.6%, gcc -O2 88.2%, clang -O2 86.6%. rustc is the weak lane at 30.9% (-O0) and 72.1% (-O2).

def-use

207 functions read an unwritten local

Across the four C and C++ lanes, 207 of 11,166 emitted functions read a local before writing it. The Rust lanes are worse and are reported separately.

closure

2,253 self-contained outputs

All 2,253 closure checks pass across three render styles. None of 751 placeholder checks found an invented name.

symbols

87 failures after stripping

Eighty-seven functions pass with debug information and fail without it. Eight go the other way.

Project files

manual names beat automatic names

A .glaurung file stores names, comments, types, prototypes, cross-references, and stack variables in SQLite. Each record says where it came from.

  1. manual an analyst typed it always wins
  2. dwarf read from debug info
  3. stdlib known library signature
  4. flirt FLIRT pattern match
  5. propagated carried along a proven edge
  6. auto inferred by analysis
  7. borrowed taken from a sibling binary

An agent writes proposed names at auto, below debug information and below anything an analyst typed. Re-running analysis cannot replace a manual name.

Security write-ups use lifted pseudo-C as a lead. They cite disassembly, emulation, or a live reproduction as evidence.

Architecture

Rust underneath. Python at the keyboard.

Rust owns parsing, disassembly, lifting, and analysis. PyO3 puts the same engine behind Python, the CLI, and the optional LLM tools.

rust 456 files · 283,732 lines

Twenty-two top-level modules include triage, formats, disasm, analysis, ir, decompile, symbols, demangle, flirt, strings, similarity, entropy, and optional modules behind feature gates.

agents optional · PydanticAI

Agents can query bounded analysis tools, propose function names, review vulnerability leads, and help recover source. Their changes enter the project at auto provenance. Manual work still wins.

python 87 exported types · PyO3

The native module returns types built by the Rust core: Binary, Function, ControlFlowGraph, Instruction, DataType. A test generates type stubs from the built extension and diffs them against the checked-in files.

Python python
import glaurung as g

art = g.triage.analyze_path("samples/containers/hello-cpp-g++-O0")
v = art.verdicts[0]
print(f"{v.format} {v.arch} {v.bits}-bit  confidence={v.confidence:.2f}")
print(f"size={art.size_bytes} entropy={art.entropy.overall:.2f}")
print("ascii strings:", art.strings.ascii_count)

# ELF x86_64 64-bit  confidence=0.92
# size=44104 entropy=4.82
# ascii strings: 298
command line 40 subcommands
$ uv run glaurung triage samples/containers/hello-cpp-g++-O0
path: samples/containers/hello-cpp-g++-O0
size: 44104 bytes (43.1 KiB)
verdicts: 1
format=ELF arch=x86_64 64-bit endianness=Little confidence=0.92
symbols: imports=41 exports=2 libs=3 flags: nx,aslr,relro,pie
strings: ascii=298 utf8=2 u16le=0 u16be=0
languages: (none)
scripts: (none)
entropy: overall=4.82

Quick start

run /bin/ls

You need uv, Rust 1.88 or newer, a C compiler, and a linker. The first run builds the Rust extension and caches it.

$ uvx --from git+https://github.com/mjbommar/glaurung.git \
    glaurung kickoff /bin/ls