Class: Msf::Util::WindowsRegistry::RegistryParser

Inherits:
Object
  • Object
show all
Defined in:
lib/msf/util/windows_registry/registry_parser.rb

Overview

This utility class processes binary Windows registry key. It is usually used when only offline processing is possible and [MS-RRP] BaseRegSaveKey() is used to save a registry key to a file.

It also includes helpers for specific registry keys (SAM, SECURITY) through the `name` key word argument during instantiation.

Defined Under Namespace

Classes: RegHash, RegHash2, RegHbin, RegHbinBlock, RegLf, RegLh, RegNk, RegRegf, RegRi, RegSk, RegVk

Constant Summary collapse

ROOT_KEY =

Constants

0x2c
REG_NONE =
0x00
REG_SZ =
0x01
REG_EXPAND_SZ =
0x02
REG_BINARY =
0x03
REG_DWORD =
0x04
REG_MULTISZ =
0x07
REG_QWORD =
0x0b
REGF_MAGIC =

REGF magic value: 'regf'

0x72656766
NK_MAGIC =

NK magic value: 'nk'

0x6E6B
VK_MAGIC =

VK magic value: 'vk'

0x766B
LF_MAGIC =

LF magic value: 'lf'

0x6C66
LH_MAGIC =

LH magic value: 'lh'

0x6C68
RI_MAGIC =

RI magic value: 'ri'

0x7269
SK_MAGIC =

SK magic value: 'sk'

0x7269
HBIN_MAGIC =

HBIN magic value: 'hbin'

0x6862696E

Instance Method Summary collapse

Constructor Details

#initialize(hive_data, name: nil) ⇒ RegistryParser

Returns a new instance of RegistryParser.

Parameters:

  • hive_data (String)

    The binary registry data

  • name (Symbol) (defaults to: nil)

    The key name to add specific helpers. Only `:sam` and `:security` are supported at the moment.



202
203
204
205
206
207
208
209
210
211
212
213
214
# File 'lib/msf/util/windows_registry/registry_parser.rb', line 202

def initialize(hive_data, name: nil)
  @hive_data = hive_data.b
  @regf = RegRegf.read(@hive_data)
  @root_key = find_root_key
  case name
  when :sam
    require_relative 'sam'
    extend Sam
  when :security
    require_relative 'security'
    extend Security
  end
end

Instance Method Details

#enum_key(key) ⇒ Array

Enumerate the subkey names under `key`

Parameters:

  • key (String)

    The parent key from which to enumerate

Returns:

  • (Array)

    The key names

Raises:

  • (ArgumentError)

    If the parent key is not a NK record



413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
# File 'lib/msf/util/windows_registry/registry_parser.rb', line 413

def enum_key(key)
  parent_key = find_key(key)
  return nil unless parent_key

  unless parent_key.data&.magic == NK_MAGIC
    raise ArgumentError, "enum_key: parent key must be a NK record"
  end
  block = get_block(parent_key.data.offset_sub_key_lf)
  records = []
  if block.data.magic == RI_MAGIC
    # ri points to lf/lh records, so we consolidate the hash records in the main records array
    block.data.hash_records.each do |hash_record|
      record = get_block(hash_record.offset_nk)
      records.concat(record.data.hash_records)
    end
  else
    records.concat(block.data.hash_records)
  end

  records.map do |reg_hash|
    nk = get_block(reg_hash.offset_nk)
    nk.data.key_name.to_s.b
  end
end

#enum_values(key) ⇒ Array

Enumerate the subkey values under `key`

Parameters:

  • key (String)

    The parent key from which to enumerate

Returns:

  • (Array)

    The key values

Raises:

  • (ArgumentError)

    If the parent key is not a NK record



443
444
445
446
447
448
449
450
451
452
453
454
455
456
# File 'lib/msf/util/windows_registry/registry_parser.rb', line 443

