← Back to Writeups
CUSTOMEasyForensics

Unfinished File

XESXOR9/10/20262 min read
#forensics#custom#easy#crdl_file_format_analysis#embedded_zip_extraction#single_byte_xor_known_plaintex

Unfinished File

Platform: tjctf | Category: Forensics | Type: Challenge | Difficulty: Easy | OS: NA | Author: XESXOR | Date: 2026-05-16 | Status: Solved Techniques: crdl_file_format_analysis, embedded_zip_extraction, single_byte_xor_known_plaintext

Summary

Task: analyze a Chrome .crdownload incomplete download file containing embedded data. Solution: extract embedded ZIP archive, recover XOR-encrypted flag using known-plaintext attack with flag prefix.

Unfinished File — TJCTF 2026

Description

"my stupid friend tried downloading this file before i shut my laptop down, what was he trying to do?"

Attachment: secret_archive.zip.crdownload (463 bytes) — a Chrome incomplete download file.

Analysis

The .crdownload extension is Chrome's format for incomplete downloads. At only 463 bytes, this file is small enough for manual binary analysis.

Examining the file structure revealed three distinct regions:

  1. CRDL header (offset 0x00): Magic bytes CRDL, version info, and the source URL https://example.com/secret_archive.zip
  2. XOR-encrypted blob (offset 0x47): Encoded data with preceding control characters
  3. Embedded ZIP archive (offset 0x100): Standard PK signature (ZIP magic bytes)

The embedded ZIP contained two files:

  • readme.txt — decoy text: "This file is incomplete. Keep looking..."
  • hidden/.flagdata — 47 bytes of XOR-encrypted data

The .flagdata hex content:

36 28 21 36 24 39 2c 71 34 71 30 1d 2e 71 36 1d
72 36 2a 27 30 1d 32 71 72 32 2e 27 1d 36 72 37
21 2a 1d 37 30 1d 21 72 2f 32 37 36 27 30 3f

Solution

Known-plaintext attack on single-byte XOR

The flag format tjctf{ provides 6 known plaintext bytes. XORing the first encrypted byte with the expected plaintext reveals the key:

0x36 ^ 0x74 ('t') = 0x42 ('B')

Verifying against all 6 prefix bytes:

EncryptedExpectedXOR result
0x36t (0x74)0x42 ✓
0x28j (0x6A)0x42 ✓
0x21c (0x63)0x42 ✓
0x36t (0x74)0x42 ✓
0x24f (0x66)0x42 ✓
0x39{ (0x7B)0x42 ✓

All 6 bytes produce the same key 0x42 — confirmed single-byte XOR with key B.

Decryption

python
#!/usr/bin/env python3
data = bytes.fromhex(
    '3628213624392c71347130'
    '1d2e71361d72362a2730'
    '1d3271723227'
    '1d36723721'
    '2a1d37301d2172'
    '2f323736273'
    '03f'
)

# Cleaner version with exact bytes
data = bytes([
    0x36, 0x28, 0x21, 0x36, 0x24, 0x39, 0x2c, 0x71,
    0x34, 0x71, 0x30, 0x1d, 0x2e, 0x71, 0x36, 0x1d,
    0x72, 0x36, 0x2a, 0x27, 0x30, 0x1d, 0x32, 0x71,
    0x72, 0x32, 0x2e, 0x27, 0x1d, 0x36, 0x72, 0x37,
    0x21, 0x2a, 0x1d, 0x37, 0x30, 0x1d, 0x21, 0x72,
    0x2f, 0x32, 0x37, 0x36, 0x27, 0x30, 0x3f
])

key = 0x42
flag = ''.join(chr(b ^ key) for b in data)
print(flag)
# tjctf{REDACTED}