Getting Started
exhume_artefacts is Exhume's registry of forensic artefact parsers. It accepts
standalone files, in-memory data, seekable filesystem streams, or compound
evidence with companion files and streams normalized JSON objects to the
caller.
Use the CLI for focused examination of one file. Use the library directly—or
through exhume_indexer—when provenance, image-backed access, SQLite sidecars,
timeline events, and persistent investigation results are required.
Install​
Install the command-line tool:
cargo install exhume_artefacts
From the Exhume workspace, replace exhume_artefacts in the examples below
with:
cargo run -p exhume_artefacts --
Discover available parsers​
The registry is the authoritative parser list for the installed build:
exhume_artefacts --list-parsers
Version 0.3.1 currently registers:
| Parser | Purpose |
|---|---|
macos_chromium | Chrome, Chromium, Brave, and Edge history, visits, and downloads. |
macos_firefox | Firefox Places history and visits. |
macos_imessage | Messages chats, messages, and attachment references. |
macos_keychain | Legacy CSSM and modern SQLite keychain item metadata without decrypting secrets. |
macos_launchd | LaunchAgent and LaunchDaemon persistence definitions and triggers. |
macos_loginwindow | System, user, and ByHost loginwindow configuration, relaunch items, and hooks. |
macos_network | Network services, interfaces, known Wi-Fi networks, and DHCP leases. |
macos_quarantine | LaunchServices quarantine download-provenance events. |
macos_safari | Safari history, visits, and downloads. |
macos_sharedfilelist | SFL/SFL2/SFL3 recent-item bookmark archives. |
macos_spotlight | Spotlight Store-V2 metadata, external dbStr maps, and reconstructed item paths. |
macos_whatsapp | WhatsApp desktop chats, messages, and media references. |
mobile_android_sms | Android SMS/MMS conversations and message records. |
mobile_ios_calendar | Calendar event records, locations, timestamps, and attendees. |
mobile_ios_callhistory | Cellular, FaceTime, and third-party CallKit call records. |
mobile_ios_contacts | Address book records and multi-value phone, email, and address data. |
mobile_ios_datausage | Per-process cellular and Wi-Fi usage. |
mobile_ios_imessage | sms.db chats, SMS/iMessage messages, and attachment references. |
mobile_ios_interactionc | CoreDuet communication interaction records. |
mobile_ios_knowledgec | CoreDuet app, web, lock-state, backlight, and notification events. |
mobile_ios_mail | Apple Mail Envelope Index, with optional Protected Index data. |
mobile_ios_notes | Apple Notes NoteStore.sqlite metadata. |
mobile_ios_photos | Photos ZASSET inventory, capture dates, GPS, and flags. |
mobile_ios_routined | Routined significant-location cache GPS fixes. |
mobile_ios_safari | Safari History.db sites and visits. |
mobile_ios_tcc | TCC privacy permission decisions. |
mobile_ios_whatsapp | WhatsApp ChatStorage.sqlite chats, messages, and media references. |
windows_evtx | Windows Event Log records. |
windows_pe | PE metadata and hashes for malware analysis. |
windows_pml | Process Monitor PML v9 events. |
Parser names are stable integration identifiers. Always use --list-parsers
to confirm what is compiled into a particular binary.
CLI usage​
Parse a standalone file​
exhume_artefacts \
--file /evidence/Windows/System32/winevt/Logs/Security.evtx \
--parser windows_evtx > security.jsonl
The CLI writes one parser JSON object per line to stdout and writes the final object count to stderr. This separation makes redirection and streaming tools safe:
exhume_artefacts \
--log-level error \
--file /evidence/sample.exe \
--parser windows_pe \
| jq .
Log levels are error, warn, info, debug, and trace.
Parse a file inside an image​
The parse_from_fs example opens an image, detects its filesystem, fetches a
file by native record identifier, and presents it to the parser as a seekable
stream:
cargo run -p exhume_artefacts --example parse_from_fs -- \
--body /evidence/disk.raw \
--format auto \
--offset 0x100000 \
--size 0x400000 \
--record 42 \
--parser windows_evtx > security.jsonl
For this example, --offset is in bytes and --size is in sectors.
--record is the native filesystem identifier, not the row ID from an Exhume
SQLite index.
The example supplies only the selected file. Prefer exhume_indexer for
parsers that need WAL/SHM files, named sibling databases, source-path metadata,
or attachment resolution from the surrounding filesystem.
Use case: reconstruct a WhatsApp conversation​
The iOS and macOS WhatsApp parsers read the Core Data ChatStorage.sqlite
schema and emit separate chat, message, and attachment objects. When parsing a
standalone database, keep ChatStorage.sqlite-wal and ChatStorage.sqlite-shm
beside the primary file. The SQLite input layer discovers and copies those
sidecars automatically.
Parse an iOS store:
exhume_artefacts \
--log-level error \
--file /evidence/ios/ChatStorage.sqlite \
--parser mobile_ios_whatsapp > whatsapp.jsonl
For a macOS desktop store, use the same workflow with
--parser macos_whatsapp.
Count the emitted record types:
jq -r '.record_type' whatsapp.jsonl | sort | uniq -c
List conversations and their message counts without displaying message text:
jq -r '
select(.record_type == "message")
| [.conversation.id, (.conversation.display_name // "<unnamed>")]
| @tsv
' whatsapp.jsonl \
| sort \
| uniq -c \
| sort -nr
After choosing a conversation ID, render a chronological, redacted-friendly conversation view:
jq -c --arg conversation '<conversation-id>' '
select(
.record_type == "message"
and .conversation.id == $conversation
)
| {
timestamp: .timestamps.message.rfc3339,
direction,
sender: (.sender.display_name // .sender.id // "<unknown>"),
body,
has_attachments
}
' whatsapp.jsonl \
| jq -s 'sort_by(.timestamp)[]'
Representative normalized output, with identifying values redacted:
{
"timestamp": "2025-01-15T08:42:17+00:00",
"direction": "incoming",
"sender": "<contact>",
"body": "<message text redacted>",
"has_attachments": false
}
To inspect media records for the same conversation:
jq -c --arg conversation '<conversation-id>' '
select(
.record_type == "attachment"
and ((.chat.rowid | tostring) == $conversation or .chat.jid == $conversation)
)
| {
timestamp: .timestamps.message.rfc3339,
kind: .attachment.kind,
file_name: .attachment.file_name,
local_path: .attachment.local_path,
mime: .attachment.mime
}
' whatsapp.jsonl
This workflow was smoke-tested against an already indexed Thanatology iPhone case. The current parser emitted 82,395 objects: 305 chats, 74,015 messages, and 8,075 attachments. A macOS case in the same test set produced 2 chats, 14 messages, and 7 attachments. These counts identify the fixtures, not an expected result for other evidence.
Output model​
Every library result is an ObjectParsed:
pub struct ObjectParsed {
pub parser: &'static str,
pub kind: &'static str,
pub text: String,
pub json: serde_json::Value,
}
parseridentifies the implementation that produced the object.kindis a stable domain type such asmobile.communication.message.textis the best concise/searchable representation of the object.jsonpreserves normalized fields, native details, and provenance.
The CLI emits the json value because it is the portable external format.
exhume_indexer also persists parser, kind, and text in dedicated
columns.
Normalized communication records​
Mobile and desktop communication parsers share canonical kinds:
mobile.communication.chatfor conversation metadata;mobile.communication.messagefor a message envelope;mobile.communication.attachmentfor a separately addressable media record.
Message objects use the chat.v1 schema and consistently expose:
- application and platform;
- stable per-store conversation identity and participants;
- incoming, outgoing, or unknown direction;
- sender and self-identification;
- canonical message time as Unix milliseconds and RFC 3339;
- body, attachment state, delivery/read/deletion state where available;
- source table, row identity, source files, and schema variant;
- app-specific values under
details, so normalization does not discard evidence.
An absent state remains null; parsers do not invent false when the source
application has no such concept.
Timeline events​
A parser can implement extract_timeline_events for each parsed object. A
TimelineEvent contains:
ts_unix_ms: normalized Unix time in milliseconds;- a dot-namespaced event type;
- an optional concise description;
- an optional actor such as a user, process, or correspondent.
The standalone CLI does not emit timeline events separately. exhume_indexer
calls this hook and stores the results in timeline_events with a link back to
the parsed object.