Skip to content

ot2i7ba/HashHunter

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

5 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

HashHunter

HashHunter indexes a storage medium (file name, path, size, timestamps, MD5/SHA-1/SHA-256), matches every file against reference hash lists, and turns the run into a self-contained case: a SQLite database of every file it saw, a tamper-evident audit log, a signable integrity manifest, and a report as PDF and CSV. It ships as a desktop application and a command-line tool built on the same engine.

Important

HashHunter compares exact hashes. It finds bit-identical copies of known files — nothing more, nothing less. It is not a similarity, carving, or content-analysis tool.

Warning

Please note that this script is currently under development, and I cannot provide a 100% guarantee that it operates in a forensically sound manner. It is tailored to meet specific needs at this stage. Use it with caution, especially in environments where forensic integrity is critical.

Why HashHunter?!

Hash matching is everyday casework: import a list of known hashes, run it against an evidence copy, document the hits. The annoying part has never been the matching — it is everything around it. Which files were skipped, and why? Which list version was actually loaded? Can I still prove, months later, that the report belongs to this exact database? Most setups answer these questions with a shrug, a heavyweight suite, or a folder full of loose scripts.

HashHunter grew out of that frustration. It does one job, does it fast, and treats the documentation as part of the job instead of an afterthought: every file ends in a defined state, every action lands in a hash-chained log, and the reference lists used by a scan are frozen with the case. The GUI exists so that colleagues who steer clear of the command line can run a clean, fully audited scan too.

Table of Contents

Features

  • Single-pass hashing — every file is read once and fed through MD5, SHA-1, and SHA-256 in a single pass; files are hashed in parallel and streamed in chunks: memory stays flat, progress is committed as the scan runs, and a cancelled scan preserves everything completed up to that point. Live progress with throughput and ETA.
  • Reference database on LMDB — binary keys in separate namespaces per hash type, bulk import in a single transaction, and a per-list index that makes deleting or replacing a list proportional to the list's own size. NSRL-scale sets are the intended workload.
  • Alert and exclude lists — flag known-bad files, account for known-good ones (e.g. NSRL). Both outcomes are recorded; a file in both kinds of list is still flagged.
  • Triage filters — restrict a scan by file type (images/videos/audio/documents — by extension, or by magic bytes to catch renamed files), by size, and by modification date. An active filter is always disclosed in the audit log and the report, and malformed filter input aborts the scan instead of quietly matching nothing.
  • Case database on SQLite — WAL mode, batched writes, one self-contained folder per case.
  • Tamper-evident audit trail — every action is appended to a SHA-256 hash chain; a single altered byte breaks the chain verifiably. The integrity manifest covering the case artifacts can be signed with Ed25519 and verified later. The reference database keeps its own chained log of imports and deletions.
  • Reports — a paginated PDF plus spreadsheet-friendly CSVs (UTF-8 BOM, semicolon-separated, formula injection neutralized). With the embedded typst renderer the PDF is properly typeset — and the tool still ships as a true single executable; the typst source is archived with the case.
  • Windows specifics — live volumes can be scanned through a read-only VSS shadow copy (administrator rights required), and paths longer than 260 characters are handled.
  • GUI and CLI — a Slint desktop interface for everyday use, a command-line binary for scripted or headless runs. Both are thin shells over the same engine, so behavior cannot drift between them.

What ends up on record

The core guarantee: every enumerated file ends in exactly one defined state — there are no silent failures. A file only counts as "no match" if it was provably read, hashed, and checked.

Status Meaning
match_confirmed Matched at least one alert list — a reportable finding
excluded_known Matched exclude lists only — known and accounted for, not reported
no_match Hashed and checked against the reference database, no match
hashed_successfully Hashed in an index-only run (no reference database present)
access_denied / read_error Could not be read — recorded with the exact error
changed_during_processing Size or mtime changed while being read — flagged, not matched
not_verifiable Scan was cancelled before this file was examined

