registry: Implement case-preserving key names and enumeration syscalls (#213)

This PR aims to improve registry handling by keeping the key names
casing in the map intact so they can be queried and the caller gets them
in their proper casing. It also implements the `NtEnumerateKey` and
`NtEnumerateValueKey` syscalls.
This commit is contained in:
Maurice Heumann
2025-04-22 06:44:58 +02:00
committed by GitHub
8 changed files with 359 additions and 13 deletions

View File

@@ -34,6 +34,24 @@ struct KEY_NAME_INFORMATION
char16_t Name[1];
};
typedef struct _KEY_BASIC_INFORMATION
{
LARGE_INTEGER LastWriteTime;
ULONG TitleIndex;
ULONG NameLength;
char16_t Name[1];
} KEY_BASIC_INFORMATION, *PKEY_BASIC_INFORMATION;
typedef struct _KEY_NODE_INFORMATION
{
LARGE_INTEGER LastWriteTime;
ULONG TitleIndex;
ULONG ClassOffset;
ULONG ClassLength;
ULONG NameLength;
char16_t Name[1];
} KEY_NODE_INFORMATION, *PKEY_NODE_INFORMATION;
typedef struct _KEY_FULL_INFORMATION
{
LARGE_INTEGER LastWriteTime;

View File

@@ -4,6 +4,7 @@
#include <string_view>
#include <unordered_set>
#include <unordered_map>
#include "string.hpp"
namespace utils
{
@@ -18,8 +19,40 @@ namespace utils
}
};
struct insensitive_string_hash
{
using is_transparent = void;
size_t operator()(const std::string_view str) const
{
size_t hash = 0;
constexpr std::hash<int> hasher{};
for (const char c : str)
{
hash ^= hasher(string::char_to_lower(c)) + 0x9e3779b9 + (hash << 6) + (hash >> 2);
}
return hash;
}
};
struct insensitive_string_equal
{
using is_transparent = void;
bool operator()(const std::string_view lhs, const std::string_view rhs) const
{
return std::ranges::equal(lhs, rhs, [](const char c1, const char c2) {
return string::char_to_lower(c1) == string::char_to_lower(c2);
});
}
};
template <typename T>
using unordered_string_map = std::unordered_map<std::string, T, string_hash, std::equal_to<>>;
template <typename T>
using unordered_insensitive_string_map =
std::unordered_map<std::string, T, insensitive_string_hash, insensitive_string_equal>;
using unordered_string_set = std::unordered_set<std::string, string_hash, std::equal_to<>>;
}

View File

@@ -159,6 +159,18 @@ const hive_value* hive_key::get_value(std::ifstream& file, const std::string_vie
return &value;
}
const hive_value* hive_key::get_value(std::ifstream& file, size_t index)
{
this->parse(file);
if (index >= values_by_index_.size())
{
return nullptr;
}
return get_value(file, values_by_index_[index]);
}
void hive_key::parse(std::ifstream& file)
{
if (this->parsed_)
@@ -189,8 +201,11 @@ void hive_key::parse(std::ifstream& file)
raw_value.data_offset = offset + static_cast<int>(offsetof(value_block_t, offset));
}
utils::string::to_lower_inplace(value_name);
this->values_[std::move(value_name)] = std::move(raw_value);
const auto [it, inserted] = this->values_.emplace(std::move(value_name), std::move(raw_value));
if (inserted)
{
this->values_by_index_.emplace_back(it->first);
}
}
// Subkeys
@@ -212,9 +227,13 @@ void hive_key::parse(std::ifstream& file)
const auto subkey = read_file_object<key_block_t>(file, subkey_block_offset);
std::string subkey_name(subkey.name, std::min(subkey.len, static_cast<int16_t>(sizeof(subkey.name))));
utils::string::to_lower_inplace(subkey_name);
this->sub_keys_.emplace(std::move(subkey_name), hive_key{subkey.subkeys, subkey.value_count, subkey.offsets});
const auto [it, inserted] = this->sub_keys_.emplace(
std::move(subkey_name), hive_key{subkey.subkeys, subkey.value_count, subkey.offsets});
if (inserted)
{
this->sub_keys_by_index_.emplace_back(it->first);
}
}
}

