canary
Leaking a stack canary to allow for buffer overflows.
Last updated
Leaking a stack canary to allow for buffer overflows.
Last updated
This problem is the first instance where the stack protector, called the canary is enabled. We need to figure out how to beat that canary to perform a buffer overflow.
When we run checksec
on the binary, we notice that the canary is enabled.
PIE is still disabled, meaning we can still use the same techniques we used in the previous binary. However, we can't use the same techniques to overflow the stack. Let's see what happens when we try to overflow the stack.
A stack smashing detected statement in the output indicates that we overwrote the canary. We need to determine how to beat the canary, and then we will proceed as usual.
It seems that our primary functions are main
, read_in
, and win
.
win
seems only to print the contents of the flag.
main
immediately calls read_in
, then has a puts
statement. We can use gdb
to show that reads "You lose!".
read_in
does a list of things, so let's break it down further. Checking the arguments as we scroll through gdb
:
puts("Hello, what is your name?")
gets(ebp-0x4c)
printf(ebp-0x4c)
puts("How can I help you today?")
gets(ebp-0x4c)
After this last check, we see that the canary check is made:
From this, we gather a few things:
We write to the same buffer both times that we write to memory.
The format string vulnerability in the first read means we can leak the canary.
The existence of a second read means we can pass in a second payload that uses the canary to pass the check, then performs a buffer overflow.
The canary is stored at ebp-0xc
.
We know the canary is stored on the stack at ebp-0xc
. We will use a format string to leak the canary to standard output and then pass that canary to the second payload.
There are two ways to find the offset on the stack that the canary is stored at:
Use gdb
to set a breakpoint before the gets
call, then count the number of DWORD frames between the stack pointer and canary.
Check the location of the stack pointer at the start of the read_in
function, account for the number of operations on the stack pointer, then count the number of DWORD frames between the stack pointer and canary.
The first one is by far easier and more practical.
gdb
tells us that the canary is at 0xffffd80b
. If we count from our location to the canary, we see that it is 23 DWORDs away. We can verify this using the format string:
This appears to work! Let's turn this into a pwntools
script:
Now that we have the canary, we can overflow the buffer. We need to do this in two steps:
Write data until we reach the canary, then write the canary to the stack (so it doesn't get modified).
Write data from the canary to the return pointer, then overwrite the return pointer with the address of win()
.
We discussed earlier that the canary is stored at ebp-0xc
. Based on the argument passed to gets()
, we start writing at ebp-0x4c
. This means we need to write 0x40=64
bytes of data before reaching the canary.
Our payload here could look like this:
Then, we need to write from the canary to the return pointer. The canary sits at ebp-0xc
, and the return pointer always sits at ebp+0x4
(because the base pointer is at ebp+0x0
). Remember that the canary takes four bytes itself, meaning we start to write at ebp-0x8
. This means we need to write 0xc
bytes of data to reach the return pointer.
Our payload here could look like this:
Why did I use "B" this time?
I can use any value to fill the empty space. I choose to use a different value for debugging purposes. In the case that my payload is wrong, it's easier to tell if I overfilled or underfilled the buffer by using two different values.
From here, we put it all together into one large payload and send it off!
Running this gets us our flag.