Understanding Buffer Overflows: A Beginner's Guide - Part 2

·

6 min read

This article is a 2nd part of https://blogs.copsiitbhu.co.in/understanding-buffer-overflows-a-beginners-guide-part-1 make sure you have read that for understanding it

In this article, we will dive into the binary and see what’s going on; for that, we need a debugger. What is a debugger? Well, a debugger is a tool that allows developers to test, inspect, and troubleshoot their code by executing it step-by-step, setting breakpoints, and examining variables and program flow. We are going to use gdb as our debugger in this article.

Installation

For Linux

run (for debian and derivatives) sudo apt install gdb

For macOS

run brew install gdb

For windows

Don’t install it on windows just use linux. Just kidding for windows use this steps

  1. Download the MinGW installer from https://sourceforge.net/projects/mingw/

  2. Run the installer and select "GDB" during the installation process.

  3. After installation, add the MinGW bin directory to your system's PATH.

Use terminal based on your OS and run this command to check your gdb installation

gdb --version

Buffer Overflow

To use gdb, we first need a binary to run. Let's try the following example:

// gcc -m32 -fno-stack-protector -z execstack -fsyntax-only -o vuln vuln.c

#include <stdio.h>
#include <string.h> 

const char MYPASS[] = "REDATED";

void win() {
    puts("This code is super secure, isn't it? :)");
}

void vuln() {
    char buffer[32];
    int password = 0;
    fgets(buffer, 0x32, stdin);
    if (strcmp(buffer, MYPASS) == 0) {
        password = 1;
    }

    if (password) {
        win();
    } else {
        puts("Incorrect password!");
    }
}

int main() {
    puts("Hello to our new bank!");

    vuln();

    return 0;
}

Save this file as vuln.c, then compile it using the following command in the same directory as vuln.c:

gcc -m32 -fno-stack-protector -z execstack -fsyntax-only -o vuln vuln.c

Now you have a binary named vuln. The password is unknown, but we can try to reach the win() function through buffer overflow manipulation. Let’s experiment and see what happens when we run the binary.

> ./vuln
Hello to our new bank!
tellmethepassword
Incorrect password!

In this code, the developer made a mistake in the fgets call by reading 50 bytes (0x32) instead of limiting it to 32 bytes. can we abuse this ??

Let’s try to adding input bigger that 32 and see what’s happening

> ./vuln
Hello to our new bank!
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
This code is super secure, isn't it? :)
Segmentation fault (core dumped)

😯 we just got in the win functions but there is Segmentation fault ?? Let’s dive into gdb and see what’s going on.

gdb

First, run this binary using GDB by executing:

gdb ./vuln

GDB comes with many powerful features. We can set breakpoints using GDB, and we can even see the assembly code of the binary. Let’s try to see the code of the main function in GDB. Run the following command:

gdb➤ disass main

You will see the output for the main function, where you can observe the esp, ebp, ebx, and ecx registers explained in the previous article. Here, we can see the call instructions for puts and vuln:

   0x0804924f <+37>:    call   0x8049060 <puts@plt>
   0x08049254 <+42>:    add    esp,0x10
   0x08049257 <+45>:    call   0x80491b1 <vuln>

Now let’s take a look at the vuln function inside GDB by running:

gdb➤ disass vuln

You may get output like this,

   0x080491b1 <+0>:    push   ebp
   0x080491b2 <+1>:    mov    ebp,esp
   0x080491b4 <+3>:    push   ebx
   0x080491b5 <+4>:    sub    esp,0x34
   0x080491b8 <+7>:    call   0x80490c0 <__x86.get_pc_thunk.bx>
   0x080491bd <+12>:    add    ebx,0x2e37
   0x080491c3 <+18>:    mov    DWORD PTR [ebp-0xc],0x0
   0x080491ca <+25>:    mov    eax,DWORD PTR [ebx-0x8]
   0x080491d0 <+31>:    mov    eax,DWORD PTR [eax]
   0x080491d2 <+33>:    sub    esp,0x4
   0x080491d5 <+36>:    push   eax
   0x080491d6 <+37>:    push   0x32
   0x080491d8 <+39>:    lea    eax,[ebp-0x2c]
   0x080491db <+42>:    push   eax
   0x080491dc <+43>:    call   0x8049050 <fgets@plt>
   0x080491e1 <+48>:    add    esp,0x10
   0x080491e4 <+51>:    sub    esp,0x8
   0x080491e7 <+54>:    lea    eax,[ebx-0x1fd4]
   0x080491ed <+60>:    push   eax
   0x080491ee <+61>:    lea    eax,[ebp-0x2c]
   0x080491f1 <+64>:    push   eax
   0x080491f2 <+65>:    call   0x8049030 <strcmp@plt>
   0x080491f7 <+70>:    add    esp,0x10
   0x080491fa <+73>:    test   eax,eax
   0x080491fc <+75>:    jne    0x8049205 <vuln+84>
   0x080491fe <+77>:    mov    DWORD PTR [ebp-0xc],0x1
   0x08049205 <+84>:    cmp    DWORD PTR [ebp-0xc],0x0
   0x08049209 <+88>:    je     0x8049212 <vuln+97>
   0x0804920b <+90>:    call   0x8049186 <win>
   0x08049210 <+95>:    jmp    0x8049224 <vuln+115>
   0x08049212 <+97>:    sub    esp,0xc
   0x08049215 <+100>:    lea    eax,[ebx-0x1f88]
   0x0804921b <+106>:    push   eax
   0x0804921c <+107>:    call   0x8049060 <puts@plt>
   0x08049221 <+112>:    add    esp,0x10
   0x08049224 <+115>:    nop
   0x08049225 <+116>:    mov    ebx,DWORD PTR [ebp-0x4]
   0x08049228 <+119>:    leave
   0x08049229 <+120>:    ret

