Hide keyboard shortcuts

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 

18 

19from __future__ import division 

20from __future__ import print_function 

21import sys 

22from struct import unpack 

23import ntpath 

24from six import b 

25 

26from impacket import LOG 

27from impacket.structure import Structure, hexdump 

28 

29 

30# Constants 

31 

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 

40 

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 ) 

60 

61class REG_HBIN(Structure): 

62 structure = ( 

63 ('Magic','"hbin'), 

64 ('OffsetFirstHBin','<L=0'), 

65 ('OffsetNextHBin','<L=0'), 

66 ('BlockSize','<L=0'), 

67 ) 

68 

69class REG_HBINBLOCK(Structure): 

70 structure = ( 

71 ('DataBlockSize','<l=0'), 

72 ('_Data','_-Data','self["DataBlockSize"]*(-1)-4'), 

73 ('Data',':'), 

74 ) 

75 

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 ) 

97 

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 ) 

110 

111class REG_LF(Structure): 

112 structure = ( 

113 ('Magic','"lf'), 

114 ('NumKeys','<H=0'), 

115 ('HashRecords',':'), 

116 ) 

117 

118class REG_LH(Structure): 

119 structure = ( 

120 ('Magic','"lh'), 

121 ('NumKeys','<H=0'), 

122 ('HashRecords',':'), 

123 ) 

124 

125class REG_RI(Structure): 

126 structure = ( 

127 ('Magic','"ri'), 

128 ('NumKeys','<H=0'), 

129 ('HashRecords',':'), 

130 ) 

131 

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 ) 

142 

143class REG_HASH(Structure): 

144 structure = ( 

145 ('OffsetNk','<L=0'), 

146 ('KeyName','4s=b""'), 

147 ) 

148 

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 } 

156 

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'])) 

173 

174 def close(self): 

175 self.fd.close() 

176 

177 def __del__(self): 

178 self.close() 

179 

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) 

197 

198 return None 

199 

200 

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 

215 

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]) 

222 

223 for valueOffset in valueList: 

224 if valueOffset > 0: 

225 block = self.__getBlock(valueOffset) 

226 res.append(block) 

227 return res 

228 

229 def __getData(self, offset, count): 

230 self.fd.seek(4096+offset, 0) 

231 return self.fd.read(count)[4:] 

232 

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) 

243 

244 block.fromString(data) 

245 blockLen = len(block) 

246 

247 if block['Data'][:2] in StructMappings: 

248 block = StructMappings[block['Data'][:2]](block['Data']) 

249 

250 res.append(block) 

251 data = data[blockLen:] 

252 return res 

253 

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) 

263 

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 

270 

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) 

289 

290 return None 

291 

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 

306 

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:] 

317 

318 return None 

319 

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 

331 

332 data = lf['HashRecords'] 

333 

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 

343 

344 for key in range(lf['NumKeys']): 

345 hashRec = REG_HASH(data[:8]) 

346 self.__walkSubNodes(hashRec) 

347 data = data[8:] 

348 

349 if isinstance(nk, REG_NK): 

350 self.indent = self.indent[:-2] 

351 

352 def walk(self, parentKey): 

353 key = self.findKey(parentKey) 

354 

355 if key is None or key['OffsetSubKeyLf'] < 0: 

356 return 

357 

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:] 

364 

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:] 

370 

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 

380 

381 return parentKey 

382 

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) 

410 

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'] 

418 

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 

428 

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 

435 

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) 

442 

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') 

448 

449 return resp 

450 

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) 

455 

456 key = self.findKey(regKey) 

457 

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 

460 

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) 

463 

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) 

469 

470 return None 

471 

472 def getClass(self, className): 

473 

474 key = self.findKey(className) 

475 

476 if key is None: 

477 return None 

478 

479 #print key.dump() 

480 if key['OffsetClassName'] > 0: 

481 value = self.__getBlock(key['OffsetClassName']) 

482 return value['Data']