PICOCTF 2019 • REVERSE ENGINEERING • MULTI-IMAGE CARVING

Investigative Reversing 1: Multi-PNG Overlay Carving

By AbdoAug 31, 2026
Investigative Reversing 1 Analysis
Official Challenge Prompt

“We have recovered a binary and a few images. See if you can recover the flag.”

Category: Forensics / Reverse EngineeringPoints: 350 PTSFlag Format: picoCTF{...}
Dissector: Ghidra + HxD

💡 THE INTUITIVE ANALOGY (The Shredded Letter)

Imagine an author writing a 26-letter secret password. Instead of mailing one letter, they tear the password into 3 pieces. They slip piece #1 into an envelope labeled mystery.png, piece #2 into mystery2.png (and add 21 to the first letter just to scramble it), and piece #3 into mystery3.png. They tape these scraps to the very outside bottom of each envelope (past the IEND seal). To solve it, we simply inspect the bottom of all 3 envelopes and solve the 26-slot jigsaw puzzle!

STEP 1: REVERSE ENGINEERING

1. Decompiling `mystery` in Ghidra

Opening mystery in Ghidra and inspecting main() reveals the crucial clue: all three PNG files are opened with mode "a" (Append mode).

int main(void) {
    FILE *flag_file = fopen("flag.txt", "r");
    FILE *f1 = fopen("mystery.png", "a");   // Append mode (writes past IEND)
    FILE *f2 = fopen("mystery2.png", "a");
    FILE *f3 = fopen("mystery3.png", "a");

    char flag[26];
    fread(flag, 26, 1, flag_file);

    // Distribution Sequence:
    fputc(flag[1], f3);          // mystery3.png gets flag[1]
    fputc(flag[0] + 21, f2);     // mystery2.png gets flag[0] + 21 (0x15)
    fputc(flag[2], f3);          // mystery3.png gets flag[2]
    fputc(flag[5], f3);          // mystery3.png gets flag[5]
    fputc(flag[4], f1);          // mystery.png  gets flag[4]

    // Indices 6..9 to mystery.png
    for (int i = 6; i <= 9; i++) {
        fputc(flag[i], f1);
    }
    fputc(flag[3] + 4, f2);      // mystery2.png gets flag[3] + 4

    // Indices 10..14 to mystery3.png
    for (int i = 10; i <= 14; i++) {
        fputc(flag[i], f3);
    }

    // Indices 15..25 to mystery.png
    for (int i = 15; i <= 25; i++) {
        fputc(flag[i], f1);
    }

    return 0;
}
Mathematical Byte Distribution Summary:
  • mystery.png receives 16 bytes: flag[4], flag[6..9], flag[15..25]
  • mystery2.png receives 2 bytes: flag[0] + 21, flag[3] + 4
  • mystery3.png receives 8 bytes: flag[1], flag[2], flag[5], flag[10..14]
  • Total: 16 + 2 + 8 = 26 bytes (Exact match for the 26-character flag).
METHOD A: HANDS-ON MANUAL RECONSTRUCTION

2. Manual Solution (Hex Editor & Pen & Paper Table)

How a beginner can solve this by hand using just HxD / HexEd.it and an ASCII decimal chart.

Step 1: Extract Trailing Bytes Past `IEND` in Each Image

In any Hex Editor, jump to the end of each image file. Look immediately past the standard PNG end marker IEND (49 45 4E 44 AE 42 60 82):

1. mystery.png (16 Bytes):
43 46 7B 41 6E 31 5F 38 61 34 34 38 63 62 32 7D
ASCII: CF{An1_8a448cb2}
2. mystery2.png (2 Bytes):
85 73
ASCII: \x85 and 's'
3. mystery3.png (8 Bytes):
69 63 54 30 74 68 61 5F
ASCII: icT0tha_

Step 2: Solve the Math for the 2 Modified Characters

Slot 0 Calculation:
mystery2[0] = 0x85 (Decimal 133)
Slot 0 = 133 - 21 = 112 = 'p'
Slot 3 Calculation:
mystery2[1] = 's' (Decimal 115)
Slot 3 = 115 - 4 = 111 = 'o'

Step 3: Complete 26-Slot Reconstruction Matrix

