How to Do Buffer Overflow Attack on 64bit Machine

giant tsunami wave

Professor’s course material was out of date. He made an example of a buffer overflow attack several years ago. Then here I am, asked to fix the code to work on a modern machine, 64bit Kali Linux.

There should be no difference with other Linux; you can practice this on any 64bit Linux machine.

Prerequisite

First, you need GCC and GDB; on Kali, GCC is already installed, so you just need to install the GDB.

sudo apt install gdb

Then we go to write the vulnerable code. Let’s create a file called demo.c.

$ vi demo.c
//demo.c​
#include<stdio.h> ​

void getinput()​
{ ​
       char buffer[8];​
       gets(buffer);​
       puts(buffer);​
}​

int main()​
{​
       getinput();​
       return 0;​
}​

This program waits for a string input of 8 characters from the user and then writes it back. However, it has no check for boundaries. So when the user inputs a string with more than 8 characters, something unpredictable will happens.

Now let’s install GDB and compile our demo.c without a stack protector. Why? Because by default, GCC already protects the program from stack smashing caused by a non-educated developer who doesn’t care about the input boundary.

$ sudo apt install gdb​
$ gcc -ggdb -fno-stack-protector -o demo demo.c​

Run the program and input more than 8 characters; try 16 characters and hit Enter.

$ ./demo​

That should cause the program to be terminated by a segmentation fault.

What happens?

Look at the stack layout below. The buffer starts from the lowest memory, it will be filled from low to high memory. What will happen when the user fills the buffer with more than 8 bytes? The Base Pointer (RBP) and return address (RET) are in danger, and they will be smashed by the user input.

Stack layout

Closer Look

Let’s take a closer look by using GDB.

$ gdb demo

Create a breakpoint at getinput() function, and run the program.

(gdb) b getinput
(gdb) run

The program will run and stop at the prologue of getinput() function. Here we can check the return address in current condition.

(gdb) x/8xg $rsp
Where is 0x5555555517f ?

That address should be pointing to the line after the getinput() function is called. Let’s confirm it by using disassembly. Note that this return address on different machines may be different.

(gdb) disas 0x55555555517f
0x55555555517f is the line after getinput() function is called

As expected, the return address points to the line right after the getinput() function is called.

Buffer Overflow!

Use n to go to the next instruction, the program will wait for input. Let’s input more than 8 characters to trigger a buffer overflow and hit Enter.

(gdb) n
1234567890abcdefghijklmn

At this time, we already overflow the buffer, lets check the stack values.

(gdb) x/8xg $rsp
The return address is smashed by overflowing user input

The figure above shows that the first 8 bytes of our input are put in the buffer space, but the other 16 smashes through the RBP and return the address. This means we can modify our last 8-byte input to a value that points to another address which concludes that we can jump to any address we want after getinput() function is called.

Smashed stack layout and current values

If you continue the program, it will be terminated by segmentation fault because the address 0x6e6d6c6b6a696867 is invalid and does not exist in our program.

We are Free to Travel Anywhere

The return address can be replaced by any values we want. Let’s use it to jump to a hidden function that can never be called. Create a new demo called demo2.c

//demo2.c​
​
#include<stdio.h> ​
void canneverexecute()​
{  
   printf(”I can never be executed in normal case!\n”);​
}​

void getinput()​
{ ​
   char buffer[8];​
   gets(buffer);​
   puts(buffer);​
}​

int main()​
{ ​
   printf(“Secret function at %p\n”, (void *) canneverexecute);​
   getinput();​
   return 0;​
}​

Compile just like before without a stack protector and run.

​$ gcc -ggdb -fno-stack-protector -o demo2 demo2.c
$ ./demo2

Each time you run the program, the address keeps changing, so we will disable the user address randomization to make it easier​.

$ sudo bash -c 'echo 0 > /proc/sys/kernel/randomize_va_space’​

Run the program again, and it will show that the address of the hidden function does not change anymore. We will use this address as input to replace the return address in the stack so that after the getinput() function is called, the hidden function will run.

Jumping to another function that is never called

Congratulations! Now you understand how the buffer overflow works. Even if this technique is classic, there are still many legacy machines out there living with this vulnerability. Even the newest machine can still suffer from this vulnerability. See it in CVE.

Even the newest machine still suffers from this classic vulnerability

I hope this post can help you understand the classic vulnerability. See you in another post, and have a nice day! 🙂


One response to “How to Do Buffer Overflow Attack on 64bit Machine”

Leave a Reply

Your email address will not be published. Required fields are marked *