1# Copyright (c) 2003-2016 CORE Security Technologies)
2#
3# This software is provided under under a slightly modified version
4# of the Apache Software License. See the accompanying LICENSE file
5# for more information.
6#
7# Author: Alberto Solino (@agsolino)
8#
9# Description: A Windows Registry Library Parser
10#
11# Data taken from http://bazaar.launchpad.net/~guadalinex-members/dumphive/trunk/view/head:/winreg.txt
12# http://sentinelchicken.com/data/TheWindowsNTRegistryFileFormat.pdf
13#
14#
15# ToDo:
16#
17# [ ] Parse li records, probable the same as the ri but couldn't find any to probe
18
19
20import sys
21from struct import unpack
22import ntpath
23
24from impacket import LOG
25from impacket.structure import Structure
26
27
28# Constants
29
30ROOT_KEY        = 0x2c
31REG_NONE        = 0x00
32REG_SZ          = 0x01
33REG_EXPAND_SZ   = 0x02
34REG_BINARY      = 0x03
35REG_DWORD       = 0x04
36REG_MULTISZ     = 0x07
37REG_QWORD       = 0x0b
38
39# Structs
40class REG_REGF(Structure):
41    structure = (
42        ('Magic','"regf'),
43        ('Unknown','<L=0'),
44        ('Unknown2','<L=0'),
45        ('lastChange','<Q=0'),
46        ('MajorVersion','<L=0'),
47        ('MinorVersion','<L=0'),
48        ('0','<L=0'),
49        ('11','<L=0'),
50        ('OffsetFirstRecord','<L=0'),
51        ('DataSize','<L=0'),
52        ('1111','<L=0'),
53        ('Name','48s=""'),
54        ('Remaining1','411s=""'),
55        ('CheckSum','<L=0xffffffff'), # Sum of all DWORDs from 0x0 to 0x1FB
56        ('Remaining2','3585s=""'),
57    )
58
59class REG_HBIN(Structure):
60    structure = (
61        ('Magic','"hbin'),
62        ('OffsetFirstHBin','<L=0'),
63        ('OffsetNextHBin','<L=0'),
64        ('BlockSize','<L=0'),
65    )
66
67class REG_HBINBLOCK(Structure):
68    structure = (
69        ('DataBlockSize','<l=0'),
70        ('_Data','_-Data','self["DataBlockSize"]*(-1)-4'),
71        ('Data',':'),
72    )
73
74class REG_NK(Structure):
75    structure = (
76        ('Magic','"nk'),
77        ('Type','<H=0'),
78        ('lastChange','<Q=0'),
79        ('Unknown','<L=0'),
80        ('OffsetParent','<l=0'),
81        ('NumSubKeys','<L=0'),
82        ('Unknown2','<L=0'),
83        ('OffsetSubKeyLf','<l=0'),
84        ('Unknown3','<L=0'),
85        ('NumValues','<L=0'),
86        ('OffsetValueList','<l=0'),
87        ('OffsetSkRecord','<l=0'),
88        ('OffsetClassName','<l=0'),
89        ('UnUsed','20s=""'),
90        ('NameLength','<H=0'),
91        ('ClassNameLength','<H=0'),
92        ('_KeyName','_-KeyName','self["NameLength"]'),
93        ('KeyName',':'),
94    )
95
96class REG_VK(Structure):
97    structure = (
98        ('Magic','"vk'),
99        ('NameLength','<H=0'),
100        ('DataLen','<l=0'),
101        ('OffsetData','<L=0'),
102        ('ValueType','<L=0'),
103        ('Flag','<H=0'),
104        ('UnUsed','<H=0'),
105        ('_Name','_-Name','self["NameLength"]'),
106        ('Name',':'),
107    )
108
109class REG_LF(Structure):
110    structure = (
111        ('Magic','"lf'),
112        ('NumKeys','<H=0'),
113        ('HashRecords',':'),
114    )
115
116class REG_LH(Structure):
117    structure = (
118        ('Magic','"lh'),
119        ('NumKeys','<H=0'),
120        ('HashRecords',':'),
121    )
122
123class REG_RI(Structure):
124    structure = (
125        ('Magic','"ri'),
126        ('NumKeys','<H=0'),
127        ('HashRecords',':'),
128    )
129
130class REG_SK(Structure):
131    structure = (
132        ('Magic','"sk'),
133        ('UnUsed','<H=0'),
134        ('OffsetPreviousSk','<l=0'),
135        ('OffsetNextSk','<l=0'),
136        ('UsageCounter','<L=0'),
137        ('SizeSk','<L=0'),
138        ('Data',':'),
139    )
140
141class REG_HASH(Structure):
142    structure = (
143        ('OffsetNk','<L=0'),
144        ('KeyName','4s=""'),
145    )
146
147StructMappings = {'nk': REG_NK,
148                  'vk': REG_VK,
149                  'lf': REG_LF,
150                  'lh': REG_LH,
151                  'ri': REG_RI,
152                  'sk': REG_SK,
153                 }
154
155class Registry:
156    def __init__(self, hive, isRemote = False):
157        self.__hive = hive
158        if isRemote is True:
159            self.fd = self.__hive
160            self.__hive.open()
161        else:
162            self.fd = open(hive,'rb')
163        data = self.fd.read(4096)
164        self.__regf = REG_REGF(data)
165        self.indent = ''
166        self.rootKey = self.__findRootKey()
167        if self.rootKey is None:
168            LOG.error("Can't find root key!")
169        elif self.__regf['MajorVersion'] != 1 and self.__regf['MinorVersion'] > 5:
170            LOG.warning("Unsupported version (%d.%d) - things might not work!" % (self.__regf['MajorVersion'], self.__regf['MinorVersion']))
171
172    def close(self):
173        self.fd.close()
174
175    def __del__(self):
176        self.close()
177
178    def __findRootKey(self):
179        self.fd.seek(0,0)
180        data = self.fd.read(4096)
181        while len(data) > 0:
182            try:
183                hbin = REG_HBIN(data[:0x20])
184                # Read the remaining bytes for this hbin
185                data += self.fd.read(hbin['OffsetNextHBin']-4096)
186                data = data[0x20:]
187                blocks = self.__processDataBlocks(data)
188                for block in blocks:
189                    if isinstance(block, REG_NK):
190                        if block['Type'] == ROOT_KEY:
191                            return block
192            except:
193                 pass
194            data = self.fd.read(4096)
195
196        return None
197
198
199    def __getBlock(self, offset):
200        self.fd.seek(4096+offset,0)
201        sizeBytes = self.fd.read(4)
202        data = sizeBytes + self.fd.read(unpack('<l',sizeBytes)[0]*-1-4)
203        if len(data) == 0:
204            return None
205        else:
206            block = REG_HBINBLOCK(data)
207            if StructMappings.has_key(block['Data'][:2]):
208                return StructMappings[block['Data'][:2]](block['Data'])
209            else:
210                LOG.debug("Unknown type 0x%s" % block['Data'][:2])
211                return block
212            return None
213
214    def __getValueBlocks(self, offset, count):
215        valueList = []
216        res = []
217        self.fd.seek(4096+offset,0)
218        for i in range(count):
219            valueList.append(unpack('<l',self.fd.read(4))[0])
220
221        for valueOffset in valueList:
222            if valueOffset > 0:
223                block = self.__getBlock(valueOffset)
224                res.append(block)
225        return res
226
227    def __getData(self, offset, count):
228        self.fd.seek(4096+offset, 0)
229        return self.fd.read(count)[4:]
230
231    def __processDataBlocks(self,data):
232        res = []
233        while len(data) > 0:
234            #blockSize = unpack('<l',data[:calcsize('l')])[0]
235            blockSize = unpack('<l',data[:4])[0]
236            block = REG_HBINBLOCK()
237            if blockSize > 0:
238                tmpList = list(block.structure)
239                tmpList[1] = ('_Data','_-Data','self["DataBlockSize"]-4')
240                block.structure =  tuple(tmpList)
241
242            block.fromString(data)
243            blockLen = len(block)
244
245            if StructMappings.has_key(block['Data'][:2]):
246                block = StructMappings[block['Data'][:2]](block['Data'])
247
248            res.append(block)
249            data = data[blockLen:]
250        return res
251
252    def __getValueData(self, rec):
253        # We should receive a VK record
254        if rec['DataLen'] == 0:
255            return ''
256        if rec['DataLen'] < 0:
257            # if DataLen < 5 the value itself is stored in the Offset field
258            return rec['OffsetData']
259        else:
260            return self.__getData(rec['OffsetData'], rec['DataLen']+4)
261
262    def __getLhHash(self, key):
263        res = 0
264        for b in key.upper():
265            res *= 37
266            res += ord(b)
267        return res % 0x100000000
268
269    def __compareHash(self, magic, hashData, key):
270        if magic == 'lf':
271            hashRec = REG_HASH(hashData)
272            if hashRec['KeyName'].strip('\x00') == key[:4]:
273                return hashRec['OffsetNk']
274        elif magic == 'lh':
275            hashRec = REG_HASH(hashData)
276            if unpack('<L',hashRec['KeyName'])[0] == self.__getLhHash(key):
277                return hashRec['OffsetNk']
278        elif magic == 'ri':
279            # Special case here, don't know exactly why, an ri pointing to a NK :-o
280            offset = unpack('<L', hashData[:4])[0]
281            nk = self.__getBlock(offset)
282            if nk['KeyName'] == key:
283                return offset
284        else:
285            LOG.critical("UNKNOWN Magic %s" % magic)
286            sys.exit(1)
287
288        return None
289
290    def __findSubKey(self, parentKey, subKey):
291        lf = self.__getBlock(parentKey['OffsetSubKeyLf'])
292        if lf is not None:
293            data = lf['HashRecords']
294            # Let's search the hash records for the name
295            if lf['Magic'] == 'ri':
296                # ri points to lf/lh records, so we must parse them before
297                records = ''
298                for i in range(lf['NumKeys']):
299                    offset = unpack('<L', data[:4])[0]
300                    l = self.__getBlock(offset)
301                    records = records + l['HashRecords'][:l['NumKeys']*8]
302                    data = data[4:]
303                data = records
304
305            #for record in range(lf['NumKeys']):
306            for record in range(parentKey['NumSubKeys']):
307                hashRec = data[:8]
308                res = self.__compareHash(lf['Magic'], hashRec, subKey)
309                if res is not None:
310                    # We have a match, now let's check the whole record
311                    nk = self.__getBlock(res)
312                    if nk['KeyName'] == subKey:
313                        return nk
314                data = data[8:]
315
316        return None
317
318    def __walkSubNodes(self, rec):
319        nk = self.__getBlock(rec['OffsetNk'])
320        if isinstance(nk, REG_NK):
321            print "%s%s" % (self.indent, nk['KeyName'])
322            self.indent += '  '
323            if nk['OffsetSubKeyLf'] < 0:
324                self.indent = self.indent[:-2]
325                return
326            lf = self.__getBlock(nk['OffsetSubKeyLf'])
327        else:
328            lf = nk
329
330        data = lf['HashRecords']
331
332        if lf['Magic'] == 'ri':
333            # ri points to lf/lh records, so we must parse them before
334            records = ''
335            for i in range(lf['NumKeys']):
336                offset = unpack('<L', data[:4])[0]
337                l = self.__getBlock(offset)
338                records = records + l['HashRecords'][:l['NumKeys']*8]
339                data = data[4:]
340            data = records
341
342        for key in range(lf['NumKeys']):
343            hashRec = REG_HASH(data[:8])
344            self.__walkSubNodes(hashRec)
345            data = data[8:]
346
347        if isinstance(nk, REG_NK):
348            self.indent = self.indent[:-2]
349
350    def walk(self, parentKey):
351        key = self.findKey(parentKey)
352
353        if key is None or key['OffsetSubKeyLf'] < 0:
354            return
355
356        lf = self.__getBlock(key['OffsetSubKeyLf'])
357        data = lf['HashRecords']
358        for record in range(lf['NumKeys']):
359            hashRec = REG_HASH(data[:8])
360            self.__walkSubNodes(hashRec)
361            data = data[8:]
362
363    def findKey(self, key):
364        # Let's strip '\' from the beginning, except for the case of
365        # only asking for the root node
366        if key[0] == '\\' and len(key) > 1:
367            key = key[1:]
368
369        parentKey = self.rootKey
370        if len(key) > 0 and key[0]!='\\':
371            for subKey in key.split('\\'):
372                res = self.__findSubKey(parentKey, subKey)
373                if res is not None:
374                    parentKey = res
375                else:
376                    #LOG.error("Key %s not found!" % key)
377                    return None
378
379        return parentKey
380
381    def printValue(self, valueType, valueData):
382        if valueType == REG_SZ or valueType == REG_EXPAND_SZ:
383            if type(valueData) is int:
384                print 'NULL'
385            else:
386                print "%s" % (valueData.decode('utf-16le'))
387        elif valueType == REG_BINARY:
388            print ''
389            hexdump(valueData, self.indent)
390        elif valueType == REG_DWORD:
391            print "%d" % valueData
392        elif valueType == REG_QWORD:
393            print "%d" % (unpack('<Q',valueData)[0])
394        elif valueType == REG_NONE:
395            try:
396                if len(valueData) > 1:
397                    print ''
398                    hexdump(valueData, self.indent)
399                else:
400                    print " NULL"
401            except:
402                print " NULL"
403        elif valueType == REG_MULTISZ:
404            print "%s" % (valueData.decode('utf-16le'))
405        else:
406            print "Unknown Type 0x%x!" % valueType
407            hexdump(valueData)
408
409    def enumKey(self, parentKey):
410        res = []
411        # If we're here.. we have a valid NK record for the key
412        # Now let's searcht the subkeys
413        if parentKey['NumSubKeys'] > 0:
414            lf = self.__getBlock(parentKey['OffsetSubKeyLf'])
415            data = lf['HashRecords']
416
417            if lf['Magic'] == 'ri':
418                # ri points to lf/lh records, so we must parse them before
419                records = ''
420                for i in range(lf['NumKeys']):
421                    offset = unpack('<L', data[:4])[0]
422                    l = self.__getBlock(offset)
423                    records = records + l['HashRecords'][:l['NumKeys']*8]
424                    data = data[4:]
425                data = records
426
427            for i in range(parentKey['NumSubKeys']):
428                hashRec = REG_HASH(data[:8])
429                nk = self.__getBlock(hashRec['OffsetNk'])
430                data = data[8:]
431                res.append('%s'%nk['KeyName'])
432        return res
433
434    def enumValues(self,key):
435        # If we're here.. we have a valid NK record for the key
436        # Now let's search its values
437        resp = []
438        if key['NumValues'] > 0:
439            valueList = self.__getValueBlocks(key['OffsetValueList'], key['NumValues']+1)
440
441            for value in valueList:
442                if value['Flag'] > 0:
443                    resp.append(value['Name'])
444                else:
445                    resp.append('default')
446
447        return resp
448
449    def getValue(self, keyValue):
450        # returns a tuple with (ValueType, ValueData) for the requested keyValue
451        regKey = ntpath.dirname(keyValue)
452        regValue = ntpath.basename(keyValue)
453
454        key = self.findKey(regKey)
455
456        if key is None:
457            return None
458
459        if key['NumValues'] > 0:
460            valueList = self.__getValueBlocks(key['OffsetValueList'], key['NumValues']+1)
461
462            for value in valueList:
463                if value['Name'] == regValue:
464                    return value['ValueType'], self.__getValueData(value)
465                elif regValue == 'default' and value['Flag'] <=0:
466                    return value['ValueType'], self.__getValueData(value)
467
468        return None
469
470    def getClass(self, className):
471
472        key = self.findKey(className)
473
474        if key is None:
475            return None
476
477        #print key.dump()
478        if key['OffsetClassName'] > 0:
479            value = self.__getBlock(key['OffsetClassName'])
480            return value['Data']
481
482def pretty_print(x):
483    if x in '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~ ':
484       return x
485    else:
486       return '.'
487
488def hexdump(data, indent = ''):
489    x=str(data)
490    strLen = len(x)
491    i = 0
492    while i < strLen:
493        print indent,
494        print "%04x  " % i,
495        for j in range(16):
496            if i+j < strLen:
497                print "%02X" % ord(x[i+j]),
498            else:
499                print "  ",
500            if j%16 == 7:
501                print "",
502        print " ",
503        print ''.join(pretty_print(x) for x in x[i:i+16] )
504        i += 16
505
506