Paths that cannot even be enumerated (an unreadable directory hides its whole subtree) are recorded as enumeration_error entries, audited, and disclosed in the report summary — a gap never masquerades as a complete scan.

Requirements

  • Rust (stable, via rustup); the embedded typst report requires a recent stable toolchain.
  • Windows: the Visual Studio Build Tools with the "Desktop development with C++" workload. The included Windows SDK also embeds the executable icon. VSS scanning is Windows-only and needs administrator rights.
  • A C compiler for the bundled LMDB and SQLite. No cmake, no fontconfig, no external services, no network access at runtime.

Linux and macOS build and run the CLI and the core the same way; VSS is simply unavailable there.

Building

  1. Install the MSVC Build Tools and rustup (see above).

  2. Clone the repository somewhere outside OneDrive-synced folders — sync clients and Controlled Folder Access like to hold locks on build artifacts:

    git clone https://github.com/ot2i7ba/HashHunter.git C:\dev\hashhunter
  3. Build the GUI (its own workspace; typst is embedded by default, so the result is a true single EXE):

    cd C:\dev\hashhunter\crates\hh-gui
    cargo build --release

    Result: crates\hh-gui\target\release\hh-gui.exe

  4. Build the CLI from the repository root:

    cargo build --release -p hh-cli --features typst-embed

    Result: target\release\hashhunter.exe — leave out --features typst-embed for a lighter build that uses the built-in PDF fallback.

Note

The first release build takes a while: every dependency is compiled once, and the final link runs thin LTO for a smaller, faster executable. Subsequent builds reuse the cached dependencies and are much quicker.

Tip

For a portable executable that runs without the VC++ redistributable, set RUSTFLAGS=-C target-feature=+crt-static before building.

Usage

The working directory (tool root) defaults to the folder the executable sits in. Hashes\ (the installation-wide reference database) and Cases\ (one folder per case) are created next to it on first use; --root overrides this.

Desktop application

Start hh-gui.exe. Everything is reachable through seven tabs:

Tab Purpose
Scan Pick source and case name, set triage filters, run and cancel with live progress
Manual Paste individual hashes and check them against the reference lists
Matches Review the current case's hits, open the report or the case folder
DB Browse the indexed files of a case — search, sort, filter by status
Export Copy confirmed matches out of the case, explicitly confirmed and audited
Import Import reference lists, review and delete them
Settings Workers, I/O profile, symlink handling, signing key, manifest verification

Command line

Command Purpose
import-hashes Import a reference list (--kind alert or --kind exclude)
refdb-info Show the reference catalog
delete-list Remove a reference list completely
scan Index and match a source into a new case
query-hash Check individual hashes manually
report Regenerate the case reports
export Copy confirmed matches out of a case (requires --confirm-export)
keygen Generate the Ed25519 signing key pair
verify-manifest Verify the signed integrity manifest of a case
verify-audit Verify the audit log's hash chain
:: Import a reference list (alert = flag matches, exclude = known-good)
hashhunter import-hashes --file known.txt --kind alert

:: Scan a folder or an evidence copy into a new case
hashhunter scan --source D:\Evidence --name "Case 4711"

:: Triage variant: images and videos only, verified by content, 2026 only
hashhunter scan --source D:\Evidence --name "Case 4711" ^
    --type images --type videos --modified-from 2026-01-01

:: Live Windows volume via a read-only VSS shadow copy (run as administrator)
hashhunter scan --source C:\Data --name "Case 4711" --vss

:: Reports, integrity, signing
hashhunter report --case Cases\Case_4711_2026-07-14_120000
hashhunter keygen
hashhunter verify-manifest --case Cases\Case_4711_2026-07-14_120000
hashhunter verify-audit --case Cases\Case_4711_2026-07-14_120000

:: Export confirmed matches (copies evidence material -> explicit flag required)
hashhunter export --case Cases\Case_4711_2026-07-14_120000 --confirm-export