View File

@@ -23,12 +23,24 @@ class hive_key
{
}
utils::unordered_string_map<hive_key>& get_sub_keys(std::ifstream& file)
utils::unordered_insensitive_string_map<hive_key>& get_sub_keys(std::ifstream& file)
{
this->parse(file);
return this->sub_keys_;
}
const std::string_view* get_sub_key_name(std::ifstream& file, size_t index)
{
this->parse(file);
if (index >= sub_keys_by_index_.size())
{
return nullptr;
}
return &sub_keys_by_index_[index];
}
hive_key* get_sub_key(std::ifstream& file, const std::string_view name)
{
auto& sub_keys = this->get_sub_keys(file);
@@ -42,7 +54,13 @@ class hive_key
return &entry->second;
}
hive_key* get_sub_key(std::ifstream& file, size_t index)
{
return get_sub_key(file, *this->get_sub_key_name(file, index));
}
const hive_value* get_value(std::ifstream& file, std::string_view name);
const hive_value* get_value(std::ifstream& file, size_t index);
private:
struct raw_hive_value : hive_value
@@ -53,8 +71,10 @@ class hive_key
};
bool parsed_{false};
utils::unordered_string_map<hive_key> sub_keys_{};
utils::unordered_string_map<raw_hive_value> values_{};
utils::unordered_insensitive_string_map<hive_key> sub_keys_{};
std::vector<std::string_view> sub_keys_by_index_{};
utils::unordered_insensitive_string_map<raw_hive_value> values_{};
std::vector<std::string_view> values_by_index_{};
const int subkey_block_offset_{};
const int value_count_{};
@@ -85,6 +105,17 @@ class hive_parser
return current_key;
}
[[nodiscard]] const std::string_view* get_sub_key_name(const std::filesystem::path& key, size_t index)
{
auto* target_key = this->get_sub_key(key);
if (!target_key)
{
return nullptr;
}
return target_key->get_sub_key_name(this->file_, index);
}
[[nodiscard]] const hive_value* get_value(const std::filesystem::path& key, const std::string_view name)
{
auto* sub_key = this->get_sub_key(key);
@@ -96,6 +127,17 @@ class hive_parser
return sub_key->get_value(this->file_, name);
}
[[nodiscard]] const hive_value* get_value(const std::filesystem::path& key, size_t index)
{
auto* sub_key = this->get_sub_key(key);
if (!sub_key)
{
return nullptr;
}
return sub_key->get_value(this->file_, index);
}
private:
std::ifstream file_{};
hive_key root_key_;

View File

