You are browsing as a guest. Sign up (or log in) to start making projects!

10h 39m 3s logged

Network was the last forensics category Trawl couldn’t touch.

Binaries read now. Registry hives trace which USB stick was plugged in when.
SQLite databases give up their tables. But hand Trawl a packet capture and it did
what it does with any file: ran the byte scan, found whatever text was sitting
there in the clear, and stopped. Which is the wrong instinct for a capture,
because the flags that matter in one are usually not sitting there in the clear.

Why the byte scan finds nothing

TCP doesn’t send a stream in one piece. It chops it into segments, and each
segment rides in its own packet. So in the file, one segment’s payload ends, then
come the headers of the next packet, fifty-odd bytes of Ethernet, IP and TCP that
have nothing to do with the message, then the next payload. A flag that happens
to cross that boundary is cut in half by bytes that were never part of it. Grep
walks straight past it. Every naive scan does.

The only way to read it back is to do what the receiving machine did: follow each
connection’s sequence numbers and lay the payloads down in order, then scan the
result. So that’s what I built. A reassembler for both capture formats, classic
pcap and the newer pcapng, that puts every TCP conversation back together before
anything looks at it.

The sample that has to defeat itself

To prove it worked I made a capture with a flag deliberately split across two
segments. That test has two assertions. One says reassembly recovers the flag.
The one that actually matters says the flag appears nowhere whole in the file,
because if a plain scan could turn it up, the sample would be testing nothing and
I’d have shipped a very confident reassembler that never had to do its job.

The row that was deleted but not erased

SQLite landed the same week and it’s the same idea wearing different bytes. Delete
a row and SQLite doesn’t wipe it. It unlinks the cell, writes a four-byte marker
over the front of it, and leaves the rest exactly where it lay. The row is still
in the file, minus its first column. Getting it back means reading the surviving
column types until they add up to precisely the size of the freed gap, and I only
found that handle by opening a real database in a hex view and watching the little
freeblock header sit down on top of a record that was otherwise completely
intact.

Showing the work

Trawl getting good created a new problem. It will now find a flag three containers
deep and just hand it to you, and a stranger has no reason to believe it. So every
recovered flag shows its route now. recursive-files.zip, then inner.zip, then
clue.png, then the tool that finally read it, drawn as one line under the answer.
The catch and the trail that led to it, in the same place.

Where the hours actually went

One honest one: I ran the Rust formatter without thinking and it reformatted forty
files it had no business touching. My clean dozen-file change turned into a wall of
whitespace nobody could review, and an hour went into unpicking that before the
real work was legible again.

AI wrote most of the parsing and a good share of these words, same as always. The
time that was mine went into the parts it can’t do for you: finding real captures
and real databases to test against instead of the tidy ones I’d have written to
pass, and catching the quiet failure, the one where the tool is completely right
and the person still can’t see it.

A pcap reader, a row nobody deleted cleanly, a breadcrumb. It reads like an
afternoon. The idea under all three took the day: the bytes were in the file the
whole time, arranged so that nothing reading it straight would ever find them.

0
24

Comments 0

No comments yet. Be the first!