Category: Forensics
Flag: CDDC2026{times_square_260420_1930}
Challenge
Defender, through your analysis, you identified the confidential file that Alex, a former employee of Company P, leaked before leaving the company. Intelligence indicates that he plans to deliver the leaked file to the client, and you must identify the rendezvous.
Artifact: Catch the criminal.zip containing Catch the criminal.ad1 (≈321 MB FTK Imager AD1 logical image) and its .ad1.txt manifest.
Required answers:
- Where is the rendezvous? (lowercase, spaces →
_) - When is the rendezvous? (
YYMMDD_HHMM)
Recon — what's in the AD1
The .ad1.txt manifest reveals the acquisition deliberately scoped to:
- Registry hives (
SAM,SECURITY,SOFTWARE,SYSTEM,NTUSER.DAT,UsrClass.dat) - Archive tools (
7-Zip,Bandizip) - Messaging apps: Wire, Signal, Telegram Desktop, WhatsApp
- AI desktop apps: Claude, Gemini, Antigravity, Microsoft Copilot, ChatGPT Desktop
The interesting evidence is going to live inside the messaging app + AI app data — not the OS.
Step 1 — Extract the AD1
AD1 is AccessData's proprietary "Custom Content Image" format (ADSEGMENTEDFILE → ADLOGICALIMAGE). FTK Imager isn't installed and 7z doesn't speak it. Used pcbje/pyad1 with two small patches:
__enter__popspaths[0]before_ReadHeadercalls_ReadLastFrom(-372)onpaths[-1]— empty when there's a single segment. Cacheself.last_path = self.paths[-1]before popping.- Sanitize Windows-illegal characters (
:|*?<>") in folder/file names from the NTFS namespace (e.g.Catch the criminal.E01:NONAME [NTFS]).
with reader.AD1Reader(ad1_path) as ad1:
for item_type, folder, filename, metadata, content in ad1:
... # write files / mkdir for dirs
Result: ~574 MB extracted to extracted/Catch the criminal.E01_NONAME [NTFS]/[root]/....
The iterator throws Exception('Incomplete read') at the very end (trailing metadata block), but by then all file content has been written.
Step 2 — Recover the Wire conversation
Wire stores messages in Chromium IndexedDB (LevelDB):
AppData\Roaming\Wire\IndexedDB\https_app.wire.com_0.indexeddb.leveldb\000003.log
Wire's payload is end-to-end encrypted at the protocol layer, but the decrypted plaintext is cached locally as UTF-16LE strings in the LevelDB log. Quick extract:
data = open(log_path, 'rb').read()
for m in re.finditer(rb'(?:[\x20-\x7e]\x00){15,}', data):
print(m.group().decode('utf-16le', errors='ignore'))
Sorted by timestamp, the conversation between Alex and the buyer (2026-04-16):
| Time (UTC) | Message |
|---|---|
| 11:53:44 | "Well done. I knew you were the right choice. Now for the final step. To keep things secure, it's best we meet in person to hand over the storage device. Have you picked a spot?" |
| 11:55:38 | "I wanted to avoid any radar from the security team, so I consulted an AI to find a high-traffic location where I could easily blend in. It gave me a few recommendations, and I've made my choice." |
| 11:57:04 | "Using AI for that? You're even sharper than I thought. Send me the details later. What time are we talking? If it's too late, it might raise some red flags." |
| 11:58:16 | "This Friday is my last day. I think it's best to meet right after I finish packing my things and clear out of the office for good." |
| 12:00:02 | "See you then. The remaining 9 BTC will be transferred to your wallet the moment I've verified the files." |
The message doesn't name the spot — it points us at the AI chat history instead.
Step 3 — The AI consult (ChatGPT Desktop)
AppData\Local\Packages\OpenAI.ChatGPT-Desktop_2p2nqsd0c76g0\LocalCache\Roaming\ChatGPT\IndexedDB\https_chatgpt.com_0.indexeddb.leveldb\000003.log
Plaintext strings reveal a conversation titled "Famous Crowded Places USA":
text"0Where is a famous and crowded place in America?
text"|There should be so many people that it's hard for someone to see who's important, is there a lot of people in Times Square?
Alex's chosen rendezvous: Times Square.
Step 4 — The date/time
A bonus image artifact shipped with the challenge gives the meeting time directly: 2026-04-20 19:30 → 260420_1930.
(Consistent with the Wire conversation: Friday Apr 17 was Alex's last day; the rendezvous follows the next Monday evening.)
Flag
| Part | Value |
|---|---|
| Location | times_square |
| Datetime | 260420_1930 |
CDDC2026{times_square_260420_1930}
Notes / lessons
- AD1 isn't an opaque blob. A 200-line Python parser + zlib gets you the filesystem tree.
pyad1works after two trivial patches. - E2EE apps still leak. Wire's protocol traffic is encrypted, but the local IndexedDB caches decrypted message bodies as UTF-16LE. A blind
re.finditerfor printable runs reconstructs the conversation in seconds — no need to parse LevelDB, decrypt SQLCipher, or pull keys fromconfig.json. - AI assistant history is forensic gold. ChatGPT Desktop's
ConversationsDatabasestores user prompts and assistant turns in the same IndexedDB. The same UTF-16LE scrape technique works.