scrutari

20 min read Original article ↗

scrutari (Latin, from scruta, "trash"): to ransack through piles of rubbish in hope of finding hidden value.

A forensic statistical analyzer for opaque firmware blobs. Given a packed or encrypted-looking binary, scrutari figures out whether the high-entropy region is compressed, encoded, encrypted, or otherwise structured, and what kind of coder produced it — without needing the algorithm specification, the original tooling, or a working disassembler.

It does this in four layers:

  1. Byte and bit statistics. Entropy, χ², mutual information, autocorrelation, n-gram diversity, bit-level χ² across 4/8/12-bit widths in both LE and BE byte-order interpretations. The bit-level test alone classifies most payloads into fixed-width packing vs. prefix/Huffman vs. range/arithmetic, and the LE-vs-BE pair surfaces hidden u32-aligned structure on firmware compiled for big-endian hosts.
  2. Encryption-detection probes. Block-cipher fingerprint (uniqueness ratios + per-position χ²), Markov mutual information per N-byte block, period detection, longest-repeat substring (via a true suffix array), crypto-constant catalogue (Blowfish full P-box + S1-S4 + AES T-tables + SHA-512 K
    • ChaCha20 sigma + Twofish + Camellia + TEA delta + CRC family — LE+BE variants), explicit ECB-mode duplicate-block detector at N=8/16, periodic-XOR-keystream sweep (Kasiski/Friedman: payload XOR payload<<lag for lag 2..1024), and an optional Hamming-distance probe against a known plaintext reference. See docs/encryption-detection.md.
  3. Brute force. 23 decoder families try every offset / parameter combination over the candidate payload region: DEFLATE inflate, LZ4, LZO1X, aPLib, LZMA1, word-LZSS, empirical Huffman, LZ77+preloaded Huffman, a custom-LZ variant sweep, fixed-width bit-unpacking, MTF-inverse, five Huffman variants (pair-list, RLE-counts, word16, multi-table, FGK-adaptive), and five RLE variants (escape, PackBits, zero-RLE, bit-run, dictionary). Plus structural detectors for Qualcomm q6zip / delta-compress with LE+BE byte-order sweeps and a 51-entry magic catalogue covering HiSilicon hi3520, MediaTek BCR, FastLZ, miniLZO, LZ4-HC, Apple LZFSE / LZHAM, Snappy, Qualcomm SBL, Apple IMG3/IMG4, Samsung SBOOT, TI Davinci AIS, BE filesystem variants (ext2/3/4 BE, cramfs BE, yaffs1, FIT/DTB BE), and the usual squashfs / JFFS2 / UBI / U-Boot / Android-boot / zstd / xz.
  4. Oracle filtering. Each candidate decode is scored by a caller-selected "is this real code?" oracle. The oracle is a trait (see docs/oracle-howto.md); scrutari ships with oracles for every major embedded-firmware ISA, listed below.

Beyond these single-image probes, scrutari supports building corpora of related firmware versions for cross-version differential analysis; see docs/differential-analysis.md for the methodology.

Scope: triage, not extraction

scrutari is a triage and fingerprinting tool. It answers "what is this blob — cipher, compressor, codec, obfuscation — and what kind?" and points you at the next step. It deliberately does not unpack filesystems or carve out the contained files, and it is not a binwalk/unblob replacement:

  • It does not extract or reconstruct a filesystem (SquashFS, JFFS2, UBIFS, cramfs, …), even when its magic catalogue recognises one.
  • It does not decrypt: it can tell you a region is AES-ECB, a periodic-XOR keystream, or an ESP32-style AES-XTS blob, and can recover a static XOR keystream, but it will not produce plaintext from a real block/stream cipher without the key.
  • It does not decompress a recognised container into its members; the brute-force decode search reports that (and roughly where) a codec stream decodes and can --dump the single best decode, but it is a detector, not a general unpacker.