Here we can see the calls to fgets, strcmp, win, and puts. By analyzing the source code, we know that before jumping to the win function, it will check the password in the if condition. Similarly, in this assembly code, we can see the cmp instruction, which compares DWORD PTR [ebp-0xc] with 0x0. Let’s set a breakpoint there and see what’s going on. In GDB, we can set a breakpoint using b or break followed by the address where we want to stop.

gdb➤ b *0x08049205

Alternatively, we can use a relative address, like so:

gdb➤ b *(vuln+84)

Both methods work the same way; you can use either. Now let’s run this binary using the command:

gdb➤ run

The program will wait for input. Let’s try IAmMrRobot as the password. After entering the password, the program will hit the breakpoint. We can now check where we are by using the command:

gdb➤ disass $eip

In 32-bit architecture, $eip always points to the instruction that is about to be executed, and the $ signifies that we are referencing the value of eip.

The output of disass $eip will look like this:

   0x080491b1 <+0>:    push   ebp
   0x080491b2 <+1>:    mov    ebp,esp
   0x080491b4 <+3>:    push   ebx
   0x080491b5 <+4>:    sub    esp,0x34
   0x080491b8 <+7>:    call   0x80490c0 <__x86.get_pc_thunk.bx>
   0x080491bd <+12>:    add    ebx,0x2e37
   0x080491c3 <+18>:    mov    DWORD PTR [ebp-0xc],0x0
   0x080491ca <+25>:    mov    eax,DWORD PTR [ebx-0x8]
   0x080491d0 <+31>:    mov    eax,DWORD PTR [eax]
   0x080491d2 <+33>:    sub    esp,0x4
   0x080491d5 <+36>:    push   eax
   0x080491d6 <+37>:    push   0x32
   0x080491d8 <+39>:    lea    eax,[ebp-0x2c]
   0x080491db <+42>:    push   eax
   0x080491dc <+43>:    call   0x8049050 <fgets@plt>
   0x080491e1 <+48>:    add    esp,0x10
   0x080491e4 <+51>:    sub    esp,0x8
   0x080491e7 <+54>:    lea    eax,[ebx-0x1fd4]
   0x080491ed <+60>:    push   eax
   0x080491ee <+61>:    lea    eax,[ebp-0x2c]
   0x080491f1 <+64>:    push   eax
   0x080491f2 <+65>:    call   0x8049030 <strcmp@plt>
   0x080491f7 <+70>:    add    esp,0x10
   0x080491fa <+73>:    test   eax,eax
   0x080491fc <+75>:    jne    0x8049205 <vuln+84>
   0x080491fe <+77>:    mov    DWORD PTR [ebp-0xc],0x1
=> 0x08049205 <+84>:    cmp    DWORD PTR [ebp-0xc],0x0
   0x08049209 <+88>:    je     0x8049212 <vuln+97>
   0x0804920b <+90>:    call   0x8049186 <win>
   0x08049210 <+95>:    jmp    0x8049224 <vuln+115>
   0x08049212 <+97>:    sub    esp,0xc
   0x08049215 <+100>:    lea    eax,[ebx-0x1f88]
   0x0804921b <+106>:    push   eax
   0x0804921c <+107>:    call   0x8049060 <puts@plt>
   0x08049221 <+112>:    add    esp,0x10
   0x08049224 <+115>:    nop
   0x08049225 <+116>:    mov    ebx,DWORD PTR [ebp-0x4]
   0x08049228 <+119>:    leave
   0x08049229 <+120>:    ret

It points to the location where we want to stop. Now, let’s see what the value of ebp-0xc is using this command:

gdb➤ x/x $ebp-0xc
0xffffd6fc:    0x0804a08000000000

Here, the first x is the command in GDB used to examine memory at a given address. The /x specifies the format, telling GDB to output in hexadecimal format. The value of DWORD PTR [ebp-0xc] will only look at the lower 32 bits, resulting in 0x00000000, which is the value of the password variable

Now, let’s rerun the binary with the r command, but this time with a larger input of AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA, and see the value of ebp-0xc:

gdb➤  x/x $ebp-0xc
0xffffd6fc:    0x4141414141414141

🤯 We have overwritten the value of password with 0x4141414141414141, which is the hexadecimal value of AAAAAAAA. This is due to the buffer overflow in the buffer. The buffer variable is set to be 32 bytes, but we tried to write 50 bytes using fgets, so we overwrite the extra bytes on the stack where other variables are stored. Hence, this leads to a stack buffer overflow.

So you remember the segmentation fault , you can try to find out what causes the segmentation fault. if you get that you can also understand the payload given in previous article.