Getting Started
exhume_indexer turns a filesystem partition or extracted folder into a
queryable Exhume SQLite database. It can then identify file signatures, match
forensic artefact paths, execute parsers from exhume_artefacts, resolve
attachment references, and build a unified timeline.
The index is designed for downstream investigation in Thanatology as well as direct SQL analysis and custom Rust integrations.
Processing pipeline​
image partition or folder
|
v
normalized filesystem records --------> system_files
|
+-- optional signature pass ---> sig_name / sig_mime / sig_exts
|
+-- artefact path matching -----> artifacts
|
v
parser registry --------> artifact_objects
| timeline_events
+----------------> artifact_attachment_refs
The CLI always performs filesystem indexing. --identify-files enables file
signature identification. --extract-artefacts performs artefact matching and
parsing; it also refreshes filesystem-derived timeline events.
Install​
cargo install exhume_indexer
From the Exhume workspace, replace exhume_indexer in the examples below
with:
cargo run -p exhume_indexer --
Important units​
Disk images require both of these values:
--offset: filesystem start in bytes.--size: filesystem length in sectors.
Decimal and 0x-prefixed hexadecimal values are accepted. Folder inputs need
neither value. Partition discovery is handled separately, for example with
exhume_partitions.
CLI use cases​
Build an index from an extracted folder​
exhume_indexer \
--body /evidence/mobile-extraction \
--database /cases/phone.sqlite \
--evidence-id 1 \
--no-progress
For folder sources, system_files.host_path records the corresponding host
path. This lets consumers open a file directly when the extracted folder is
still mounted. Image-backed records deliberately leave host_path empty.
If --database is omitted, the default is <body-name>.sqlite beside the
source. For /evidence/mobile-extraction, that is
/evidence/mobile-extraction.sqlite.
Index a filesystem partition inside an image​
exhume_indexer \
--body /evidence/disk.raw \
--format raw \
--offset 0x100000 \
--size 0x400000 \
--database /cases/disk.sqlite \
--evidence-id 2
If the target database already has a partition row managed by another
application, pass --partition-id <ID> to update and reuse it. Otherwise the
CLI creates a logical partition row.
Run the full forensic pipeline​
exhume_indexer \
--body /evidence/mobile-extraction \
--database /cases/phone.sqlite \
--evidence-id 1 \
--identify-files \
--extract-artefacts
This command:
- walks the source and inserts normalized records for the selected partition;
- identifies signatures when
--identify-filesis present; - matches indexed paths against the embedded
artifacts.yamlcatalogue; - runs registered parsers for matching artefacts;
- persists normalized parser objects and parser-derived timeline events;
- resolves supported message/media references to indexed filesystem records;
- refreshes created, modified, and accessed filesystem timeline events.
The artefact identification pass is repeatable: existing artefact matches, parsed objects, attachment references, and artefact-derived timeline entries for the selected evidence partition are cleared before being rebuilt.
Filesystem indexing itself is append-only in the current version. Re-running
it for the same evidence and partition can duplicate system_files rows. Use a
fresh database or clear/reset that partition through the owning application
before a complete re-index.
Index a BitLocker volume​
exhume_indexer \
--body /evidence/encrypted.raw \
--format raw \
--offset 0x100000 \
--size 0x400000 \
--fvek 00112233aabbccdd... \
--database /cases/encrypted.sqlite \
--identify-files \
--extract-artefacts
The hexadecimal FVEK is stored on the partition row so the filesystem can be reopened for post-indexing passes. Treat the resulting database as sensitive case material.
Use a custom artefact catalogue​
--artifacts-yaml replaces the embedded catalogue for that run:
artifacts:
- name: "Case-specific Windows event logs"
description: "Additional operational logs selected for this case"
paths:
- path: '(?i)^([A-Z]:)?[\\/]Windows[\\/]System32[\\/]winevt[\\/]Logs[\\/]Custom-App%4Operational\.evtx$'
regexp: true
parser: "windows_evtx"
tag: "Event Logs"
category: "system"
exhume_indexer \
--body /evidence/windows-export \
--database /cases/windows.sqlite \
--extract-artefacts \
--artifacts-yaml ./case-artifacts.yaml
Allowed categories are system, network, users, media, and
application. A path without regexp: true is treated as a literal path and
anchored automatically. A configured parser must exist in the
exhume_artefacts registry; catalogue entries without a parser are still useful
as identified artefact files.
WhatsApp investigation example​
After indexing and extracting an iOS filesystem, find WhatsApp stores and summarize the objects they produced:
SELECT id, identifier, absolute_path, size
FROM system_files
WHERE name = 'ChatStorage.sqlite';
SELECT parser, kind, COUNT(*) AS object_count
FROM artifact_objects
WHERE parser IN ('mobile_ios_whatsapp', 'macos_whatsapp')
GROUP BY parser, kind
ORDER BY parser, kind;
List available conversations without exposing message bodies:
SELECT
json_extract(json, '$.conversation.id') AS conversation_id,
json_extract(json, '$.conversation.display_name') AS display_name,
COUNT(*) AS messages
FROM artifact_objects
WHERE parser = 'mobile_ios_whatsapp'
AND kind = 'mobile.communication.message'
GROUP BY conversation_id, display_name
ORDER BY messages DESC;
Reconstruct one selected conversation chronologically:
SELECT
json_extract(json, '$.timestamps.message.rfc3339') AS timestamp,
json_extract(json, '$.direction') AS direction,
json_extract(json, '$.sender.display_name') AS sender,
json_extract(json, '$.body') AS body
FROM artifact_objects
WHERE parser = 'mobile_ios_whatsapp'
AND kind = 'mobile.communication.message'
AND json_extract(json, '$.conversation.id') = '<conversation-id>'
ORDER BY json_extract(json, '$.timestamps.message.unix_ms');
The current Thanatology iPhone fixture was used to validate this workflow. Its
indexed ChatStorage.sqlite produced 305 chat records, 74,015 message records,
and 8,075 attachment records. Those counts describe the local test fixture;
they are not expected output for other evidence.
Database output​
| Table | Purpose |
|---|---|
evidence | Source identity, type, and path. |
partitions | Filesystem offset, size, sector size, kind, and optional FVEK. |
system_files | Normalized filesystem records, tree keys, signatures, host paths, and native metadata. |
artifacts | Indexed files matched by the artefact catalogue. |
artifact_objects | JSON and searchable text emitted by artefact parsers. |
timeline_events | Filesystem and parser-derived events in Unix milliseconds. |
artifact_attachment_refs | Parsed media references and their resolved filesystem records or previews. |
Useful general queries:
-- Files identified as SQLite regardless of extension
SELECT absolute_path, size, sig_mime
FROM system_files
WHERE sig_mime LIKE '%sqlite%'
ORDER BY size DESC;
-- Artefact parsers that produced objects
SELECT parser, kind, COUNT(*) AS object_count
FROM artifact_objects
GROUP BY parser, kind
ORDER BY object_count DESC;
-- Timeline range and sources
SELECT
source,
MIN(datetime(ts / 1000, 'unixepoch')) AS first_event,
MAX(datetime(ts / 1000, 'unixepoch')) AS last_event,
COUNT(*) AS events
FROM timeline_events
GROUP BY source;
-- Attachments resolved to a file in the indexed evidence
SELECT parser, kind, file_name, resolved_absolute_path, resolved_sig_mime
FROM artifact_attachment_refs
WHERE resolved_file_id IS NOT NULL;
SQLite is configured in WAL mode by the CLI. When copying a live index, include
its -wal and -shm files or checkpoint it first.
Artefact companions and provenance​
Parsers can declare CompanionSpec rules for files that belong with the primary
artefact. The indexer resolves suffix companions such as ChatStorage.sqlite-wal
and ChatStorage.sqlite-shm, or named sibling databases such as Apple Mail's
Protected Index, from system_files.
The resolved inputs are supplied as a compound parser input with:
- the indexed path and native filesystem identifier of the primary file;
- the role and provenance of every resolved companion;
- a provider that copies bytes through the filesystem abstraction;
- primary source metadata for parsers that request it even without companions.
This preserves evidence provenance and avoids requiring files from a disk image to be exported before parsing.
CLI reference​
| Option | Meaning |
|---|---|
-b, --body <PATH> | Required folder, image, or forensic body. |
-d, --database <PATH> | Output SQLite path; defaults to <body>.sqlite. |
-f, --format <FORMAT> | Body format: raw, ewf, or auto. Ignored for folders. |
-o, --offset <BYTES> | Filesystem start in bytes; required for image sources. |
-s, --size <SECTORS> | Filesystem length in sectors; required for image sources. |
--evidence-id <ID> | Evidence ID stored in SQLite; defaults to 1. |
--partition-id <ID> | Existing partition row to update and reuse. |
--fvek <HEX> | BitLocker Full Volume Encryption Key. |
--identify-files | Populate signature name, MIME, and extension fields. |
--extract-artefacts | Match catalogue paths, run parsers, resolve attachments, and populate timeline data. |
--artifacts-yaml <PATH> | Replace the embedded catalogue for this run. |
--no-progress | Print plain progress messages instead of interactive bars. |
-l, --log-level <LEVEL> | error, warn, info, debug, or trace. |
Interactive progress bars are enabled only when stderr is a terminal. Use
--no-progress for logs, automation, and reproducible test output.