Use scrutari to decide what a blob is and how to attack it, then hand off to the right specialised tool: binwalk/unblob for known containers, the vendor/openssl path once a cipher and key are identified, or a dedicated decompressor for a confirmed codec. The worked cases in docs/examples.md follow exactly this "fingerprint here, extract elsewhere" flow.

Status

Research tooling. The output is:

  • a coloured CLI summary, including a region map, payload fingerprint, bit-level family guess (LE + BE byte-order passes), top decode candidates, encryption-fingerprint block, Kasiski/Friedman periodic- XOR sweep, ECB-mode block-collision detector, and a verdict;
  • a multi-page Typst report (compile to PDF) with entropy curves, bigram heatmaps, autocorrelation plots, histograms, the full sweep results, the encryption-probe panel, crypto-constant hits, and the Hamming-distance histogram;
  • an optional raw --dump of the best decode for manual inspection.

When every codec family comes up empty, the conclusion is itself useful: a negative result over a sufficiently broad parameter space, combined with cipher-shaped block statistics, is evidence of encryption rather than compression. The encryption-detection probes also disambiguate which kind of cipher: a proper block/stream cipher (uniform per-position χ², near-50% Hamming vs reference) vs. a static-XOR keystream (the broken-crypto failure mode every embedded vendor flirts with).

Probe inventory

Probe family Endianness handling
Byte-level entropy, χ², serial correlation, IoC byte-order invariant
Bit-level: balance, run spectrum, symbol χ², autocorrelation LE + BE sweep (bitlevel::compute_with_order)
Markov mutual information (arbitrary order 1..16 sweep) byte-order invariant
Higher-moment fingerprint (m5..m8) byte-order invariant
Period detection, suffix-array longest-repeat byte-order invariant
Cipher fingerprint (block uniqueness + position χ²) byte-order invariant (positions are global)
Kasiski / Friedman periodic XOR sweep byte-order invariant
Crypto-constant catalogue (existing tables) LE + BE variants for every multi-byte table
Modular-arithmetic constants (NIST/Brainpool/SEC-Koblitz/SM2/GOST/Curve25519/Curve448 ECC primes; RFC 2409/3526/5114 MODP, RFC 7919 FFDHE, RFC 5054 SRP groups, plus the RFC 5114 group-22/23/24 generators & prime-order subgroups) LE + BE sweep, 48 catalogue entries
RSA-modulus shape candidates (1024..8192-bit) BE only
Post-quantum crypto blobs (Kyber / Dilithium / Falcon / SPHINCS+) size-based, 18 catalogue entries
Hash-output zones (MD5 / SHA-1 / SHA-256 / SHA-384 / SHA-512) aligned scan, 5 sizes × 6 strides
AEAD tag candidates (Poly1305 / GMAC) trailing-tag layouts at end of buffer
MAC / authentication-tag candidates (HMAC-SHA-1/256/384/512, CMAC, Poly1305, RSA-1024/2048/3072/4096 signatures) fixed-size high-entropy windows at the head of multi-KiB padding tails
Compression-family fingerprints (DEFLATE-dynamic / range-coder / ANS-FSE) bit-level for DEFLATE, statistical for the rest
LZ codec family (LZ4-frame / Snappy / LZJB / FastLZ) magic-based + statistical
q6zip / delta-compress structural detectors LE + BE sweep
Auto-paramblock detection (multi-granularity 1 KiB primary + 256 B fine entropy-island isolated by uniform borders) byte-order invariant
Hamming-distance probe vs --ref byte-order invariant
Multi-codec brute-force decode (23 families) each family chooses its own order
Magic-byte signatures (51 entries) per-format LE/BE variants where both encodings exist

In --diff (multi-version corpus) mode, scrutari runs 15 probes plus the paramblock-fingerprint clustering primitive. Probes 1-10 cover the single-corpus toolkit (variability map, pairwise XOR, time-series, plaintext search, byte-level leaks, IV-reuse, padding-oracle, crib-drag, clustering, per-version oracle scoring). Probes 11-15 add per-window-entropy time-series, codec-drift clustering by KL divergence, per-version keystream cross-validation across triples, auto-paramblock + paramblock-fingerprint integration that detects and clusters cipher-domain boundaries end-to-end, and cross-version key-reuse / shared-prefix detection that surfaces accidental AES key + IV reuse between builds as a byte-level "≥ 16 KiB identical leading-bytes" signature.