def enum_values(key)
  key_obj = find_key(key)
  return nil unless key_obj

  unless key_obj&.data&.magic == NK_MAGIC
    raise ArgumentError, "enum_values: key must be a NK record"
  end
  res = []
  value_list = get_value_blocks(key_obj.data.offset_value_list, key_obj.data.num_values + 1)
  value_list.each do |value|
    res << (value.data.flag > 0 ? value.data.name : nil)
  end
  res
end

#find_key(key) ⇒ RegHbinBlock?

Search for a given key fro the ROOT key and returns it as a block

Parameters:

  • key (String)

    The registry key to look for

Returns:



263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
# File 'lib/msf/util/windows_registry/registry_parser.rb', line 263

def find_key(key)
  # Let's strip '\' from the beginning, except for the case of
  # only asking for the root node
  key = key[1..-1] if key[0] == '\\' && key.size > 1

  parent_key = @root_key
  if key.size > 0 && key[0] != '\\'
    key.split('\\').each do |sub_key|
      res = find_sub_key(parent_key, sub_key)
      return nil unless res
      parent_key = res
    end
  end
  parent_key
end

#find_root_keyRegHbinBlock

Returns the ROOT key as a block

Returns:

Raises:

  • (StandardError)

    If an error occurs during parsing or if the ROOT key is not found



221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
# File 'lib/msf/util/windows_registry/registry_parser.rb', line 221

def find_root_key
  reg_hbin = nil
  # Split the data in 4096-bytes blocks
  @hive_data.unpack('a4096' * (@hive_data.size / 4096)).each do |data|
    next unless data[0,4] == 'hbin'
    reg_hbin = RegHbin.read(data)
    root_key = reg_hbin.reg_hbin_blocks.find do |block|
      block.data.respond_to?(:magic) && block.data.magic == NK_MAGIC && block.data.nk_type == ROOT_KEY
    end
    return root_key if root_key
  rescue IOError
    raise StandardError, 'Cannot parse the RegHbin structure'
  end
  raise StandardError, 'Cannot find the RootKey' unless reg_hbin
end

#find_sub_key(parent_key, sub_key) ⇒ RegHbinBlock?

Search for a sub key from a given base key

Parameters:

  • parent_key (String)

    The base key

  • sub_key (String)

    The sub key to look for under parent_key

Returns:

Raises:

  • (ArgumentError)

    If the parent key is not a NK record



285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
# File 'lib/msf/util/windows_registry/registry_parser.rb', line 285

def find_sub_key(parent_key, sub_key)
  unless parent_key&.data&.magic == NK_MAGIC
    raise ArgumentError, "find_sub_key: parent key must be a NK record"
  end
  block = get_block(parent_key.data.offset_sub_key_lf)
  blocks = []
  if block.data.magic == RI_MAGIC
    # ri points to lf/lh records, so we consolidate them in the main blocks array
    block.data.hash_records.each do |hash_record|
      blocks << get_block(hash_record.offset_nk)
    end
  else
    blocks << block
  end

  # Let's search the hash records for the name
  blocks.each do |block|
    block.data.hash_records.each do |hash_record|
      res = get_offset(block.data.magic, hash_record, sub_key)
      if res
        nk = get_block(res)
        return nk if nk.data.key_name == sub_key
      end
    end
  end

  nil
end

#get_block(offset) ⇒ RegHbinBlock

Returns a registry block given its offset

Parameters:

  • offset (String)

    The offset of the block

Returns:



318
319
320
# File 'lib/msf/util/windows_registry/registry_parser.rb', line 318

def get_block(offset)
  RegHbinBlock.read(@hive_data[4096+offset..-1])
end

#get_data(offset, count) ⇒ String

Returns the data at a given offset from the end of the header in the raw hive binary.

Parameters:

  • offset (String)

    The offset from the end of the header

  • count (Integer)

    The size of the data. Since the 4 first bytes are ignored, the data returned will be (count - 4) long.

Returns:

  • (String)

    The resulting data



404
405
406
# File 'lib/msf/util/windows_registry/registry_parser.rb', line 404

def get_data(offset, count)
  @hive_data[4096+offset, count][4..-1]
end

#get_lh_hash(key) ⇒ Integer

