github.com/Coalfire-Research/Slackor@v0.0.0-20191010164036-aa32a7f9250b/impacket/examples/wmiexec.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 smbexec but executing commands through WMI.
     9  # Main advantage here is it runs under the user (has to be Admin) 
    10  # account, not SYSTEM, plus, it doesn't generate noisy messages
    11  # in the event log that smbexec.py does when creating a service.
    12  # Drawback is it needs DCOM, hence, I have to be able to access 
    13  # DCOM ports at the target machine.
    14  #
    15  # Author:
    16  #  beto (@agsolino)
    17  #
    18  # Reference for:
    19  #  DCOM
    20  #
    21  from __future__ import division
    22  from __future__ import print_function
    23  import sys
    24  import os
    25  import cmd
    26  import argparse
    27  import time
    28  import logging
    29  import ntpath
    30  
    31  from impacket.examples import logger
    32  from impacket import version
    33  from impacket.smbconnection import SMBConnection, SMB_DIALECT, SMB2_DIALECT_002, SMB2_DIALECT_21
    34  from impacket.dcerpc.v5.dcomrt import DCOMConnection
    35  from impacket.dcerpc.v5.dcom import wmi
    36  from impacket.dcerpc.v5.dtypes import NULL
    37  from six import PY2
    38  
    39  OUTPUT_FILENAME = '__' + str(time.time())
    40  CODEC = sys.stdout.encoding
    41  
    42  class WMIEXEC:
    43      def __init__(self, command='', username='', password='', domain='', hashes=None, aesKey=None, share=None,
    44                   noOutput=False, doKerberos=False, kdcHost=None):
    45          self.__command = command
    46          self.__username = username
    47          self.__password = password
    48          self.__domain = domain
    49          self.__lmhash = ''
    50          self.__nthash = ''
    51          self.__aesKey = aesKey
    52          self.__share = share
    53          self.__noOutput = noOutput
    54          self.__doKerberos = doKerberos
    55          self.__kdcHost = kdcHost
    56          self.shell = None
    57          if hashes is not None:
    58              self.__lmhash, self.__nthash = hashes.split(':')
    59  
    60      def run(self, addr):
    61          if self.__noOutput is False:
    62              smbConnection = SMBConnection(addr, addr)
    63              if self.__doKerberos is False:
    64                  smbConnection.login(self.__username, self.__password, self.__domain, self.__lmhash, self.__nthash)
    65              else:
    66                  smbConnection.kerberosLogin(self.__username, self.__password, self.__domain, self.__lmhash,
    67                                              self.__nthash, self.__aesKey, kdcHost=self.__kdcHost)
    68  
    69              dialect = smbConnection.getDialect()
    70              if dialect == SMB_DIALECT:
    71                  logging.info("SMBv1 dialect used")
    72              elif dialect == SMB2_DIALECT_002:
    73                  logging.info("SMBv2.0 dialect used")
    74              elif dialect == SMB2_DIALECT_21:
    75                  logging.info("SMBv2.1 dialect used")
    76              else:
    77                  logging.info("SMBv3.0 dialect used")
    78          else:
    79              smbConnection = None
    80  
    81          dcom = DCOMConnection(addr, self.__username, self.__password, self.__domain, self.__lmhash, self.__nthash,
    82                                self.__aesKey, oxidResolver=True, doKerberos=self.__doKerberos, kdcHost=self.__kdcHost)
    83          try:
    84              iInterface = dcom.CoCreateInstanceEx(wmi.CLSID_WbemLevel1Login,wmi.IID_IWbemLevel1Login)
    85              iWbemLevel1Login = wmi.IWbemLevel1Login(iInterface)
    86              iWbemServices= iWbemLevel1Login.NTLMLogin('//./root/cimv2', NULL, NULL)
    87              iWbemLevel1Login.RemRelease()
    88  
    89              win32Process,_ = iWbemServices.GetObject('Win32_Process')
    90  
    91              self.shell = RemoteShell(self.__share, win32Process, smbConnection)
    92              if self.__command != ' ':
    93                  self.shell.onecmd(self.__command)
    94              else:
    95                  self.shell.cmdloop()
    96          except  (Exception, KeyboardInterrupt) as e:
    97              if logging.getLogger().level == logging.DEBUG:
    98                  import traceback
    99                  traceback.print_exc()
   100              logging.error(str(e))
   101              if smbConnection is not None:
   102                  smbConnection.logoff()
   103              dcom.disconnect()
   104              sys.stdout.flush()
   105              sys.exit(1)
   106  
   107          if smbConnection is not None:
   108              smbConnection.logoff()
   109          dcom.disconnect()
   110  
   111  class RemoteShell(cmd.Cmd):
   112      def __init__(self, share, win32Process, smbConnection):
   113          cmd.Cmd.__init__(self)
   114          self.__share = share
   115          self.__output = '\\' + OUTPUT_FILENAME
   116          self.__outputBuffer = str('')
   117          self.__shell = 'cmd.exe /Q /c '
   118          self.__win32Process = win32Process
   119          self.__transferClient = smbConnection
   120          self.__pwd = str('C:\\')
   121          self.__noOutput = False
   122          self.intro = '[!] Launching semi-interactive shell - Careful what you execute\n[!] Press help for extra shell commands'
   123  
   124          # We don't wanna deal with timeouts from now on.
   125          if self.__transferClient is not None:
   126              self.__transferClient.setTimeout(100000)
   127              self.do_cd('\\')
   128          else:
   129              self.__noOutput = True
   130  
   131      def do_shell(self, s):
   132          os.system(s)
   133  
   134      def do_help(self, line):
   135          print("""
   136   lcd {path}                 - changes the current local directory to {path}
   137   exit                       - terminates the server process (and this session)
   138   put {src_file, dst_path}   - uploads a local file to the dst_path (dst_path = default current directory)
   139   get {file}                 - downloads pathname to the current local dir 
   140   ! {cmd}                    - executes a local shell cmd
   141  """) 
   142  
   143      def do_lcd(self, s):
   144          if s == '':
   145              print(os.getcwd())
   146          else:
   147              try:
   148                  os.chdir(s)
   149              except Exception as e:
   150                  logging.error(str(e))
   151  
   152      def do_get(self, src_path):
   153  
   154          try:
   155              import ntpath
   156              newPath = ntpath.normpath(ntpath.join(self.__pwd, src_path))
   157              drive, tail = ntpath.splitdrive(newPath) 
   158              filename = ntpath.basename(tail)
   159              fh = open(filename,'wb')
   160              logging.info("Downloading %s\\%s" % (drive, tail))
   161              self.__transferClient.getFile(drive[:-1]+'$', tail, fh.write)
   162              fh.close()
   163  
   164          except Exception as e:
   165              logging.error(str(e))
   166  
   167              if os.path.exists(filename):
   168                  os.remove(filename)
   169  
   170  
   171  
   172      def do_put(self, s):
   173          try:
   174              params = s.split(' ')
   175              if len(params) > 1:
   176                  src_path = params[0]
   177                  dst_path = params[1]
   178              elif len(params) == 1:
   179                  src_path = params[0]
   180                  dst_path = ''
   181  
   182              src_file = os.path.basename(src_path)
   183              fh = open(src_path, 'rb')
   184              dst_path = dst_path.replace('/','\\')
   185              import ntpath
   186              pathname = ntpath.join(ntpath.join(self.__pwd,dst_path), src_file)
   187              drive, tail = ntpath.splitdrive(pathname)
   188              logging.info("Uploading %s to %s" % (src_file, pathname))
   189              self.__transferClient.putFile(drive[:-1]+'$', tail, fh.read)
   190              fh.close()
   191          except Exception as e:
   192              logging.critical(str(e))
   193              pass
   194  
   195      def do_exit(self, s):
   196          return True
   197  
   198      def emptyline(self):
   199          return False
   200  
   201      def do_cd(self, s):
   202          self.execute_remote('cd ' + s)
   203          if len(self.__outputBuffer.strip('\r\n')) > 0:
   204              print(self.__outputBuffer)
   205              self.__outputBuffer = ''
   206          else:
   207              if PY2:
   208                  self.__pwd = ntpath.normpath(ntpath.join(self.__pwd, s.decode(sys.stdin.encoding)))
   209              else:
   210                  self.__pwd = ntpath.normpath(ntpath.join(self.__pwd, s))
   211              self.execute_remote('cd ')
   212              self.__pwd = self.__outputBuffer.strip('\r\n')
   213              self.prompt = (self.__pwd + '>')
   214              self.__outputBuffer = ''
   215  
   216      def default(self, line):
   217          # Let's try to guess if the user is trying to change drive
   218          if len(line) == 2 and line[1] == ':':
   219              # Execute the command and see if the drive is valid
   220              self.execute_remote(line)
   221              if len(self.__outputBuffer.strip('\r\n')) > 0: 
   222                  # Something went wrong
   223                  print(self.__outputBuffer)
   224                  self.__outputBuffer = ''
   225              else:
   226                  # Drive valid, now we should get the current path
   227                  self.__pwd = line
   228                  self.execute_remote('cd ')
   229                  self.__pwd = self.__outputBuffer.strip('\r\n').encode(CODEC)
   230                  self.prompt = (self.__pwd + b'>')
   231                  self.__outputBuffer = ''
   232          else:
   233              if line != '':
   234                  self.send_data(line)
   235  
   236      def get_output(self):
   237          def output_callback(data):
   238              try:
   239                  self.__outputBuffer += data.decode(CODEC)
   240              except UnicodeDecodeError:
   241                  logging.error('Decoding error detected, consider running chcp.com at the target,\nmap the result with '
   242                                'https://docs.python.org/2.4/lib/standard-encodings.html\nand then execute wmiexec.py '
   243                                'again with -codec and the corresponding codec')
   244                  self.__outputBuffer += data.decode(CODEC, errors='replace')
   245  
   246          if self.__noOutput is True:
   247              self.__outputBuffer = ''
   248              return
   249  
   250          while True:
   251              try:
   252                  self.__transferClient.getFile(self.__share, self.__output, output_callback)
   253                  break
   254              except Exception as e:
   255                  if str(e).find('STATUS_SHARING_VIOLATION') >=0:
   256                      # Output not finished, let's wait
   257                      time.sleep(1)
   258                      pass
   259                  elif str(e).find('Broken') >= 0:
   260                      # The SMB Connection might have timed out, let's try reconnecting
   261                      logging.debug('Connection broken, trying to recreate it')
   262                      self.__transferClient.reconnect()
   263                      return self.get_output()
   264          self.__transferClient.deleteFile(self.__share, self.__output)
   265  
   266      def execute_remote(self, data):
   267          command = self.__shell + data 
   268          if self.__noOutput is False:
   269              command += ' 1> ' + '\\\\127.0.0.1\\%s' % self.__share + self.__output  + ' 2>&1'
   270          if PY2:
   271              self.__win32Process.Create(command.decode(sys.stdin.encoding), self.__pwd, None)
   272          else:
   273              self.__win32Process.Create(command, self.__pwd, None)
   274          self.get_output()
   275  
   276      def send_data(self, data):
   277          self.execute_remote(data)
   278          print(self.__outputBuffer)
   279          self.__outputBuffer = ''
   280  
   281  class AuthFileSyntaxError(Exception):
   282      
   283      '''raised by load_smbclient_auth_file if it encounters a syntax error
   284      while loading the smbclient-style authentication file.'''
   285  
   286      def __init__(self, path, lineno, reason):
   287          self.path=path
   288          self.lineno=lineno
   289          self.reason=reason
   290      
   291      def __str__(self):
   292          return 'Syntax error in auth file %s line %d: %s' % (
   293              self.path, self.lineno, self.reason )
   294  
   295  def load_smbclient_auth_file(path):
   296  
   297      '''Load credentials from an smbclient-style authentication file (used by
   298      smbclient, mount.cifs and others).  returns (domain, username, password)
   299      or raises AuthFileSyntaxError or any I/O exceptions.'''
   300  
   301      lineno=0
   302      domain=None
   303      username=None
   304      password=None
   305      for line in open(path):
   306          lineno+=1
   307  
   308          line = line.strip()
   309  
   310          if line.startswith('#') or line=='':
   311              continue
   312              
   313          parts = line.split('=',1)
   314          if len(parts) != 2:
   315              raise AuthFileSyntaxError(path, lineno, 'No "=" present in line')
   316          
   317          (k,v) = (parts[0].strip(), parts[1].strip())
   318          
   319          if k=='username':
   320              username=v
   321          elif k=='password':
   322              password=v
   323          elif k=='domain':
   324              domain=v
   325          else:
   326              raise AuthFileSyntaxError(path, lineno, 'Unknown option %s' % repr(k))
   327              
   328      return (domain, username, password)
   329  
   330  # Process command-line arguments.
   331  if __name__ == '__main__':
   332      # Init the example's logger theme
   333      logger.init()
   334      print(version.BANNER)
   335  
   336      parser = argparse.ArgumentParser(add_help = True, description = "Executes a semi-interactive shell using Windows "
   337                                                                      "Management Instrumentation.")
   338      parser.add_argument('target', action='store', help='[[domain/]username[:password]@]<targetName or address>')
   339      parser.add_argument('-share', action='store', default = 'ADMIN$', help='share where the output will be grabbed from '
   340                                                                             '(default ADMIN$)')
   341      parser.add_argument('-nooutput', action='store_true', default = False, help='whether or not to print the output '
   342                                                                                  '(no SMB connection created)')
   343      parser.add_argument('-debug', action='store_true', help='Turn DEBUG output ON')
   344      parser.add_argument('-codec', action='store', help='Sets encoding used (codec) from the target\'s output (default '
   345                                                         '"%s"). If errors are detected, run chcp.com at the target, '
   346                                                         'map the result with '
   347                            'https://docs.python.org/2.4/lib/standard-encodings.html and then execute wmiexec.py '
   348                            'again with -codec and the corresponding codec ' % CODEC)
   349  
   350      parser.add_argument('command', nargs='*', default = ' ', help='command to execute at the target. If empty it will '
   351                                                                    'launch a semi-interactive shell')
   352  
   353      group = parser.add_argument_group('authentication')
   354  
   355      group.add_argument('-hashes', action="store", metavar = "LMHASH:NTHASH", help='NTLM hashes, format is LMHASH:NTHASH')
   356      group.add_argument('-no-pass', action="store_true", help='don\'t ask for password (useful for -k)')
   357      group.add_argument('-k', action="store_true", help='Use Kerberos authentication. Grabs credentials from ccache file '
   358                         '(KRB5CCNAME) based on target parameters. If valid credentials cannot be found, it will use the '
   359                         'ones specified in the command line')
   360      group.add_argument('-aesKey', action="store", metavar = "hex key", help='AES key to use for Kerberos Authentication '
   361                                                                              '(128 or 256 bits)')
   362      group.add_argument('-dc-ip', action='store',metavar = "ip address",  help='IP Address of the domain controller. If '
   363                         'ommited it use the domain part (FQDN) specified in the target parameter')
   364      group.add_argument('-A', action="store", metavar = "authfile", help="smbclient/mount.cifs-style authentication file. "
   365                                                                          "See smbclient man page's -A option.")
   366  
   367      if len(sys.argv)==1:
   368          parser.print_help()
   369          sys.exit(1)
   370  
   371      options = parser.parse_args()
   372  
   373      if options.codec is not None:
   374          CODEC = options.codec
   375      else:
   376          if CODEC is None:
   377              CODEC = 'UTF-8'
   378  
   379      if ' '.join(options.command) == ' ' and options.nooutput is True:
   380          logging.error("-nooutput switch and interactive shell not supported")
   381          sys.exit(1)
   382      
   383      if options.debug is True:
   384          logging.getLogger().setLevel(logging.DEBUG)
   385      else:
   386          logging.getLogger().setLevel(logging.INFO)
   387  
   388      import re
   389  
   390      domain, username, password, address = re.compile('(?:(?:([^/@:]*)/)?([^@:]*)(?::([^@]*))?@)?(.*)').match(
   391          options.target).groups('')
   392  
   393      #In case the password contains '@'
   394      if '@' in address:
   395          password = password + '@' + address.rpartition('@')[0]
   396          address = address.rpartition('@')[2]
   397  
   398      try:
   399          if options.A is not None:
   400              (domain, username, password) = load_smbclient_auth_file(options.A)
   401              logging.debug('loaded smbclient auth file: domain=%s, username=%s, password=%s' % (repr(domain), repr(username), repr(password)))
   402          
   403          if domain is None:
   404              domain = ''
   405  
   406          if password == '' and username != '' and options.hashes is None and options.no_pass is False and options.aesKey is None:
   407              from getpass import getpass
   408              password = getpass("Password:")
   409  
   410          if options.aesKey is not None:
   411              options.k = True
   412  
   413          executer = WMIEXEC(' '.join(options.command), username, password, domain, options.hashes, options.aesKey,
   414                             options.share, options.nooutput, options.k, options.dc_ip)
   415          executer.run(address)
   416      except KeyboardInterrupt as e:
   417          logging.error(str(e))
   418      except Exception as e:
   419          if logging.getLogger().level == logging.DEBUG:
   420              import traceback
   421              traceback.print_exc()
   422          logging.error(str(e))
   423          sys.exit(1)
   424          
   425      sys.exit(0)