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
- Write encryption and decryption algorithm using high level language
- Test encryption algorithm on shellcode
- 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 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 = ("\x31\xc0\x89\xc2\x89\xc1\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xb0\x0b\xcd\x80") def main(argv): shellcode_len = len(bytearray(shellcode)) if shellcode_len &amp;amp;gt; 255 or shellcode_len &amp;amp;lt;1: print ("Shellcode is %d bytes, Must be less than 255 bytes" % shellcode_len) else: print "Original decrypted shellcde: %s" % 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 "\nGoing to crack shellcode encryption key ..." 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 "SHELL KEY:"+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 "Cracked the keys. Key used to encrypt shellcode: %x. Real shellcode length: %x" %( 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 ("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) 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
Reblogged this on youremindmeofmymother.
Pingback: Shellcode XOR Encoder and Decoder for Linux (x86) | doyler.net