CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
rapid7

CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!

GitHub Repository: rapid7/metasploit-framework
Path: blob/master/lib/msf/util/windows_registry/registry_parser.rb
Views: 1904
1
module Msf
2
module Util
3
module WindowsRegistry
4
5
#
6
# This utility class processes binary Windows registry key. It is usually
7
# used when only offline processing is possible and [MS-RRP] BaseRegSaveKey()
8
# is used to save a registry key to a file.
9
#
10
# It also includes helpers for specific registry keys (SAM, SECURITY) through
11
# the `name` key word argument during instantiation.
12
#
13
class RegistryParser
14
# Constants
15
ROOT_KEY = 0x2c
16
REG_NONE = 0x00
17
REG_SZ = 0x01
18
REG_EXPAND_SZ = 0x02
19
REG_BINARY = 0x03
20
REG_DWORD = 0x04
21
REG_MULTISZ = 0x07
22
REG_QWORD = 0x0b
23
24
25
# Magic strings
26
27
# REGF magic value: 'regf'
28
REGF_MAGIC = 0x72656766
29
# NK magic value: 'nk'
30
NK_MAGIC = 0x6E6B
31
# VK magic value: 'vk'
32
VK_MAGIC = 0x766B
33
# LF magic value: 'lf'
34
LF_MAGIC = 0x6C66
35
# LH magic value: 'lh'
36
LH_MAGIC = 0x6C68
37
# RI magic value: 'ri'
38
RI_MAGIC = 0x7269
39
# SK magic value: 'sk'
40
SK_MAGIC = 0x7269
41
# HBIN magic value: 'hbin'
42
HBIN_MAGIC = 0x6862696E
43
44
#
45
# [Windows NT Registry File (REGF) format specification](https://github.com/libyal/libregf/blob/main/documentation/Windows%20NT%20Registry%20File%20(REGF)%20format.asciidoc)
46
#
47
48
# Registry File Header
49
class RegRegf < BinData::Record
50
endian :little
51
52
bit32 :magic, initial_value: REGF_MAGIC
53
uint32 :sequence1
54
uint32 :sequence2
55
uint64 :last_change
56
uint32 :major_version
57
uint32 :minor_version
58
uint32 :unknown1
59
uint32 :unknown2
60
uint32 :offset_first_record
61
uint32 :data_size
62
uint32 :unknown3
63
string :name, length: 48
64
string :remaining1, length: 411
65
uint32 :checksum, initial_value: 0xFFFFFFFF
66
string :remaining2, length: 3585
67
end
68
69
# Named key
70
class RegNk < BinData::Record
71
endian :little
72
73
bit16 :magic, initial_value: NK_MAGIC
74
uint16 :nk_type
75
uint64 :last_change
76
uint32 :unknown
77
int32 :offset_parent
78
uint32 :num_sub_keys
79
uint32 :unknown2
80
int32 :offset_sub_key_lf
81
uint32 :unknown3
82
uint32 :num_values
83
int32 :offset_value_list
84
int32 :offset_sk_rRecord
85
int32 :offset_class_name
86
string :unused, length: 20
87
uint16 :name_length, initial_value: -> { self.key_name.length }
88
uint16 :class_name_length
89
string :key_name, read_length: -> { self.name_length }
90
end
91
92
# Value key
93
class RegVk < BinData::Record
94
endian :little
95
96
bit16 :magic, initial_value: VK_MAGIC
97
uint16 :name_length, initial_value: -> { self.name.length }
98
int32 :data_len
99
uint32 :offset_data
100
uint32 :value_type
101
uint16 :flag
102
uint16 :unused
103
string :name, read_length: -> { self.name_length }
104
end
105
106
class RegHash < BinData::Record
107
endian :little
108
109
int32 :offset_nk
110
string :key_name, length: 4
111
end
112
113
class RegHash2 < BinData::Record
114
endian :little
115
116
int32 :offset_nk
117
end
118
119
# Sub keys list (LF)
120
class RegLf < BinData::Record
121
endian :little
122
123
bit16 :magic, initial_value: LF_MAGIC
124
uint16 :num_keys
125
array :hash_records, type: :reg_hash, read_until: -> { index == (self.num_keys - 1) }
126
end
127
128
# Sub keys list (LH)
129
class RegLh < BinData::Record
130
endian :little
131
132
bit16 :magic, initial_value: LH_MAGIC
133
uint16 :num_keys
134
array :hash_records, type: :reg_hash, read_until: -> { index == (self.num_keys - 1) }
135
end
136
137
# Sub keys list (RI)
138
class RegRi < BinData::Record
139
endian :little
140
141
bit16 :magic, initial_value: RI_MAGIC
142
uint16 :num_keys
143
array :hash_records, type: :reg_hash2, read_until: -> { index == (self.num_keys - 1) }
144
end
145
146
# Security key
147
class RegSk < BinData::Record
148
endian :little
149
150
bit16 :magic, initial_value: SK_MAGIC
151
uint16 :unused
152
int32 :offset_previous_sk
153
int32 :offset_next_sk
154
uint32 :usage_counter
155
uint32 :size_sk, initial_length: -> { self.data.do_num_bytes }
156
string :data, read_length: -> { self.size_sk }
157
end
158
159
# Hive bin cell
160
class RegHbinBlock < BinData::Record
161
attr_reader :record_type
162
163
endian :little
164
165
int32 :data_block_size#, byte_align: 4
166
choice :data, selection: -> { @obj.parent.record_type } do
167
reg_nk 'nk'
168
reg_vk 'vk'
169
reg_lf 'lf'
170
reg_lh 'lh'
171
reg_ri 'ri'
172
reg_sk 'sk'
173
string :default, read_length: -> { self.data_block_size == 0 ? 0 : self.data_block_size.abs - 4 }
174
end
175
string :unknown, length: -> { self.data_block_size.abs - self.data.do_num_bytes - 4 }
176
177
def do_read(io)
178
io.with_readahead do
179
io.seekbytes(4)
180
@record_type = io.readbytes(2)
181
end
182
super(io)
183
end
184
end
185
186
# Hive bin
187
class RegHbin < BinData::Record
188
endian :little
189
190
bit32 :magic, initial_value: HBIN_MAGIC
191
uint32 :offset_first_hbin
192
uint32 :hbin_size
193
string :unknown, length: 16
194
uint32 :offset_next_hbin # hbin_size
195
array :reg_hbin_blocks, type: :reg_hbin_block, read_until: :eof
196
end
197
198
199
# @param hive_data [String] The binary registry data
200
# @param name [Symbol] The key name to add specific helpers. Only `:sam`
201
# @param root [String] The root key and subkey corresponding to the hive_data
202
# and `:security` are supported at the moment.
203
def initialize(hive_data, name: nil, root: nil)
204
@hive_data = hive_data.b
205
@regf = RegRegf.read(@hive_data)
206
@root_key_block = find_root_key
207
@root = root
208
@root << '\\' unless root.end_with?('\\')
209
case name
210
when :sam
211
require_relative 'sam'
212
extend Sam
213
when :security
214
require_relative 'security'
215
extend Security
216
else
217
wlog("[Msf::Util::WindowsRegistry::RegistryParser] Unknown :name argument: #{name}") unless name.blank?
218
end
219
end
220
221
# Returns the ROOT key as a block
222
#
223
# @return [RegHbinBlock] The ROOT key block
224
# @raise [StandardError] If an error occurs during parsing or if the ROOT
225
# key is not found
226
def find_root_key
227
reg_hbin = nil
228
# Split the data in 4096-bytes blocks
229
@hive_data.unpack('a4096' * (@hive_data.size / 4096)).each do |data|
230
next unless data[0,4] == 'hbin'
231
reg_hbin = RegHbin.read(data)
232
root_key_block = reg_hbin.reg_hbin_blocks.find do |block|
233
block.data.respond_to?(:magic) && block.data.magic == NK_MAGIC && block.data.nk_type == ROOT_KEY
234
end
235
return root_key_block if root_key_block
236
rescue IOError
237
raise StandardError, 'Cannot parse the RegHbin structure'
238
end
239
raise StandardError, 'Cannot find the RootKey' unless reg_hbin
240
end
241
242
# Returns the type and the data of a given key/value pair
243
#
244
# @param reg_key [String] The registry key
245
# @param reg_value [String] The value in the registry key
246
# @return [Array] The type (Integer) and data (String) of the given
247
# key/value as the first and second element of an array, respectively
248
def get_value(reg_key, reg_value = nil)
249
reg_key = find_key(reg_key)
250
return nil unless reg_key
251
252
if reg_key.data.num_values > 0
253
value_list = get_value_blocks(reg_key.data.offset_value_list, reg_key.data.num_values + 1)
254
value_list.each do |value|
255
if value.data.name == reg_value.to_s ||
256
reg_value.nil? && value.data.flag <= 0
257
return value.data.value_type.to_i, get_value_data(value.data)
258
end
259
end
260
end
261
nil
262
end
263
264
# Search for a given key from the ROOT key and returns it as a block
265
#
266
# @param key [String] The registry key to look for
267
# @return [RegHbinBlock, nil] The key, if found, nil otherwise
268
def find_key(key)
269
# Let's strip '\' from the beginning, except for the case of
270
# only asking for the root node
271
key = key[1..-1] if key[0] == '\\' && key.size > 1
272
273
parent_key = @root_key_block
274
if key.size > 0 && key[0] != '\\'
275
key.split('\\').each do |sub_key|
276
res = find_sub_key(parent_key, sub_key)
277
return nil unless res
278
parent_key = res
279
end
280
end
281
parent_key
282
end
283
284
# Search for a sub key from a given base key
285
#
286
# @param parent_key [String] The base key
287
# @param sub_key [String] The sub key to look for under parent_key
288
# @return [RegHbinBlock, nil] The key, if found, nil otherwise
289
# @raise [ArgumentError] If the parent key is not a NK record
290
def find_sub_key(parent_key, sub_key)
291
unless parent_key&.data&.magic == NK_MAGIC
292
raise ArgumentError, "find_sub_key: parent key must be a NK record"
293
end
294
block = get_block(parent_key.data.offset_sub_key_lf)
295
blocks = []
296
if block.data.magic == RI_MAGIC
297
# ri points to lf/lh records, so we consolidate them in the main blocks array
298
block.data.hash_records.each do |hash_record|
299
blocks << get_block(hash_record.offset_nk)
300
end
301
else
302
blocks << block
303
end
304
305
# Let's search the hash records for the name
306
blocks.each do |block|
307
block.data.hash_records.each do |hash_record|
308
res = get_offset(block.data.magic, hash_record, sub_key)
309
if res
310
nk = get_block(res)
311
return nk if nk.data.key_name == sub_key
312
end
313
end
314
end
315
316
nil
317
end
318
319
# Returns a registry block given its offset
320
#
321
# @param offset [String] The offset of the block
322
# @return [RegHbinBlock] The registry block
323
def get_block(offset)
324
RegHbinBlock.read(@hive_data[4096+offset..-1])
325
end
326
327
# Returns the offset of a given subkey in a hash record
328
#
329
# @param magic [Integer] The signtaure (MAGIC)
330
# @param hash_rec [Integer] The hash record
331
# @param key [Integer] The subkey to look for
332
# @return [Integer] The offset of the subkey
333
def get_offset(magic, hash_rec, key)
334
case magic
335
when LF_MAGIC
336
if hash_rec.key_name.gsub(/(^\x00*)|(\x00*$)/, '') == key[0,4]
337
return hash_rec.offset_nk
338
end
339
when LH_MAGIC
340
if hash_rec.key_name.unpack('L<').first == get_lh_hash(key)
341
return hash_rec.offset_nk
342
end
343
when RI_MAGIC
344
# Special case here, don't know exactly why, an RI pointing to a NK
345
offset = hash_rec.offset_nk
346
nk = get_block(offset)
347
return offset if nk.key_name == key
348
else
349
raise ArgumentError, "Unknown magic: #{magic}"
350
end
351
end
352
353
# Returns the hash of a LH subkey
354
# from http://www.sentinelchicken.com/data/TheWindowsNTRegistryFileFormat.pdf (Appendix C)
355
#
356
# @param key [Integer] The LH subkey
357
# @return [Integer] The hash
358
def get_lh_hash(key)
359
res = 0
360
key.upcase.bytes do |byte|
361
res *= 37
362
res += byte.ord
363
end
364
return res % 0x100000000
365
end
366
367
# Returns a list of `count``value blocks from the offsets located at `offset`
368
#
369
# @param offset [Integer] The offset where the offsets of each value is located
370
# @param count [Integer] The number of value blocks to retrieve
371
# @return [Array] An array of registry blocks
372
def get_value_blocks(offset, count)
373
value_list = []
374
res = []
375
count.times do |i|
376
value_list << @hive_data[4096+offset+i*4, 4].unpack('l<').first
377
end
378
value_list.each do |value_offset|
379
if value_offset > 0
380
block = get_block(value_offset)
381
res << block
382
end
383
end
384
return res
385
end
386
387
# Returns the data of a VK record value
388
#
389
# @param record [String] The VK record
390
# @return [String] The data
391
# @raise [ArgumentError] If the parent key is not a VK record
392
def get_value_data(record)
393
unless record&.magic == VK_MAGIC
394
raise ArgumentError, "get_value_data: record must be a VK record"
395
end
396
return '' if record.data_len == 0
397
# if DataLen < 5 the value itself is stored in the Offset field
398
return record.offset_data.to_binary_s if record.data_len < 0
399
return self.get_data(record.offset_data, record.data_len + 4)
400
end
401
402
# Returns the data at a given offset from the end of the header in the raw
403
# hive binary.
404
#
405
# @param offset [String] The offset from the end of the header
406
# @param count [Integer] The size of the data. Since the 4 first bytes are
407
# ignored, the data returned will be (count - 4) long.
408
# @return [String] The resulting data
409
def get_data(offset, count)
410
@hive_data[4096+offset, count][4..-1]
411
end
412
413
# Enumerate the subkey names under `key`
414
#
415
# @param key [String] The parent key from which to enumerate
416
# @return [Array] The key names
417
# @raise [ArgumentError] If the parent key is not a NK record
418
def enum_key(key)
419
parent_key = find_key(key)
420
return nil unless parent_key
421
422
unless parent_key.data&.magic == NK_MAGIC
423
raise ArgumentError, "enum_key: parent key must be a NK record"
424
end
425
block = get_block(parent_key.data.offset_sub_key_lf)
426
records = []
427
if block.data.magic == RI_MAGIC
428
# ri points to lf/lh records, so we consolidate the hash records in the main records array
429
block.data.hash_records.each do |hash_record|
430
record = get_block(hash_record.offset_nk)
431
records.concat(record.data.hash_records)
432
end
433
else
434
records.concat(block.data.hash_records)
435
end
436
437
records.map do |reg_hash|
438
nk = get_block(reg_hash.offset_nk)
439
nk.data.key_name.to_s.b
440
end
441
end
442
443
# Enumerate the subkey values under `key`
444
#
445
# @param key [String] The parent key from which to enumerate
446
# @return [Array] The key values
447
# @raise [ArgumentError] If the parent key is not a NK record
448
def enum_values(key)
449
key_obj = find_key(key)
450
return nil unless key_obj
451
452
unless key_obj&.data&.magic == NK_MAGIC
453
raise ArgumentError, "enum_values: key must be a NK record"
454
end
455
res = []
456
value_list = get_value_blocks(key_obj.data.offset_value_list, key_obj.data.num_values + 1)
457
value_list.each do |value|
458
# TODO: use #to_s to make sure value.data.name is a String
459
res << (value.data.flag > 0 ? value.data.name : nil)
460
end
461
res
462
end
463
end
464
465
end
466
end
467
end
468
469
470