Dust to Dust
Dust to Dust
Platform: Dawgctf | Category: Misc | Type: Challenge | Difficulty: Easy | OS: NA | Author: D3v0o0Nu11 | Date: 2026-04-10 | Status: Solved Techniques: bitmap_reconstruction_from_packed_text, reverse_custom_encoding, six_bit_unpacking, source_guided_decoder_reimplementation
Summary
Task: a C encoder compresses a binary image by packing each 2x3 pixel block into one printable ASCII byte, with } as row separators and ~ as EOF. Solution: subtract 0x20, expand every byte back to 6 bits, rebuild paired bitmap rows, and render the recovered image to read the handwritten flag.
Recon
Port scan
bash read-only • copy allowednmap -p- -sV -sC <TARGET> --min-rate 1000 -Pn
| Port | Service | Version | Notes |
|---|---|---|---|
| <PORT> | <SVC> | <VER> | <notes> |
Enumeration highlights
- Event:
dawgctf| ID:20260410_dawgctf_dust_to_dust - Tags: custom_encoding, dawgctf, bitmap_reconstruction, binary_image, printable_ascii, six_bit_packing, source_review
- Indicators: Source code reads
input.txtas rows of only0and1characters, Image width must be a multiple of 3 and height must be a multiple of 2, Each output byte is forced into printable ASCII via0x20 + value, Row separators are explicit non-image delimiters (}) and the stream ends with~, Recovered data looks like a monochrome bitmap rather than text or ciphertext - Source:
20260410_dawgctf_dust_to_dust.md
Foothold
Vulnerability / Misconfiguration
- bitmap_reconstruction_from_packed_text
- reverse_custom_encoding
- six_bit_unpacking
- source_guided_decoder_reimplementation
bash read-only • copy allowed<command>
Exploitation
- See original writeup content for detailed exploitation.
Privilege Escalation
Enumeration
bash read-only • copy allowedsudo -l find / -perm -4000 2>/dev/null getcap -r / 2>/dev/null cat /etc/crontab ps aux
Exploitation
- N/A for challenge-type writeup; see exploitation above.
- Flag obtained via challenge solve.
bash read-only • copy allowed<command>
Flags
| Flag | Location | Value |
|---|---|---|
| flag | REDACTED |
Key Takeaways / Lessons
- bitmap_reconstruction_from_packed_text
- reverse_custom_encoding
- six_bit_unpacking
- source_guided_decoder_reimplementation
- Tags: custom_encoding, dawgctf, bitmap_reconstruction, binary_image, printable_ascii, six_bit_packing, source_review
Original Writeup
<details><summary>Click to expand original content</summary>Description
File:
chal.zip
The repository entry only exposes the archive, so the practical challenge is to reverse the included encoder and decode output.txt. The recovered data is not normal plaintext: it is a packed monochrome bitmap that must be reconstructed into an image before the flag becomes readable.
Summary
encoder.c shows that the challenge takes a binary image, packs every 2x3 pixel block into one printable ASCII character, separates compressed rows with }, and terminates the stream with ~. Reversing that packing reconstructs the original bitmap, and rendering it reveals a handwritten DawgCTF flag.
Analysis
1. Read the encoder instead of guessing the format
The key artifact is encoder.c. Its input validation immediately tells us what the original data looked like:
input.txtmust contain only0and1- every row length must be a multiple of 3
- all rows must have equal length
- the number of rows must be a multiple of 2
That is a strong sign that the encoder is treating the file as a binary image or bitmap, not as text.
2. Understand the 2x3 packing rule
Inside compressArray, the encoder reads pixels in this order:
c read-only • copy allowedbuffer[0] = arr[l*2][w*3]; buffer[1] = arr[l*2][w*3 + 1]; buffer[2] = arr[l*2][w*3 + 2]; buffer[3] = arr[l*2 + 1][w*3]; buffer[4] = arr[l*2 + 1][w*3 + 1]; buffer[5] = arr[l*2 + 1][w*3 + 2];
So each encoded character represents a 2-row by 3-column block:
text read-only • copy allowedtop row: b0 b1 b2 bottom row: b3 b4 b5
The six bits are parsed as a binary integer and shifted into the printable ASCII range:
c read-only • copy allowedlong bin = strtol(buffer, NULL, 2); c = (char)(0b00100000 + bin);
So the mapping is:
text read-only • copy allowedencoded_char = chr(int(six_bits, 2) + 0x20)
3. Understand the row delimiters
writeArray writes each compressed row followed by 0x7d (}), then appends 0x7e (~) once at the end:
c read-only • copy allowedfprintf(output, "%s%c", arr[i], (char)0b01111101); ... fprintf(output, "%c", (char)0b01111110);
That means the decoding procedure is deterministic:
- remove the final
~ - split the stream on
} - for each character in a compressed row, compute
ord(ch) - 32 - convert that value back to a 6-bit binary string
- use the first 3 bits for the top row chunk and the last 3 bits for the bottom row chunk
- append those chunks to reconstruct the full bitmap
4. Rebuild and render the bitmap
Once the rows are reconstructed, the output is a large black-and-white image. Writing it back as plain bitmap text or as a PBM image makes the content visible immediately.
The recovered image contains handwritten text spelling the flag. A few letters are visually ambiguous in the raw handwriting, but the correct final flag is the user-confirmed one below.
Solution
The decoder can be implemented in a few lines of Python:
python read-only • copy allowed#!/usr/bin/env python3 from pathlib import Path def decode(path="output.txt"): data = Path(path).read_text() assert data.endswith("~"), "missing stream terminator" rows = [] for packed_row in data[:-1].split("}"): if not packed_row: continue top = [] bottom = [] for ch in packed_row: value = ord(ch) - 0x20 bits = f"{value:06b}" top.append(bits[:3]) bottom.append(bits[3:]) rows.append("".join(top)) rows.append("".join(bottom)) return rows def save_outputs(rows): Path("recovered_input.txt").write_text("\n".join(rows) + "\n") width = len(rows[0]) height = len(rows) with open("recovered.pbm", "w") as f: f.write(f"P1\n{width} {height}\n") for row in rows: f.write(" ".join(row) + "\n") if __name__ == "__main__": bitmap_rows = decode("output.txt") save_outputs(bitmap_rows) print(f"Recovered bitmap: {len(bitmap_rows)} rows x {len(bitmap_rows[0])} cols") print("Saved: recovered_input.txt, recovered.pbm")
Step-by-step
- Open
encoder.cand notice it readsinput.txtas a binary bitmap. - Observe that compression operates on 2x3 pixel blocks, producing 6-bit values.
- Notice the printable ASCII bias: each 6-bit value is stored as
value + 0x20. - Notice the structural delimiters:
}separates compressed rows and~marks end-of-stream. - Reverse the transform by subtracting
0x20, re-expanding to 6 bits, and splitting the bits back into top and bottom 3-pixel chunks. - Save the reconstructed rows and render them as a monochrome image.
- Read the handwritten flag from the recovered bitmap.
signed by XESXOR