For static-keystream targets, scrutari also exposes the --anchor, --apply-keystream, and --crib flags that chain into a complete keystream-recovery workflow without external scripting; see Two-time-pad keystream recovery below.

See docs/examples.md for worked, real-world walk-throughs of the analysis methodology — single-image probes, the compression-vs-cipher verdict, and cross-version diff. docs/encryption-detection.md documents each single-image probe, docs/differential-analysis.md covers the cross-version corpus methodology, and docs/oracle-howto.md walks through adding an oracle for a new ISA.

Architecture

+-------------------------+    +-------------------------+
|      scrutari-scan      |    |     scrutari-report     |
|  (platform-agnostic)    |    |  (rendering: Typst,     |
|                         |    |   ANSI console)         |
|  - stats / regions      |    |                         |
|  - bitlevel / lz        |    |  consumes Analysis      |
|  - huffman / codecs     |    |  produces .typ + .pdf   |
|  - bruteforce           |    |                         |
|  - cipher_fp            |    |                         |
|  - crypto_constants     |    |                         |
|  - hamming_probe        |    |                         |
|  - codec_detect (q6zip) |    |                         |
|  - advanced_stats       |    |                         |
|  - mac_detect           |    |                         |
|  - differential (15)    |    |                         |
|  - Oracle trait         |<---|                         |
+------------A------------+    +-------------------------+
             |
             | Oracle impls
             |
+------------V------------+
|        scrutari         |
|  (binary crate)         |
|                         |
|  - CLI parsing          |
|  - src/oracle/<name>    |  <- 20 per-ISA / data oracles
|                         |     (arm32/64, mips, riscv,
|                         |      x86, ppc, sparc, c55x±,
|                         |      c6x, hexagon, xtensa,
|                         |      strings, cstrings, ...)
+-------------------------+

Three crates, one binary. The scanning library has no knowledge of any particular ISA; the binary's --oracle flag picks one and passes it to the scanner.

Build & run

cargo build --release
./target/release/scrutari firmware.bin --oracle <ISA> --pdf report.pdf

--oracle is required: there's no default. Pick the ISA whose code shape you want to score candidate decodes against — see the table below. Multiple oracles can be combined (one code oracle plus any number of data oracles) by passing a comma-separated list:

./target/release/scrutari firmware.bin --oracle arm32,strings

The codec sweep ranks recipes by max() of the per-oracle scores; the report shows the per-oracle breakdown inline in every relevant table.

Useful options:

--oracle NAME[,NAME...]  code-shape oracle(s) (required). Combine
                         one code oracle with any number of data
                         oracles, e.g. `c55x+,strings`.
--payload-start 0xN      explicit payload region (overrides auto-detect)
--payload-end   0xN
--limit N                max decode bytes per recipe (raise for deeper sweeps)
--deep                   widen every parameter grid, walk every offset
--ref FILE               reference plaintext for the Hamming-distance probe
                         (e.g. a related bootloader or .text dump)
--plaintext FILE         plaintext side-channel search (XOR + byte permutation
                         sweep across the payload for the given plaintext;
                         strong XOR hits get auto-extended across the full
                         payload and oracle-scored)
--dump out.bin           write the best decode to a file for inspection
--diff F1 F2 ...         cross-version differential analysis
--cross F1 F2 ...        cross-firmware crypto-constant correlation
                         (find shared non-catalogue S-boxes / T-tables
                         across unrelated firmware blobs)

Output formats — independent flags, any combination is allowed. When no format is requested, text goes to stdout by default:

--text PATH              text report (use `-` for stdout)
--json PATH              machine-readable JSON (use `-` for stdout)
--typst FILE.typ         Typst source for the report
--pdf FILE.pdf           PDF; needs `typst` on PATH and implies a
                         sibling .typ when --typst is not given
