rsleigh 0.4.2

SLEIGH (.slaspec) parser and Rust decoder/P-code emitter codegen — Ghidra-compatible disassembly in pure Rust
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
# rsleigh

[![crates.io](https://img.shields.io/crates/v/rsleigh.svg)](https://crates.io/crates/rsleigh)
[![License: Apache-2.0](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](LICENSE)
[![Rust](https://img.shields.io/badge/rust-2021%20stable-orange.svg)](https://www.rust-lang.org)

rsleigh is a Rust workspace for decoding machine code with Ghidra SLEIGH
specifications, lifting instructions to P-code, and experimenting with C-like
decompilation and binary triage workflows.

> **Showcase:**
> [crackme3 PyVMProtect white paper v3]https://github.com/ShaneBreazeale/pyvmprotect-static-lift/blob/main/WHITEPAPER.md
> [crackme3 PyVMProtect white paper v5]https://github.com/ShaneBreazeale/crackme-pyvmprotect-v5#11-tooling--rsleigh-recon-suite

The short version:

- It parses `.slaspec` files and generates Rust decoder crates.
- `rsleigh-api` exposes a reusable decoder/lifter API.
- `rsleigh-cli` can list, disassemble, and decompile functions from common
  binary formats.
- `rsleigh-decompile` is an active, useful, but still experimental decompiler.

This is not a drop-in replacement for Ghidra, IDA, or Binary Ninja. The decoder
and lifter are the most stable part of the project. The decompiler, malware
heuristics, and text output are moving quickly and should be treated as analysis
assistance, not ground truth.

## Contents

- [Showcase]#showcase
- [Why this exists]#why-this-exists
- [Status]#status
- [Supported targets]#supported-targets
- [Installation]#installation
- [Quickstart]#quickstart
- [Rust API]#rust-api
- [Decompiler]#decompiler
- [Malware and Triage Features]#malware-and-triage-features
- [SMT-Aided Taint Flow]#smt-aided-taint-flow---features-smt
- [Testing and Benchmarks]#testing-and-benchmarks
- [Known Limitations]#known-limitations
- [Security Posture]#security-posture
- [Workspace Layout]#workspace-layout
- [Prior Art]#prior-art
- [Contributing]#contributing
- [License]#license

## Showcase

End-to-end real-world solve, no live debugger, no Ghidra JVM:

- **[crackme3 PyVMProtect — `CTF{pyvm_r0cks}`]docs/showcase/crackme3-pyvmprotect.md**
  — PE64 Python C-extension with a 53-opcode custom VM, 117-stage polymorphic
  init chain, two-pass PCG + zlib bytecode decryption, sbox-permuted handler
  dispatch, and per-entry VARINT-encoded flag bytes. rsleigh's
  `--annotate-crypto`, `--vm-classify-handlers`, `--summarise-handlers`, and
  `--vm-bytecode` modes did the heavy lifting; the finishing decoder is a
  short Python script.

## Why this exists

rsleigh started as the native analysis backend for
[Spectra](https://github.com/ShaneBreazeale/spectra). The goal was to get a
SLEIGH-driven decoder and P-code pipeline without depending on a long-running
Ghidra JVM process or C++ libsla bindings.

The project is now useful as:

- a pure-Rust SLEIGH decoder/lifter for supported architectures;
- a scriptable CLI for batch disassembly, pseudocode, xrefs, and triage;
- a testbed for P-code based decompilation passes;
- a place to experiment with malware-oriented static-analysis heuristics.

It is still a v0.x, single-maintainer project. If correctness on a specific
target matters, compare against another tool and inspect the P-code or assembly.

## Status

The stable surface is intentionally narrow:

- `rsleigh-api::Decoder`
- `Architecture`
- `Decoder::decode`
- register-name lookup
- re-exported `pcode-ir` types such as `Instruction`, `PcodeOp`, `Varnode`,
  `AddressSpaceId`, and `DecodeError`

Everything else should be considered experimental unless documented otherwise:
the CLI output format, decompiler internals, pseudocode text, signature
heuristics, function-ID behavior, malware annotations, and analysis passes may
change without a deprecation cycle.

## Supported targets

Instruction decoding and P-code lifting are generated from SLEIGH for:

| Architecture | Notes |
|---|---|
| x86-64 | 64-bit mode, SysV and Windows x64 decompiler conventions |
| x86-32 | 32-bit protected mode, cdecl/thiscall heuristics |
| AArch64 | AAPCS64-oriented decompiler support |
| ARM32 | ARMv7 and Thumb; floating-point folding is incomplete |
| MIPS32 | Big-endian MIPS, including PIC-oriented call resolution work |
| RISC-V 64 | RV64-oriented decoder support |

The CLI handles ELF, PE, Mach-O, raw blobs, and WebAssembly. WASM uses a native
parser path rather than SLEIGH because it is a stack VM and does not fit the
same register-machine model cleanly.

Generated decoder crates are large. Compile time and final binary size are real
costs, especially for x86 and AArch64.

## Installation

From crates.io (CLI only):

```bash
cargo install rsleigh               # installs `rsleigh` binary
```

The `rsleigh-api` and `pcode-ir` crates are published for library use:

```toml
[dependencies]
rsleigh-api = "0.3"
pcode-ir    = "0.3"
```

### From source

Requires Rust 2021 stable and `make`. From a checkout:

```bash
make test                          # generate + build + test (~30s slaspec parse, then build)
cargo install --path rsleigh-cli   # install the `rsleigh` binary
```

Step by step, if `make test` is too coarse:

```bash
cargo run -p rsleigh-generate      # parse .slaspec → generate decoder crates
cargo test -p test-harness         # compile and run the regression suite
cargo install --path rsleigh-cli
```

## Quickstart

Basic CLI usage:

```bash
rsleigh ./binary                         # list discovered functions
rsleigh ./binary main                    # decompile one function
rsleigh ./binary 0x140001000             # decompile by address
rsleigh ./binary --all                   # decompile all discovered functions
rsleigh ./binary --disasm main           # disassembly plus P-code
rsleigh ./binary --json                  # machine-readable output where supported
rsleigh ./binary --xrefs main            # callers and callees
rsleigh ./binary --raw x86-64            # treat input as a raw blob
```

Debug and integration-oriented output:

```bash
rsleigh ./binary --pcode-json main       # raw lifted P-code
rsleigh ./binary --ssa-json main         # post-fold SSA
rsleigh ./binary --sigs extra.json       # load extra function signatures
rsleigh ./binary --fid custom.fidb       # load an extra function-ID database
rsleigh ./binary --no-fid-auto           # disable bundled FID databases
```

Output-reduction modes for large binaries and LLM workflows:

```bash
rsleigh ./binary --all --compact         # remove some declarations and blank space
rsleigh ./binary --all --brief           # calls and control-flow oriented output
rsleigh ./binary --all --min-complexity 10
```

Experimental analysis modes:

```bash
rsleigh ./binary --search "recv"
rsleigh ./binary --search --api LoadLibrary --const 0xCAFEBABE
rsleigh ./binary --summary
rsleigh ./binary --callgraph
rsleigh ./binary --classes [--json]
rsleigh ./binary --diff ./binary_v2
rsleigh ./binary --taint main
rsleigh ./binary --vulnscan
rsleigh ./binary --yara
rsleigh ./binary --ioc        [--json]
rsleigh ./binary --sigcheck   [--json]
rsleigh ./binary --resources  [--dump DIR] [--json]
```

Those modes are heuristics over the current analysis pipeline. They are useful
for triage, but they are not sound vulnerability detection, taint analysis, or
semantic differencing. The last three (`--ioc`, `--sigcheck`, `--resources`)
are constant-time string + structure scans; see [`docs/cli-triage.md`](docs/cli-triage.md)
for the full reference, JSON schemas, and a recommended IR workflow.

Custom-VM packer recon helpers (PE64-focused; auto-banners run on every PE64
binary, the flags below take comma-separated hex VA lists):

```bash
rsleigh ./packed.exe --vm-dispatch 0x18001fc70           # dispatcher data slots
rsleigh ./packed.exe --vm-classify-handlers 0x18001eb00,0x180018960
rsleigh ./packed.exe --tag-dispatch 0x180012ec0          # CMP/JZ chain extract
rsleigh ./packed.exe --summarise-handlers 0x180018960    # IAT-API per handler
rsleigh ./packed.exe --vm-bytecode 0x180063858:0x400 \
                    --vm-handlers handlers.json          # bytecode disasm
rsleigh ./packed.exe main --annotate-crypto              # rewrite crypto consts
```

Auto-banners surface family fingerprint, JMP `<reg>` trampolines, XOR-encoded
dispatchers, hash-resolved API resolvers (ROR13/DJB2/DJB2a/FNV-1), PEB-walk
sites, RDTSC/RDPMC timing-probe pairs, scratch-buffer leak candidates, and
SHA-256 implementation regions. See `docs/features.md` for the full module list.

## Rust API

```rust
use rsleigh_api::{Architecture, Decoder};

let mut decoder = Decoder::new(Architecture::X86_64);
let inst = decoder.decode(&[0x48, 0x89, 0xd8], 0x1000).unwrap();

assert_eq!(inst.disassembly, "MOV RAX,RBX");
assert_eq!(inst.len, 3);
```

The decompiler can also be embedded via `rsleigh-decompile`. Top-level entry
points — `decompile`, `decompile_with_binary`, `extract_learned_types`,
`extract_learned_structs` — rarely change in shape. Internal IR
(`ssa::*`, `fold::Expr`, `printer::*`, signature enums) churns between
releases as passes evolve, and public enums are not yet `#[non_exhaustive]`.
Pin an exact version or commit if you build on the internals.

## Decompiler

The decompiler turns lifted P-code into C-like pseudocode through:

1. CFG construction
2. SSA conversion
3. expression folding and type hints
4. control-flow structuring
5. printing and annotations

It can produce readable output for many simple and moderately complex
functions, especially when imports, signatures, strings, and straightforward
control flow are available.

Example shape:

```c
int factorial(int n) {
    if (n > 1) {
        return n * factorial(n - 1);
    }
    return 1;
}
```

Expect output quality to degrade on optimized code, unusual ABI patterns,
floating-point-heavy code, complex stack layouts, exception-heavy code,
indirect calls, hand-written assembly, and aggressive obfuscation. In normal
use, falling back to `--disasm`, `--pcode-json`, or another reverse-engineering
tool is part of the workflow.

More detail:

- `docs/decompiler-passes.md`
- `docs/architectures.md`
- `docs/features.md`

## Malware and Triage Features

The PE-focused analysis code is intentionally practical and pattern-based. It
tries to surface useful hints without pretending to be a full program-analysis
system.

Current examples include:

- PE64 SEH/TLS static patch discovery for some self-modifying-code patterns
- direct x64 syscall annotation for a Win11 24H2-oriented table
- ROR13 / DJB2 / DJB2a / FNV-1 API-hash resolver classification
- bundled function-ID databases for selected libc/libstdc++/musl builds
- Win32 and C/POSIX signature hints used by the decompiler printer
- C++ RTTI-oriented class recovery experiments
- Custom-VM packer recon (PyVMProtect / Themida / Stantinko / Trickbot Anchor style):
  vm-fingerprint, JMP `<reg>` trampoline gadgets, XOR-encoded dispatcher detection,
  PEB-walk anti-debug, RDTSC/RDPMC timing-pair anti-emu, scratch-buffer leak heuristic,
  SHA-256 constant-density region detection, crypto-constant inline annotation.
- `--vm-dispatch <addr>` extracts dispatcher metadata; `--vm-classify-handlers`
  classifies variable-length opcode handlers; `--tag-dispatch` extracts
  `CMP r8, imm; JZ` chains; `--summarise-handlers` reports IAT-API + stack-pop
  signature per handler; `--vm-bytecode <bc_va>:<size> --vm-handlers <path.json>`
  disassembles VM bytecode once handlers are classified.
- `--annotate-crypto` rewrites raw hex literals and `DAT_<hex>` labels to stable
  symbolic names (`KNUTH_9E3779B9`, `PCG_045D9F3B`, `SHA_256_6A09E667`) for
  readability across crypto-heavy functions.

These features can miss real behavior and can produce false positives. Treat
them as leads to inspect, not conclusions.

## SMT-Aided Taint Flow (`--features smt`)

Optional Z3-backed taint-flow prover. Inter-procedural source→sink path
collection with a labeled-CVE calibration corpus. Designed as static
evidence for an LLM analyst, not an auto-CVE-prover.

Build:

```
CPATH=$(brew --prefix z3)/include LIBRARY_PATH=$(brew --prefix z3)/lib \
  cargo build --release --features smt -p rsleigh-cli
```

Modes:

- `rsleigh <bin> --smt-explore <fn> [--smt-summaries]` — single-function SAT proof.
- `rsleigh <bin> --smt-explore-all --smt-summaries` — sweep, only prints `REACHABLE` hits.
- `rsleigh <bin> --smt-candidates [<fn>]` — NDJSON dump of every Source→Sink path
  with verdict, filter reasons, source/sink VarIds, call_chain, trigger bytes,
  and per-event memory-flow trace (region/AllocSite per VarId). LLM-consumable.
  Flags: `--smt-candidates-cap N` (per-fn output cap), `--smt-candidates-top N`
  (highest-scored N after dedup), `--smt-candidates-no-dedup`.
- `rsleigh <bin> --smt-diag` — per-binary aggregate stats (BL site classification,
  source/sink resolution, summary build counts, per-kind v2-path verdict breakdown).

Sink kinds with SAT models: `StackBuffer` (strcpy / strcat), `FormatArg`
(printf / sprintf class), `Command` (system / popen / execve), `LengthArg`
(memcpy / strncpy / memmove), `TaintedStore` (compiler-emitted store-loop
extract_name pattern). Source kinds: recv-class / read / fread / fgets / scanf
/ getenv / argv (auto-injected at `main`).

Calibration: `python3 scripts/smt-calibrate.py test-harness/fixtures/smt/calibration`
walks each `<entry>/EXPECTED.json` and prints TP / TN / FP / FN per CVE.
Current corpus is 12 entries (synthetic Heartbleed shapes + cve_synth +
real-world Linux ARM64 ELF: dnsmasq-2.71, dropbear-2016.74, busybox-1.21.0).
100% TP / 100% TN target; documented out-of-scope CVE shapes (pointer-arith
OOB, fprintf-to-popen-pipe, path-traversal) are marked Unsupported / TN.

See `docs/smt-candidates.md` for the candidate JSON schema and the
`--smt-candidates` consumer recipe.

## Testing and Benchmarks

The test suite is a regression net for the project, not proof of full SLEIGH or
decompiler correctness.

Coverage includes:

- golden P-code tests across supported architectures;
- focused regression tests for previously fixed decoder/decompiler bugs;
- CLI integration tests against curated fixtures;
- Ghidra-oracle comparisons for selected instructions and binaries;
- fuzz-style panic checks for random byte streams;
- SEH/static-analysis fixture tests.

There is also a benchmark harness that compares rsleigh output against cached or
fresh Ghidra output:

```bash
make decomp-bench
python3 scripts/decomp-regress.py --binary ./some.bin --sample 12
scripts/bench-compare.sh <binary> [--sample N]
scripts/bench-score.py --binary X --rsleigh target/release/rsleigh --ghidra cached.json --out DIR
```

`decomp-regress.py` is the fast local gate: it compiles deterministic C fixtures
at `-O0` and `-O2`, decompiles selected functions, and fails if output quality
drops against `test-harness/fixtures/bench/pseudocode_baseline.json`. The Ghidra
bench is the heavier reference comparison for periodic runs. These scores are
coarse regression signals, not scientific rankings. Small movements are
expected; repeated and larger drops matter more than single-run noise.

See `docs/TESTING.md` for the current test philosophy and gaps.

## Known Limitations

The most important limitations today:

- The decompiler still loses some use-def links, which can leave variables like
  `iVar1` where the original source-level value should be recoverable.
- Type recovery is shallow. There are useful pointer, bool, signedness, Win32,
  and signature hints, but no full constraint-based type system.
- Stack-frame recovery is heuristic and can misrepresent aliased stack slots or
  structs.
- Control-flow structuring is improving but still prints some awkward or wrong
  shapes for loops, nested branches, and dead regions.
- Floating-point value propagation is incomplete, especially in ARM32 VFP/NEON
  paths.
- Register-indirect calls are only partly resolved.
- MBA/deobfuscation support handles a useful subset, not arbitrary obfuscation.
- Syscall annotations are Windows-build-specific hints.
- Full virtualization protectors remain out of scope for static recovery of the
  original program.

If you need trustworthy answers, use rsleigh as one signal among several.

## Security Posture

The project is intended to run on untrusted binaries, but it has not gone
through a dedicated security audit.

Current posture:

- safe Rust in the API and decompiler crates;
- bounds checks and recursion limits in analysis code;
- fuzz tests aimed at panic-freedom, not semantic correctness;
- no claim of sandboxing, exploit resistance, or service-hardening.

If you expose rsleigh in a network service or automated malware pipeline, isolate
the process and audit the code for your threat model.

## Workspace Layout

```text
rsleigh/
  src/                  SLEIGH parser and code-generation library
  pcode-ir/             P-code IR types and peephole optimizer
  rsleigh-api/          stable decoder/lifter API
  rsleigh-decompile/    experimental P-code to C-like decompiler
  rsleigh-fid/          function-ID database support
  rsleigh-cli/          command-line interface
  rsleigh-generate/     slaspec to generated Rust crates
  generated/            generated decoder crates
  test-harness/         fixtures, oracle tests, fuzz and integration tests
  slaspec/              bundled Ghidra SLEIGH specs
  scripts/              benchmark, oracle, signature, and FID tooling
  docs/                 detailed design and testing notes
```

## Prior Art

- [rbran/sleigh-rs]https://github.com/rbran/sleigh-rs: pure-Rust SLEIGH
  parser work. rsleigh's parser layer is independent; early semantic work was
  forked from sleigh-rs and has since diverged substantially.
- [mnemonikr/libsla]https://github.com/mnemonikr/libsla: Rust bindings to
  Ghidra's C++ libsla.
- jingle_sleigh and related projects: libsla-oriented bindings and tools.

If you only need a SLEIGH frontend and do not need rsleigh's generated Rust
decoder crates or decompiler experiments, one of those projects may be a better
fit.

## Contributing

Issues and PRs welcome. Before opening a PR:

```bash
make test                              # full regression sweep
cargo test -p rsleigh-decompile --release --lib   # fast inner loop
```

Guidelines:

- Land regression tests with bug fixes — the harness in `test-harness/` is the
  primary safety net.
- Keep `rsleigh-api` source-compatible; experimental changes belong in
  `rsleigh-decompile` or behind a CLI flag.
- For new SLEIGH targets, generate decoder crates via `cargo run -p
  rsleigh-generate` and add golden P-code coverage.
- See `docs/TESTING.md` for the test philosophy and `docs/decompiler-passes.md`
  for the pass pipeline.

Near-term focus is reliability over new analysis modes: tighter use-def
linking, broader Ghidra-differential coverage, encoded-instruction fuzzing,
reproducible benchmark fixtures, and clearer separation between stable and
experimental CLI surface.

## License

Apache-2.0. Bundled `.slaspec` files are from Ghidra and are also Apache-2.0.