PICOCTF 2019 • REVERSE ENGINEERING • BITMAP LSB CARVING

Investigative Reversing 2: Bitmap LSB Deconstruction & Shift Recovery

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

“We have recovered another binary and an image. See what was modified this time.”

Category: Forensics / Reverse EngineeringPoints: 350 PTSFlag Format: picoCTF{...}
Provided Files (Download & Practice)
Dissector: Ghidra + HxD

💡 THE INTUITIVE ANALOGY (The Even/Odd Morse Code)

Suppose a spy wants to send a secret number. Instead of writing the number down, he gives you a list of 8 large numbers. If a number is Even, it represents a 0. If a number is Odd, it represents a 1. By reading the even/odd state of 8 consecutive pixels in an uncompressed Bitmap image, you get an 8-bit binary number. Then add 5 to reveal the secret letter!

DECOMPILATION ANALYSIS

1. Inspecting the `mystery` Binary

// Ghidra Decompilation: mystery (main & codedChar)
int main(void) {
    FILE *orig_bmp = fopen("original.bmp", "r");
    FILE *enc_bmp  = fopen("encoded.bmp", "a");

    // 1. Copy first 2000 bytes directly (Offset 0x7D0)
    for (int i = 0; i < 2000; i++) {
        char b = fgetc(orig_bmp);
        fputc(b, enc_bmp);
    }

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

    // 2. Encode 50 characters using LSB Steganography
    for (int i = 0; i < 50; i++) {
        char val = flag[i] - 5; // Subtracted 5 from each character!
        for (int bit = 0; bit < 8; bit++) {
            char orig_b = fgetc(orig_bmp);
            char enc_b  = codedChar(bit, val, orig_b);
            fputc(enc_b, enc_bmp);
        }
    }
    // ...
}

char codedChar(int bit, char val, char orig_byte) {
    char bit_val = (val >> bit) & 1;            // Extract 1 bit
    char result  = (orig_byte & 0xFE) | bit_val; // Replace lowest bit of image byte
    return result;
}
METHOD A: STEP-BY-STEP MANUAL LSB DECODING

2. Manual LSB Bit Assembly for Character 0 ('p')

Jump to offset 0x7D0 (Decimal: 2000) in HxD and inspect the first 8 bytes: BC C1 BC BD BC BD C1 BC.

Byte PositionHex ByteEven or Odd?LSB BitBit WeightValue
Byte 0 (Bit 0)BCEven02⁰ = 10
Byte 1 (Bit 1)C1Odd12¹ = 22
Byte 2 (Bit 2)BCEven02² = 40
Byte 3 (Bit 3)BDOdd12³ = 88
Byte 4 (Bit 4)BCEven02⁴ = 160
Byte 5 (Bit 5)BDOdd12⁵ = 3232
Byte 6 (Bit 6)C1Odd12⁶ = 6464
Byte 7 (Bit 7)BCEven02⁷ = 1280

Calculation: 0 + 2 + 0 + 8 + 0 + 32 + 64 + 0 = 106 (+ 5 shift) = 112 = 'p'.

METHOD B: AUTOMATED PYTHON PARSER & ONELINER

3. Automated Extraction Script (`solve.py`)

# Read the encoded BMP image
with open('encoded.bmp', 'rb') as f:
    data = f.read()

offset = 2000  # Starting offset identified from decompilation
flag = []

# 50 characters, 8 bytes per character (LSB)
for i in range(50):
    val = 0
    for bit in range(8):
        byte = data[offset + i * 8 + bit]
        lsb = byte & 1
        val |= (lsb << bit)
    flag.append(chr(val + 5))  # Apply inverse math (+5)

print("🎉 Decoded Flag:", ''.join(flag))
⚡ Terminal One-Liner (PowerShell / Bash):
python -c "d=open('encoded.bmp','rb').read(); print(''.join(chr(sum(((d[2000+i*8+b]&1)<<b) for b in range(8))+5) for i in range(50)))"

4. Decoded Flag

Extracted Secret Flag:

picoCTF{n3xt_0n30000000000000000000000000ce8d5cad}

5. The Complete Investigation Path & Mental Roadmap

Here is the step-by-step mental roadmap from binary decompilation to LSB flag recovery:

STEP 1
Decompilation & Offset Discovery

Opened mystery in Ghidra. Traced the initial 2000-byte loop to locate the starting payload offset: 2000 (0x7D0).

STEP 2
Steganography Algorithm Identification

Analyzed codedChar(): spotted (orig & 0xFE) | bit_val confirming Least Significant Bit (LSB) embedding across 8 bytes per character.

STEP 3
Mathematical Shift Reversal

Identified flag[i] - 5 in the encoding loop, requiring an addition of 5 during bitstream recovery.

STEP 4
Flag Capture

Submitted recovered 50-character flag: picoCTF{n3xt_0n30000000000000000000000000ce8d5cad}.

Cyber Amber
#f59e0b
PresetsClick to lock