diff --git a/examples/registry-read.py b/examples/registry-read.py index 4b5656199..11f506f3e 100755 --- a/examples/registry-read.py +++ b/examples/registry-read.py @@ -148,13 +148,16 @@ def main(): walk_parser = subparsers.add_parser('walk', help='walks the registry from the name node down') walk_parser.add_argument('-name', action='store', required=True, help='registry class name to start walking down from') + # Hive format + parser.add_argument('-format', action='store', choices=['save', 'export'], default='save',help="Hive format, either 'save' or 'export' (default: 'save')") + if len(sys.argv)==1: parser.print_help() sys.exit(1) options = parser.parse_args() - reg = winregistry.Registry(options.hive) + reg = winregistry.Registry(options.hive,hiveFormat = options.format) if options.action.upper() == 'ENUM_KEY': print("[%s]" % options.name) diff --git a/examples/secretsdump.py b/examples/secretsdump.py index 1ab5616ed..73c46cdc1 100755 --- a/examples/secretsdump.py +++ b/examples/secretsdump.py @@ -95,8 +95,14 @@ def __init__(self, remoteName, username='', password='', domain='', options=None self.__rodc = options.rodcNo self.__systemHive = options.system self.__bootkey = options.bootkey + self.__jdClass = options.lsa_jd + self.__skew1Class = options.lsa_skew1 + self.__gbgClass = options.lsa_gbg + self.__dataClass = options.lsa_data self.__securityHive = options.security + self.__securityHiveExport = options.security_export self.__samHive = options.sam + self.__samHiveExport = options.sam_export self.__ntdsFile = options.ntds self.__skipSam = options.skip_sam self.__skipSecurity = options.skip_security @@ -170,6 +176,19 @@ def ldapConnect(self): self.__aesKey, kdcHost=self.__kdcHost) else: raise + + def getBootKey(self, jdClass, skew1Class, gbgClass, dataClass): + import binascii + bootKey = b'' + tmpKey = jdClass.encode('utf-8') + skew1Class.encode('utf-8') + gbgClass.encode('utf-8') + dataClass.encode('utf-8') + transforms = [8, 5, 4, 2, 11, 9, 13, 3, 0, 6, 1, 12, 14, 10, 15, 7] + tmpKey = binascii.unhexlify(tmpKey) + for i in range(len(tmpKey)): + bootKey += tmpKey[transforms[i]:transforms[i] + 1] + + logging.info('Target system bootKey: 0x%s' % binascii.hexlify(bootKey).decode('utf-8')) + + return bootKey def dump(self): try: @@ -217,10 +236,11 @@ def dump(self): if self.__ntdsFile is not None: # Let's grab target's configuration about LM Hashes storage self.__noLMHash = localOperations.checkNoLMHashPolicy() - else: + elif self.__bootkey: import binascii bootKey = binascii.unhexlify(self.__bootkey) - + elif self.__jdClass and self.__skew1Class and self.__gbgClass and self.__dataClass: + bootKey = self.getBootKey(self.__jdClass, self.__skew1Class, self.__gbgClass, self.__dataClass) else: self.__isRemote = True bootKey = None @@ -274,10 +294,15 @@ def dump(self): try: if self.__isRemote is True: SAMFileName = self.__remoteOps.saveSAM() - else: + samFormat = "save" + elif self.__samHive: SAMFileName = self.__samHive + samFormat = "save" + elif self.__samHiveExport: + SAMFileName = self.__samHiveExport + samFormat = "export" - self.__SAMHashes = SAMHashes(SAMFileName, bootKey, isRemote = self.__isRemote) + self.__SAMHashes = SAMHashes(SAMFileName, bootKey, isRemote = self.__isRemote, format = samFormat) self.__SAMHashes.dump() if self.__outputFileName is not None: self.__SAMHashes.export(self.__outputFileName) @@ -288,11 +313,16 @@ def dump(self): try: if self.__isRemote is True: SECURITYFileName = self.__remoteOps.saveSECURITY() - else: + securityFormat = "save" + elif self.__securityHive: SECURITYFileName = self.__securityHive + securityFormat = "save" + elif self.__securityHiveExport: + SECURITYFileName = self.__securityHiveExport + securityFormat = "export" self.__LSASecrets = LSASecrets(SECURITYFileName, bootKey, self.__remoteOps, - isRemote=self.__isRemote, history=self.__history) + isRemote=self.__isRemote, history=self.__history, format = securityFormat) self.__LSASecrets.dumpCachedHashes() if self.__outputFileName is not None: self.__LSASecrets.exportCached(self.__outputFileName) @@ -400,8 +430,14 @@ def cleanup(self): parser.add_argument('-debug', action='store_true', help='Turn DEBUG output ON') parser.add_argument('-system', action='store', help='SYSTEM hive to parse') parser.add_argument('-bootkey', action='store', help='bootkey for SYSTEM hive') - parser.add_argument('-security', action='store', help='SECURITY hive to parse') - parser.add_argument('-sam', action='store', help='SAM hive to parse') + parser.add_argument('-lsa-jd', action='store', help='Class name of HKLM\SYSTEM\CurrentControlSet\Control\Lsa\JD to compute the bootkey') + parser.add_argument('-lsa-skew1', action='store', help='Class name of HKLM\SYSTEM\CurrentControlSet\Control\Lsa\Skew1 to compute the bootkey') + parser.add_argument('-lsa-gbg', action='store', help='Class name of HKLM\SYSTEM\CurrentControlSet\Control\Lsa\GBG to compute the bootkey') + parser.add_argument('-lsa-data', action='store', help='Class name of HKLM\SYSTEM\CurrentControlSet\Control\Lsa\Data to compute the bootkey') + parser.add_argument('-security', action='store', help='SECURITY hive to parse in "save" format') + parser.add_argument('-security-export', action='store', help='SECURITY hive to parse in "export" format') + parser.add_argument('-sam', action='store', help='SAM hive to parse in "save" format') + parser.add_argument('-sam-export', action='store', help='SAM hive to parse in "export" format') parser.add_argument('-ntds', action='store', help='NTDS.DIT file to parse') 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 ' @@ -506,8 +542,8 @@ def cleanup(self): sys.exit(1) if remoteName.upper() == 'LOCAL' and username == '': - if options.system is None and options.bootkey is None: - logging.error('Either the SYSTEM hive or bootkey is required for local parsing, check help') + if options.system is None and options.bootkey is None and (options.lsa_jd is None or options.lsa_skew1 is None or options.lsa_gbg is None or options.lsa_data is None): + logging.error('Either the SYSTEM hive, bootkey or the LSA\'s class names is required for local parsing, check help') sys.exit(1) else: diff --git a/impacket/examples/secretsdump.py b/impacket/examples/secretsdump.py index 1d97516c3..832986af9 100644 --- a/impacket/examples/secretsdump.py +++ b/impacket/examples/secretsdump.py @@ -1293,10 +1293,10 @@ def decryptAES(key, value, iv=b'\x00'*16): class OfflineRegistry: - def __init__(self, hiveFile = None, isRemote = False): + def __init__(self, hiveFile = None, isRemote = False, format = "save"): self.__hiveFile = hiveFile if self.__hiveFile is not None: - self.__registryHive = winregistry.Registry(self.__hiveFile, isRemote) + self.__registryHive = winregistry.Registry(self.__hiveFile, isRemote, format) def enumKey(self, searchKey): parentKey = self.__registryHive.findKey(searchKey) @@ -1340,8 +1340,8 @@ def finish(self): self.__registryHive.close() class SAMHashes(OfflineRegistry): - def __init__(self, samFile, bootKey, isRemote = False, perSecretCallback = lambda secret: _print_helper(secret)): - OfflineRegistry.__init__(self, samFile, isRemote) + def __init__(self, samFile, bootKey, isRemote = False, format = "save", perSecretCallback = lambda secret: _print_helper(secret)): + OfflineRegistry.__init__(self, samFile, isRemote, format) self.__samFile = samFile self.__hashedBootKey = b'' self.__bootKey = bootKey @@ -1488,9 +1488,9 @@ class SECRET_TYPE: LSA_RAW = 2 LSA_KERBEROS = 3 - def __init__(self, securityFile, bootKey, remoteOps=None, isRemote=False, history=False, + def __init__(self, securityFile, bootKey, remoteOps=None, isRemote=False, history=False, format = "save", perSecretCallback=lambda secretType, secret: _print_helper(secret)): - OfflineRegistry.__init__(self, securityFile, isRemote) + OfflineRegistry.__init__(self, securityFile, isRemote, format) self.__hashedBootKey = b'' self.__bootKey = bootKey self.__LSAKey = b'' diff --git a/impacket/winregistry.py b/impacket/winregistry.py index 6089f2a91..af34b26bf 100644 --- a/impacket/winregistry.py +++ b/impacket/winregistry.py @@ -25,6 +25,8 @@ from __future__ import division from __future__ import print_function import sys +import re +from binascii import unhexlify from struct import unpack import ntpath from six import b @@ -160,7 +162,7 @@ class REG_HASH(Structure): b'sk': REG_SK, } -class Registry: +class saveRegistryParser: def __init__(self, hive, isRemote = False): self.__hive = hive if isRemote is True: @@ -493,3 +495,252 @@ def getClass(self, className): if key['OffsetClassName'] > 0: value = self.__getBlock(key['OffsetClassName']) return value['Data'] + + +class RegistryNode: + def __init__(self, keyName, nodeName, data = None): + self.keyName = keyName + self.nodeName = nodeName + self.data = data + self.childKeys = {} + + def addChildNode(self, childKey): + self.childKeys = self.childKeys | childKey + + +class exportRegistryParser: + def __init__(self, hive): + self.indent = '' + self.__hive = hive + self.fd = open(hive, encoding='utf-16-le') + self.__buildRegistryTree() + + def close(self): + if hasattr(self, 'fd'): + self.fd.close() + + def __del__(self): + self.close() + + def __parseType(self, ValueType): + if ValueType == 'hex(0)': + return REG_NONE + elif ValueType == 'hex(2)': + return REG_EXPAND_SZ + elif ValueType == 'hex': + return REG_BINARY + elif ValueType == 'dword': + return REG_DWORD + elif ValueType == 'hex(7)': + return REG_MULTISZ + elif ValueType == 'hex(b)': + return REG_QWORD + else: + return int(ValueType.replace('hex(','0x').replace(')',''),16) + + def __keyToNodePath(self, key): + return key.replace(f'{self.registryTree.keyName}\\','').strip('\\').split('\\') + + def __findNode(self, nodePath): + node = self.registryTree + try: + if nodePath != ['']: + for tempNode in nodePath: + node = node.childKeys[tempNode] + return node + except: + return None + + def __extractData(self, regkey_values): + if not regkey_values: + return { 'default' : [REG_SZ, '']} + else: + data = {} + pattern_regsz = re.compile(r'^(?:"(.*)"|(@))="(.*)"$') + pattern_other = re.compile(r'^(?:"(.*)"|(@))=(.*):([\S\s]*)$') + + pattern_split_values = re.compile(r'^([\S\s]*?)$(? 1: + print('') + hexdump(valueData, self.indent) + else: + print(" NULL") + except: + print(" NULL") + else: + print("Unknown Type 0x%x!" % valueType) + hexdump(valueData) + + def findKey(self, key): + if key == '\\': + return '\\' + else: + return '\\'.join(self.__keyToNodePath(key)) + + def enumKey(self, key): + path = self.__keyToNodePath(key) + node = self.__findNode(path) + return list(node.childKeys.keys()) + + def enumValues(self,key): + path = self.__keyToNodePath(key) + node = self.__findNode(path) + if not node: + return None + else: + values = list(node.data.keys()) + return [s.encode('utf-8') for s in values] + + def getValue(self, keyValue, valueName=None): + """ returns a tuple with (ValueType, ValueData) for the requested keyValue + valueName is the name of the value (which can contain '\\') + if valueName is not given, keyValue must be a string containing the full path to the value + if valueName is given, keyValue should be the string containing the path to the key containing valueName + """ + path = self.__keyToNodePath(keyValue) + if valueName is None: + keyPath = path[:-1] + regValue = ''.join(path[-1:]) + else: + keyPath = path + regValue = valueName + + try: + node = self.__findNode(keyPath) + ValueType, ValueData = node.data[regValue] + if ValueType in [REG_SZ]: + return ValueType, ValueData.encode("utf-16-le") + else: + return ValueType, unhexlify(ValueData) + except: + return None + + def getClass(self, className): + # Export format does not contain class name + return None + + +class Registry: + def __init__(self, hive, isRemote = False, hiveFormat = 'save'): + self.indent = '' + self.__hiveFormat = hiveFormat + if self.__hiveFormat == 'save': + self.__registryParser = saveRegistryParser(hive, isRemote) + elif self.__hiveFormat == 'export': + self.__registryParser = exportRegistryParser(hive) + + def close(self): + if hasattr(self.__registryParser, 'fd'): + self.__registryParser.fd.close() + + def __del__(self): + self.close() + + def walk(self, parentKey): + return self.__registryParser.walk(parentKey) + + def findKey(self, key): + return self.__registryParser.findKey(key) + + def printValue(self, valueType, valueData): + return self.__registryParser.printValue(valueType, valueData) + + def enumKey(self, parentKey): + return self.__registryParser.enumKey(parentKey) + + def enumValues(self,key): + return self.__registryParser.enumValues(key) + + def getValue(self, keyValue, valueName=None): + return self.__registryParser.getValue(keyValue, valueName) + + def getClass(self, className): + return self.__registryParser.getClass(className) \ No newline at end of file