# SECUREAUTH LABS. Copyright 2018 SecureAuth Corporation. All rights reserved. # # This software is provided under under a slightly modified version # of the Apache Software License. See the accompanying LICENSE file # for more information. # # Author: Alberto Solino (@agsolino) # # Description: A Windows Registry Library Parser # # Data taken from https://bazaar.launchpad.net/~guadalinex-members/dumphive/trunk/view/head:/winreg.txt # http://sentinelchicken.com/data/TheWindowsNTRegistryFileFormat.pdf # # # ToDo: # # [ ] Parse li records, probable the same as the ri but couldn't find any to probe from __future__ import division from __future__ import print_function import sys from struct import unpack import ntpath from six import b from impacket import LOG from impacket.structure import Structure, hexdump # Constants ROOT_KEY = 0x2c REG_NONE = 0x00 REG_SZ = 0x01 REG_EXPAND_SZ = 0x02 REG_BINARY = 0x03 REG_DWORD = 0x04 REG_MULTISZ = 0x07 REG_QWORD = 0x0b # Structs class REG_REGF(Structure): structure = ( ('Magic','"regf'), ('Unknown',' 5: LOG.warning("Unsupported version (%d.%d) - things might not work!" % (self.__regf['MajorVersion'], self.__regf['MinorVersion'])) def close(self): self.fd.close() def __del__(self): self.close() def __findRootKey(self): self.fd.seek(0,0) data = self.fd.read(4096) while len(data) > 0: try: hbin = REG_HBIN(data[:0x20]) # Read the remaining bytes for this hbin data += self.fd.read(hbin['OffsetNextHBin']-4096) data = data[0x20:] blocks = self.__processDataBlocks(data) for block in blocks: if isinstance(block, REG_NK): if block['Type'] == ROOT_KEY: return block except Exception as e: pass data = self.fd.read(4096) return None def __getBlock(self, offset): self.fd.seek(4096+offset,0) sizeBytes = self.fd.read(4) data = sizeBytes + self.fd.read(unpack(' 0: block = self.__getBlock(valueOffset) res.append(block) return res def __getData(self, offset, count): self.fd.seek(4096+offset, 0) return self.fd.read(count)[4:] def __processDataBlocks(self,data): res = [] while len(data) > 0: #blockSize = unpack(' 0: tmpList = list(block.structure) tmpList[1] = ('_Data','_-Data','self["DataBlockSize"]-4') block.structure = tuple(tmpList) block.fromString(data) blockLen = len(block) if block['Data'][:2] in StructMappings: block = StructMappings[block['Data'][:2]](block['Data']) res.append(block) data = data[blockLen:] return res def __getValueData(self, rec): # We should receive a VK record if rec['DataLen'] == 0: return '' if rec['DataLen'] < 0: # if DataLen < 5 the value itself is stored in the Offset field return rec['OffsetData'] else: return self.__getData(rec['OffsetData'], rec['DataLen']+4) def __getLhHash(self, key): res = 0 for bb in key.upper(): res *= 37 res += ord(bb) return res % 0x100000000 def __compareHash(self, magic, hashData, key): if magic == 'lf': hashRec = REG_HASH(hashData) if hashRec['KeyName'].strip(b'\x00') == b(key[:4]): return hashRec['OffsetNk'] elif magic == 'lh': hashRec = REG_HASH(hashData) if unpack(' 1: key = key[1:] parentKey = self.rootKey if len(key) > 0 and key[0]!='\\': for subKey in key.split('\\'): res = self.__findSubKey(parentKey, subKey) if res is not None: parentKey = res else: #LOG.error("Key %s not found!" % key) return None return parentKey def printValue(self, valueType, valueData): if valueType == REG_SZ or valueType == REG_EXPAND_SZ: if type(valueData) is int: print('NULL') else: print("%s" % (valueData.decode('utf-16le'))) elif valueType == REG_BINARY: print('') hexdump(valueData, self.indent) elif valueType == REG_DWORD: print("%d" % valueData) elif valueType == REG_QWORD: print("%d" % (unpack(' 1: print('') hexdump(valueData, self.indent) else: print(" NULL") except: print(" NULL") elif valueType == REG_MULTISZ: print("%s" % (valueData.decode('utf-16le'))) else: print("Unknown Type 0x%x!" % valueType) hexdump(valueData) def enumKey(self, parentKey): res = [] # If we're here.. we have a valid NK record for the key # Now let's searcht the subkeys if parentKey['NumSubKeys'] > 0: lf = self.__getBlock(parentKey['OffsetSubKeyLf']) data = lf['HashRecords'] if lf['Magic'] == 'ri': # ri points to lf/lh records, so we must parse them before records = '' for i in range(lf['NumKeys']): offset = unpack(' 0: valueList = self.__getValueBlocks(key['OffsetValueList'], key['NumValues']+1) for value in valueList: if value['Flag'] > 0: resp.append(value['Name']) else: resp.append(b'default') return resp def getValue(self, keyValue): # returns a tuple with (ValueType, ValueData) for the requested keyValue regKey = ntpath.dirname(keyValue) regValue = ntpath.basename(keyValue) key = self.findKey(regKey) if key is None: return None if key['NumValues'] > 0: valueList = self.__getValueBlocks(key['OffsetValueList'], key['NumValues']+1) for value in valueList: if value['Name'] == b(regValue): return value['ValueType'], self.__getValueData(value) elif regValue == 'default' and value['Flag'] <=0: return value['ValueType'], self.__getValueData(value) return None def getClass(self, className): key = self.findKey(className) if key is None: return None #print key.dump() if key['OffsetClassName'] > 0: value = self.__getBlock(key['OffsetClassName']) return value['Data']