github.com/Coalfire-Research/Slackor@v0.0.0-20191010164036-aa32a7f9250b/impacket/examples/netview.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  # Author:
     9  #  beto (@agsolino)
    10  #
    11  # Description:
    12  #   The idea of this script is to get a list of the sessions
    13  #   opened at the remote hosts and keep track of them.
    14  #   Coincidentally @mubix did something similar a few years
    15  #   ago so credit goes to him (and the script's name ;)).
    16  #   Check it out at https://github.com/mubix/netview
    17  #   The main difference with our approach is we keep 
    18  #   looping over the hosts found and keep track of who logged
    19  #   in/out from remote servers. Plus, we keep the connections
    20  #   with the target systems and just send a few DCE-RPC packets.
    21  #
    22  #   One VERY IMPORTANT thing is:
    23  #   
    24  #   YOU HAVE TO BE ABLE TO RESOLV THE DOMAIN MACHINES NETBIOS 
    25  #   NAMES. That's usually solved by setting your DNS to the 
    26  #   domain DNS (and the right search domain).
    27  #   
    28  #   Some examples of usage are:
    29  #
    30  #   netview.py -target 192.168.1.10 beto
    31  #   
    32  #   This will show the sessions on 192.168.1.10 and will authenticate as 'beto'
    33  #   (password will be prompted)
    34  #
    35  #   netview.py FREEFLY.NET/beto
    36  #
    37  #   This will download all machines from FREEFLY.NET, authenticated as 'beto'
    38  #   and will gather the session information for those machines that appear
    39  #   to be up. There is a background thread checking aliveness of the targets
    40  #   at all times.
    41  #
    42  #   netview.py -users /tmp/users -dc-ip freefly-dc.freefly.net -k FREEFLY.NET/beto
    43  #  
    44  #   This will download all machines from FREEFLY.NET, authenticating using
    45  #   Kerberos (that's why -dc-ip parameter is needed), and filter
    46  #   the output based on the list of users specified in /tmp/users file.
    47  #
    48  #
    49  from __future__ import division
    50  from __future__ import print_function
    51  import sys
    52  import argparse
    53  import logging
    54  import socket
    55  from threading import Thread, Event
    56  from queue import Queue
    57  from time import sleep
    58  
    59  from impacket.examples import logger
    60  from impacket import version
    61  from impacket.smbconnection import SessionError
    62  from impacket.dcerpc.v5 import transport, wkst, srvs, samr
    63  from impacket.dcerpc.v5.ndr import NULL
    64  from impacket.dcerpc.v5.rpcrt import DCERPCException
    65  from impacket.nt_errors import STATUS_MORE_ENTRIES
    66  
    67  machinesAliveQueue = Queue()
    68  machinesDownQueue = Queue()
    69  
    70  myIP = None
    71  
    72  def checkMachines(machines, stopEvent, singlePass=False):
    73      origLen = len(machines)
    74      deadMachines = machines
    75      done = False
    76      while not done:
    77          if stopEvent.is_set():
    78               done = True
    79               break
    80          for machine in deadMachines:
    81              s = socket.socket()
    82              try:
    83                  s = socket.create_connection((machine, 445), 2)
    84                  global myIP
    85                  myIP = s.getsockname()[0]
    86                  s.close()
    87                  machinesAliveQueue.put(machine)
    88              except Exception as e:
    89                  logging.debug('%s: not alive (%s)' % (machine, e))
    90                  pass
    91              else:
    92                  logging.debug('%s: alive!' % machine)
    93                  deadMachines.remove(machine)
    94              if stopEvent.is_set():
    95                   done = True
    96                   break
    97  
    98          logging.debug('up: %d, down: %d, total: %d' % (origLen-len(deadMachines), len(deadMachines), origLen))
    99          if singlePass is True:
   100              done = True
   101          if not done:
   102              sleep(10)
   103              # Do we have some new deadMachines to add?
   104              while machinesDownQueue.empty() is False:
   105                  deadMachines.append(machinesDownQueue.get())
   106  
   107  class USERENUM:
   108      def __init__(self, username='', password='', domain='', hashes=None, aesKey=None, doKerberos=False, options=None):
   109          self.__username = username
   110          self.__password = password
   111          self.__domain = domain
   112          self.__lmhash = ''
   113          self.__nthash = ''
   114          self.__aesKey = aesKey
   115          self.__doKerberos = doKerberos
   116          self.__kdcHost = options.dc_ip
   117          self.__options = options
   118          self.__machinesList = list()
   119          self.__targets = dict()
   120          self.__filterUsers = None
   121          self.__targetsThreadEvent = None
   122          self.__targetsThread = None
   123          self.__maxConnections = int(options.max_connections)
   124          if hashes is not None:
   125              self.__lmhash, self.__nthash = hashes.split(':')
   126  
   127      def getDomainMachines(self):
   128          if self.__kdcHost is not None:
   129              domainController = self.__kdcHost
   130          elif self.__domain is not '':
   131              domainController = self.__domain
   132          else:
   133              raise Exception('A domain is needed!')
   134  
   135          logging.info('Getting machine\'s list from %s' % domainController)
   136          rpctransport = transport.SMBTransport(domainController, 445, r'\samr', self.__username, self.__password,
   137                                                self.__domain, self.__lmhash, self.__nthash, self.__aesKey,
   138                                                doKerberos=self.__doKerberos, kdcHost = self.__kdcHost)
   139          dce = rpctransport.get_dce_rpc()
   140          dce.connect()
   141          dce.bind(samr.MSRPC_UUID_SAMR)
   142          try:
   143              resp = samr.hSamrConnect(dce)
   144              serverHandle = resp['ServerHandle'] 
   145  
   146              resp = samr.hSamrEnumerateDomainsInSamServer(dce, serverHandle)
   147              domains = resp['Buffer']['Buffer']
   148  
   149              logging.info("Looking up users in domain %s" % domains[0]['Name'])
   150  
   151              resp = samr.hSamrLookupDomainInSamServer(dce, serverHandle,domains[0]['Name'] )
   152  
   153              resp = samr.hSamrOpenDomain(dce, serverHandle = serverHandle, domainId = resp['DomainId'])
   154              domainHandle = resp['DomainHandle']
   155  
   156              status = STATUS_MORE_ENTRIES
   157              enumerationContext = 0
   158              while status == STATUS_MORE_ENTRIES:
   159                  try:
   160                      resp = samr.hSamrEnumerateUsersInDomain(dce, domainHandle, samr.USER_WORKSTATION_TRUST_ACCOUNT,
   161                                                              enumerationContext=enumerationContext)
   162                  except DCERPCException as e:
   163                      if str(e).find('STATUS_MORE_ENTRIES') < 0:
   164                          raise
   165                      resp = e.get_packet()
   166  
   167                  for user in resp['Buffer']['Buffer']:
   168                      self.__machinesList.append(user['Name'][:-1])
   169                      logging.debug('Machine name - rid: %s - %d'% (user['Name'], user['RelativeId']))
   170  
   171                  enumerationContext = resp['EnumerationContext'] 
   172                  status = resp['ErrorCode']
   173          except Exception as e:
   174              raise e
   175  
   176          dce.disconnect()
   177  
   178      def getTargets(self):
   179          logging.info('Importing targets')
   180          if self.__options.target is None and self.__options.targets is None:
   181              # We need to download the list of machines from the domain
   182              self.getDomainMachines()
   183          elif self.__options.targets is not None:
   184              for line in self.__options.targets.readlines():
   185                  self.__machinesList.append(line.strip(' \r\n'))
   186          else:
   187              # Just a single machine
   188              self.__machinesList.append(self.__options.target)
   189          logging.info("Got %d machines" % len(self.__machinesList))
   190            
   191      def filterUsers(self):
   192          if self.__options.user is not None:
   193              self.__filterUsers = list()
   194              self.__filterUsers.append(self.__options.user)
   195          elif self.__options.users is not None:
   196              # Grab users list from a file
   197              self.__filterUsers = list()
   198              for line in self.__options.users.readlines():
   199                  self.__filterUsers.append(line.strip(' \r\n'))
   200          else:
   201              self.__filterUsers = None
   202  
   203      def run(self):
   204          self.getTargets()
   205          self.filterUsers()
   206          #self.filterGroups()
   207         
   208          # Up to here we should have figured out the scope of our work
   209          self.__targetsThreadEvent = Event()
   210          if self.__options.noloop is False:
   211              # Start a separate thread checking the targets that are up
   212              self.__targetsThread = Thread(target=checkMachines, args=(self.__machinesList,self.__targetsThreadEvent))
   213              self.__targetsThread.start()
   214          else:
   215              # Since it's gonna be a one shoot test, we need to wait till it finishes
   216              checkMachines(self.__machinesList,self.__targetsThreadEvent, singlePass=True)
   217  
   218          while True:
   219              # Do we have more machines to add?
   220              while machinesAliveQueue.empty() is False:
   221                  machine = machinesAliveQueue.get()
   222                  logging.debug('Adding %s to the up list' % machine)
   223                  self.__targets[machine] = {}
   224                  self.__targets[machine]['SRVS'] = None
   225                  self.__targets[machine]['WKST'] = None
   226                  self.__targets[machine]['Admin'] = True
   227                  self.__targets[machine]['Sessions'] = list()
   228                  self.__targets[machine]['LoggedIn'] = set()
   229              
   230              for target in list(self.__targets.keys()):
   231                  try:
   232                      self.getSessions(target)
   233                      self.getLoggedIn(target) 
   234                  except (SessionError, DCERPCException) as e:
   235                      # We will silently pass these ones, might be issues with Kerberos, or DCE
   236                      if str(e).find('LOGON_FAILURE') >=0:
   237                          # For some reason our credentials don't work there, 
   238                          # taking it out from the list.
   239                          logging.error('STATUS_LOGON_FAILURE for %s, discarding' % target)
   240                          del(self.__targets[target])
   241                      elif str(e).find('INVALID_PARAMETER') >=0:
   242                          del(self.__targets[target])
   243                      elif str(e).find('access_denied') >=0:
   244                          # Can't access the target RPC call, most probably a Unix host
   245                          # taking it out from the list
   246                          del(self.__targets[target])
   247                      else:
   248                          logging.info(str(e))
   249                      pass 
   250                  except KeyboardInterrupt:
   251                      raise
   252                  except Exception as e:
   253                      #import traceback
   254                      #traceback.print_exc()
   255                      if str(e).find('timed out') >=0:
   256                          # Most probably this site went down. taking it out
   257                          # ToDo: add it back to the list of machines to check in
   258                          # the separate thread - DONE
   259                          del(self.__targets[target])
   260                          machinesDownQueue.put(target)
   261                      else:
   262                          # These ones we will report
   263                          logging.error(e)
   264                      pass
   265  
   266              if self.__options.noloop is True:
   267                  break
   268  
   269              logging.debug('Sleeping for %s seconds' % self.__options.delay)
   270              logging.debug('Currently monitoring %d active targets' % len(self.__targets))
   271              sleep(int(self.__options.delay))
   272  
   273      def getSessions(self, target):
   274          if self.__targets[target]['SRVS'] is None:
   275              stringSrvsBinding = r'ncacn_np:%s[\PIPE\srvsvc]' % target
   276              rpctransportSrvs = transport.DCERPCTransportFactory(stringSrvsBinding)
   277              if hasattr(rpctransportSrvs, 'set_credentials'):
   278              # This method exists only for selected protocol sequences.
   279                  rpctransportSrvs.set_credentials(self.__username, self.__password, self.__domain, self.__lmhash,
   280                                                   self.__nthash, self.__aesKey)
   281                  rpctransportSrvs.set_kerberos(self.__doKerberos, self.__kdcHost)
   282  
   283              dce = rpctransportSrvs.get_dce_rpc()
   284              dce.connect()
   285              dce.bind(srvs.MSRPC_UUID_SRVS)
   286              self.__maxConnections -= 1
   287          else:
   288              dce = self.__targets[target]['SRVS']
   289  
   290          try:
   291              resp = srvs.hNetrSessionEnum(dce, '\x00', NULL, 10)
   292          except Exception as e:
   293              if str(e).find('Broken pipe') >= 0:
   294                  # The connection timed-out. Let's try to bring it back next round
   295                  self.__targets[target]['SRVS'] = None
   296                  self.__maxConnections += 1
   297                  return
   298              else:
   299                  raise
   300  
   301          if self.__maxConnections < 0:
   302              # Can't keep this connection open. Closing it
   303              dce.disconnect()
   304              self.__maxConnections = 0
   305          else:
   306               self.__targets[target]['SRVS'] = dce
   307  
   308          # Let's see who createad a connection since last check
   309          tmpSession = list()
   310          printCRLF = False
   311          for session in resp['InfoStruct']['SessionInfo']['Level10']['Buffer']:
   312              userName = session['sesi10_username'][:-1]
   313              sourceIP = session['sesi10_cname'][:-1][2:]
   314              key = '%s\x01%s' % (userName, sourceIP)
   315              myEntry = '%s\x01%s' % (self.__username, myIP)
   316              tmpSession.append(key)
   317              if not(key in self.__targets[target]['Sessions']):
   318                  # Skipping myself
   319                  if key != myEntry:
   320                      self.__targets[target]['Sessions'].append(key)
   321                      # Are we filtering users?
   322                      if self.__filterUsers is not None:
   323                          if userName in self.__filterUsers:
   324                              print("%s: user %s logged from host %s - active: %d, idle: %d" % (
   325                              target, userName, sourceIP, session['sesi10_time'], session['sesi10_idle_time']))
   326                              printCRLF = True
   327                      else:
   328                          print("%s: user %s logged from host %s - active: %d, idle: %d" % (
   329                          target, userName, sourceIP, session['sesi10_time'], session['sesi10_idle_time']))
   330                          printCRLF = True
   331  
   332          # Let's see who deleted a connection since last check
   333          for nItem, session in enumerate(self.__targets[target]['Sessions']):
   334              userName, sourceIP = session.split('\x01')
   335              if session not in tmpSession:
   336                  del(self.__targets[target]['Sessions'][nItem])
   337                  # Are we filtering users?
   338                  if self.__filterUsers is not None:
   339                      if userName in self.__filterUsers:
   340                          print("%s: user %s logged off from host %s" % (target, userName, sourceIP))
   341                          printCRLF=True
   342                  else:
   343                      print("%s: user %s logged off from host %s" % (target, userName, sourceIP))
   344                      printCRLF=True
   345                  
   346          if printCRLF is True:
   347              print()
   348          
   349      def getLoggedIn(self, target):
   350          if self.__targets[target]['Admin'] is False:
   351              return
   352  
   353          if self.__targets[target]['WKST'] is None:
   354              stringWkstBinding = r'ncacn_np:%s[\PIPE\wkssvc]' % target
   355              rpctransportWkst = transport.DCERPCTransportFactory(stringWkstBinding)
   356              if hasattr(rpctransportWkst, 'set_credentials'):
   357                  # This method exists only for selected protocol sequences.
   358                  rpctransportWkst.set_credentials(self.__username, self.__password, self.__domain, self.__lmhash,
   359                                                   self.__nthash, self.__aesKey)
   360                  rpctransportWkst.set_kerberos(self.__doKerberos, self.__kdcHost)
   361  
   362              dce = rpctransportWkst.get_dce_rpc()
   363              dce.connect()
   364              dce.bind(wkst.MSRPC_UUID_WKST)
   365              self.__maxConnections -= 1
   366          else:
   367              dce = self.__targets[target]['WKST']
   368  
   369          try:
   370              resp = wkst.hNetrWkstaUserEnum(dce,1)
   371          except Exception as e:
   372              if str(e).find('Broken pipe') >= 0:
   373                  # The connection timed-out. Let's try to bring it back next round
   374                  self.__targets[target]['WKST'] = None
   375                  self.__maxConnections += 1
   376                  return
   377              elif str(e).upper().find('ACCESS_DENIED'):
   378                  # We're not admin, bye
   379                  dce.disconnect()
   380                  self.__maxConnections += 1
   381                  self.__targets[target]['Admin'] = False
   382                  return
   383              else:
   384                  raise
   385  
   386          if self.__maxConnections < 0:
   387              # Can't keep this connection open. Closing it
   388              dce.disconnect()
   389              self.__maxConnections = 0
   390          else:
   391               self.__targets[target]['WKST'] = dce
   392  
   393          # Let's see who looged in locally since last check
   394          tmpLoggedUsers = set()
   395          printCRLF = False
   396          for session in resp['UserInfo']['WkstaUserInfo']['Level1']['Buffer']:
   397              userName = session['wkui1_username'][:-1]
   398              logonDomain = session['wkui1_logon_domain'][:-1]
   399              key = '%s\x01%s' % (userName, logonDomain)
   400              tmpLoggedUsers.add(key)
   401              if not(key in self.__targets[target]['LoggedIn']):
   402                  self.__targets[target]['LoggedIn'].add(key)
   403                  # Are we filtering users?
   404                  if self.__filterUsers is not None:
   405                      if userName in self.__filterUsers:
   406                          print("%s: user %s\\%s logged in LOCALLY" % (target,logonDomain,userName))
   407                          printCRLF=True
   408                  else:
   409                      print("%s: user %s\\%s logged in LOCALLY" % (target,logonDomain,userName))
   410                      printCRLF=True
   411  
   412          # Let's see who logged out since last check
   413          for session in self.__targets[target]['LoggedIn'].copy():
   414              userName, logonDomain = session.split('\x01')
   415              if session not in tmpLoggedUsers:
   416                  self.__targets[target]['LoggedIn'].remove(session)
   417                  # Are we filtering users?
   418                  if self.__filterUsers is not None:
   419                      if userName in self.__filterUsers:
   420                          print("%s: user %s\\%s logged off LOCALLY" % (target,logonDomain,userName))
   421                          printCRLF=True
   422                  else:
   423                      print("%s: user %s\\%s logged off LOCALLY" % (target,logonDomain,userName))
   424                      printCRLF=True
   425                  
   426          if printCRLF is True:
   427              print()
   428  
   429      def stop(self):
   430          if self.__targetsThreadEvent is not None:
   431              self.__targetsThreadEvent.set()
   432          
   433  
   434  # Process command-line arguments.
   435  if __name__ == '__main__':
   436      print(version.BANNER)
   437      # Init the example's logger theme
   438      logger.init()
   439  
   440      parser = argparse.ArgumentParser()
   441  
   442      parser.add_argument('identity', action='store', help='[domain/]username[:password]')
   443      parser.add_argument('-user', action='store', help='Filter output by this user')
   444      parser.add_argument('-users', type=argparse.FileType('r'), help='input file with list of users to filter to output for')
   445      #parser.add_argument('-group', action='store', help='Filter output by members of this group')
   446      #parser.add_argument('-groups', type=argparse.FileType('r'), help='Filter output by members of the groups included in the input file')
   447      parser.add_argument('-target', action='store', help='target system to query info from. If not specified script will '
   448                                                          'run in domain mode.')
   449      parser.add_argument('-targets', type=argparse.FileType('r'), help='input file with targets system to query info '
   450                          'from (one per line). If not specified script will run in domain mode.')
   451      parser.add_argument('-noloop', action='store_true', default=False, help='Stop after the first probe')
   452      parser.add_argument('-delay', action='store', default = '10', help='seconds delay between starting each batch probe '
   453                                                                         '(default 10 seconds)')
   454      parser.add_argument('-max-connections', action='store', default='1000', help='Max amount of connections to keep '
   455                                                                                   'opened (default 1000)')
   456      parser.add_argument('-debug', action='store_true', help='Turn DEBUG output ON')
   457  
   458      group = parser.add_argument_group('authentication')
   459  
   460      group.add_argument('-hashes', action="store", metavar = "LMHASH:NTHASH", help='NTLM hashes, format is LMHASH:NTHASH')
   461      group.add_argument('-no-pass', action="store_true", help='don\'t ask for password (useful for -k)')
   462      group.add_argument('-k', action="store_true", help='Use Kerberos authentication. Grabs credentials from ccache file '
   463                         '(KRB5CCNAME) based on target parameters. If valid credentials cannot be found, it will use the '
   464                         'ones specified in the command line')
   465      group.add_argument('-aesKey', action="store", metavar = "hex key", help='AES key to use for Kerberos Authentication '
   466                                                                              '(128 or 256 bits)')
   467      group.add_argument('-dc-ip', action='store',metavar = "ip address",  help='IP Address of the domain controller. If '
   468                         'ommited it use the domain part (FQDN) specified in the target parameter')
   469  
   470      if len(sys.argv)==1:
   471          parser.print_help()
   472          sys.exit(1)
   473  
   474      options = parser.parse_args()
   475  
   476      if options.debug is True:
   477          logging.getLogger().setLevel(logging.DEBUG)
   478      else:
   479          logging.getLogger().setLevel(logging.INFO)
   480  
   481      import re
   482  
   483      domain, username, password = re.compile('(?:(?:([^/:]*)/)?([^:]*)(?::([^@]*))?)?').match(options.identity).groups(
   484          '')
   485  
   486      try:
   487          if domain is None:
   488              domain = ''
   489  
   490          if password == '' and username != '' and options.hashes is None and options.no_pass is False and options.aesKey is None:
   491              from getpass import getpass
   492              password = getpass("Password:")
   493  
   494          if options.aesKey is not None:
   495              options.k = True
   496  
   497          executer = USERENUM(username, password, domain, options.hashes, options.aesKey, options.k, options)
   498          executer.run()
   499      except Exception as e:
   500          if logging.getLogger().level == logging.DEBUG:
   501              import traceback
   502              traceback.print_exc()
   503          logging.error(e)
   504          executer.stop()
   505      except KeyboardInterrupt:
   506          logging.info('Quitting.. please wait') 
   507          executer.stop()
   508      sys.exit(0)