--diff-max-render-versions N
                         cap the visual sections of the differential
                         Typst report at N representative versions
                         (numerical probes always run on the full
                         corpus). Required when --diff has more
                         than ~16 versions and --typst or --pdf is
                         requested.

Oracles

scrutari ships a code-shape oracle per major embedded ISA. Each oracle has a kind: Code (ISA-specific machine-code detector) or Data (non-code shape detector like .rodata strings). The CLI enforces a simple rule: at most one Code oracle, any number of Data oracles. Combining different ISAs (arm32,mips) is rejected; combining a code oracle with a data oracle (arm32,strings) is the common case.

name kind target
c55x+ code TI TMS320 C55x+ (extended ISA)
c55x code TI TMS320 C55x classic
c6x code TI TMS320 C6000 family (C62x/C64x/C67x/C674x)
arm32 code ARM 32-bit (ARMv5/v6/v7), both ARM + Thumb-2
arm64 code AArch64 (ARMv8+)
mips code MIPS32 (BE or LE; auto-detected)
nanomips code MediaTek nanoMIPS
riscv code RISC-V (RV32 / RV64, with or without C-ext)
x86 code x86 32-bit (i386 / IA-32)
ppc code PowerPC 32-bit (BE or LE; auto-detected)
sparc code SPARC V8/V9
hexagon code Qualcomm Hexagon QDSP6 (V1–V79+)
qdsp code Qualcomm QDSP4/QDSP5 (pre-Hexagon, heuristic only)
xtensa code Tensilica Xtensa LX6/LX7 (ESP8266/ESP32, basebands)
strings data ASCII string tables — for .rodata decodes
cstrings data C-style null-terminated string tables
ptrtable data Pointer tables / vtables (aligned address arrays)
vectortable data Interrupt-vector / ISR tables
floattable data IEEE-754 f32/f64 number arrays
base64 data Base64 / PEM-encoded blobs
delta_numeric data Delta-encoded numeric sequences (PCM, ADPCM)
compressed data Compressed-data shape (Huffman / DEFLATE / LZ) — high
marginal entropy + low 4-gram diversity. Useful to
distinguish compressed blobs from cipher output.
null code no-op (always returns 0.5; for testing)

Each code oracle scores how plausibly a buffer's bytes look like real machine code for its target — combining instruction-length / alignment checks, common-opcode rate, entropy band, and an n-gram diversity gate that catches LZ false positives. The strings oracle scores ASCII-text-table likeness instead of code likeness, and is the right pick when an analysed region looks like a .rodata symbol table rather than executable code. The qdsp oracle is heuristic-only — the QDSP4/5 ISA is not publicly documented and its score is confidence-capped accordingly; treat scores above 0.40 as a "likely candidate, investigate further" signal.

Many aliases are accepted (armv8/aarch64arm64, i386/ia32x86, mips32/mipsel/mipsbemips, mediateknanomips, powerpcppc, qdsp6/qdsp6v65hexagon, qdsp5/qdsp4000qdsp, esp32/esp8266/lx6/lx7/tensilicaxtensa, huffman/deflate/lzcompressed, etc.). Case is ignored.

Side-channel cipher state

Even when the cipher code is obfuscated, the data segment of any binary that calls a hash or EC primitive contains recognisable initial state. scrutari scans for:

  • SHA-256/-224/-1/-512/-384 initial H values (BE and LE encodings).
  • MD5 A/B/C/D initial state.
  • Generator points: NIST P-256, secp256k1, Curve25519 base, Ed25519 base.
  • IV/counter patterns at the payload prefix: 16 zero bytes (CTR/GCM starting at 0), or a 4-byte field that looks like a Unix timestamp (BE or LE, range 2000-01-01..2050-01-01).
  • Motorola baseband signing-trailer tag (b4 01 00 ca) — a fixed 4-byte record marker that introduces the per-build signing trailer (embedded RSA-1024 key(s) + signature material) in Motorola baseband firmware. It is an observed magic, not an i.MX HAB CSF command (these images carry no Image Vector Table).
  • Motorola RSA-1024 public-key encoding header (02 00 00 03 01 00 01 00 80) — the 9-byte preamble that precedes every embedded RSA-1024 modulus in the signing trailer: a tag/reserved field, the length-prefixed exponent 03 01 00 01 (e = 65537), and a 16-bit modulus length (00 80 = 128 bytes).