--help lists every option.

Step-by-step workflow

From an empty installation to a sealed case report. Steps name the GUI route; the CLI equivalent follows in parentheses.

1. One-time setup

  • Place hh-gui.exe in a folder of its own (e.g. D:\Tools\HashHunter). Hashes\ and Cases\ are created next to it on first use — the installation is portable.
  • Generate the signing key once: Settings tab → key section (CLI: keygen). From then on every scan signs its integrity manifest automatically. Protect the private key; hand the public key to whoever needs to verify your cases later.

2. Import your reference hash sets

  • Anything with one hash per line works: your own alert lists, md5sum/sha256sum or certutil output, NSRL-style known-good sets. MD5, SHA-1, and SHA-256 may be mixed freely.
  • Import tab → Browse… (multiple files at once are fine) → tick "Exclude list instead of alert list" for known-good sets → import. (CLI: hashhunter import-hashes --file bad_hashes.txt --kind alert / --kind exclude.)
  • Check the list overview afterwards: ID, kind, and counts per hash type. Re-importing a file with the same name replaces that list cleanly; every import and deletion lands in Hashes\refdb-audit.jsonl.

3. Attach the evidence

Pick the route that matches your material:

  • Forensic image (E01, raw/dd, …): mount it read-only, for example with Arsenal Image Mounter (Mount disk image, read-only or write-temporary mode — both leave the image untouched). Note the drive letter; that letter, or any folder below it, is your scan source.
  • Physical drive: attach it through a hardware write blocker and scan the mounted letter.
  • Folder of extracted or copied data: scan the folder directly.
  • Live Windows volume: enable the VSS checkbox on the Scan tab (administrator rights required) — HashHunter creates a read-only shadow copy and scans that snapshot instead of the moving target.

4. Run the scan

  • Scan tab: pick the source, name the case.
  • Triage filters (type/size/date) are optional; an active filter is always disclosed in the audit log and the report. Leave everything off for a complete scan.
  • About the read-only gate: HashHunter checks the source's read-only attribute. Volume-level protection — a write blocker or a read-only mount — is often invisible to that attribute, so the gate may still object. In that case tick "Source is not read-only — continue anyway (audited)" (CLI: --force): your mount or blocker provides the actual protection, and the override itself is documented in the audit log.
  • Start the scan. Live progress shows files/s, MB/s, and ETA. Cancelling is clean: everything processed so far is kept, the remainder is recorded as not_verifiable.

5. Review the results

  • Matches tab: every alert hit with its list and hash type; open the report or the case folder from there.
  • DB tab: everything the scan saw — search, sort, filter by status. Look at read_error, access_denied, and enumeration errors so you know exactly what could not be checked.
  • The report (PDF + CSVs) is written automatically at scan end. (CLI: report --case … regenerates it — on a signed case only where the signing key is available.)

6. Export confirmed matches (when needed)

  • Export tab → confirm the prompt (CLI: export --case … --confirm-export).
  • Only alert matches are copied. Every copy is re-hashed and compared against the recorded values, listed in export-manifest.csv, and audited file by file.

7. Verify and archive

  • verify-audit --case … checks the audit hash chain; verify-manifest --case … checks the Ed25519 signature — at any later point, on any machine that has the public key.
  • Archive the whole case folder: it is self-contained (case database, audit chain, signed manifest, reports, the frozen reference-list snapshot, and logs).

Reference lists

Plain text, one hash per line. The type is detected from the token length (32 hex characters = MD5, 40 = SHA-1, 64 = SHA-256) and a single list may mix types freely. Real-world formats are tolerated: md5sum/sha256sum output ("hash filename"), certutil output (grouped hex), UTF-8 BOM, CRLF line endings, uppercase hex, even stray non-UTF-8 bytes in file-name columns. Invalid lines are counted and reported, never silently dropped. Each import records the source file's SHA-256 for chain of custody.

Case folder structure

Each scan creates one self-contained folder under Cases\:

Cases\Case_4711_2026-07-14_120000\
├── case.sqlite                    every enumerated file: hashes, timestamps, status
├── audit\
│   ├── audit.jsonl                tamper-evident audit log (SHA-256 hash chain)
│   ├── integrity.json             SHA-256 manifest of the case artifacts
│   └── integrity.json.sig         Ed25519 signature (when a signing key exists)
├── reports\
│   ├── report.pdf                 the case report
│   ├── report.typ                 its typst source (archival, manual re-rendering)
│   ├── report.csv                 one row per match, spreadsheet-friendly
│   └── technical-annex.csv        status counts, errors, lists, key figures
├── exports\                       exported matches + export-manifest.csv
├── config\
│   ├── effective-config.json      the configuration this scan actually ran with
│   └── reference-lists.json       the reference lists as frozen for this case
└── logs\technical.log             the technical log of the run

Next to the reference database, Hashes\refdb-audit.jsonl records every list import, replacement, and deletion, and Hashes\query-audit.jsonl every manual lookup — both as hash chains.

Project structure

Crate Role
hh-core The engine: hashing, enumeration, LMDB/SQLite, audit chain, signing, filters
hh-app Orchestration shared by CLI and GUI — scans, imports, reports, exports
hh-report PDF/CSV generation, decoupled from the engine
hh-cli Command-line binary (hashhunter.exe)
hh-gui Slint desktop interface (its own workspace, hh-gui.exe)

Forensic notes

Warning

The read-only check on the source is a heuristic. It is no substitute for a hardware write blocker or a read-only mounted image, and neither the UI nor the report claims otherwise.

  • MD5 and SHA-1 serve recognition; a SHA-256 match is the defensible result. The report keeps that distinction visible.
  • Exporting matches copies evidence material. That is legally sensitive, which is why it demands explicit confirmation and is audited file by file.
  • Regenerating reports re-seals the integrity manifest. On a signed case this requires the signing key — otherwise the old signature would be orphaned and the case would wrongly look tampered with.
  • Protect the private signing key (Hashes\keys\hashhunter-signing.key). On Windows its ACL is restricted to the current user at creation.

Third-party libraries

HashHunter stands on a stack of excellent open-source work. Copyright remains with the respective authors; their licenses apply.

Core: heed (LMDB) · rusqlite (SQLite) · rayon · walkdir · sha2 · sha1 · md-5 · hex · ed25519-dalek · rand · serde · serde_json · anyhow · thiserror · tracing · fs2 · infer · time Interfaces: slint · rfd · open · clap · tracing-subscriber · tracing-appender Reports: printpdf · typst (embedded via typst-as-lib, optional) Build: slint-build · winresource

License

This project is licensed under the MIT license, providing users with flexibility and freedom to use and modify the software according to their needs.

Contributing

Contributions are welcome! Please fork the repository and submit a pull request for review.

Disclaimer

This software is provided "as is", without warranty of any kind. It supports forensic triage; it does not replace the examiner's judgment or responsibility. Verify results independently before relying on them in proceedings, seek legal review before using the export function in production casework, and use the tool only on data you are authorized to examine.

Conclusion

This tool has been tailored to fit my specific professional needs, and while it may seem like a small tool, it has a significant impact on my workflow. HashHunter will not replace a full forensic suite, and it does not try to. It answers the daily "is any of this already known?" question quickly, completely, and with documentation you can hand over as-is. If it saves you from re-running a scan just because the paperwork was missing — or spares a console-averse colleague their first encounter with a command line — it has done its job. Greetings to my dear colleagues [^4] who avoid scripts like the plague and think that consoles and Bash are some sort of dark magic – the compiled version will spare you the console kung-fu and hopefully be a helpful tool for you as well. 😉

About

Forensic hash triage for storage media — fast, complete, and with a paper trail that holds up.

Topics

Resources

License

Stars

1 star

Watchers

0 watching

Forks

Contributors