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)