Hits appear in the side-channel cipher state section of the single-file report.

Cross-version differential analysis

./target/release/scrutari --diff v1.bin v2.bin v3.bin \
    --diff-dates 20240101,20240301,20240601 \
    --plaintext bootloader.bin \
    --typst diff.typ --pdf diff.pdf

--diff-dates is optional; it enables Probe 3 (byte-position vs. build-date correlation). --plaintext enables Probe 4 (cross-version known-plaintext search). Any number of versions ≥ 2 is supported.

The differential report has its own cover, TOC, and one section per probe:

  1. Probe 1 — per-byte variability map (which offsets vary, by how much, and the longest contiguous variable runs).
  2. Probe 2 — constant-XOR-pair detection (static-keystream smoking gun).
  3. Probe 3 — byte values vs. build dates (monotone byte-position regressions = version counters / build timestamps).
  4. Probe 4 — cross-version known-plaintext search.
  5. Probe 5 — byte-level XOR plaintext leaks: XOR every version pair and scan for ASCII windows, counter-zero runs, and magic XOR signatures (ELFCLASS32^64, PE^ELF, Mach-O 32^64).
  6. Probe 6 — IV-reuse / static-prefix collisions: for CBC + static IV, multiple versions share the same first 16+ bytes of ciphertext.
  7. Probe 7 — per-version PKCS#7 padding-oracle detector (tail-shape signatures at block sizes 8 / 16 / 32).
  8. Probe 8 — crib-drag across pairs (when --crib-text is passed, slides each crib byte-wise over every XOR pair looking for high-printable-density windows).
  9. Probe 9 — corpus clustering by pairwise dissimilarity (groups near-byte-duplicates so the analyst can pick a smaller representative set).
  10. Probe 10 — per-version oracle scoring (when an oracle is supplied), highlighting the per-version spread — strong evidence of a moving encryption boundary or codec parameter.
  11. Probe 11 — per-window entropy time-series correlated against build dates (when --diff-dates is supplied), surfacing entropy drifts at fixed offsets.
  12. Probe 12 — codec-drift clustering by KL divergence on byte histograms.
  13. Probe 13 — per-version keystream cross-validation across triples (c_va ⊕ c_vb should agree with c_va ⊕ c_vc for a true static-keystream cipher).
  14. Probe 14 — auto-paramblock + paramblock-fingerprint integration. Multi-granularity entropy-island detection at 1 KiB primary + 256 B fine, intersection across versions, and clustering of the agreed range by FNV-1a fingerprint — surfaces per-build wrapped DEKs and family-static cipher anchors in a single line per cluster.
  15. Probe 15 — cross-version key-reuse / shared-prefix detection. For every file pair, finds pairs with ≥ 16 KiB of identical leading ciphertext bytes — the byte-level signature of an accidental AES key + IV reuse across builds, or a deliberately shared first-stage bootstrap.

Visual sections include a stacked per-version bytemap (one entropy strip per version, aligned vertically by file offset — the closest single-page visual to a true byte-level diff), the cross-version entropy overlay, and per-version histograms.

Cross-firmware crypto-constant correlation

./target/release/scrutari --cross vendor_a.bin vendor_b.bin vendor_c.bin

Scans every named file for high-entropy aligned constant tables (256, 1024, and 4096-byte sizes; 192+ distinct bytes at the 256-byte size, 200+ at 1024, 220+ at 4096) and reports those that appear in two or more files but don't match any entry in the known-cipher catalogue (Blowfish, AES, ChaCha, SHA, CRC, etc.).

