mirror of
https://github.com/momo5502/emulator.git
synced 2026-01-22 05:03:56 +00:00
Merge pull request #24 from momo5502/feature/registry
Add registry support
This commit is contained in:
3
.github/workflows/build.yml
vendored
3
.github/workflows/build.yml
vendored
@@ -48,5 +48,8 @@ jobs:
|
||||
path: |
|
||||
build/${{matrix.preset}}/artifacts/*
|
||||
|
||||
- name: Dump Registry
|
||||
run: cd build/${{matrix.preset}}/artifacts && ../../../src/grab-registry.bat
|
||||
|
||||
- name: CMake Test
|
||||
run: cd build/${{matrix.preset}} && ctest --verbose
|
||||
|
||||
2
deps/CMakeLists.txt
vendored
2
deps/CMakeLists.txt
vendored
@@ -13,4 +13,4 @@ target_include_directories(reflect INTERFACE
|
||||
##########################################
|
||||
|
||||
include(mini-gdbstub.cmake)
|
||||
include(googletest.cmake)
|
||||
include(googletest.cmake)
|
||||
|
||||
25
src/common/utils/container.hpp
Normal file
25
src/common/utils/container.hpp
Normal file
@@ -0,0 +1,25 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <unordered_set>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace utils
|
||||
{
|
||||
struct string_hash
|
||||
{
|
||||
using is_transparent = void;
|
||||
|
||||
size_t operator()(const std::string_view str) const
|
||||
{
|
||||
constexpr std::hash<std::string_view> hasher{};
|
||||
return hasher(str);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
using unordered_string_map = std::unordered_map<std::string, T, string_hash, std::equal_to<>>;
|
||||
|
||||
using unordered_string_set = std::unordered_set<std::string, string_hash, std::equal_to<>>;
|
||||
}
|
||||
@@ -38,8 +38,9 @@ namespace utils
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct has_serialize_function<T, std::void_t<decltype(serialize(std::declval<buffer_serializer&>(),
|
||||
std::declval<const T&>()))>>
|
||||
struct has_serialize_function<T, std::void_t<decltype(::serialize(std::declval<buffer_serializer&>(),
|
||||
std::declval<const std::remove_cvref_t<T>&>())
|
||||
)>>
|
||||
: std::true_type
|
||||
{
|
||||
};
|
||||
@@ -50,8 +51,9 @@ namespace utils
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct has_deserialize_function<T, std::void_t<decltype(deserialize(
|
||||
std::declval<buffer_deserializer&>(), std::declval<T&>()))>>
|
||||
struct has_deserialize_function<T, std::void_t<decltype(::deserialize(
|
||||
std::declval<buffer_deserializer&>(),
|
||||
std::declval<std::remove_cvref_t<T>&>()))>>
|
||||
: std::true_type
|
||||
{
|
||||
};
|
||||
@@ -122,7 +124,7 @@ namespace utils
|
||||
}
|
||||
else if constexpr (detail::has_deserialize_function<T>::value)
|
||||
{
|
||||
deserialize(*this, object);
|
||||
::deserialize(*this, object);
|
||||
}
|
||||
else if constexpr (std::is_trivially_copyable_v<T>)
|
||||
{
|
||||
@@ -338,7 +340,7 @@ namespace utils
|
||||
}
|
||||
else if constexpr (detail::has_serialize_function<T>::value)
|
||||
{
|
||||
serialize(*this, object);
|
||||
::serialize(*this, object);
|
||||
}
|
||||
else if constexpr (std::is_trivially_copyable_v<T>)
|
||||
{
|
||||
|
||||
30
src/emulator/serialization_helper.hpp
Normal file
30
src/emulator/serialization_helper.hpp
Normal file
@@ -0,0 +1,30 @@
|
||||
#pragma once
|
||||
|
||||
#include "serialization.hpp"
|
||||
|
||||
#include <chrono>
|
||||
#include <filesystem>
|
||||
|
||||
inline void serialize(utils::buffer_serializer& buffer, const std::chrono::steady_clock::time_point& tp)
|
||||
{
|
||||
buffer.write(tp.time_since_epoch().count());
|
||||
}
|
||||
|
||||
inline void deserialize(utils::buffer_deserializer& buffer, std::chrono::steady_clock::time_point& tp)
|
||||
{
|
||||
using time_point = std::chrono::steady_clock::time_point;
|
||||
using duration = time_point::duration;
|
||||
|
||||
const auto count = buffer.read<duration::rep>();
|
||||
tp = time_point{duration{count}};
|
||||
}
|
||||
|
||||
inline void serialize(utils::buffer_serializer& buffer, const std::filesystem::path& path)
|
||||
{
|
||||
buffer.write_string<wchar_t>(path.wstring());
|
||||
}
|
||||
|
||||
inline void deserialize(utils::buffer_deserializer& buffer, std::filesystem::path& path)
|
||||
{
|
||||
path = buffer.read_string<wchar_t>();
|
||||
}
|
||||
17
src/grab-registry.bat
Normal file
17
src/grab-registry.bat
Normal file
@@ -0,0 +1,17 @@
|
||||
@echo off
|
||||
|
||||
NET SESSIONS > NUL 2>&1
|
||||
IF %ERRORLEVEL% NEQ 0 (
|
||||
ECHO Error: This script requires administrative privileges.
|
||||
EXIT /B 1
|
||||
)
|
||||
|
||||
SET REGDIR="registry"
|
||||
MKDIR %REGDIR%
|
||||
|
||||
REG SAVE HKLM\SYSTEM %REGDIR%\SYSTEM /Y
|
||||
REG SAVE HKLM\SECURITY %REGDIR%\SECURITY /Y
|
||||
REG SAVE HKLM\SOFTWARE %REGDIR%\SOFTWARE /Y
|
||||
REG SAVE HKLM\HARDWARE %REGDIR%\HARDWARE /Y
|
||||
REG SAVE HKLM\SAM %REGDIR%\SAM /Y
|
||||
COPY /B /Y C:\Users\Default\NTUSER.DAT "%REGDIR%\NTUSER.DAT"
|
||||
@@ -13,6 +13,7 @@ struct handle_types
|
||||
semaphore,
|
||||
port,
|
||||
thread,
|
||||
registry,
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -2,12 +2,14 @@
|
||||
|
||||
#include "emulator_utils.hpp"
|
||||
#include "handles.hpp"
|
||||
#include "registry/registry_manager.hpp"
|
||||
|
||||
#include "module/module_manager.hpp"
|
||||
#include <utils/nt_handle.hpp>
|
||||
#include <utils/file_handle.hpp>
|
||||
|
||||
#include <x64_emulator.hpp>
|
||||
#include <serialization_helper.hpp>
|
||||
|
||||
|
||||
#define PEB_SEGMENT_SIZE (20 << 20) // 20 MB
|
||||
@@ -23,20 +25,6 @@
|
||||
#define GDT_LIMIT 0x1000
|
||||
#define GDT_ENTRY_SIZE 0x8
|
||||
|
||||
inline void serialize(utils::buffer_serializer& buffer, const std::chrono::steady_clock::time_point& tp)
|
||||
{
|
||||
buffer.write(tp.time_since_epoch().count());
|
||||
}
|
||||
|
||||
inline void deserialize(utils::buffer_deserializer& buffer, std::chrono::steady_clock::time_point& tp)
|
||||
{
|
||||
using time_point = std::chrono::steady_clock::time_point;
|
||||
using duration = time_point::duration;
|
||||
|
||||
const auto count = buffer.read<duration::rep>();
|
||||
tp = time_point{duration{count}};
|
||||
}
|
||||
|
||||
struct ref_counted_object
|
||||
{
|
||||
uint32_t ref_count{1};
|
||||
@@ -375,6 +363,8 @@ struct process_context
|
||||
{
|
||||
}
|
||||
|
||||
registry_manager registry{};
|
||||
|
||||
uint64_t executed_instructions{0};
|
||||
uint64_t current_ip{0};
|
||||
uint64_t previous_ip{0};
|
||||
@@ -404,6 +394,7 @@ struct process_context
|
||||
handle_store<handle_types::file, file> files{};
|
||||
handle_store<handle_types::semaphore, semaphore> semaphores{};
|
||||
handle_store<handle_types::port, port> ports{};
|
||||
handle_store<handle_types::registry, registry_key> registry_keys{};
|
||||
std::map<uint16_t, std::wstring> atoms{};
|
||||
|
||||
std::vector<std::byte> default_register_set{};
|
||||
@@ -414,6 +405,7 @@ struct process_context
|
||||
|
||||
void serialize(utils::buffer_serializer& buffer) const
|
||||
{
|
||||
buffer.write(this->registry);
|
||||
buffer.write(this->executed_instructions);
|
||||
buffer.write(this->current_ip);
|
||||
buffer.write(this->previous_ip);
|
||||
@@ -438,6 +430,7 @@ struct process_context
|
||||
buffer.write(this->files);
|
||||
buffer.write(this->semaphores);
|
||||
buffer.write(this->ports);
|
||||
buffer.write(this->registry_keys);
|
||||
buffer.write_map(this->atoms);
|
||||
|
||||
buffer.write_vector(this->default_register_set);
|
||||
@@ -449,6 +442,7 @@ struct process_context
|
||||
|
||||
void deserialize(utils::buffer_deserializer& buffer)
|
||||
{
|
||||
buffer.read(this->registry);
|
||||
buffer.read(this->executed_instructions);
|
||||
buffer.read(this->current_ip);
|
||||
buffer.read(this->previous_ip);
|
||||
@@ -477,6 +471,7 @@ struct process_context
|
||||
buffer.read(this->files);
|
||||
buffer.read(this->semaphores);
|
||||
buffer.read(this->ports);
|
||||
buffer.read(this->registry_keys);
|
||||
buffer.read_map(this->atoms);
|
||||
|
||||
buffer.read_vector(this->default_register_set);
|
||||
|
||||
224
src/windows-emulator/registry/hive_parser.cpp
Normal file
224
src/windows-emulator/registry/hive_parser.cpp
Normal file
@@ -0,0 +1,224 @@
|
||||
#include "hive_parser.hpp"
|
||||
|
||||
// Based on this implementation: https://github.com/reahly/windows-hive-parser
|
||||
|
||||
namespace
|
||||
{
|
||||
constexpr uint64_t MAIN_ROOT_OFFSET = 0x1000;
|
||||
constexpr uint64_t MAIN_KEY_BLOCK_OFFSET = MAIN_ROOT_OFFSET + 0x20;
|
||||
|
||||
struct offset_entry_t
|
||||
{
|
||||
long offset;
|
||||
long hash;
|
||||
};
|
||||
|
||||
struct offsets_t
|
||||
{
|
||||
long block_size;
|
||||
char block_type[2];
|
||||
short count;
|
||||
offset_entry_t entries[1];
|
||||
};
|
||||
|
||||
struct key_block_t
|
||||
{
|
||||
long block_size;
|
||||
char block_type[2];
|
||||
char dummya[18];
|
||||
int subkey_count;
|
||||
char dummyb[4];
|
||||
int subkeys;
|
||||
char dummyc[4];
|
||||
int value_count;
|
||||
int offsets;
|
||||
char dummyd[28];
|
||||
short len;
|
||||
short du;
|
||||
char name[255];
|
||||
};
|
||||
|
||||
struct value_block_t
|
||||
{
|
||||
long block_size;
|
||||
char block_type[2];
|
||||
short name_len;
|
||||
long size;
|
||||
long offset;
|
||||
long value_type;
|
||||
short flags;
|
||||
short dummy;
|
||||
char name[255];
|
||||
};
|
||||
|
||||
bool read_file_data_safe(std::ifstream& file, const uint64_t offset, void* buffer, const size_t size)
|
||||
{
|
||||
if (file.bad())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
file.clear();
|
||||
|
||||
if (!file.good())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
file.seekg(static_cast<std::streamoff>(offset));
|
||||
|
||||
if (!file.good())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
file.read(static_cast<char*>(buffer), static_cast<std::streamsize>(size));
|
||||
|
||||
return file.good();
|
||||
}
|
||||
|
||||
void read_file_data(std::ifstream& file, const uint64_t offset, void* buffer, const size_t size)
|
||||
{
|
||||
if (!read_file_data_safe(file, offset, buffer, size))
|
||||
{
|
||||
throw std::runtime_error("Failed to read file data");
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::byte> read_file_data(std::ifstream& file, const uint64_t offset, const size_t size)
|
||||
{
|
||||
std::vector<std::byte> result{};
|
||||
result.resize(size);
|
||||
|
||||
read_file_data(file, offset, result.data(), size);
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string read_file_data_string(std::ifstream& file, const uint64_t offset, const size_t size)
|
||||
{
|
||||
std::string result{};
|
||||
result.resize(size);
|
||||
|
||||
read_file_data(file, offset, result.data(), size);
|
||||
return result;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
requires(std::is_trivially_copyable_v<T>)
|
||||
T read_file_object(std::ifstream& file, const uint64_t offset, const size_t array_index = 0)
|
||||
{
|
||||
T obj{};
|
||||
read_file_data(file, offset + (array_index * sizeof(T)), &obj, sizeof(T));
|
||||
return obj;
|
||||
}
|
||||
|
||||
hive_key parse_root_block(std::ifstream& file, const std::filesystem::path& file_path)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (read_file_data_string(file, 0, 4) != "regf")
|
||||
{
|
||||
throw std::runtime_error("Invalid signature");
|
||||
}
|
||||
|
||||
const auto key_block = read_file_object<key_block_t>(file, MAIN_KEY_BLOCK_OFFSET);
|
||||
|
||||
return {key_block.subkeys, key_block.value_count, key_block.offsets};
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
throw std::runtime_error("Bad hive file '" + file_path.string() + "': " + e.what());
|
||||
}
|
||||
}
|
||||
|
||||
char char_to_lower(const char val)
|
||||
{
|
||||
return static_cast<char>(std::tolower(static_cast<unsigned char>(val)));
|
||||
}
|
||||
}
|
||||
|
||||
const hive_value* hive_key::get_value(std::ifstream& file, const std::string_view name)
|
||||
{
|
||||
this->parse(file);
|
||||
|
||||
const auto entry = this->values_.find(name);
|
||||
if (entry == this->values_.end())
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto& value = entry->second;
|
||||
|
||||
if (value.parsed)
|
||||
{
|
||||
value.data = read_file_data(file, MAIN_ROOT_OFFSET + value.data_offset, value.data_length);
|
||||
value.parsed = true;
|
||||
}
|
||||
|
||||
return &value;
|
||||
}
|
||||
|
||||
void hive_key::parse(std::ifstream& file)
|
||||
{
|
||||
if (this->parsed_)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
this->parsed_ = true;
|
||||
|
||||
// Values
|
||||
|
||||
for (auto i = 0; i < this->value_count_; i++)
|
||||
{
|
||||
const auto offset = read_file_object<int>(file, MAIN_ROOT_OFFSET + this->value_offsets_ + 4, i);
|
||||
const auto value = read_file_object<value_block_t>(file, MAIN_ROOT_OFFSET + offset);
|
||||
|
||||
std::string value_name(value.name, std::min(value.name_len, static_cast<short>(sizeof(value.name))));
|
||||
|
||||
raw_hive_value raw_value{};
|
||||
raw_value.parsed = false;
|
||||
raw_value.type = value.value_type;
|
||||
raw_value.name = value_name;
|
||||
raw_value.data_length = value.size & 0xffff;
|
||||
raw_value.data_offset = value.offset + 4;
|
||||
|
||||
if (value.size & 1 << 31)
|
||||
{
|
||||
raw_value.data_offset = offset + static_cast<int>(offsetof(value_block_t, offset));
|
||||
}
|
||||
|
||||
std::ranges::transform(value_name, value_name.begin(), char_to_lower);
|
||||
this->values_[std::move(value_name)] = std::move(raw_value);
|
||||
}
|
||||
|
||||
// Subkeys
|
||||
|
||||
const auto item = read_file_object<offsets_t>(file, MAIN_ROOT_OFFSET + this->subkey_block_offset_);
|
||||
|
||||
if (item.block_type[1] != 'f' && item.block_type[1] != 'h')
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const auto entry_offsets = this->subkey_block_offset_ + offsetof(offsets_t, entries);
|
||||
|
||||
for (short i = 0; i < item.count; ++i)
|
||||
{
|
||||
const auto offset_entry = read_file_object<offset_entry_t>(file, MAIN_ROOT_OFFSET + entry_offsets, i);
|
||||
|
||||
const auto subkey_block_offset = MAIN_ROOT_OFFSET + offset_entry.offset;
|
||||
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<short>(sizeof(subkey.name))));
|
||||
std::ranges::transform(subkey_name, subkey_name.begin(), char_to_lower);
|
||||
|
||||
this->sub_keys_.emplace(std::move(subkey_name), hive_key{subkey.subkeys, subkey.value_count, subkey.offsets});
|
||||
}
|
||||
}
|
||||
|
||||
hive_parser::hive_parser(const std::filesystem::path& file_path)
|
||||
: file_(file_path, std::ios::binary)
|
||||
, root_key_(parse_root_block(file_, file_path))
|
||||
{
|
||||
}
|
||||
99
src/windows-emulator/registry/hive_parser.hpp
Normal file
99
src/windows-emulator/registry/hive_parser.hpp
Normal file
@@ -0,0 +1,99 @@
|
||||
#pragma once
|
||||
|
||||
#include <fstream>
|
||||
#include <utils/container.hpp>
|
||||
|
||||
struct hive_value
|
||||
{
|
||||
uint32_t type{};
|
||||
std::string name{};
|
||||
std::vector<std::byte> data{};
|
||||
};
|
||||
|
||||
class hive_key
|
||||
{
|
||||
public:
|
||||
hive_key(const int subkey_block_offset, const int value_count, const int value_offsets)
|
||||
: subkey_block_offset_(subkey_block_offset)
|
||||
, value_count_(value_count)
|
||||
, value_offsets_(value_offsets)
|
||||
{
|
||||
}
|
||||
|
||||
utils::unordered_string_map<hive_key>& get_sub_keys(std::ifstream& file)
|
||||
{
|
||||
this->parse(file);
|
||||
return this->sub_keys_;
|
||||
}
|
||||
|
||||
hive_key* get_sub_key(std::ifstream& file, const std::string_view name)
|
||||
{
|
||||
auto& sub_keys = this->get_sub_keys(file);
|
||||
const auto entry = sub_keys.find(name);
|
||||
|
||||
if (entry == sub_keys.end())
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return &entry->second;
|
||||
}
|
||||
|
||||
const hive_value* get_value(std::ifstream& file, const std::string_view name);
|
||||
|
||||
private:
|
||||
struct raw_hive_value : hive_value
|
||||
{
|
||||
bool parsed{false};
|
||||
int data_offset{};
|
||||
size_t data_length{};
|
||||
};
|
||||
|
||||
bool parsed_{false};
|
||||
utils::unordered_string_map<hive_key> sub_keys_{};
|
||||
utils::unordered_string_map<raw_hive_value> values_{};
|
||||
|
||||
const int subkey_block_offset_{};
|
||||
const int value_count_{};
|
||||
const int value_offsets_{};
|
||||
|
||||
void parse(std::ifstream& file);
|
||||
};
|
||||
|
||||
class hive_parser
|
||||
{
|
||||
public:
|
||||
explicit hive_parser(const std::filesystem::path& file_path);
|
||||
|
||||
[[nodiscard]] hive_key* get_sub_key(const std::filesystem::path& key)
|
||||
{
|
||||
hive_key* current_key = &this->root_key_;
|
||||
|
||||
for (const auto& key_part : key)
|
||||
{
|
||||
if (!current_key)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
current_key = current_key->get_sub_key(this->file_, key_part.string());
|
||||
}
|
||||
|
||||
return current_key;
|
||||
}
|
||||
|
||||
[[nodiscard]] const hive_value* get_value(const std::filesystem::path& key, const std::string_view name)
|
||||
{
|
||||
auto* sub_key = this->get_sub_key(key);
|
||||
if (!sub_key)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return sub_key->get_value(this->file_, name);
|
||||
}
|
||||
|
||||
private:
|
||||
std::ifstream file_{};
|
||||
hive_key root_key_;
|
||||
};
|
||||
162
src/windows-emulator/registry/registry_manager.cpp
Normal file
162
src/windows-emulator/registry/registry_manager.cpp
Normal file
@@ -0,0 +1,162 @@
|
||||
#include "registry_manager.hpp"
|
||||
|
||||
#include <cwctype>
|
||||
#include <serialization_helper.hpp>
|
||||
|
||||
#include "hive_parser.hpp"
|
||||
|
||||
namespace
|
||||
{
|
||||
std::filesystem::path canonicalize_path(const std::filesystem::path& key)
|
||||
{
|
||||
auto path = key.lexically_normal().wstring();
|
||||
std::ranges::transform(path, path.begin(), std::towlower);
|
||||
return {std::move(path)};
|
||||
}
|
||||
|
||||
bool is_subpath(const std::filesystem::path& root, const std::filesystem::path& p)
|
||||
{
|
||||
auto root_it = root.begin();
|
||||
auto p_it = p.begin();
|
||||
|
||||
for (; root_it != root.end(); ++root_it, ++p_it)
|
||||
{
|
||||
if (p_it == p.end() || *root_it != *p_it)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void register_hive(registry_manager::hive_map& hives,
|
||||
const std::filesystem::path& key, const std::filesystem::path& file)
|
||||
{
|
||||
hives[canonicalize_path(key)] = std::make_unique<hive_parser>(file);
|
||||
}
|
||||
}
|
||||
|
||||
registry_manager::registry_manager() = default;
|
||||
registry_manager::~registry_manager() = default;
|
||||
registry_manager::registry_manager(registry_manager&&) noexcept = default;
|
||||
registry_manager& registry_manager::operator=(registry_manager&&) noexcept = default;
|
||||
|
||||
registry_manager::registry_manager(const std::filesystem::path& hive_path)
|
||||
: hive_path_(absolute(hive_path))
|
||||
{
|
||||
this->setup();
|
||||
}
|
||||
|
||||
void registry_manager::setup()
|
||||
{
|
||||
this->path_mapping_.clear();
|
||||
this->hives_.clear();
|
||||
|
||||
const std::filesystem::path root = R"(\registry)";
|
||||
const std::filesystem::path machine = root / "machine";
|
||||
|
||||
register_hive(this->hives_, machine / "system", this->hive_path_ / "SYSTEM");
|
||||
register_hive(this->hives_, machine / "security", this->hive_path_ / "SECURITY");
|
||||
register_hive(this->hives_, machine / "sam", this->hive_path_ / "SAM");
|
||||
register_hive(this->hives_, machine / "software", this->hive_path_ / "SOFTWARE");
|
||||
register_hive(this->hives_, machine / "system", this->hive_path_ / "SYSTEM");
|
||||
register_hive(this->hives_, machine / "hardware", this->hive_path_ / "HARDWARE");
|
||||
|
||||
register_hive(this->hives_, root / "user", this->hive_path_ / "NTUSER.dat");
|
||||
|
||||
this->add_path_mapping(machine / "system" / "CurrentControlSet", machine / "system" / "ControlSet001");
|
||||
}
|
||||
|
||||
void registry_manager::serialize(utils::buffer_serializer& buffer) const
|
||||
{
|
||||
buffer.write(this->hive_path_);
|
||||
}
|
||||
|
||||
void registry_manager::deserialize(utils::buffer_deserializer& buffer)
|
||||
{
|
||||
buffer.read(this->hive_path_);
|
||||
this->setup();
|
||||
}
|
||||
|
||||
std::filesystem::path registry_manager::normalize_path(const std::filesystem::path& path) const
|
||||
{
|
||||
auto canonical_path = canonicalize_path(path);
|
||||
|
||||
for (const auto& mapping : this->path_mapping_)
|
||||
{
|
||||
if (is_subpath(mapping.first, canonical_path))
|
||||
{
|
||||
return mapping.second / canonical_path.lexically_relative(mapping.first);
|
||||
}
|
||||
}
|
||||
|
||||
return canonical_path;
|
||||
}
|
||||
|
||||
void registry_manager::add_path_mapping(const std::filesystem::path& key, const std::filesystem::path& value)
|
||||
{
|
||||
this->path_mapping_[canonicalize_path(key)] = canonicalize_path(value);
|
||||
}
|
||||
|
||||
std::optional<registry_key> registry_manager::get_key(const std::filesystem::path& key)
|
||||
{
|
||||
const auto normal_key = this->normalize_path(key);
|
||||
const auto iterator = this->find_hive(normal_key);
|
||||
if (iterator == this->hives_.end())
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
registry_key reg_key{};
|
||||
reg_key.hive = iterator->first;
|
||||
reg_key.path = normal_key.lexically_relative(reg_key.hive);
|
||||
|
||||
if (reg_key.path.empty())
|
||||
{
|
||||
return {std::move(reg_key)};
|
||||
}
|
||||
|
||||
const auto entry = iterator->second->get_sub_key(reg_key.path);
|
||||
if (!entry)
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
return {std::move(reg_key)};
|
||||
}
|
||||
|
||||
std::optional<registry_value> registry_manager::get_value(const registry_key& key, const std::string_view name)
|
||||
{
|
||||
const auto iterator = this->hives_.find(key.hive);
|
||||
if (iterator == this->hives_.end())
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
auto* entry = iterator->second->get_value(key.path, name);
|
||||
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 std::filesystem::path& key)
|
||||
{
|
||||
for (auto i = this->hives_.begin(); i != this->hives_.end(); ++i)
|
||||
{
|
||||
if (is_subpath(i->first, key))
|
||||
{
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return this->hives_.end();
|
||||
}
|
||||
67
src/windows-emulator/registry/registry_manager.hpp
Normal file
67
src/windows-emulator/registry/registry_manager.hpp
Normal file
@@ -0,0 +1,67 @@
|
||||
#pragma once
|
||||
|
||||
#include "../std_include.hpp"
|
||||
#include <serialization_helper.hpp>
|
||||
|
||||
class hive_parser;
|
||||
|
||||
struct registry_key
|
||||
{
|
||||
std::filesystem::path hive{};
|
||||
std::filesystem::path path{};
|
||||
|
||||
void serialize(utils::buffer_serializer& buffer) const
|
||||
{
|
||||
buffer.write(this->hive);
|
||||
buffer.write(this->path);
|
||||
}
|
||||
|
||||
void deserialize(utils::buffer_deserializer& buffer)
|
||||
{
|
||||
buffer.read(this->hive);
|
||||
buffer.read(this->path);
|
||||
}
|
||||
};
|
||||
|
||||
struct registry_value
|
||||
{
|
||||
uint32_t type;
|
||||
std::string_view name;
|
||||
std::span<const std::byte> data;
|
||||
};
|
||||
|
||||
class registry_manager
|
||||
{
|
||||
public:
|
||||
using hive_ptr = std::unique_ptr<hive_parser>;
|
||||
using hive_map = std::unordered_map<std::filesystem::path, hive_ptr>;
|
||||
|
||||
registry_manager();
|
||||
registry_manager(const std::filesystem::path& hive_path);
|
||||
~registry_manager();
|
||||
|
||||
registry_manager(registry_manager&&) noexcept;
|
||||
registry_manager& operator=(registry_manager&&) noexcept;
|
||||
|
||||
registry_manager(const registry_manager&) = delete;
|
||||
registry_manager& operator=(const registry_manager&) = delete;
|
||||
|
||||
|
||||
void serialize(utils::buffer_serializer& buffer) const;
|
||||
void deserialize(utils::buffer_deserializer& buffer);
|
||||
|
||||
std::optional<registry_key> get_key(const std::filesystem::path& key);
|
||||
std::optional<registry_value> get_value(const registry_key& key, const std::string_view name);
|
||||
|
||||
private:
|
||||
std::filesystem::path hive_path_{};
|
||||
hive_map hives_{};
|
||||
std::unordered_map<std::filesystem::path, std::filesystem::path> path_mapping_{};
|
||||
|
||||
std::filesystem::path normalize_path(const std::filesystem::path& path) const;
|
||||
void add_path_mapping(const std::filesystem::path& key, const std::filesystem::path& value);
|
||||
|
||||
hive_map::iterator find_hive(const std::filesystem::path& key);
|
||||
|
||||
void setup();
|
||||
};
|
||||
@@ -1,12 +1,10 @@
|
||||
#include "std_include.hpp"
|
||||
#include "syscall_dispatcher.hpp"
|
||||
|
||||
#include <numeric>
|
||||
|
||||
#include "context_frame.hpp"
|
||||
#include "emulator_utils.hpp"
|
||||
#include "syscall_utils.hpp"
|
||||
|
||||
#include <numeric>
|
||||
#include <utils/io.hpp>
|
||||
|
||||
namespace
|
||||
@@ -53,20 +51,153 @@ namespace
|
||||
return STATUS_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
NTSTATUS handle_NtOpenKey(const syscall_context& c, const emulator_object<uint64_t> /*key_handle*/,
|
||||
NTSTATUS handle_NtOpenKey(const syscall_context& c, const emulator_object<uint64_t> key_handle,
|
||||
const ACCESS_MASK /*desired_access*/,
|
||||
const emulator_object<OBJECT_ATTRIBUTES> object_attributes)
|
||||
{
|
||||
const auto attributes = object_attributes.read();
|
||||
const auto key = read_unicode_string(c.emu, attributes.ObjectName);
|
||||
auto key = read_unicode_string(c.emu, attributes.ObjectName);
|
||||
|
||||
if (attributes.RootDirectory)
|
||||
{
|
||||
const auto* parent_handle = c.proc.registry_keys.get(reinterpret_cast<uint64_t>(attributes.RootDirectory));
|
||||
if (!parent_handle)
|
||||
{
|
||||
return STATUS_INVALID_HANDLE;
|
||||
}
|
||||
|
||||
key = parent_handle->hive / parent_handle->path / key;
|
||||
}
|
||||
|
||||
c.win_emu.logger.print(color::dark_gray, "--> Registry key: %S\n", key.c_str());
|
||||
|
||||
return STATUS_OBJECT_NAME_NOT_FOUND;
|
||||
auto entry = c.proc.registry.get_key(key);
|
||||
if (!entry.has_value())
|
||||
{
|
||||
return STATUS_OBJECT_NAME_NOT_FOUND;
|
||||
}
|
||||
|
||||
const auto handle = c.proc.registry_keys.store(std::move(entry.value()));
|
||||
key_handle.write(handle.bits);
|
||||
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
NTSTATUS handle_NtOpenKeyEx()
|
||||
NTSTATUS handle_NtOpenKeyEx(const syscall_context& c, const emulator_object<uint64_t> key_handle,
|
||||
const ACCESS_MASK desired_access,
|
||||
const emulator_object<OBJECT_ATTRIBUTES> object_attributes,
|
||||
ULONG /*open_options*/)
|
||||
{
|
||||
return handle_NtOpenKey(c, key_handle, desired_access, object_attributes);
|
||||
}
|
||||
|
||||
NTSTATUS handle_NtQueryValueKey(const syscall_context& c, handle key_handle,
|
||||
const emulator_object<UNICODE_STRING> value_name,
|
||||
KEY_VALUE_INFORMATION_CLASS key_value_information_class,
|
||||
uint64_t key_value_information,
|
||||
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 query_name = read_unicode_string(c.emu, value_name);
|
||||
const std::string name(query_name.begin(), query_name.end());
|
||||
|
||||
const auto value = c.proc.registry.get_value(*key, name);
|
||||
if (!value)
|
||||
{
|
||||
return STATUS_OBJECT_NAME_NOT_FOUND;
|
||||
}
|
||||
|
||||
const std::wstring original_name(value->name.begin(), value->name.end());
|
||||
|
||||
if (key_value_information_class == KeyValueBasicInformation)
|
||||
{
|
||||
const auto required_size = sizeof(KEY_VALUE_BASIC_INFORMATION) + (original_name.size() * 2) - 1;
|
||||
result_length.write(static_cast<ULONG>(required_size));
|
||||
|
||||
if (required_size > length)
|
||||
{
|
||||
return STATUS_BUFFER_TOO_SMALL;
|
||||
}
|
||||
|
||||
KEY_VALUE_BASIC_INFORMATION info{};
|
||||
info.TitleIndex = 0;
|
||||
info.Type = value->type;
|
||||
info.NameLength = static_cast<ULONG>(original_name.size() * 2);
|
||||
|
||||
const emulator_object<KEY_VALUE_BASIC_INFORMATION> info_obj{c.emu, key_value_information};
|
||||
info_obj.write(info);
|
||||
|
||||
c.emu.write_memory(key_value_information + offsetof(KEY_VALUE_BASIC_INFORMATION, Name),
|
||||
original_name.data(),
|
||||
info.NameLength);
|
||||
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
if (key_value_information_class == KeyValuePartialInformation)
|
||||
{
|
||||
const auto required_size = sizeof(KEY_VALUE_PARTIAL_INFORMATION) + value->data.size() - 1;
|
||||
result_length.write(static_cast<ULONG>(required_size));
|
||||
|
||||
if (required_size > length)
|
||||
{
|
||||
return STATUS_BUFFER_TOO_SMALL;
|
||||
}
|
||||
|
||||
KEY_VALUE_PARTIAL_INFORMATION info{};
|
||||
info.TitleIndex = 0;
|
||||
info.Type = value->type;
|
||||
info.DataLength = static_cast<ULONG>(value->data.size());
|
||||
|
||||
const emulator_object<KEY_VALUE_PARTIAL_INFORMATION> info_obj{c.emu, key_value_information};
|
||||
info_obj.write(info);
|
||||
|
||||
c.emu.write_memory(key_value_information + offsetof(KEY_VALUE_PARTIAL_INFORMATION, Data),
|
||||
value->data.data(),
|
||||
value->data.size());
|
||||
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
if (key_value_information_class == KeyValueFullInformation)
|
||||
{
|
||||
const auto name_size = original_name.size() * 2;
|
||||
const auto value_size = value->data.size();
|
||||
const auto required_size = sizeof(KEY_VALUE_FULL_INFORMATION) + name_size + value_size + -1;
|
||||
result_length.write(static_cast<ULONG>(required_size));
|
||||
|
||||
if (required_size > length)
|
||||
{
|
||||
return STATUS_BUFFER_TOO_SMALL;
|
||||
}
|
||||
|
||||
KEY_VALUE_FULL_INFORMATION info{};
|
||||
info.TitleIndex = 0;
|
||||
info.Type = value->type;
|
||||
info.DataLength = static_cast<ULONG>(value->data.size());
|
||||
info.NameLength = static_cast<ULONG>(original_name.size() * 2);
|
||||
|
||||
const emulator_object<KEY_VALUE_FULL_INFORMATION> info_obj{c.emu, key_value_information};
|
||||
info_obj.write(info);
|
||||
|
||||
c.emu.write_memory(key_value_information + offsetof(KEY_VALUE_BASIC_INFORMATION, Name),
|
||||
original_name.data(),
|
||||
info.NameLength);
|
||||
|
||||
c.emu.write_memory(key_value_information + offsetof(KEY_VALUE_FULL_INFORMATION, Name) + info.NameLength,
|
||||
value->data.data(),
|
||||
value->data.size());
|
||||
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
c.win_emu.logger.print(color::gray, "Unsupported registry class: %X\n", key_value_information_class);
|
||||
c.emu.stop();
|
||||
return STATUS_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
@@ -156,6 +287,11 @@ namespace
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
if (value.type == handle_types::registry && c.proc.registry_keys.erase(handle))
|
||||
{
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
return STATUS_INVALID_HANDLE;
|
||||
}
|
||||
|
||||
@@ -1532,6 +1668,11 @@ namespace
|
||||
return STATUS_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
NTSTATUS handle_NtGetNlsSectionPtr()
|
||||
{
|
||||
return STATUS_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
NTSTATUS handle_NtAlpcSendWaitReceivePort(const syscall_context& c, const uint64_t port_handle,
|
||||
const ULONG /*flags*/,
|
||||
const emulator_object<PORT_MESSAGE> /*send_message*/,
|
||||
@@ -2267,6 +2408,8 @@ void syscall_dispatcher::add_handlers(std::map<std::string, syscall_handler>& ha
|
||||
add_handler(NtReadFile);
|
||||
add_handler(NtSetInformationFile);
|
||||
add_handler(NtUserRegisterWindowMessage);
|
||||
add_handler(NtQueryValueKey);
|
||||
add_handler(NtGetNlsSectionPtr);
|
||||
|
||||
#undef add_handler
|
||||
}
|
||||
|
||||
@@ -255,6 +255,8 @@ namespace
|
||||
{
|
||||
setup_gdt(emu);
|
||||
|
||||
context.registry = registry_manager(settings.registry_directory);
|
||||
|
||||
context.kusd = setup_kusd(emu);
|
||||
|
||||
context.base_allocator = create_allocator(emu, PEB_SEGMENT_SIZE);
|
||||
|
||||
@@ -11,9 +11,10 @@ std::unique_ptr<x64_emulator> create_default_x64_emulator();
|
||||
|
||||
struct emulator_settings
|
||||
{
|
||||
std::filesystem::path application;
|
||||
std::filesystem::path working_directory;
|
||||
std::vector<std::wstring> arguments;
|
||||
std::filesystem::path application{};
|
||||
std::filesystem::path working_directory{};
|
||||
std::filesystem::path registry_directory{"./registry"};
|
||||
std::vector<std::wstring> arguments{};
|
||||
bool disable_logging{false};
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user