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