@@ -114,10 +114,8 @@ std::optional<registry_key> registry_manager::get_key(const utils::path_key& key
return {std::move(reg_key)};
}
std::optional<registry_value> registry_manager::get_value(const registry_key& key, std::string name)
std::optional<registry_value> registry_manager::get_value(const registry_key& key, std::string_view name)
{
utils::string::to_lower_inplace(name);
const auto iterator = this->hives_.find(key.hive);
if (iterator == this->hives_.end())
{
@@ -138,6 +136,28 @@ std::optional<registry_value> registry_manager::get_value(const registry_key& ke
return v;
}
std::optional<registry_value> registry_manager::get_value(const registry_key& key, size_t index)
{
const auto iterator = this->hives_.find(key.hive);
if (iterator == this->hives_.end())
{
return std::nullopt;
}
const auto* entry = iterator->second->get_value(key.path.get(), index);
if (!entry)
{
return std::nullopt;
}
registry_value v{};
v.type = entry->type;
v.name = entry->name;
v.data = entry->data;
return v;
}
registry_manager::hive_map::iterator registry_manager::find_hive(const utils::path_key& key)
{
for (auto i = this->hives_.begin(); i != this->hives_.end(); ++i)
@@ -150,3 +170,20 @@ registry_manager::hive_map::iterator registry_manager::find_hive(const utils::pa
return this->hives_.end();
}
std::optional<std::string_view> registry_manager::get_sub_key_name(const registry_key& key, size_t index)
{
const auto iterator = this->hives_.find(key.hive);
if (iterator == this->hives_.end())
{
return std::nullopt;
}
const auto* name = iterator->second->get_sub_key_name(key.path.get(), index);
if (!name)
{
return std::nullopt;
}
return *name;
}

View File

@@ -47,7 +47,10 @@ class registry_manager
registry_manager& operator=(const registry_manager&) = delete;
std::optional<registry_key> get_key(const utils::path_key& key);
std::optional<registry_value> get_value(const registry_key& key, std::string name);
std::optional<registry_value> get_value(const registry_key& key, std::string_view name);
std::optional<registry_value> get_value(const registry_key& key, size_t index);
std::optional<std::string_view> get_sub_key_name(const registry_key& key, size_t index);
private:
std::filesystem::path hive_path_{};

View File

@@ -220,7 +220,13 @@ namespace syscalls
NTSTATUS handle_NtCreateKey();
NTSTATUS handle_NtNotifyChangeKey();
NTSTATUS handle_NtSetInformationKey();
NTSTATUS handle_NtEnumerateKey();
NTSTATUS handle_NtEnumerateKey(const syscall_context& c, handle key_handle, ULONG index,
KEY_INFORMATION_CLASS key_information_class, uint64_t key_information, ULONG length,
emulator_object<ULONG> result_length);
NTSTATUS handle_NtEnumerateValueKey(const syscall_context& c, handle key_handle, ULONG index,
KEY_VALUE_INFORMATION_CLASS key_value_information_class,
uint64_t key_value_information, ULONG length,
emulator_object<ULONG> result_length);
// syscalls/section.cpp:
NTSTATUS handle_NtCreateSection(const syscall_context& c, emulator_object<handle> section_handle,
@@ -778,6 +784,7 @@ void syscall_dispatcher::add_handlers(std::map<std::string, syscall_handler>& ha
add_handler(NtUserGetDpiForCurrentProcess);
add_handler(NtReleaseSemaphore);
add_handler(NtEnumerateKey);
add_handler(NtEnumerateValueKey);
add_handler(NtAlpcConnectPort);
add_handler(NtGetNextThread);
add_handler(NtSetInformationObject);

View File

@@ -242,8 +242,195 @@ namespace syscalls
return STATUS_NOT_SUPPORTED;
}
NTSTATUS handle_NtEnumerateKey()
NTSTATUS handle_NtEnumerateKey(const syscall_context& c, const handle key_handle, const ULONG index,
const KEY_INFORMATION_CLASS key_information_class, const uint64_t key_information,
const ULONG length, const emulator_object<ULONG> result_length)
{
const auto* key = c.proc.registry_keys.get(key_handle);
if (!key)
{
return STATUS_INVALID_HANDLE;
}
const auto subkey_name = c.win_emu.registry.get_sub_key_name(*key, index);
if (!subkey_name)
{
return STATUS_NO_MORE_ENTRIES;
}
const std::u16string subkey_name_u16(subkey_name->begin(), subkey_name->end());
if (key_information_class == KeyBasicInformation)
{
constexpr auto base_size = offsetof(KEY_BASIC_INFORMATION, Name);
const auto name_size = subkey_name_u16.size() * 2;
const auto required_size = base_size + name_size;
result_length.write(static_cast<ULONG>(required_size));
KEY_BASIC_INFORMATION info{};
info.LastWriteTime.QuadPart = 0;
info.TitleIndex = 0;
info.NameLength = static_cast<ULONG>(name_size);
if (base_size <= length)
{
c.emu.write_memory(key_information, &info, base_size);
}
if (required_size > length)
{
return STATUS_BUFFER_OVERFLOW;
}
c.emu.write_memory(key_information + base_size, subkey_name_u16.data(), name_size);
return STATUS_SUCCESS;
}
if (key_information_class == KeyNodeInformation)
{
constexpr auto base_size = offsetof(KEY_NODE_INFORMATION, Name);
const auto name_size = subkey_name_u16.size() * 2;
constexpr auto class_size = 0; // TODO: Class Name
const auto required_size = base_size + name_size + class_size;
result_length.write(static_cast<ULONG>(required_size));
KEY_NODE_INFORMATION info{};
info.LastWriteTime.QuadPart = 0;
info.TitleIndex = 0;
info.ClassOffset = static_cast<ULONG>(base_size + name_size);
info.ClassLength = static_cast<ULONG>(class_size);
info.NameLength = static_cast<ULONG>(name_size);
if (base_size <= length)
{
c.emu.write_memory(key_information, &info, base_size);
}
if (required_size > length)
{
return STATUS_BUFFER_OVERFLOW;
}
c.emu.write_memory(key_information + base_size, subkey_name_u16.data(), name_size);
// TODO: Write Class Name
return STATUS_SUCCESS;
}
c.win_emu.log.print(color::gray, "Unsupported registry enumeration class: %X\n", key_information_class);
return STATUS_NOT_SUPPORTED;
}
NTSTATUS handle_NtEnumerateValueKey(const syscall_context& c, const handle key_handle, const ULONG index,
const KEY_VALUE_INFORMATION_CLASS key_value_information_class,
const uint64_t key_value_information, const ULONG length,
const emulator_object<ULONG> result_length)
{
const auto* key = c.proc.registry_keys.get(key_handle);
if (!key)
{
return STATUS_INVALID_HANDLE;
}
const auto value = c.win_emu.registry.get_value(*key, index);
if (!value)
{
return STATUS_NO_MORE_ENTRIES;
}
const std::u16string value_name_u16(value->name.begin(), value->name.end());
if (key_value_information_class == KeyValueBasicInformation)
{
constexpr auto base_size = offsetof(KEY_VALUE_BASIC_INFORMATION, Name);
const auto name_size = value_name_u16.size() * 2;
const auto required_size = base_size + name_size;
result_length.write(static_cast<ULONG>(required_size));
KEY_VALUE_BASIC_INFORMATION info{};
info.TitleIndex = 0;
info.Type = value->type;
info.NameLength = static_cast<ULONG>(name_size);
if (base_size <= length)
{
c.emu.write_memory(key_value_information, &info, base_size);
}
if (required_size > length)
{
return STATUS_BUFFER_OVERFLOW;
}
c.emu.write_memory(key_value_information + base_size, value_name_u16.data(), name_size);
return STATUS_SUCCESS;
}
if (key_value_information_class == KeyValuePartialInformation)
{
constexpr auto base_size = offsetof(KEY_VALUE_PARTIAL_INFORMATION, Data);
const auto data_size = value->data.size();
const auto required_size = base_size + data_size;
result_length.write(static_cast<ULONG>(required_size));
KEY_VALUE_PARTIAL_INFORMATION info{};
info.TitleIndex = 0;
info.Type = value->type;
info.DataLength = static_cast<ULONG>(data_size);
if (base_size <= length)
{
c.emu.write_memory(key_value_information, &info, base_size);
}
if (required_size > length)
{
return STATUS_BUFFER_OVERFLOW;
}
c.emu.write_memory(key_value_information + base_size, value->data.data(), data_size);
return STATUS_SUCCESS;
}
if (key_value_information_class == KeyValueFullInformation)
{
constexpr auto base_size = offsetof(KEY_VALUE_FULL_INFORMATION, Name);
const auto name_size = value_name_u16.size() * 2;
const auto data_size = value->data.size();
const auto data_offset = static_cast<ULONG>(base_size + name_size);
const auto required_size = data_offset + data_size;
result_length.write(static_cast<ULONG>(required_size));
KEY_VALUE_FULL_INFORMATION info{};
info.TitleIndex = 0;
info.Type = value->type;
info.DataOffset = data_offset;
info.DataLength = static_cast<ULONG>(data_size);
info.NameLength = static_cast<ULONG>(name_size);
if (base_size <= length)
{
c.emu.write_memory(key_value_information, &info, base_size);
}
if (required_size > length)
{
return STATUS_BUFFER_OVERFLOW;
}
c.emu.write_memory(key_value_information + base_size, value_name_u16.data(), name_size);
c.emu.write_memory(key_value_information + data_offset, value->data.data(), data_size);
return STATUS_SUCCESS;
}
c.win_emu.log.print(color::gray, "Unsupported registry value enumeration class: %X\n",
key_value_information_class);
return STATUS_NOT_SUPPORTED;
}
}