Coverage for /root/GitHubProjects/impacket/impacket/winregistry.py : 57%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1# SECUREAUTH LABS. Copyright 2018 SecureAuth Corporation. All rights reserved.
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 https://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
19from __future__ import division
20from __future__ import print_function
21import sys
22from struct import unpack
23import ntpath
24from six import b
26from impacket import LOG
27from impacket.structure import Structure, hexdump
30# Constants
32ROOT_KEY = 0x2c
33REG_NONE = 0x00
34REG_SZ = 0x01
35REG_EXPAND_SZ = 0x02
36REG_BINARY = 0x03
37REG_DWORD = 0x04
38REG_MULTISZ = 0x07
39REG_QWORD = 0x0b
41# Structs
42class REG_REGF(Structure):
43 structure = (
44 ('Magic','"regf'),
45 ('Unknown','<L=0'),
46 ('Unknown2','<L=0'),
47 ('lastChange','<Q=0'),
48 ('MajorVersion','<L=0'),
49 ('MinorVersion','<L=0'),
50 ('0','<L=0'),
51 ('11','<L=0'),
52 ('OffsetFirstRecord','<L=0'),
53 ('DataSize','<L=0'),
54 ('1111','<L=0'),
55 ('Name','48s=""'),
56 ('Remaining1','411s=b""'),
57 ('CheckSum','<L=0xffffffff'), # Sum of all DWORDs from 0x0 to 0x1FB
58 ('Remaining2','3585s=b""'),
59 )
61class REG_HBIN(Structure):
62 structure = (
63 ('Magic','"hbin'),
64 ('OffsetFirstHBin','<L=0'),
65 ('OffsetNextHBin','<L=0'),
66 ('BlockSize','<L=0'),
67 )
69class REG_HBINBLOCK(Structure):
70 structure = (
71 ('DataBlockSize','<l=0'),
72 ('_Data','_-Data','self["DataBlockSize"]*(-1)-4'),
73 ('Data',':'),
74 )
76class REG_NK(Structure):
77 structure = (
78 ('Magic','"nk'),
79 ('Type','<H=0'),
80 ('lastChange','<Q=0'),
81 ('Unknown','<L=0'),
82 ('OffsetParent','<l=0'),
83 ('NumSubKeys','<L=0'),
84 ('Unknown2','<L=0'),
85 ('OffsetSubKeyLf','<l=0'),
86 ('Unknown3','<L=0'),
87 ('NumValues','<L=0'),
88 ('OffsetValueList','<l=0'),
89 ('OffsetSkRecord','<l=0'),
90 ('OffsetClassName','<l=0'),
91 ('UnUsed','20s=b""'),
92 ('NameLength','<H=0'),
93 ('ClassNameLength','<H=0'),
94 ('_KeyName','_-KeyName','self["NameLength"]'),
95 ('KeyName',':'),
96 )
98class REG_VK(Structure):
99 structure = (
100 ('Magic','"vk'),
101 ('NameLength','<H=0'),
102 ('DataLen','<l=0'),
103 ('OffsetData','<L=0'),
104 ('ValueType','<L=0'),
105 ('Flag','<H=0'),
106 ('UnUsed','<H=0'),
107 ('_Name','_-Name','self["NameLength"]'),
108 ('Name',':'),
109 )
111class REG_LF(Structure):
112 structure = (
113 ('Magic','"lf'),
114 ('NumKeys','<H=0'),
115 ('HashRecords',':'),
116 )
118class REG_LH(Structure):
119 structure = (
120 ('Magic','"lh'),
121 ('NumKeys','<H=0'),
122 ('HashRecords',':'),
123 )
125class REG_RI(Structure):
126 structure = (
127 ('Magic','"ri'),
128 ('NumKeys','<H=0'),
129 ('HashRecords',':'),
130 )
132class REG_SK(Structure):
133 structure = (
134 ('Magic','"sk'),
135 ('UnUsed','<H=0'),
136 ('OffsetPreviousSk','<l=0'),
137 ('OffsetNextSk','<l=0'),
138 ('UsageCounter','<L=0'),
139 ('SizeSk','<L=0'),
140 ('Data',':'),
141 )
143class REG_HASH(Structure):
144 structure = (
145 ('OffsetNk','<L=0'),
146 ('KeyName','4s=b""'),
147 )
149StructMappings = {b'nk': REG_NK,
150 b'vk': REG_VK,
151 b'lf': REG_LF,
152 b'lh': REG_LH,
153 b'ri': REG_RI,
154 b'sk': REG_SK,
155 }
157class Registry:
158 def __init__(self, hive, isRemote = False):
159 self.__hive = hive
160 if isRemote is True: 160 ↛ 164line 160 didn't jump to line 164, because the condition on line 160 was never false
161 self.fd = self.__hive
162 self.__hive.open()
163 else:
164 self.fd = open(hive,'rb')
165 data = self.fd.read(4096)
166 self.__regf = REG_REGF(data)
167 self.indent = ''
168 self.rootKey = self.__findRootKey()
169 if self.rootKey is None: 169 ↛ 170line 169 didn't jump to line 170, because the condition on line 169 was never true
170 LOG.error("Can't find root key!")
171 elif self.__regf['MajorVersion'] != 1 and self.__regf['MinorVersion'] > 5: 171 ↛ 172line 171 didn't jump to line 172, because the condition on line 171 was never true
172 LOG.warning("Unsupported version (%d.%d) - things might not work!" % (self.__regf['MajorVersion'], self.__regf['MinorVersion']))
174 def close(self):
175 self.fd.close()
177 def __del__(self):
178 self.close()
180 def __findRootKey(self):
181 self.fd.seek(0,0)
182 data = self.fd.read(4096)
183 while len(data) > 0: 183 ↛ 198line 183 didn't jump to line 198, because the condition on line 183 was never false
184 try:
185 hbin = REG_HBIN(data[:0x20])
186 # Read the remaining bytes for this hbin
187 data += self.fd.read(hbin['OffsetNextHBin']-4096)
188 data = data[0x20:]
189 blocks = self.__processDataBlocks(data)
190 for block in blocks: 190 ↛ 196line 190 didn't jump to line 196, because the loop on line 190 didn't complete
191 if isinstance(block, REG_NK): 191 ↛ 190line 191 didn't jump to line 190, because the condition on line 191 was never false
192 if block['Type'] == ROOT_KEY: 192 ↛ 190line 192 didn't jump to line 190, because the condition on line 192 was never false
193 return block
194 except Exception as e:
195 pass
196 data = self.fd.read(4096)
198 return None
201 def __getBlock(self, offset):
202 self.fd.seek(4096+offset,0)
203 sizeBytes = self.fd.read(4)
204 data = sizeBytes + self.fd.read(unpack('<l',sizeBytes)[0]*-1-4)
205 if len(data) == 0: 205 ↛ 206line 205 didn't jump to line 206, because the condition on line 205 was never true
206 return None
207 else:
208 block = REG_HBINBLOCK(data)
209 if block['Data'][:2] in StructMappings: 209 ↛ 212line 209 didn't jump to line 212, because the condition on line 209 was never false
210 return StructMappings[block['Data'][:2]](block['Data'])
211 else:
212 LOG.debug("Unknown type 0x%s" % block['Data'][:2])
213 return block
214 return None
216 def __getValueBlocks(self, offset, count):
217 valueList = []
218 res = []
219 self.fd.seek(4096+offset,0)
220 for i in range(count):
221 valueList.append(unpack('<l',self.fd.read(4))[0])
223 for valueOffset in valueList:
224 if valueOffset > 0:
225 block = self.__getBlock(valueOffset)
226 res.append(block)
227 return res
229 def __getData(self, offset, count):
230 self.fd.seek(4096+offset, 0)
231 return self.fd.read(count)[4:]
233 def __processDataBlocks(self,data):
234 res = []
235 while len(data) > 0:
236 #blockSize = unpack('<l',data[:calcsize('l')])[0]
237 blockSize = unpack('<l',data[:4])[0]
238 block = REG_HBINBLOCK()
239 if blockSize > 0:
240 tmpList = list(block.structure)
241 tmpList[1] = ('_Data','_-Data','self["DataBlockSize"]-4')
242 block.structure = tuple(tmpList)
244 block.fromString(data)
245 blockLen = len(block)
247 if block['Data'][:2] in StructMappings:
248 block = StructMappings[block['Data'][:2]](block['Data'])
250 res.append(block)
251 data = data[blockLen:]
252 return res
254 def __getValueData(self, rec):
255 # We should receive a VK record
256 if rec['DataLen'] == 0: 256 ↛ 257line 256 didn't jump to line 257, because the condition on line 256 was never true
257 return ''
258 if rec['DataLen'] < 0:
259 # if DataLen < 5 the value itself is stored in the Offset field
260 return rec['OffsetData']
261 else:
262 return self.__getData(rec['OffsetData'], rec['DataLen']+4)
264 def __getLhHash(self, key):
265 res = 0
266 for bb in key.upper():
267 res *= 37
268 res += ord(bb)
269 return res % 0x100000000
271 def __compareHash(self, magic, hashData, key):
272 if magic == 'lf': 272 ↛ 276line 272 didn't jump to line 276, because the condition on line 272 was never false
273 hashRec = REG_HASH(hashData)
274 if hashRec['KeyName'].strip(b'\x00') == b(key[:4]):
275 return hashRec['OffsetNk']
276 elif magic == 'lh':
277 hashRec = REG_HASH(hashData)
278 if unpack('<L',hashRec['KeyName'])[0] == self.__getLhHash(key):
279 return hashRec['OffsetNk']
280 elif magic == 'ri':
281 # Special case here, don't know exactly why, an ri pointing to a NK :-o
282 offset = unpack('<L', hashData[:4])[0]
283 nk = self.__getBlock(offset)
284 if nk['KeyName'] == key:
285 return offset
286 else:
287 LOG.critical("UNKNOWN Magic %s" % magic)
288 sys.exit(1)
290 return None
292 def __findSubKey(self, parentKey, subKey):
293 lf = self.__getBlock(parentKey['OffsetSubKeyLf'])
294 if lf is not None: 294 ↛ 318line 294 didn't jump to line 318, because the condition on line 294 was never false
295 data = lf['HashRecords']
296 # Let's search the hash records for the name
297 if lf['Magic'] == 'ri': 297 ↛ 299line 297 didn't jump to line 299, because the condition on line 297 was never true
298 # ri points to lf/lh records, so we must parse them before
299 records = b''
300 for i in range(lf['NumKeys']):
301 offset = unpack('<L', data[:4])[0]
302 l = self.__getBlock(offset)
303 records = records + l['HashRecords'][:l['NumKeys']*8]
304 data = data[4:]
305 data = records
307 #for record in range(lf['NumKeys']):
308 for record in range(parentKey['NumSubKeys']): 308 ↛ 318line 308 didn't jump to line 318, because the loop on line 308 didn't complete
309 hashRec = data[:8]
310 res = self.__compareHash(lf['Magic'], hashRec, subKey)
311 if res is not None:
312 # We have a match, now let's check the whole record
313 nk = self.__getBlock(res)
314 if nk['KeyName'].decode('utf-8') == subKey:
315 return nk
316 data = data[8:]
318 return None
320 def __walkSubNodes(self, rec):
321 nk = self.__getBlock(rec['OffsetNk'])
322 if isinstance(nk, REG_NK):
323 print("%s%s" % (self.indent, nk['KeyName'].decode('utf-8')))
324 self.indent += ' '
325 if nk['OffsetSubKeyLf'] < 0:
326 self.indent = self.indent[:-2]
327 return
328 lf = self.__getBlock(nk['OffsetSubKeyLf'])
329 else:
330 lf = nk
332 data = lf['HashRecords']
334 if lf['Magic'] == 'ri':
335 # ri points to lf/lh records, so we must parse them before
336 records = ''
337 for i in range(lf['NumKeys']):
338 offset = unpack('<L', data[:4])[0]
339 l = self.__getBlock(offset)
340 records = records + l['HashRecords'][:l['NumKeys']*8]
341 data = data[4:]
342 data = records
344 for key in range(lf['NumKeys']):
345 hashRec = REG_HASH(data[:8])
346 self.__walkSubNodes(hashRec)
347 data = data[8:]
349 if isinstance(nk, REG_NK):
350 self.indent = self.indent[:-2]
352 def walk(self, parentKey):
353 key = self.findKey(parentKey)
355 if key is None or key['OffsetSubKeyLf'] < 0:
356 return
358 lf = self.__getBlock(key['OffsetSubKeyLf'])
359 data = lf['HashRecords']
360 for record in range(lf['NumKeys']):
361 hashRec = REG_HASH(data[:8])
362 self.__walkSubNodes(hashRec)
363 data = data[8:]
365 def findKey(self, key):
366 # Let's strip '\' from the beginning, except for the case of
367 # only asking for the root node
368 if key[0] == '\\' and len(key) > 1:
369 key = key[1:]
371 parentKey = self.rootKey
372 if len(key) > 0 and key[0]!='\\': 372 ↛ 381line 372 didn't jump to line 381, because the condition on line 372 was never false
373 for subKey in key.split('\\'):
374 res = self.__findSubKey(parentKey, subKey)
375 if res is not None: 375 ↛ 379line 375 didn't jump to line 379, because the condition on line 375 was never false
376 parentKey = res
377 else:
378 #LOG.error("Key %s not found!" % key)
379 return None
381 return parentKey
383 def printValue(self, valueType, valueData):
384 if valueType == REG_SZ or valueType == REG_EXPAND_SZ:
385 if type(valueData) is int:
386 print('NULL')
387 else:
388 print("%s" % (valueData.decode('utf-16le')))
389 elif valueType == REG_BINARY:
390 print('')
391 hexdump(valueData, self.indent)
392 elif valueType == REG_DWORD:
393 print("%d" % valueData)
394 elif valueType == REG_QWORD:
395 print("%d" % (unpack('<Q',valueData)[0]))
396 elif valueType == REG_NONE:
397 try:
398 if len(valueData) > 1:
399 print('')
400 hexdump(valueData, self.indent)
401 else:
402 print(" NULL")
403 except:
404 print(" NULL")
405 elif valueType == REG_MULTISZ:
406 print("%s" % (valueData.decode('utf-16le')))
407 else:
408 print("Unknown Type 0x%x!" % valueType)
409 hexdump(valueData)
411 def enumKey(self, parentKey):
412 res = []
413 # If we're here.. we have a valid NK record for the key
414 # Now let's searcht the subkeys
415 if parentKey['NumSubKeys'] > 0: 415 ↛ 434line 415 didn't jump to line 434, because the condition on line 415 was never false
416 lf = self.__getBlock(parentKey['OffsetSubKeyLf'])
417 data = lf['HashRecords']
419 if lf['Magic'] == 'ri': 419 ↛ 421line 419 didn't jump to line 421, because the condition on line 419 was never true
420 # ri points to lf/lh records, so we must parse them before
421 records = ''
422 for i in range(lf['NumKeys']):
423 offset = unpack('<L', data[:4])[0]
424 l = self.__getBlock(offset)
425 records = records + l['HashRecords'][:l['NumKeys']*8]
426 data = data[4:]
427 data = records
429 for i in range(parentKey['NumSubKeys']):
430 hashRec = REG_HASH(data[:8])
431 nk = self.__getBlock(hashRec['OffsetNk'])
432 data = data[8:]
433 res.append('%s'%nk['KeyName'].decode('utf-8'))
434 return res
436 def enumValues(self,key):
437 # If we're here.. we have a valid NK record for the key
438 # Now let's search its values
439 resp = []
440 if key['NumValues'] > 0: 440 ↛ 449line 440 didn't jump to line 449, because the condition on line 440 was never false
441 valueList = self.__getValueBlocks(key['OffsetValueList'], key['NumValues']+1)
443 for value in valueList:
444 if value['Flag'] > 0: 444 ↛ 447line 444 didn't jump to line 447, because the condition on line 444 was never false
445 resp.append(value['Name'])
446 else:
447 resp.append(b'default')
449 return resp
451 def getValue(self, keyValue):
452 # returns a tuple with (ValueType, ValueData) for the requested keyValue
453 regKey = ntpath.dirname(keyValue)
454 regValue = ntpath.basename(keyValue)
456 key = self.findKey(regKey)
458 if key is None: 458 ↛ 459line 458 didn't jump to line 459, because the condition on line 458 was never true
459 return None
461 if key['NumValues'] > 0: 461 ↛ 470line 461 didn't jump to line 470, because the condition on line 461 was never false
462 valueList = self.__getValueBlocks(key['OffsetValueList'], key['NumValues']+1)
464 for value in valueList: 464 ↛ 470line 464 didn't jump to line 470, because the loop on line 464 didn't complete
465 if value['Name'] == b(regValue):
466 return value['ValueType'], self.__getValueData(value)
467 elif regValue == 'default' and value['Flag'] <=0:
468 return value['ValueType'], self.__getValueData(value)
470 return None
472 def getClass(self, className):
474 key = self.findKey(className)
476 if key is None:
477 return None
479 #print key.dump()
480 if key['OffsetClassName'] > 0:
481 value = self.__getBlock(key['OffsetClassName'])
482 return value['Data']