3
votes

I'm learning about basic buffer overflows, and I have the following C code:

int your_fcn()
{
    char buffer[4];
    int *ret;

    ret = buffer + 8;
    (*ret) += 16;

    return 1;
}

int main()
{
    int mine = 0;
    int yours = 0;

    yours = your_fcn();
    mine = yours + 1;

    if(mine > yours)
        printf("You lost!\n");
    else
        printf("You won!\n");

    return EXIT_SUCCESS;
}

My goal is to bypass the line mine = yours + 1;, skip straight to the if statement comparison, so I can "win". main() cannot be touched, only your_fcn() can.

My approach is to override the return address with a buffer overflow. So in this case, I identified that the return address should be 8 bytes away from buffer, since buffer is 4 bytes and EBP is 4 bytes. I then used gdb to identify that the line I want to jump to is 16 bytes away from the function call. Here is the result from gdb:

(gdb) disassemble main
Dump of assembler code for function main:
   0x0000054a <+0>:     lea    0x4(%esp),%ecx
   0x0000054e <+4>:     and    $0xfffffff0,%esp
   0x00000551 <+7>:     pushl  -0x4(%ecx)
   0x00000554 <+10>:    push   %ebp
   0x00000555 <+11>:    mov    %esp,%ebp
   0x00000557 <+13>:    push   %ebx
   0x00000558 <+14>:    push   %ecx
   0x00000559 <+15>:    sub    $0x10,%esp
   0x0000055c <+18>:    call   0x420 <__x86.get_pc_thunk.bx>
   0x00000561 <+23>:    add    $0x1a77,%ebx
   0x00000567 <+29>:    movl   $0x0,-0xc(%ebp)
   0x0000056e <+36>:    movl   $0x0,-0x10(%ebp)
   0x00000575 <+43>:    call   0x51d <your_fcn>
   0x0000057a <+48>:    mov    %eax,-0x10(%ebp)
   0x0000057d <+51>:    mov    -0x10(%ebp),%eax
   0x00000580 <+54>:    add    $0x1,%eax
   0x00000583 <+57>:    mov    %eax,-0xc(%ebp)
   0x00000586 <+60>:    mov    -0xc(%ebp),%eax
   0x00000589 <+63>:    cmp    -0x10(%ebp),%eax
   0x0000058c <+66>:    jle    0x5a2 <main+88>
   0x0000058e <+68>:    sub    $0xc,%esp
   0x00000591 <+71>:    lea    -0x1988(%ebx),%eax

I see the line 0x00000575 <+43>: call 0x51d <your_fcn> and 0x00000583 <+57>: mov %eax,-0xc(%ebp) are four lines away from each other, which tells me I should offset ret by 16 bytes. But the address from gdb says something different. That is, the function call starts on 0x00000575 and the line I want to jump to is on 0x00000583, which means that they are 15 bytes away?

Either way, whether I use 16 bytes or 15 bytes, I get a segmentation fault error and I still "lose".

Question: What am I doing wrong? Why don't the address given in gdb go by 4 bytes at a time and what's actually going on here. How can I correctly jump to the line I want?


Clarification: This is being done on a x32 machine on a VM running linux Ubuntu. I'm compiling with the command gcc -fno-stack-protector -z execstack -m32 -g guesser.c -o guesser.o, which turns stack protector off and forces x32 compilation.


gdb of your_fcn() as requested:

(gdb) disassemble your_fcn
Dump of assembler code for function your_fcn:
   0x0000051d <+0>: push   %ebp
   0x0000051e <+1>: mov    %esp,%ebp
   0x00000520 <+3>: sub    $0x10,%esp
   0x00000523 <+6>: call   0x5c3 <__x86.get_pc_thunk.ax>
   0x00000528 <+11>:    add    $0x1ab0,%eax
   0x0000052d <+16>:    lea    -0x8(%ebp),%eax
   0x00000530 <+19>:    add    $0x8,%eax
   0x00000533 <+22>:    mov    %eax,-0x4(%ebp)
   0x00000536 <+25>:    mov    -0x4(%ebp),%eax
   0x00000539 <+28>:    mov    (%eax),%eax
   0x0000053b <+30>:    lea    0xc(%eax),%edx
   0x0000053e <+33>:    mov    -0x4(%ebp),%eax
   0x00000541 <+36>:    mov    %edx,(%eax)
   0x00000543 <+38>:    mov    $0x1,%eax
   0x00000548 <+43>:    leave  
   0x00000549 <+44>:    ret  
1
Since you’re playing with assembler, you should diagnose which CPU you are targetting and whether you are building 32-but or 64-but, and probably which o/s since the layouts can be different. If you include -fomit -frame-pointer and you’re working with GCC, you may get a different result. And a good optimizing compiler night spot the UB and arrange to reformat your disk before you do any real damage — or maybe you’ll be lucky and your compiler will let your hard drive live to see another dayJonathan Leffler
"Why don't the address given in gdb go by 4 bytes at a time?" because many processors have variable length instructions?Weather Vane
The call itself is not relevant since you are overwriting the return address. Hence, you should calculate the offset relative to 0x0000057a <+48>. Assuming you have found the correct place on the stack, that should mean an offset of 9.Jester
You need to post disassembly of your_func. Presumably your offset of ret = buffer + 8; is wrong.Jester
The compiler allocates ret at ebp-4 and buffer at ebp-8, so the return address is at buffer+12.prl

1 Answers

3
votes

x86 has variable length instructions, so you cannot simply count instructions and multiply by 4. Since you have the output from gdb, trust it to determine the address of each instruction.

The return address from the function is the address after the call instruction. In the code shown, this would be main+48.

The if statement starts at main+60, not main+57. The instruction at main+57 stores yours+1 into mine. So to adjust the return address to return to the if statement, you should add 12 (that is, 60 - 48).

Doing that skips the assignments to both yours and mine. Since they are both initialized to 0, it will print "You won".