Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Path: blob/master/lib/msf/util/windows_registry/registry_parser.rb
Views: 11784
module Msf1module Util2module WindowsRegistry34#5# This utility class processes binary Windows registry key. It is usually6# used when only offline processing is possible and [MS-RRP] BaseRegSaveKey()7# is used to save a registry key to a file.8#9# It also includes helpers for specific registry keys (SAM, SECURITY) through10# the `name` key word argument during instantiation.11#12class RegistryParser13# Constants14ROOT_KEY = 0x2c15REG_NONE = 0x0016REG_SZ = 0x0117REG_EXPAND_SZ = 0x0218REG_BINARY = 0x0319REG_DWORD = 0x0420REG_MULTISZ = 0x0721REG_QWORD = 0x0b222324# Magic strings2526# REGF magic value: 'regf'27REGF_MAGIC = 0x7265676628# NK magic value: 'nk'29NK_MAGIC = 0x6E6B30# VK magic value: 'vk'31VK_MAGIC = 0x766B32# LF magic value: 'lf'33LF_MAGIC = 0x6C6634# LH magic value: 'lh'35LH_MAGIC = 0x6C6836# RI magic value: 'ri'37RI_MAGIC = 0x726938# SK magic value: 'sk'39SK_MAGIC = 0x726940# HBIN magic value: 'hbin'41HBIN_MAGIC = 0x6862696E4243#44# [Windows NT Registry File (REGF) format specification](https://github.com/libyal/libregf/blob/main/documentation/Windows%20NT%20Registry%20File%20(REGF)%20format.asciidoc)45#4647# Registry File Header48class RegRegf < BinData::Record49endian :little5051bit32 :magic, initial_value: REGF_MAGIC52uint32 :sequence153uint32 :sequence254uint64 :last_change55uint32 :major_version56uint32 :minor_version57uint32 :unknown158uint32 :unknown259uint32 :offset_first_record60uint32 :data_size61uint32 :unknown362string :name, length: 4863string :remaining1, length: 41164uint32 :checksum, initial_value: 0xFFFFFFFF65string :remaining2, length: 358566end6768# Named key69class RegNk < BinData::Record70endian :little7172bit16 :magic, initial_value: NK_MAGIC73uint16 :nk_type74uint64 :last_change75uint32 :unknown76int32 :offset_parent77uint32 :num_sub_keys78uint32 :unknown279int32 :offset_sub_key_lf80uint32 :unknown381uint32 :num_values82int32 :offset_value_list83int32 :offset_sk_rRecord84int32 :offset_class_name85string :unused, length: 2086uint16 :name_length, initial_value: -> { self.key_name.length }87uint16 :class_name_length88string :key_name, read_length: -> { self.name_length }89end9091# Value key92class RegVk < BinData::Record93endian :little9495bit16 :magic, initial_value: VK_MAGIC96uint16 :name_length, initial_value: -> { self.name.length }97int32 :data_len98uint32 :offset_data99uint32 :value_type100uint16 :flag101uint16 :unused102string :name, read_length: -> { self.name_length }103end104105class RegHash < BinData::Record106endian :little107108int32 :offset_nk109string :key_name, length: 4110end111112class RegHash2 < BinData::Record113endian :little114115int32 :offset_nk116end117118# Sub keys list (LF)119class RegLf < BinData::Record120endian :little121122bit16 :magic, initial_value: LF_MAGIC123uint16 :num_keys124array :hash_records, type: :reg_hash, read_until: -> { index == (self.num_keys - 1) }125end126127# Sub keys list (LH)128class RegLh < BinData::Record129endian :little130131bit16 :magic, initial_value: LH_MAGIC132uint16 :num_keys133array :hash_records, type: :reg_hash, read_until: -> { index == (self.num_keys - 1) }134end135136# Sub keys list (RI)137class RegRi < BinData::Record138endian :little139140bit16 :magic, initial_value: RI_MAGIC141uint16 :num_keys142array :hash_records, type: :reg_hash2, read_until: -> { index == (self.num_keys - 1) }143end144145# Security key146class RegSk < BinData::Record147endian :little148149bit16 :magic, initial_value: SK_MAGIC150uint16 :unused151int32 :offset_previous_sk152int32 :offset_next_sk153uint32 :usage_counter154uint32 :size_sk, initial_length: -> { self.data.do_num_bytes }155string :data, read_length: -> { self.size_sk }156end157158# Hive bin cell159class RegHbinBlock < BinData::Record160attr_reader :record_type161162endian :little163164int32 :data_block_size#, byte_align: 4165choice :data, selection: -> { @obj.parent.record_type } do166reg_nk 'nk'167reg_vk 'vk'168reg_lf 'lf'169reg_lh 'lh'170reg_ri 'ri'171reg_sk 'sk'172string :default, read_length: -> { self.data_block_size == 0 ? 0 : self.data_block_size.abs - 4 }173end174string :unknown, length: -> { self.data_block_size.abs - self.data.do_num_bytes - 4 }175176def do_read(io)177io.with_readahead do178io.seekbytes(4)179@record_type = io.readbytes(2)180end181super(io)182end183end184185# Hive bin186class RegHbin < BinData::Record187endian :little188189bit32 :magic, initial_value: HBIN_MAGIC190uint32 :offset_first_hbin191uint32 :hbin_size192string :unknown, length: 16193uint32 :offset_next_hbin # hbin_size194array :reg_hbin_blocks, type: :reg_hbin_block, read_until: :eof195end196197198# @param hive_data [String] The binary registry data199# @param name [Symbol] The key name to add specific helpers. Only `:sam`200# @param root [String] The root key and subkey corresponding to the hive_data201# and `:security` are supported at the moment.202def initialize(hive_data, name: nil, root: nil)203@hive_data = hive_data.b204@regf = RegRegf.read(@hive_data)205@root_key_block = find_root_key206@root = root207@root << '\\' unless root.end_with?('\\')208case name209when :sam210require_relative 'sam'211extend Sam212when :security213require_relative 'security'214extend Security215else216wlog("[Msf::Util::WindowsRegistry::RegistryParser] Unknown :name argument: #{name}") unless name.blank?217end218end219220# Returns the ROOT key as a block221#222# @return [RegHbinBlock] The ROOT key block223# @raise [StandardError] If an error occurs during parsing or if the ROOT224# key is not found225def find_root_key226reg_hbin = nil227# Split the data in 4096-bytes blocks228@hive_data.unpack('a4096' * (@hive_data.size / 4096)).each do |data|229next unless data[0,4] == 'hbin'230reg_hbin = RegHbin.read(data)231root_key_block = reg_hbin.reg_hbin_blocks.find do |block|232block.data.respond_to?(:magic) && block.data.magic == NK_MAGIC && block.data.nk_type == ROOT_KEY233end234return root_key_block if root_key_block235rescue IOError236raise StandardError, 'Cannot parse the RegHbin structure'237end238raise StandardError, 'Cannot find the RootKey' unless reg_hbin239end240241# Returns the type and the data of a given key/value pair242#243# @param reg_key [String] The registry key244# @param reg_value [String] The value in the registry key245# @return [Array] The type (Integer) and data (String) of the given246# key/value as the first and second element of an array, respectively247def get_value(reg_key, reg_value = nil)248reg_key = find_key(reg_key)249return nil unless reg_key250251if reg_key.data.num_values > 0252value_list = get_value_blocks(reg_key.data.offset_value_list, reg_key.data.num_values + 1)253value_list.each do |value|254if value.data.name == reg_value.to_s ||255reg_value.nil? && value.data.flag <= 0256return value.data.value_type.to_i, get_value_data(value.data)257end258end259end260nil261end262263# Search for a given key from the ROOT key and returns it as a block264#265# @param key [String] The registry key to look for266# @return [RegHbinBlock, nil] The key, if found, nil otherwise267def find_key(key)268# Let's strip '\' from the beginning, except for the case of269# only asking for the root node270key = key[1..-1] if key[0] == '\\' && key.size > 1271272parent_key = @root_key_block273if key.size > 0 && key[0] != '\\'274key.split('\\').each do |sub_key|275res = find_sub_key(parent_key, sub_key)276return nil unless res277parent_key = res278end279end280parent_key281end282283# Search for a sub key from a given base key284#285# @param parent_key [String] The base key286# @param sub_key [String] The sub key to look for under parent_key287# @return [RegHbinBlock, nil] The key, if found, nil otherwise288# @raise [ArgumentError] If the parent key is not a NK record289def find_sub_key(parent_key, sub_key)290unless parent_key&.data&.magic == NK_MAGIC291raise ArgumentError, "find_sub_key: parent key must be a NK record"292end293block = get_block(parent_key.data.offset_sub_key_lf)294blocks = []295if block.data.magic == RI_MAGIC296# ri points to lf/lh records, so we consolidate them in the main blocks array297block.data.hash_records.each do |hash_record|298blocks << get_block(hash_record.offset_nk)299end300else301blocks << block302end303304# Let's search the hash records for the name305blocks.each do |block|306block.data.hash_records.each do |hash_record|307res = get_offset(block.data.magic, hash_record, sub_key)308if res309nk = get_block(res)310return nk if nk.data.key_name == sub_key311end312end313end314315nil316end317318# Returns a registry block given its offset319#320# @param offset [String] The offset of the block321# @return [RegHbinBlock] The registry block322def get_block(offset)323RegHbinBlock.read(@hive_data[4096+offset..-1])324end325326# Returns the offset of a given subkey in a hash record327#328# @param magic [Integer] The signtaure (MAGIC)329# @param hash_rec [Integer] The hash record330# @param key [Integer] The subkey to look for331# @return [Integer] The offset of the subkey332def get_offset(magic, hash_rec, key)333case magic334when LF_MAGIC335if hash_rec.key_name.gsub(/(^\x00*)|(\x00*$)/, '') == key[0,4]336return hash_rec.offset_nk337end338when LH_MAGIC339if hash_rec.key_name.unpack('L<').first == get_lh_hash(key)340return hash_rec.offset_nk341end342when RI_MAGIC343# Special case here, don't know exactly why, an RI pointing to a NK344offset = hash_rec.offset_nk345nk = get_block(offset)346return offset if nk.key_name == key347else348raise ArgumentError, "Unknown magic: #{magic}"349end350end351352# Returns the hash of a LH subkey353# from http://www.sentinelchicken.com/data/TheWindowsNTRegistryFileFormat.pdf (Appendix C)354#355# @param key [Integer] The LH subkey356# @return [Integer] The hash357def get_lh_hash(key)358res = 0359key.upcase.bytes do |byte|360res *= 37361res += byte.ord362end363return res % 0x100000000364end365366# Returns a list of `count``value blocks from the offsets located at `offset`367#368# @param offset [Integer] The offset where the offsets of each value is located369# @param count [Integer] The number of value blocks to retrieve370# @return [Array] An array of registry blocks371def get_value_blocks(offset, count)372value_list = []373res = []374count.times do |i|375value_list << @hive_data[4096+offset+i*4, 4].unpack('l<').first376end377value_list.each do |value_offset|378if value_offset > 0379block = get_block(value_offset)380res << block381end382end383return res384end385386# Returns the data of a VK record value387#388# @param record [String] The VK record389# @return [String] The data390# @raise [ArgumentError] If the parent key is not a VK record391def get_value_data(record)392unless record&.magic == VK_MAGIC393raise ArgumentError, "get_value_data: record must be a VK record"394end395return '' if record.data_len == 0396# if DataLen < 5 the value itself is stored in the Offset field397return record.offset_data.to_binary_s if record.data_len < 0398return self.get_data(record.offset_data, record.data_len + 4)399end400401# Returns the data at a given offset from the end of the header in the raw402# hive binary.403#404# @param offset [String] The offset from the end of the header405# @param count [Integer] The size of the data. Since the 4 first bytes are406# ignored, the data returned will be (count - 4) long.407# @return [String] The resulting data408def get_data(offset, count)409@hive_data[4096+offset, count][4..-1]410end411412# Enumerate the subkey names under `key`413#414# @param key [String] The parent key from which to enumerate415# @return [Array] The key names416# @raise [ArgumentError] If the parent key is not a NK record417def enum_key(key)418parent_key = find_key(key)419return nil unless parent_key420421unless parent_key.data&.magic == NK_MAGIC422raise ArgumentError, "enum_key: parent key must be a NK record"423end424block = get_block(parent_key.data.offset_sub_key_lf)425records = []426if block.data.magic == RI_MAGIC427# ri points to lf/lh records, so we consolidate the hash records in the main records array428block.data.hash_records.each do |hash_record|429record = get_block(hash_record.offset_nk)430records.concat(record.data.hash_records)431end432else433records.concat(block.data.hash_records)434end435436records.map do |reg_hash|437nk = get_block(reg_hash.offset_nk)438nk.data.key_name.to_s.b439end440end441442# Enumerate the subkey values under `key`443#444# @param key [String] The parent key from which to enumerate445# @return [Array] The key values446# @raise [ArgumentError] If the parent key is not a NK record447def enum_values(key)448key_obj = find_key(key)449return nil unless key_obj450451unless key_obj&.data&.magic == NK_MAGIC452raise ArgumentError, "enum_values: key must be a NK record"453end454res = []455value_list = get_value_blocks(key_obj.data.offset_value_list, key_obj.data.num_values + 1)456value_list.each do |value|457# TODO: use #to_s to make sure value.data.name is a String458res << (value.data.flag > 0 ? value.data.name : nil)459end460res461end462end463464end465end466end467468469470