A shared non-catalogue 256-byte S-box across two unrelated firmware blobs is strong evidence both ship the same vendor-private codec or cipher. Reverse-engineer one and the table tells you where to look in the others.

Per-region oracle scoring

Single-image analyses include a per-region oracle scores section under the segment map, showing each detected region's raw-bytes oracle score(s). A high score in a region that isn't the auto-selected payload is a hint to re-run with explicit --payload-start / --payload-end — common on signed firmware where small ARM AP code segments hide between padding and the encrypted main payload.

Suggested next probes

The CLI summary ends with a rules-based suggester that prints up to five concrete next steps based on the analysis state: which flag to pass next (--ref / --plaintext / --deep / --cross), which region to re-target, which decode limit to raise, which side-channel hits to investigate. Skipped silently when a decode already succeeded.

Static-key XOR-reversal

When --plaintext finds a strong XOR alignment, scrutari now extends the candidate keystream periodically across the full payload and oracle-scores the result. Detected period, oracle score, and the first 32 bytes of the recovered plaintext are shown in the plaintext-search section. The closest scrutari gets to "actually decrypting" — without implementing arbitrary ciphers.

Extended statistical fingerprint

Beyond Shannon entropy, χ², and adjacent-byte mutual information, the single-image report also surfaces:

  • LZ76 complexity and LZ/Shannon ratio — model-free estimator that converges to the true source entropy including memory effects. A ratio near 1.0 means IID source (real cipher); below 1.0 means the source has internal structure (CBC over compressible plaintext, OFB chains, delta-coded numerics).
  • Kolmogorov-Smirnov and Anderson-Darling statistics. KS is sensitive to localised over-representation that χ² spreads out; Anderson-Darling is tail-weighted. The KS + χ² + entropy triple separates well-distributed sources from structured ones more cleanly than any single statistic.
  • Higher-order conditional entropy H(b[i+k] | b[i..i+k]) via a 2^16-bin rolling-hash joint distribution, sampled at orders 1, 2, 4, 8, 16. A sharp drop at order = block-size identifies CBC / OFB / CFB feedback even when order-1 MI is zero.

Suffix-array pattern mining

The suffix array also surfaces every distinct substring of length ≥ 32 that appears ≥ 2 times in the payload sample (not just the single longest repeat). Empty on a real cipher; on a static-XOR keystream cipher over structured plaintext (memset regions, aligned function prologues, identical padding) the list is dozens of entries clustered at predictable strides.

Deep-region constant scan

The side-channel constant catalogue (hash IVs, EC generator points) now runs in two passes: once over the whole file as before, plus a re-pass explicitly over the analysed payload region. Surfaces cipher state hidden inside the high-entropy ciphertext rather than at file- prefix offsets — common when the cipher implementation initialises state inline rather than at boot.

Padding-oracle detector

Single-image PKCS#7 padding detector inspecting the final block at sizes 8 / 16 / 32. Reports any tail matching N copies of byte N with a head-mismatch filter to avoid false positives. Either a buggy CBC-without-MAC implementation that left plaintext padding visible, or an unencrypted payload misclassified as cipher, will fire this probe.

Two-time-pad keystream recovery

When two ciphertexts encrypted under the same key are available, c1 ⊕ c2 = p1 ⊕ p2 cancels the keystream and exposes the XOR of the plaintexts. Scrutari supports two complementary entry points:

Standalone --crib mode is the one-shot solver:

./target/release/scrutari --crib c1.bin c2.bin \
    --crib-text "the " --crib-text "GET /" --crib-text $'\x7fELF'

Loads the two ciphertexts, computes c1 ⊕ c2, drags each --crib-text string across the result, and prints the top-16 candidate offsets ranked by printable-ASCII score. The scorer is pluggable via crib_drag::drag_crib_with_scorer — swap in an n-gram LM or a code-shape oracle when the underlying plaintext isn't English prose.

In-corpus pipeline for static-keystream payloads, chained across the three new flags:

# 1. Crib-drag identifies one plausible (offset, plaintext byte) pair.
scrutari --crib v1.bin v2.bin --crib-text "the lazy "

