Skip to main content

Getting Started

Exhume Indexer dependencies and current support

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:

  1. walks the source and inserts normalized records for the selected partition;
  2. identifies signatures when --identify-files is present;
  3. matches indexed paths against the embedded artifacts.yaml catalogue;
  4. runs registered parsers for matching artefacts;
  5. persists normalized parser objects and parser-derived timeline events;
  6. resolves supported message/media references to indexed filesystem records;
  7. 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​

TablePurpose
evidenceSource identity, type, and path.
partitionsFilesystem offset, size, sector size, kind, and optional FVEK.
system_filesNormalized filesystem records, tree keys, signatures, host paths, and native metadata.
artifactsIndexed files matched by the artefact catalogue.
artifact_objectsJSON and searchable text emitted by artefact parsers.
timeline_eventsFilesystem and parser-derived events in Unix milliseconds.
artifact_attachment_refsParsed 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​

OptionMeaning
-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-filesPopulate signature name, MIME, and extension fields.
--extract-artefactsMatch catalogue paths, run parsers, resolve attachments, and populate timeline data.
--artifacts-yaml <PATH>Replace the embedded catalogue for this run.
--no-progressPrint 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.