Understanding Buffer Overflows: A Beginner's Guide - Part 2
Table of contents
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
Download the MinGW installer from https://sourceforge.net/projects/mingw/
Run the installer and select "GDB" during the installation process.
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.
Subscribe to my newsletter
Read articles from Wizard079 directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by