github.com/Coalfire-Research/Slackor@v0.0.0-20191010164036-aa32a7f9250b/impacket/examples/psexec.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  # PSEXEC like functionality example using RemComSvc (https://github.com/kavika13/RemCom)
     9  #
    10  # Author:
    11  #  beto (@agsolino)
    12  #
    13  # Reference for:
    14  #  DCE/RPC and SMB.
    15  
    16  import sys
    17  import os
    18  import cmd
    19  import logging
    20  from threading import Thread, Lock
    21  import argparse
    22  import random
    23  import string
    24  import time
    25  from six import PY3
    26  
    27  from impacket.examples import logger
    28  from impacket import version, smb
    29  from impacket.smbconnection import SMBConnection
    30  from impacket.dcerpc.v5 import transport
    31  from impacket.structure import Structure
    32  from impacket.examples import remcomsvc, serviceinstall
    33  
    34  
    35  class RemComMessage(Structure):
    36      structure = (
    37          ('Command','4096s=""'),
    38          ('WorkingDir','260s=""'),
    39          ('Priority','<L=0x20'),
    40          ('ProcessID','<L=0x01'),
    41          ('Machine','260s=""'),
    42          ('NoWait','<L=0'),
    43      )
    44  
    45  class RemComResponse(Structure):
    46      structure = (
    47          ('ErrorCode','<L=0'),
    48          ('ReturnCode','<L=0'),
    49      )
    50  
    51  RemComSTDOUT         = "RemCom_stdout"
    52  RemComSTDIN          = "RemCom_stdin"
    53  RemComSTDERR         = "RemCom_stderr"
    54  
    55  lock = Lock()
    56  
    57  class PSEXEC:
    58      def __init__(self, command, path, exeFile, copyFile, port=445,
    59                   username='', password='', domain='', hashes=None, aesKey=None, doKerberos=False, kdcHost=None, serviceName=None):
    60          self.__username = username
    61          self.__password = password
    62          self.__port = port
    63          self.__command = command
    64          self.__path = path
    65          self.__domain = domain
    66          self.__lmhash = ''
    67          self.__nthash = ''
    68          self.__aesKey = aesKey
    69          self.__exeFile = exeFile
    70          self.__copyFile = copyFile
    71          self.__doKerberos = doKerberos
    72          self.__kdcHost = kdcHost
    73          self.__serviceName = serviceName
    74          if hashes is not None:
    75              self.__lmhash, self.__nthash = hashes.split(':')
    76  
    77      def run(self, remoteName, remoteHost):
    78  
    79          stringbinding = r'ncacn_np:%s[\pipe\svcctl]' % remoteName
    80          logging.debug('StringBinding %s'%stringbinding)
    81          rpctransport = transport.DCERPCTransportFactory(stringbinding)
    82          rpctransport.set_dport(self.__port)
    83          rpctransport.setRemoteHost(remoteHost)
    84  
    85          if hasattr(rpctransport, 'set_credentials'):
    86              # This method exists only for selected protocol sequences.
    87              rpctransport.set_credentials(self.__username, self.__password, self.__domain, self.__lmhash,
    88                                           self.__nthash, self.__aesKey)
    89  
    90          rpctransport.set_kerberos(self.__doKerberos, self.__kdcHost)
    91          self.doStuff(rpctransport)
    92  
    93      def openPipe(self, s, tid, pipe, accessMask):
    94          pipeReady = False
    95          tries = 50
    96          while pipeReady is False and tries > 0:
    97              try:
    98                  s.waitNamedPipe(tid,pipe)
    99                  pipeReady = True
   100              except:
   101                  tries -= 1
   102                  time.sleep(2)
   103                  pass
   104  
   105          if tries == 0:
   106              raise Exception('Pipe not ready, aborting')
   107  
   108          fid = s.openFile(tid,pipe,accessMask, creationOption = 0x40, fileAttributes = 0x80)
   109  
   110          return fid
   111  
   112      def doStuff(self, rpctransport):
   113  
   114          dce = rpctransport.get_dce_rpc()
   115          try:
   116              dce.connect()
   117          except Exception as e:
   118              if logging.getLogger().level == logging.DEBUG:
   119                  import traceback
   120                  traceback.print_exc()
   121              logging.critical(str(e))
   122              sys.exit(1)
   123  
   124          global dialect
   125          dialect = rpctransport.get_smb_connection().getDialect()
   126  
   127          try:
   128              unInstalled = False
   129              s = rpctransport.get_smb_connection()
   130  
   131              # We don't wanna deal with timeouts from now on.
   132              s.setTimeout(100000)
   133              if self.__exeFile is None:
   134                  installService = serviceinstall.ServiceInstall(rpctransport.get_smb_connection(), remcomsvc.RemComSvc(), self.__serviceName)
   135              else:
   136                  try:
   137                      f = open(self.__exeFile)
   138                  except Exception as e:
   139                      logging.critical(str(e))
   140                      sys.exit(1)
   141                  installService = serviceinstall.ServiceInstall(rpctransport.get_smb_connection(), f)
   142      
   143              if installService.install() is False:
   144                  return
   145  
   146              if self.__exeFile is not None:
   147                  f.close()
   148  
   149              # Check if we need to copy a file for execution
   150              if self.__copyFile is not None:
   151                  installService.copy_file(self.__copyFile, installService.getShare(), os.path.basename(self.__copyFile))
   152                  # And we change the command to be executed to this filename
   153                  self.__command = os.path.basename(self.__copyFile) + ' ' + self.__command
   154  
   155              tid = s.connectTree('IPC$')
   156              fid_main = self.openPipe(s,tid,r'\RemCom_communicaton',0x12019f)
   157  
   158              packet = RemComMessage()
   159              pid = os.getpid()
   160  
   161              packet['Machine'] = ''.join([random.choice(string.ascii_letters) for _ in range(4)])
   162              if self.__path is not None:
   163                  packet['WorkingDir'] = self.__path
   164              packet['Command'] = self.__command
   165              packet['ProcessID'] = pid
   166  
   167              s.writeNamedPipe(tid, fid_main, packet.getData())
   168  
   169              # Here we'll store the command we type so we don't print it back ;)
   170              # ( I know.. globals are nasty :P )
   171              global LastDataSent
   172              LastDataSent = ''
   173  
   174              # Create the pipes threads
   175              stdin_pipe = RemoteStdInPipe(rpctransport,
   176                                           r'\%s%s%d' % (RemComSTDIN, packet['Machine'], packet['ProcessID']),
   177                                           smb.FILE_WRITE_DATA | smb.FILE_APPEND_DATA, installService.getShare())
   178              stdin_pipe.start()
   179              stdout_pipe = RemoteStdOutPipe(rpctransport,
   180                                             r'\%s%s%d' % (RemComSTDOUT, packet['Machine'], packet['ProcessID']),
   181                                             smb.FILE_READ_DATA)
   182              stdout_pipe.start()
   183              stderr_pipe = RemoteStdErrPipe(rpctransport,
   184                                             r'\%s%s%d' % (RemComSTDERR, packet['Machine'], packet['ProcessID']),
   185                                             smb.FILE_READ_DATA)
   186              stderr_pipe.start()
   187              
   188              # And we stay here till the end
   189              ans = s.readNamedPipe(tid,fid_main,8)
   190  
   191              if len(ans):
   192                  retCode = RemComResponse(ans)
   193                  logging.info("Process %s finished with ErrorCode: %d, ReturnCode: %d" % (
   194                  self.__command, retCode['ErrorCode'], retCode['ReturnCode']))
   195              installService.uninstall()
   196              if self.__copyFile is not None:
   197                  # We copied a file for execution, let's remove it
   198                  s.deleteFile(installService.getShare(), os.path.basename(self.__copyFile))
   199              unInstalled = True
   200              sys.exit(retCode['ErrorCode'])
   201  
   202          except SystemExit:
   203              raise
   204          except Exception as e:
   205              if logging.getLogger().level == logging.DEBUG:
   206                  import traceback
   207                  traceback.print_exc()
   208              logging.debug(str(e))
   209              if unInstalled is False:
   210                  installService.uninstall()
   211                  if self.__copyFile is not None:
   212                      s.deleteFile(installService.getShare(), os.path.basename(self.__copyFile))
   213              sys.stdout.flush()
   214              sys.exit(1)
   215  
   216  class Pipes(Thread):
   217      def __init__(self, transport, pipe, permissions, share=None):
   218          Thread.__init__(self)
   219          self.server = 0
   220          self.transport = transport
   221          self.credentials = transport.get_credentials()
   222          self.tid = 0
   223          self.fid = 0
   224          self.share = share
   225          self.port = transport.get_dport()
   226          self.pipe = pipe
   227          self.permissions = permissions
   228          self.daemon = True
   229  
   230      def connectPipe(self):
   231          try:
   232              lock.acquire()
   233              global dialect
   234              #self.server = SMBConnection('*SMBSERVER', self.transport.get_smb_connection().getRemoteHost(), sess_port = self.port, preferredDialect = SMB_DIALECT)
   235              self.server = SMBConnection(self.transport.get_smb_connection().getRemoteName(), self.transport.get_smb_connection().getRemoteHost(),
   236                                          sess_port=self.port, preferredDialect=dialect)
   237              user, passwd, domain, lm, nt, aesKey, TGT, TGS = self.credentials
   238              if self.transport.get_kerberos() is True:
   239                  self.server.kerberosLogin(user, passwd, domain, lm, nt, aesKey, kdcHost=self.transport.get_kdcHost(), TGT=TGT, TGS=TGS)
   240              else:
   241                  self.server.login(user, passwd, domain, lm, nt)
   242              lock.release()
   243              self.tid = self.server.connectTree('IPC$') 
   244  
   245              self.server.waitNamedPipe(self.tid, self.pipe)
   246              self.fid = self.server.openFile(self.tid,self.pipe,self.permissions, creationOption = 0x40, fileAttributes = 0x80)
   247              self.server.setTimeout(1000000)
   248          except:
   249              if logging.getLogger().level == logging.DEBUG:
   250                  import traceback
   251                  traceback.print_exc()
   252              logging.error("Something wen't wrong connecting the pipes(%s), try again" % self.__class__)
   253  
   254  
   255  class RemoteStdOutPipe(Pipes):
   256      def __init__(self, transport, pipe, permisssions):
   257          Pipes.__init__(self, transport, pipe, permisssions)
   258  
   259      def run(self):
   260          self.connectPipe()
   261          while True:
   262              try:
   263                  ans = self.server.readFile(self.tid,self.fid, 0, 1024)
   264              except:
   265                  pass
   266              else:
   267                  try:
   268                      global LastDataSent
   269                      if ans != LastDataSent:
   270                          sys.stdout.write(ans.decode('cp437'))
   271                          sys.stdout.flush()
   272                      else:
   273                          # Don't echo what I sent, and clear it up
   274                          LastDataSent = ''
   275                      # Just in case this got out of sync, i'm cleaning it up if there are more than 10 chars, 
   276                      # it will give false positives tho.. we should find a better way to handle this.
   277                      if LastDataSent > 10:
   278                          LastDataSent = ''
   279                  except:
   280                      pass
   281  
   282  class RemoteStdErrPipe(Pipes):
   283      def __init__(self, transport, pipe, permisssions):
   284          Pipes.__init__(self, transport, pipe, permisssions)
   285  
   286      def run(self):
   287          self.connectPipe()
   288          while True:
   289              try:
   290                  ans = self.server.readFile(self.tid,self.fid, 0, 1024)
   291              except:
   292                  pass
   293              else:
   294                  try:
   295                      sys.stderr.write(str(ans))
   296                      sys.stderr.flush()
   297                  except:
   298                      pass
   299  
   300  class RemoteShell(cmd.Cmd):
   301      def __init__(self, server, port, credentials, tid, fid, share, transport):
   302          cmd.Cmd.__init__(self, False)
   303          self.prompt = '\x08'
   304          self.server = server
   305          self.transferClient = None
   306          self.tid = tid
   307          self.fid = fid
   308          self.credentials = credentials
   309          self.share = share
   310          self.port = port
   311          self.transport = transport
   312          self.intro = '[!] Press help for extra shell commands'
   313  
   314      def connect_transferClient(self):
   315          #self.transferClient = SMBConnection('*SMBSERVER', self.server.getRemoteHost(), sess_port = self.port, preferredDialect = SMB_DIALECT)
   316          self.transferClient = SMBConnection('*SMBSERVER', self.server.getRemoteHost(), sess_port=self.port,
   317                                              preferredDialect=dialect)
   318          user, passwd, domain, lm, nt, aesKey, TGT, TGS = self.credentials
   319          if self.transport.get_kerberos() is True:
   320              self.transferClient.kerberosLogin(user, passwd, domain, lm, nt, aesKey,
   321                                                kdcHost=self.transport.get_kdcHost(), TGT=TGT, TGS=TGS)
   322          else:
   323              self.transferClient.login(user, passwd, domain, lm, nt)
   324  
   325      def do_help(self, line):
   326          print("""
   327   lcd {path}                 - changes the current local directory to {path}
   328   exit                       - terminates the server process (and this session)
   329   put {src_file, dst_path}   - uploads a local file to the dst_path RELATIVE to the connected share (%s)
   330   get {file}                 - downloads pathname RELATIVE to the connected share (%s) to the current local dir 
   331   ! {cmd}                    - executes a local shell cmd
   332  """ % (self.share, self.share))
   333          self.send_data('\r\n', False)
   334  
   335      def do_shell(self, s):
   336          os.system(s)
   337          self.send_data('\r\n')
   338  
   339      def do_get(self, src_path):
   340          try:
   341              if self.transferClient is None:
   342                  self.connect_transferClient()
   343  
   344              import ntpath
   345              filename = ntpath.basename(src_path)
   346              fh = open(filename,'wb')
   347              logging.info("Downloading %s\\%s" % (self.share, src_path))
   348              self.transferClient.getFile(self.share, src_path, fh.write)
   349              fh.close()
   350          except Exception as e:
   351              logging.critical(str(e))
   352              pass
   353  
   354          self.send_data('\r\n')
   355   
   356      def do_put(self, s):
   357          try:
   358              if self.transferClient is None:
   359                  self.connect_transferClient()
   360              params = s.split(' ')
   361              if len(params) > 1:
   362                  src_path = params[0]
   363                  dst_path = params[1]
   364              elif len(params) == 1:
   365                  src_path = params[0]
   366                  dst_path = '/'
   367  
   368              src_file = os.path.basename(src_path)
   369              fh = open(src_path, 'rb')
   370              f = dst_path + '/' + src_file
   371              pathname = f.replace('/','\\')
   372              logging.info("Uploading %s to %s\\%s" % (src_file, self.share, dst_path))
   373              if PY3:
   374                  self.transferClient.putFile(self.share, pathname, fh.read)
   375              else:
   376                  self.transferClient.putFile(self.share, pathname.decode(sys.stdin.encoding), fh.read)
   377              fh.close()
   378          except Exception as e:
   379              logging.error(str(e))
   380              pass
   381  
   382          self.send_data('\r\n')
   383  
   384      def do_lcd(self, s):
   385          if s == '':
   386              print(os.getcwd())
   387          else:
   388              os.chdir(s)
   389          self.send_data('\r\n')
   390  
   391      def emptyline(self):
   392          self.send_data('\r\n')
   393          return
   394  
   395      def default(self, line):
   396          if PY3:
   397              self.send_data(line.encode('cp437')+b'\r\n')
   398          else:
   399              self.send_data(line.decode(sys.stdin.encoding).encode('cp437')+'\r\n')
   400  
   401      def send_data(self, data, hideOutput = True):
   402          if hideOutput is True:
   403              global LastDataSent
   404              LastDataSent = data
   405          else:
   406              LastDataSent = ''
   407          self.server.writeFile(self.tid, self.fid, data)
   408  
   409  class RemoteStdInPipe(Pipes):
   410      def __init__(self, transport, pipe, permisssions, share=None):
   411          self.shell = None
   412          Pipes.__init__(self, transport, pipe, permisssions, share)
   413  
   414      def run(self):
   415          self.connectPipe()
   416          self.shell = RemoteShell(self.server, self.port, self.credentials, self.tid, self.fid, self.share, self.transport)
   417          self.shell.cmdloop()
   418  
   419  # Process command-line arguments.
   420  if __name__ == '__main__':
   421      # Init the example's logger theme
   422      logger.init()
   423      print(version.BANNER)
   424  
   425      parser = argparse.ArgumentParser(add_help = True, description = "PSEXEC like functionality example using RemComSvc.")
   426  
   427      parser.add_argument('target', action='store', help='[[domain/]username[:password]@]<targetName or address>')
   428      parser.add_argument('command', nargs='*', default = ' ', help='command (or arguments if -c is used) to execute at '
   429                                                                    'the target (w/o path) - (default:cmd.exe)')
   430      parser.add_argument('-c', action='store',metavar = "pathname",  help='copy the filename for later execution, '
   431                                                                           'arguments are passed in the command option')
   432      parser.add_argument('-path', action='store', help='path of the command to execute')
   433      parser.add_argument('-file', action='store', help="alternative RemCom binary (be sure it doesn't require CRT)")
   434      parser.add_argument('-debug', action='store_true', help='Turn DEBUG output ON')
   435  
   436      group = parser.add_argument_group('authentication')
   437  
   438      group.add_argument('-hashes', action="store", metavar = "LMHASH:NTHASH", help='NTLM hashes, format is LMHASH:NTHASH')
   439      group.add_argument('-no-pass', action="store_true", help='don\'t ask for password (useful for -k)')
   440      group.add_argument('-k', action="store_true", help='Use Kerberos authentication. Grabs credentials from ccache file '
   441                         '(KRB5CCNAME) based on target parameters. If valid credentials cannot be found, it will use the '
   442                         'ones specified in the command line')
   443      group.add_argument('-aesKey', action="store", metavar = "hex key", help='AES key to use for Kerberos Authentication '
   444                                                                              '(128 or 256 bits)')
   445  
   446      group = parser.add_argument_group('connection')
   447  
   448      group.add_argument('-dc-ip', action='store', metavar="ip address",
   449                         help='IP Address of the domain controller. If omitted it will use the domain part (FQDN) specified in '
   450                              'the target parameter')
   451      group.add_argument('-target-ip', action='store', metavar="ip address",
   452                         help='IP Address of the target machine. If omitted it will use whatever was specified as target. '
   453                              'This is useful when target is the NetBIOS name and you cannot resolve it')
   454      group.add_argument('-port', choices=['139', '445'], nargs='?', default='445', metavar="destination port",
   455                         help='Destination port to connect to SMB Server')
   456      group.add_argument('-service-name', action='store', metavar="service name", default = '', help='This will be the name of the service')
   457  
   458      if len(sys.argv)==1:
   459          parser.print_help()
   460          sys.exit(1)
   461  
   462      options = parser.parse_args()
   463  
   464      if options.debug is True:
   465          logging.getLogger().setLevel(logging.DEBUG)
   466      else:
   467          logging.getLogger().setLevel(logging.INFO)
   468  
   469      import re
   470  
   471      domain, username, password, remoteName = re.compile('(?:(?:([^/@:]*)/)?([^@:]*)(?::([^@]*))?@)?(.*)').match(
   472          options.target).groups('')
   473      
   474      #In case the password contains '@'
   475      if '@' in remoteName:
   476          password = password + '@' + remoteName.rpartition('@')[0]
   477          remoteName = remoteName.rpartition('@')[2]
   478  
   479      if domain is None:
   480          domain = ''
   481  
   482      if options.target_ip is None:
   483          options.target_ip = remoteName
   484  
   485      if password == '' and username != '' and options.hashes is None and options.no_pass is False and options.aesKey is None:
   486          from getpass import getpass
   487          password = getpass("Password:")
   488  
   489      if options.aesKey is not None:
   490          options.k = True
   491  
   492      command = ' '.join(options.command)
   493      if command == ' ':
   494          command = 'cmd.exe'
   495  
   496      executer = PSEXEC(command, options.path, options.file, options.c, int(options.port), username, password, domain, options.hashes,
   497                        options.aesKey, options.k, options.dc_ip, options.service_name)
   498      executer.run(remoteName, options.target_ip)