# 2. Anchor that byte and propagate through Probe 2's XOR-pair graph;
#    emits one "0xOFFSET=0xBB" line per recovered keystream byte.
scrutari --diff v*.bin --anchor 0x1f=0x16 > keystream.txt

# 3. Apply the recovered keystream to one ciphertext, oracle-score
#    the covered region, dump the candidate plaintext.
scrutari v1.bin --oracle strings --apply-keystream keystream.txt \
    --dump recovered.bin

The keystream_recovery and crib_drag modules also expose xor_ascii_runs and drag_crib as a library API for analyst scripts that want finer control than the CLI provides.

Differential-encoder hypotheses

For payloads that aren't encrypted but are encoded as a delta / predictor / DoD stream (audio codec parameters, font tables, codebooks, monotonic counter sequences), scrutari now tries three inverse-transform hypotheses on every 4 KiB window:

  • Variable-width delta (signed-8 with 0xFF escape to signed-16).
  • Second-order linear-predictor residual r[i] = b[i] - 2*b[i-1] + b[i-2].
  • Delta-of-delta on word-aligned u16 / u32 reads.

Each candidate is scored by the byte-entropy drop between the raw window and the inverted residual. Drops ≥ 0.4 b/B are reported, sorted by drop descending. Monotonic u32 counter sequences produce a near-zero residual entropy (drop ≈ raw entropy), unambiguously identifying the encoding.

Adding a new oracle

To analyse firmware for, say, Cortex-M Thumb-2 code, drop a new file at scrutari/src/oracle/thumb2.rs:

pub struct Thumb2Oracle;
impl scrutari_scan::Oracle for Thumb2Oracle {
    fn name(&self) -> &'static str { "thumb2" }
    fn code_likeness(&self, buf: &[u8]) -> f64 {
        // Inspect 16-bit words, count common Thumb-2 prefixes, check
        // PC-relative load alignment, etc. Return a score in [0, 1].
        0.0
    }
}

Register it in scrutari/src/oracle/mod.rs — add pub mod thumb2;, extend AVAILABLE, and add a match arm in by_name. The scanning pipeline picks up the new oracle without any other changes. See docs/oracle-howto.md for a worked example.

Test coverage

The scrutari-scan crate ships with:

  • Unit tests for every codec, every Huffman/RLE variant, every statistical measure, every encryption-detection probe, the suffix-array, Kasiski sweep, ECB detector, crypto-constant catalogue, q6zip LE+BE sweep, the endian module's byte-order helpers, plus the FFT spectral, plaintext-search, and differential-analysis probes (15 probes total).
  • Invariant tests (rotational, scale, monotonicity, range) with a deterministic in-tree PRNG (scrutari-scan/tests/invariants.rs).
  • Property-based tests (scrutari-scan/tests/proptest_props.rs, opt-in via --features proptest) exercising every decoder for safety, time bounds, the ≥64-byte output invariant, and all probes.
  • External cross-validation against SciPy/NumPy/SymPy reference implementations in tests/verify/run_all.py — 418 cases across 13 modules (stats, advanced_stats, cipher_fp, bitlevel, entropy_dips, known_plaintext, markov, spectral, permutation_entropy, differential, nist, format_id, fragment).

Plus tests for every bundled oracle in the binary crate and the layout helpers in scrutari-report. 491 / 491 passing under workspace layout (100 binary + 9 report + 375 scan + 7 invariants). Run with:

cargo test --release --workspace                                  # unit + invariants
cargo test --release --features proptest -p scrutari-scan         # + proptests (rustc >= 1.85)
cargo build --release --example verify_probe -p scrutari-scan     # build verifier helper
python3 tests/verify/run_all.py                                   # SciPy/NumPy/SymPy

(--features proptest requires rustc >= 1.85 — proptest's transitive dependencies use edition2024.)

License

LGPL-3.0-or-later. See LICENSE.md. All source files carry SPDX headers; modifying a single file requires only that you preserve or update its header.