The Buffer Overflow defence
Stack Canaries are very simple - at the beginning of the function, a random value is placed on the stack. Before the program executes ret
, the current value of that variable is compared to the initial: if they are the same, no buffer overflow has occurred.
If they are not, the attacker attempted to overflow to control the return pointer and the program crashes, often with a ***stack smashing detected***
error message.
On Linux, stack canaries end in 00
. This is so that they null-terminate any strings in case you make a mistake when using print functions, but it also makes them much easier to spot.
There are two ways to bypass a canary.
This is quite broad and will differ from binary to binary, but the main aim is to read the value. The simplest option is using format string if it is present - the canary, like other local variables, is on the stack, so if we can leak values off the stack it's easy.
The source is very simple - it gives you a format string vulnerability, then a buffer overflow vulnerability. The format string we can use to leak the canary value, then we can use that value to overwrite the canary with itself. This way, we can overflow past the canary but not trigger the check as its value remains constant. And of course, we just have to run win()
.
First let's check there is a canary:
Yup, there is. Now we need to calculate at what offset the canary is at, and to do this we'll use radare2.
The last value there is the canary. We can tell because it's roughly 64 bytes after the "buffer start", which should be close to the end of the buffer. Additionally, it ends in 00
and looks very random, unlike the libc and stack addresses that start with f7
and ff
. If we count the number of address it's around 24 until that value, so we go one before and one after as well to make sure.
It appears to be at %23$p
. Remember, stack canaries are randomised for each new process, so it won't be the same.
Now let's just automate grabbing the canary with pwntools:
Now all that's left is work out what the offset is until the canary, and then the offset from after the canary to the return pointer.
We see the canary is at 0xffea8afc
. A little later on the return pointer (we assume) is at 0xffea8b0c
. Let's break just after the next gets()
and check what value we overwrite it with (we'll use a De Bruijn pattern).
Now we can check the canary and EIP offsets:
Return pointer is 16 bytes after the canary start, so 12 bytes after the canary.
Same source, same approach, just 64-bit. Try it yourself before checking the solution.
Remember, in 64-bit format string goes to the relevant registers first and the addresses can fit 8 bytes each so the offset may be different.
This is possible on 32-bit, and sometimes unavoidable. It's not, however, feasible on 64-bit.
As you can expect, the general idea is to run the process loads and load of times with random canary values until you get a hit, which you can differentiate by the presence of a known plaintext, e.g. flag{
and this can take ages to run and is frankly not a particularly interesting challenge.