Returns the hash of a LH subkey from www.sentinelchicken.com/data/TheWindowsNTRegistryFileFormat.pdf (Appendix C)

Parameters:

  • key (Integer)

    The LH subkey

Returns:

  • (Integer)

    The hash



353
354
355
356
357
358
359
360
# File 'lib/msf/util/windows_registry/registry_parser.rb', line 353

def get_lh_hash(key)
  res = 0
  key.upcase.bytes do |byte|
    res *= 37
    res += byte.ord
  end
  return res % 0x100000000
end

#get_offset(magic, hash_rec, key) ⇒ Integer

Returns the offset of a given subkey in a hash record

Parameters:

  • magic (Integer)

    The signtaure (MAGIC)

  • hash_rec (Integer)

    The hash record

  • key (Integer)

    The subkey to look for

Returns:

  • (Integer)

    The offset of the subkey



328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
# File 'lib/msf/util/windows_registry/registry_parser.rb', line 328

def get_offset(magic, hash_rec, key)
  case magic
  when LF_MAGIC
    if hash_rec.key_name.gsub(/(^\x00*)|(\x00*$)/, '') == key[0,4]
      return hash_rec.offset_nk
    end
  when LH_MAGIC
    if hash_rec.key_name.unpack('<L').first == get_lh_hash(key)
      return hash_rec.offset_nk
    end
  when RI_MAGIC
    # Special case here, don't know exactly why, an RI pointing to a NK
    offset = hash_rec.offset_nk
    nk = get_block(offset)
    return offset if nk.key_name == key
  else
    raise ArgumentError, "Unknow magic: #{magic}"
  end
end

#get_value(reg_key, reg_value = nil) ⇒ Array

Returns the type and the data of a given key/value pair

Parameters:

  • reg_key (String)

    The registry key

  • reg_value (String) (defaults to: nil)

    The value in the registry key

Returns:

  • (Array)

    The type (Integer) and data (String) of the given key/value as the first and second element of an array, respectively



243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
# File 'lib/msf/util/windows_registry/registry_parser.rb', line 243

def get_value(reg_key, reg_value = nil)
  reg_key = find_key(reg_key)
  return nil unless reg_key

  if reg_key.data.num_values > 0
    value_list = get_value_blocks(reg_key.data.offset_value_list, reg_key.data.num_values + 1)
    value_list.each do |value|
      if value.data.name == reg_value.to_s ||
         reg_value.nil? && value.data.flag <= 0
        return value.data.value_type.to_i, get_value_data(value.data)
      end
    end
  end
  nil
end

#get_value_blocks(offset, count) ⇒ Array

Returns a list of `count“value blocks from the offsets located at `offset`

Parameters:

  • offset (Integer)

    The offset where the offsets of each value is located

  • count (Integer)

    The number of value blocks to retrieve

Returns:

  • (Array)

    An array of registry blocks



367
368
369
370
371
372
373
374
375
376
377
378
379
380
# File 'lib/msf/util/windows_registry/registry_parser.rb', line 367

def get_value_blocks(offset, count)
  value_list = []
  res = []
  count.times do |i|
    value_list << @hive_data[4096+offset+i*4, 4].unpack('<l').first
  end
  value_list.each do |value_offset|
    if value_offset > 0
      block = get_block(value_offset)
      res << block
    end
  end
  return res
end

#get_value_data(record) ⇒ String

Returns the data of a VK record value

Parameters:

  • record (String)

    The VK record

Returns:

  • (String)

    The data

Raises:

  • (ArgumentError)

    If the parent key is not a VK record



387
388
389
390
391
392
393
394
395
# File 'lib/msf/util/windows_registry/registry_parser.rb', line 387

def get_value_data(record)
  unless record&.magic == VK_MAGIC
    raise ArgumentError, "get_value_data: record must be a VK record"
  end
  return '' if record.data_len == 0
  # if DataLen < 5 the value itself is stored in the Offset field
  return record.offset_data.to_binary_s if record.data_len < 0
  return self.get_data(record.offset_data, record.data_len + 4)
end