innospect 0.1.0

Parse and inspect Inno Setup installer binaries
Documentation

innospect

A pure-Rust parser for Inno Setup installer binaries. Provides typed access to the loader stub overlay, setup headers, every typed record stream, and on-demand file extraction across Inno Setup 5.0 through the 7.x preview series — including the modern XChaCha20 / euFiles / euFull encryption modes and the legacy ARC4 + SHA-1 / MD5 password-verifier path.

Built for malware analysis and reverse engineering. The crate is adversarial-input-safe: unsafe_code, panicking unwraps, slice indexing, and arithmetic-with-side-effects are all denied at the lib root.

Crates.io Docs.rs License: Apache-2.0

Features

  • PE locator — both the modern signature-scan path (5.1.5+) and the legacy 0x30 file-offset pointer used by pre-5.1.5 installers; tolerates BlackBox / GOG re-packagers.
  • Setup-0 decompression — Stored / Zlib / LZMA1 / LZMA2 across every documented version cutover, with the standalone-encryption-header path for 6.5+ and the inline-verifier path for 6.4.x.
  • Setup-1 file extraction — solid-LZMA chunks with per-file BCJ inverse filtering (4108 / 5200 / 5.3.9-flip variants) and SHA-256 / SHA-1 / MD5 / CRC-32 / Adler-32 checksum verification at EOF.
  • Per-version TSetupHeader — every String / AnsiString / count / tail field across the 1.x..7.x history, with [Files] / [Run] / [Icons] / [Registry] / [INI] / [Components] / [Tasks] / [Types] / [Languages] / [CustomMessages] / [Permissions] records typed individually.
  • Encryption — XChaCha20 with PBKDF2-SHA-256 key derivation (Inno 6.4+); ARC4 with salted SHA-1 (5.3.9..6.4) or MD5 (4.2..5.3.9); CRC32 verifier (pre-4.2). Password trial via from_bytes_with_passwords.
  • Embedded [Code] / IFPS — re-exports the pascalscript container parser; the inno_api_description table maps Inno-runtime imports (RegWriteStringValue, Exec, ShellExec, …) to one-line summaries for triage.
  • Analysis APIexec_commands() / registry_ops() / shortcuts() walk the relevant record streams and tag each entry with install-vs-uninstall phase, registry-write classification, or icon-target resolution.
  • Uninstaller reconstructionextract_uninstaller() patches the loader stub bytes back to the canonical InUn form.

Quick start

use innospect::{HeaderOption, InnoInstaller, RegistryOpKind};

let bytes = std::fs::read("setup.exe")?;
let inst = InnoInstaller::from_bytes(&bytes)?;

let v = inst.version();
println!("Inno {}.{}.{}.{}  {}", v.a, v.b, v.c, v.d, v.marker_str());

if let Some(h) = inst.header() {
    println!("AppName: {}", h.app_name().unwrap_or(""));
    println!("Files:   {}", h.counts().files);
    println!("Encrypted: {}", h.has_option(HeaderOption::Password));
}

// Extract every file (skipping the uninstaller-stub entry).
for (file, contents) in inst.extract_files().filter_map(Result::ok) {
    println!("{} -> {} bytes", file.destination, contents.len());
}

// Analysis: exec commands, registry writes, resolved shortcut targets.
for cmd in inst.exec_commands() {
    println!("{:?} {} {}", cmd.phase, cmd.filename(), cmd.parameters());
}
let writes = inst
    .registry_ops()
    .filter(|op| op.kind == RegistryOpKind::Write)
    .count();
println!("Registry writes: {writes}");

For password-protected installers:

use innospect::{Error, InnoInstaller};

let bytes = std::fs::read("encrypted-setup.exe")?;
match InnoInstaller::from_bytes_with_passwords(&bytes, &["test123"]) {
    Ok(inst)                   => println!("Unlocked with {:?}", inst.password_used()),
    Err(Error::PasswordRequired) => eprintln!("Installer is encrypted; no password supplied"),
    Err(Error::WrongPassword)    => eprintln!("None of the candidate passwords matched"),
    Err(e)                       => eprintln!("Parse failed: {e}"),
}

A full inspection tool ships in examples/dump.rs:

cargo run --example dump -- path/to/setup.exe

Coverage

End-to-end tested against:

  • Real-world: HeidiSQL 12.17 (6.4.0.1), ImageMagick 7.1.2 (6.1.0).
  • Synthetic plain matrix: 5.0.8, 5.1.14, 5.2.3, 5.3.11, 5.4.3, 5.5.5, 5.5.7, 6.0.0u, 6.3.0, 6.4.3, 6.5.2, 6.5.2-alt, 6.6.1, 6.7.0, 7.0.0-preview-3.
  • Synthetic encrypted matrix (password test123): same version ladder for enc-files-* (per-chunk encryption) plus enc-full-* for 6.5.2+ (euFull whole-stream encryption).

Format paths recognized but not yet exercised by the public sample matrix: 4.x, 3.x / 2.x, 16-bit 1.2.x, ISX (My Inno Setup Extensions), and multi-slice (DiskSpanning=yes).

Minimum Rust version

1.88 (edition 2024). Pinned by rust-version in Cargo.toml; the CI matrix exercises both 1.88 and stable.

License

Apache-2.0. See LICENSE.