Inspiration
Almost every chat app, browser and phone stores its data in SQLite: messages, history, contacts. When you delete a row, SQLite usually doesn't erase it. It marks the space as free, and the old bytes sit there until something overwrites them. Deleted messages are often the most important evidence on a seized device, yet a 2019 DFRWS evaluation found the best available tool recovered only about 53% of deleted records, and handed investigators a table with no proof of where any row came from.
We wanted to build the tool an investigator actually needs: not just what a deleted message said, but when it changed and the exact bytes on disk that prove it.
What it does
Cyber Arche takes a seized SQLite database (plus its -wal and -journal files) and:
- Recovers deleted messages and puts them back into the conversation, in order, outlined in red, each with a confidence score and the reasons behind it.
- Replays the write-ahead log commit by commit. A timeline slider lets you scrub through the database's history and watch messages get sent, edited and deleted. Edited messages expand to show every earlier version.
- Proves every record at the byte level. Click any message and a hex viewer highlights the exact bytes it was decoded from: page, offset, WAL frame and commit, with each column colour-coded.
- Detects cover-ups. It flags patterns consistent with secure_delete, VACUUM, auto-vacuum, WAL resets, and messages deleted without leaving any trace, citing the bytes and counts behind each finding.
- Keeps evidence forensically sound. Every file is SHA-256 hashed and stored read-only on intake, and the hashes are re-verified when you export a case report.
For the live demo, a judge chats in our fake messaging app (on their own phone), deletes a message, and we click Seize device. Seconds later the deleted message is back on screen, with its bytes highlighted.
How we built it
- A SQLite parser written from the file-format spec in pure Python, standard library only. It reads raw bytes and never opens evidence with the SQLite library, which can silently modify files. It walks b-tree pages, decodes record headers and varints, follows overflow chains and parses freelists.
- A carver scans freeblocks, unallocated space and freelist pages for deleted records, validating each candidate against the table's schema (column count, types, text encoding, NOT NULL rules).
- A WAL time machine validates every frame with SQLite's cumulative checksum, builds a snapshot of the database at each commit, and diffs consecutive snapshots into inserts, edits and deletes.
- A FastAPI backend serves cases, records, commits, hex ranges and findings as JSON, and also runs the demo chat app.
- A React + TypeScript + Tailwind frontend renders the conversation view, timeline slider, hex viewer, records table and findings.
- Tests: 27 pytest tests against generated fixture databases, plus 18 synthetic sample cases covering every recovery path and cover-up pattern.
Challenges we ran into
The 4-byte overwrite. When SQLite frees a cell, it writes a 4-byte freeblock header over the start of the old record, destroying the payload size, the rowid and usually the first bytes of the record header. That's why many carvers miss these records entirely. We rebuild them by enumerating the possible original layouts, filling the lost serial types from the table schema, and keeping the hypothesis that decodes cleanly and fits the freeblock exactly. The lost rowid is then inferred from neighbouring timestamps, and clearly labelled as inferred.
Overflow math. For a usable page size U and payload size P, a record stays on the page only if P <= X, where X = U - 35. Otherwise SQLite keeps K = M + ((P - M) mod (U - 4)), where M = floor(((U - 12) * 32) / 255) - 23, bytes locally (or M if K > X), and the rest spills onto a chain of overflow pages.
WAL checksums. Each frame is valid only if a running checksum over the header and every frame so far matches. For each pair of 32-bit words (w_i, w_(i+1)): s_0 = s_0 + w_i + s_1, and s_1 = s_1 + w_(i+1) + s_0, with both values taken modulo 2^32. The word byte order is chosen by the magic number. One flipped bit invalidates every later frame.
No clock in the WAL. Commits are ordered but not timestamped, so we refuse to invent a time. Instead we bracket each change using the app's own message timestamps: "deleted after the message sent at 14:04 and before the one sent at 14:05."
Keeping evidence untouched. Simply opening a database with SQLite can checkpoint the WAL and destroy exactly the history we're trying to recover. So the engine only ever reads raw bytes, and the demo app copies the files while it is still running.
Accomplishments that we're proud of
- A working parser for a binary file format, written from its specification, with zero evidence modification (verified by hashes).
- Recovering deleted records whose headers were overwritten, which basic carvers skip.
- A live, judge-driven demo: delete a message on your phone and watch it come back, with proof.
- Cover-up detection that turns missing data into evidence: zeroed bytes and rowid gaps tell a story too.
What we learned
How SQLite actually deletes data, at the byte level, and why that leaves so much behind. Above all, we learned that in forensics provenance matters more than recall: a recovered message is only useful if you can show exactly where it came from and how confident you are.
What's next for Cyber Arche
- Schema adapters that render real app layouts (WhatsApp, iMessage, Chrome) as conversations.
- Recovering deleted messages that spilled onto overflow pages.
- A command-line mode for batch casework.
Log in or sign up for Devpost to join the conversation.