Challenge Info
Can you handle function pointers? Downloads the binary here. Downloads the source here.
Additional details will be available after launching your challenge instance.
Understanding chall.c
The code for your convenience:
1 |
|
- Defines a constant for the max size of the flag string (
FLAGSIZE_MAX
). - Two char pointers are declared:
x
is used to store a string,input_data
stores user input- each being 5 bytes of size. - A
win()
function is declared. It reads the flag from a file (flag.txt
) and prints it for us. It uses a buffer to store said flag, and ensures that it doesn’t exceedFLAGSIZE_MAX
- A
check_win()
is declared. It executes a function at the address stored in thex
pointer. - The
init()
function allocates memory forinput_data
andx
, and initializes them with the strings “pico” and “bico” respectively. - The
write_buffer()
function asks the use for input, which the function will then store ininput_data
usingscanf
(recall thatscanf
is unsafe, as it does not check for buffer overflows).
Vulnerabilities
There’s several vulnerabilities to note:
- The
write_buffer()
function is usingscanf
to read user input.scanf
is unsecure and can be overflowed. - The
input_data
andx
buffer are allocated to hold only 5 bytes (4 bytes and then a null character) - The
check_win()
function executes code at the memory address being stored inx
.
Connecting to the netcat listener
1 | > nc mimas.picoctf.net 55662 |
Again, pico
and bico
are the values inside the buffers (input_data
& x
respectively) that were declared at the start. Again, the reason they’re declared to be 5 bytes, is to leave 1 byte for the null character.
Just like last time, we’re given the addresses. The only thing that’s different is that these are buffers instead of variables. Again, we’ll subtract the address of pico
with the address of bico
.
0x22b82b0 - 0x22b82d0 = -0x20
. When ran through cyber chef (from hex to decimal) we get a value of 32. And because like last time, our initial hex value was negative, this means that input_data
is 32 bytes behind x
.
The Plan
This now know that input_data
is 32 bytes behind x
. Additionally, we know that the check_win()
function executes a function at the address stored in the x
pointer. Finally we know that if a win()
function is declared, it’ll read us the flag.
So, in short, we want to: overflow to reach the x
pointer, and then get it to hold a value identical to the address of the win()
function, so that when check_win()
is automatically ran, instead of executing ‘bico’ at x
, it will execute win()
- thus giving us our flag.
Solution
Before we write our payloads, we need to know the address corresponding to win()
. A simple objdump will reveal this:
1 | > objdump -d ./chall | grep win |
This now know that win()
is at 0x080484b6
. However, because of C’s memory layout, we need to consider C’s memory layout. C uses a little-endian system to ensure that the least significant bytes are placed first. Because of this, we want to input the address of win()
in little-endian order.
Our payloads should look something like this:
1 | from pwn import * |
flag: picoCTF{and_down_the_road_we_go_dde41590}