Skip to main content

dust

Challenge

Imported from local notes.md.

Solution

Original Notes

dust

Challenge Summary

  • Given: A zip archive containing encoder.c and an encoded output file, output.txt.
  • Goal: Reconstruct the original input to the custom binary image compression algorithm.
  • Constraints: Only the encoder was provided, so the decompressor had to be derived from the implementation.

Initial Recon / Triage

  • Observations:
    • encoder.c never reads image formats directly; it reads an input.txt file made of 0 and 1 characters.
    • Input validation enforces even row count and widths that are multiples of 3.
    • Compression level 1 converts each 2x3 block of bits into a printable character via 0b00100000 + value.
    • writeArray serializes each compressed row followed by } and terminates the stream with ~.
  • File identification:
    • artifacts/unpacked/encoder.c - compressor implementation.
    • artifacts/unpacked/output.txt - compressed text stream.
    • artifacts/decoded_binary.txt - recovered binary image.
    • artifacts/decoded.png - rendered bitmap showing the flag.
  • Entry points:
    • compressArray defines the 2x3-to-character packing.
    • writeArray defines the row and end-of-stream delimiters.

Hypotheses & Approach

  • Hypothesis 1: The original input was a monochrome bitmap stored as ASCII 0/1 rows.
  • Hypothesis 2: Decompression is the inverse of compressArray: subtract 32 from each character, unpack 6 bits, and expand them back into two rows of three pixels.

Execution Steps (Reproducible)

Stage 1

Commands:

cd /root/dawg2026CTF/dust
unzip -o starting_files/chal.zip -d artifacts/unpacked
sed -n '1,220p' artifacts/unpacked/encoder.c
sed -n '1,5p' artifacts/unpacked/output.txt

Results:

  • The archive contains only encoder.c and output.txt.
  • Each compressed character encodes a 6-bit value taken from a 2x3 block of source pixels.
  • Rows in output.txt are separated by } and the full stream ends with ~.

Stage 2

Commands:

cd /root/dawg2026CTF/dust
python3 - <<'PY'
from pathlib import Path

text = Path('artifacts/unpacked/output.txt').read_text()
assert text.endswith('~')
rows = text[:-1].split('}')
if rows and rows[-1] == '':
rows = rows[:-1]

decoded = []
for row in rows:
top = []
bottom = []
for ch in row:
bits = f'{ord(ch) - 32:06b}'
top.append(bits[:3])
bottom.append(bits[3:])
decoded.append(''.join(top))
decoded.append(''.join(bottom))

Path('artifacts/decoded_binary.txt').write_text('\n'.join(decoded) + '\n')
PY

python3 - <<'PY'
from pathlib import Path

lines = Path('artifacts/decoded_binary.txt').read_text().splitlines()
with open('artifacts/decoded.pbm', 'w') as f:
f.write(f'P1\n{len(lines[0])} {len(lines)}\n')
for line in lines:
f.write(' '.join(line) + '\n')
PY

convert artifacts/decoded.pbm artifacts/decoded.png

Results:

  • The decompressed bitmap is 198x100 pixels.
  • Rendering the PBM reveals the flag text in the recovered image.
  • The flag reads DawgCTF{Th1s_w45_1nspIr3d_By_UND3RT4L3!}.

Artifacts Produced

  • artifacts/unpacked/encoder.c - extracted source for the compressor.
  • artifacts/unpacked/output.txt - extracted compressed stream.
  • artifacts/decoded_binary.txt - recovered binary pixel matrix.
  • artifacts/decoded.pbm - reconstructed bitmap.
  • artifacts/decoded.png - rendered image containing the flag.

Flag

DawgCTF{Th1s_w45_1nspIr3d_By_UND3RT4L3!}