Slot IndexSource File & PositionEncoded ByteMath / OperationDecoded Character
0mystery2.png [0]0x85 (133)133 - 21p
1mystery3.png [0]'i' (105)Unchangedi
2mystery3.png [1]'c' (99)Unchangedc
3mystery2.png [1]'s' (115)115 - 4o
4mystery.png [0]'C' (67)UnchangedC
5mystery3.png [2]'T' (84)UnchangedT
6mystery.png [1]'F' (70)UnchangedF
7mystery.png [2]'{' (123)Unchanged{
8mystery.png [3]'A' (65)UnchangedA
9mystery.png [4]'n' (110)Unchangedn
10mystery3.png [3]'0' (48)Unchanged0
11mystery3.png [4]'t' (116)Unchangedt
12mystery3.png [5]'h' (104)Unchangedh
13mystery3.png [6]'a' (97)Unchangeda
14mystery3.png [7]'_' (95)Unchanged_
15mystery.png [5]'1' (49)Unchanged1
16..25mystery.png [6..15]'_8a448cb2}'Unchanged_8a448cb2}
METHOD B: AUTOMATED SCRIPT & ONELINER

3. Automated Python Solver (`solve.py`)

Automating the 26-slot reconstruction with a clean Python script:

with open('mystery.png', 'rb') as f:
    m1 = f.read()[-16:]
with open('mystery2.png', 'rb') as f:
    m2 = f.read()[-2:]
with open('mystery3.png', 'rb') as f:
    m3 = f.read()[-8:]

flag = [None] * 26

# Slot arithmetic from binary reverse engineering
flag[0] = chr(m2[0] - 0x15)  # 133 - 21 = 112 ('p')
flag[1] = chr(m3[0])         # 'i'
flag[2] = chr(m3[1])         # 'c'
flag[3] = chr(m2[1] - 4)     # 115 - 4 = 111 ('o')
flag[4] = chr(m1[0])         # 'C'
flag[5] = chr(m3[2])         # 'T'

for i in range(4):
    flag[6 + i] = chr(m1[1 + i])

for i in range(5):
    flag[10 + i] = chr(m3[3 + i])

for i in range(11):
    flag[15 + i] = chr(m1[5 + i])

print("🎉 Decoded Flag:", ''.join(flag))
⚡ Terminal One-Liner (PowerShell / Bash):
python -c "m1=open('mystery.png','rb').read()[-16:]; m2=open('mystery2.png','rb').read()[-2:]; m3=open('mystery3.png','rb').read()[-8:]; f=[chr(m2[0]-21), chr(m3[0]), chr(m3[1]), chr(m2[1]-4), chr(m1[0]), chr(m3[2])] + [chr(m1[1+i]) for i in range(4)] + [chr(m3[3+i]) for i in range(5)] + [chr(m1[5+i]) for i in range(11)]; print(''.join(f))"

4. Verified Flag

Decoded Flag Output:

picoCTF{An0tha_1_8a448cb2}

5. Key Takeaways & Lessons

PrincipleTechnical RuleWhy It Matters
Append Mode ("a")fopen(..., "a")Writes directly past the end of the file, creating trailing overlay data.
PNG TerminatorIEND (49 45 4E 44 ...)Standard PNG parsers stop reading at IEND; anything past it is hidden payload.
Byte Count Verification16 + 2 + 8 = 26 bytesAlways sum extracted pieces against the initial buffer size to confirm zero data loss.

6. The Complete Investigation Path & Mental Roadmap

Here is the step-by-step mental roadmap from binary decompilation to manual jigsaw assembly:

STEP 1
Decompiling the Encoding Binary

Opened mystery in Ghidra. Traced main() and spotted fopen(..., "a") appending 26 bytes across mystery.png, mystery2.png, and mystery3.png.

STEP 2
Carving Trailing Overlays Past IEND

Opened all 3 images in HxD / HexEd.it. Jumped past the IEND marker (49 45 4E 44 AE 42 60 82) and extracted 16 bytes from mystery.png, 2 bytes from mystery2.png, and 8 bytes from mystery3.png (Total: 26 bytes).

STEP 3
Reverse Math & Scrambled Byte Recovery

Calculated the 2 shifted bytes from mystery2.png: Slot 0 was $133 - 21 = 112$ ('p'), and Slot 3 was $115 - 4 = 111$ ('o').

STEP 4
Jigsaw Assembly & Automated Verification

Constructed the 26-slot table mapping each byte slice back to its original index. Validated the assembly using our automated Python script solve.py.

STEP 5
Flag Capture

Submitted the verified flag string: picoCTF{An0tha_1_8a448cb2}.

Cyber Amber
#f59e0b
PresetsClick to lock