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:
- 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.
- 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.
- 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.
- 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
--dumpthe 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
--dumpof 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/aarch64 → arm64, i386/ia32
→ x86, mips32/mipsel/mipsbe → mips, mediatek → nanomips,
powerpc → ppc, qdsp6/qdsp6v65 → hexagon, qdsp5/qdsp4000
→ qdsp, esp32/esp8266/lx6/lx7/tensilica → xtensa,
huffman/deflate/lz → compressed, 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 exponent03 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:
- Probe 1 — per-byte variability map (which offsets vary, by how much, and the longest contiguous variable runs).
- Probe 2 — constant-XOR-pair detection (static-keystream smoking gun).
- Probe 3 — byte values vs. build dates (monotone byte-position regressions = version counters / build timestamps).
- Probe 4 — cross-version known-plaintext search.
- 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).
- Probe 6 — IV-reuse / static-prefix collisions: for CBC + static IV, multiple versions share the same first 16+ bytes of ciphertext.
- Probe 7 — per-version PKCS#7 padding-oracle detector (tail-shape signatures at block sizes 8 / 16 / 32).
- Probe 8 — crib-drag across pairs (when
--crib-textis passed, slides each crib byte-wise over every XOR pair looking for high-printable-density windows). - Probe 9 — corpus clustering by pairwise dissimilarity (groups near-byte-duplicates so the analyst can pick a smaller representative set).
- 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.
- Probe 11 — per-window entropy time-series correlated against
build dates (when
--diff-datesis supplied), surfacing entropy drifts at fixed offsets. - Probe 12 — codec-drift clustering by KL divergence on byte histograms.
- Probe 13 — per-version keystream cross-validation across
triples (
c_va ⊕ c_vbshould agree withc_va ⊕ c_vcfor a true static-keystream cipher). - 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.
- 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 a2^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
0xFFescape 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/u32reads.
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
endianmodule'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.