CTF Forensics and Steganography Analysis: Practical Solution Methods

Binary String Enumeration

Examining the downloaded file in a hex editor (such as 010 Editor) allows for a direct search of specific signatures. By querying the string "CATCTF", the hidden credential can be directly retrieved.

Credential: CatCTF{EFI_1sv3ry_funn9}

Image Color Channel Analysis and Morse Decoding

When standard hex inspection and metadata extraction (e.g., binwalk) yield no results on an image file, manipulating color channels often reveals concealed elements. Loading the image into Stegsolve and adjusting the color planes exposes a hidden QR code.

Scanning the QR code yields a Morse code sequence: ...--.----...--... Referencing the challenge title "4433", decoding this sequence results in "VYGUD". In standard Morse code abbreviations, VY translates to VERY, and GUD translates to GOOD.

Credential: flag{VERYGOOD}

Power Consumption Trace Analysis

Side-channel attacks often rely on analyzing discrepancies in energy consumption. Executing a conditional branch (like an if statement) requires more power and time than skipping it. In a trace visualization, the sparse regions between dense operational bands represent these conditional checks. A shorter sparse band width corresponds to a 0 (branch not taken), while a longer width corresponds to a 1 (branch taken).

Credential: SCTF{0110111010}

Python Bytecode Steganography and Multimedia Extraction

A .pyc file that fails to decompile may contain concealed payloads or be structurally altered. Inspecting the hex view in this scenario reveals a reversed compressed archive.

with open('output.pyc', 'wb') as out_file:
    with open('input.pyc', 'rb') as in_file:
        out_file.write(in_file.read()[::-1])

After reversing the byte order, the missing header 03F30D0A must be restored. Within the archive, an MP3 file exists, secured by ZIP pseudo-encryption (modifying the flag bytes to 0008 bypasses this). The audio file's metadata contains an encryption key.

Additionally, base32 steganography embedded within the .pyc structure requires a custom extraction script:

import base64

B32_CHARSET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'

def extract_diff(stego, original):
    for idx in range(len(original)):
        if stego[idx] != original[idx]:
            return abs(B32_CHARSET.index(chr(stego[idx])) - B32_CHARSET.index(chr(original[idx])))
    return 0

def decode_b32_stego(data_lines):
    binary_result = ''
    for line in data_lines:
        stripped = line.strip()
        norm = base64.b32encode(base64.b32decode(stripped))
        delta = extract_diff(stripped, norm)
        if '=' not in stripped:
            continue
        binary_result += bin(delta)[2:] if delta else '0'
    return binary_result

The extracted binary string can be rendered into an image using Pillow:

from PIL import Image

def render_binary_image(data_path, output_path, width=240, height=30):
    pixel_data = open(data_path, 'r').read()
    img = Image.new('RGB', (width, height))
    idx = 0
    for x in range(width):
        for y in range(height):
            color = (0, 0, 0) if pixel_data[idx] == '0' else (255, 255, 255)
            img.putpixel((x, y), color)
            idx += 1
    img.save(output_path)

CRC Polynomial Brute-forcing and Calculation

Given decoded messages from a temperature sensor, the payload begins at the second byte (the first byte is a start bit). To determine the correct checksum for a new message, the CRC polynomial must be identified.

def compute_crc(dividend, divisor):
    while dividend > divisor:
        shift = len(bin(dividend)) - len(bin(divisor))
        aligned_divisor = int(bin(divisor)[2:] + '0' * shift, 2)
        dividend ^= aligned_divisor
    return dividend

polynomial = 0
for candidate in range(0x200):
    if candidate < 100:
        continue
    if compute_crc(0x24d8893ca584100, candidate) == 0x81 and compute_crc(0x24d8845abf34100, candidate) == 0x19:
        polynomial = candidate
        break

print(hex(compute_crc(0x00024ddeadbeef4100, polynomial)))
print(hex(compute_crc(0x00024dbaada5554100, polynomial)))

The calculated checksums are 0xb5 and 0x15.

Credential: flag{b515}

ZIP Pseudo-Encryption Recovery

Encrypted ZIP archives sometimes utilize modified flags to simulate encryption. By repairing the encryption flag bytes to their standard values, the archive decompresses without a password, exposing the internal files (such as Minecraft map data) and revealing the flag.

Filesystem String Extraction

Disk images or ISO files may contain plaintext credentials. By loading the raw binary data into a hex editor and executing a string search operation, a base64-encoded flag can be located. Decoding the string yields the final result.

Credential: flag{sajbcibzskjjcnbhsbvcjbjszcszbkzj}

RAR Block Manipulation and QR Code Reconstruction

Corrupted RAR archives often require manual header fixes. Changing the hex value 7A to 74 converts a sub-block into a file block, allowing the internal image to be extracted. The extracted file may actually be in GIF format rather than PNG. Processing the GIF frames with an image editor reveals multiple blank layers.

Analyzing the individual frames using Stegsolve uncovers partial QR codes. Combining the two extracted frames mathematically reconstructs the full QR code.

Credential: flag{yanji4n_bu_we1shi}

Base64 Padding Steganography

Concealed data within Base64 encoded strings often resides in the padding characters. A string containing one = hides 2 bits, while a string containing == hides 4 bits.

B64_CHARSET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'

def char_to_index(char):
    return B64_CHARSET.index(char)

def extract_stego(file_path):
    with open(file_path, 'r') as f:
        content = f.readlines()

    hidden_bits = ''
    for row in content:
        clean_row = row.strip()
        if clean_row.endswith('=='):
            idx = char_to_index(clean_row[-3])
            hidden_bits += bin(idx)[2:].zfill(4)[-4:]
        elif clean_row.endswith('='):
            idx = char_to_index(clean_row[-2])
            hidden_bits += bin(idx)[2:].zfill(2)[-2:]

    result = ''
    for i in range(0, len(hidden_bits), 8):
        byte_chunk = hidden_bits[i:i+8]
        if len(byte_chunk) == 8:
            result += chr(int(byte_chunk, 2))
    return result

Credential: flag{Base_sixty_four_point_five}

Tags: CTF Forensics Steganography python Binary Analysis

Posted on Sun, 17 May 2026 19:56:59 +0000 by Bramme