Implement root fs handling

This commit is contained in:
momo5502
2025-01-22 06:41:39 +01:00
parent b581d74433
commit ec1333278b
17 changed files with 372 additions and 98 deletions

View File

@@ -224,8 +224,13 @@ jobs:
name: Temp ${{ matrix.filesystem }} Root FS
path: build/${{matrix.preset}}/artifacts/root
- name: Copy Test Sample
run: cp build/${{matrix.preset}}/artifacts/test-sample.exe build/${{matrix.preset}}/artifacts/root/filesys/c/
- name: CMake Test
run: cd build/${{matrix.preset}} && ctest --verbose
env:
EMULATOR_ROOT: ${{github.workspace}}/build/${{matrix.preset}}/artifacts/root
summary:
name: Pipeline Summary

View File

@@ -91,6 +91,8 @@ if(MSVC)
momo_add_release_link_options(
#/LTCG
)
add_compile_definitions(_CRT_SECURE_NO_WARNINGS)
endif()
##########################################

View File

@@ -12,7 +12,7 @@ namespace
bool use_gdb{false};
bool concise_logging{false};
bool verbose_logging{false};
std::string registry_path{"./registry"};
std::string root_filesystem{"./root"};
};
void watch_system_objects(windows_emulator& win_emu, const bool cache_logging)
@@ -107,7 +107,7 @@ namespace
emulator_settings settings{
.application = args[0],
.registry_directory = options.registry_path,
.root_filesystem = options.root_filesystem,
.arguments = parse_arguments(args),
.silent_until_main = options.concise_logging,
};
@@ -213,10 +213,10 @@ namespace
{
if (args.size() < 2)
{
throw std::runtime_error("No registry path provided after -r");
throw std::runtime_error("No root path provided after -r");
}
arg_it = args.erase(arg_it);
options.registry_path = args[0];
options.root_filesystem = args[0];
}
else
{

View File

@@ -1,7 +1,9 @@
#pragma once
#if _WIN32
#ifndef _CRT_SECURE_NO_WARNINGS
#define _CRT_SECURE_NO_WARNINGS
#endif
#define _CRT_NO_POSIX_ERROR_CODES
#define NOMINMAX
#define WIN32_LEAN_AND_MEAN

View File

@@ -1,5 +1,6 @@
#pragma once
#include <list>
#include <span>
#include <vector>
#include <string>
@@ -205,6 +206,26 @@ namespace utils
return result;
}
template <typename T>
void read_list(std::list<T>& result)
{
const auto size = this->read<uint64_t>();
result.clear();
for (uint64_t i = 0; i < size; ++i)
{
result.emplace_back(this->read<T>());
}
}
template <typename T>
std::list<T> read_list()
{
std::list<T> result{};
this->read_list(result);
return result;
}
template <typename Map>
void read_map(Map& map)
{
@@ -397,6 +418,17 @@ namespace utils
this->write_span(std::span(vec));
}
template <typename T>
void write_list(const std::list<T> vec)
{
this->write(static_cast<uint64_t>(vec.size()));
for (const auto& v : vec)
{
this->write(v);
}
}
template <typename T>
void write_string(const std::basic_string_view<T> str)
{

View File

@@ -42,12 +42,12 @@ namespace
struct fuzzer_executer : fuzzer::executer
{
windows_emulator emu{};
windows_emulator emu{"./"}; // TODO: Fix root directory
std::span<const std::byte> emulator_data{};
std::unordered_set<uint64_t> visited_blocks{};
const std::function<fuzzer::coverage_functor>* handler{nullptr};
fuzzer_executer(std::span<const std::byte> data)
fuzzer_executer(const std::span<const std::byte> data)
: emulator_data(data)
{
emu.fuzzing = true;

View File

@@ -1,5 +1,6 @@
#pragma once
#include <cstdlib>
#include <gtest/gtest.h>
#include <windows_emulator.hpp>
@@ -20,9 +21,21 @@
namespace test
{
inline std::filesystem::path get_emulator_root()
{
auto* env = getenv("EMULATOR_ROOT");
if (!env)
{
throw std::runtime_error("No EMULATOR_ROOT set!");
}
return env;
}
inline windows_emulator create_sample_emulator(emulator_settings settings, emulator_callbacks callbacks = {})
{
settings.application = "./test-sample.exe";
settings.application = "c:/test-sample.exe";
settings.root_filesystem = get_emulator_root();
return windows_emulator{std::move(settings), std::move(callbacks)};
}

View File

@@ -14,7 +14,7 @@ namespace test
utils::buffer_deserializer deserializer{serializer1.get_buffer()};
windows_emulator new_emu{};
windows_emulator new_emu{get_emulator_root()};
new_emu.deserialize(deserializer);
utils::buffer_serializer serializer2{};
@@ -57,7 +57,7 @@ namespace test
utils::buffer_deserializer deserializer{serializer.get_buffer()};
windows_emulator new_emu{};
windows_emulator new_emu{get_emulator_root()};
new_emu.log.disable_output(true);
new_emu.deserialize(deserializer);

View File

@@ -1 +0,0 @@
#include "std_include.hpp"

View File

@@ -1,85 +1,67 @@
#pragma once
#include "std_include.hpp"
#include <utils/path_key.hpp>
#include "windows_path.hpp"
class file_system
{
public:
static constexpr std::u16string_view nt_prefix = u"\\??\\";
file_system(std::filesystem::path root, std::u16string working_dir)
file_system(std::filesystem::path root, windows_path working_dir)
: root_(std::move(root)),
working_dir_(std::move(working_dir))
{
}
static std::filesystem::path canonicalize_windows_path(const std::filesystem::path& file)
std::filesystem::path translate(const windows_path& win_path) const
{
const auto wide_file = file.u16string();
if (!wide_file.starts_with(nt_prefix))
if (win_path.is_absolute())
{
return file;
return this->root_ / win_path.to_portable_path();
}
return canonicalize_windows_path(wide_file.substr(nt_prefix.size()));
return this->root_ / (this->working_dir_ / win_path).to_portable_path();
}
static std::filesystem::path canonicalize(const std::filesystem::path& path)
{
return utils::path_key::canonicalize_path(canonicalize_windows_path(path));
}
static std::filesystem::path canonicalize_drive_letter(const std::filesystem::path& path)
{
auto canonical_path = canonicalize(path);
if (canonical_path.empty() || !path.has_root_path())
{
return canonical_path;
}
return adapt_drive_component(canonical_path);
}
std::filesystem::path translate(const std::filesystem::path& win_path) const
{
const auto absolute_win_dir = make_absolute_windows_path(win_path.u16string());
return this->root_ / canonicalize_drive_letter(absolute_win_dir);
}
void set_working_directory(std::u16string working_dir)
void set_working_directory(windows_path working_dir)
{
this->working_dir_ = std::move(working_dir);
}
const std::u16string& get_working_directory() const
const windows_path& get_working_directory() const
{
return this->working_dir_;
}
windows_path local_to_windows_path(const std::filesystem::path& local_path) const
{
const auto absolute_local_path = absolute(local_path);
const auto relative_path = relative(absolute_local_path, this->root_);
if (relative_path.empty() || *relative_path.begin() == "..")
{
throw std::runtime_error("Path '" + local_path.string() + "' is not within the root filesystem!");
}
char drive{};
std::list<std::u16string> folders{};
for (auto i = relative_path.begin(); i != relative_path.end(); ++i)
{
if (i == relative_path.begin())
{
const auto str = i->string();
assert(str.size() == 1);
drive = str[0];
}
else
{
folders.push_back(i->u16string());
}
}
return windows_path{drive, std::move(folders)};
}
private:
std::filesystem::path root_;
std::u16string working_dir_;
std::u16string make_absolute_windows_path(const std::u16string& path) const
{
if (!path.starts_with(nt_prefix) && (path.size() < 2 || path[1] != ':'))
{
return this->working_dir_ + u'/' + path;
}
return path;
}
static std::filesystem::path adapt_drive_component(const std::filesystem::path& original_path)
{
auto root_name = original_path.root_name().u16string();
if (!root_name.empty() && root_name.back() == u':')
{
root_name.pop_back();
}
return root_name + original_path.root_directory().u16string() + original_path.relative_path().u16string();
}
std::filesystem::path root_{};
windows_path working_dir_{};
};

View File

@@ -80,7 +80,7 @@ namespace
kusd.QpcFrequency = std::chrono::steady_clock::period::den;
}
constexpr std::wstring_view root_dir{L"C:\\WINDOWS"};
constexpr std::u16string_view root_dir{u"C:\\WINDOWS"};
memcpy(&kusd.NtSystemRoot.arr[0], root_dir.data(), root_dir.size() * 2);
kusd.ImageNumberLow = IMAGE_FILE_MACHINE_I386;

View File

@@ -28,8 +28,8 @@ namespace utils
static void serialize(buffer_serializer& buffer, const mapped_module& mod)
{
buffer.write_string(mod.name);
buffer.write(mod.path.u16string());
buffer.write(mod.name);
buffer.write(mod.path);
buffer.write(mod.image_base);
buffer.write(mod.size_of_image);
@@ -41,8 +41,8 @@ namespace utils
static void deserialize(buffer_deserializer& buffer, mapped_module& mod)
{
mod.name = buffer.read_string();
mod.path = buffer.read_string<std::u16string::value_type>();
buffer.read(mod.name);
buffer.read(mod.path);
buffer.read(mod.image_base);
buffer.read(mod.size_of_image);
@@ -59,7 +59,7 @@ module_manager::module_manager(emulator& emu, file_system& file_sys)
{
}
mapped_module* module_manager::map_module(const std::filesystem::path& file, const logger& logger)
mapped_module* module_manager::map_module(const windows_path& file, const logger& logger)
{
return this->map_local_module(this->file_sys_->translate(file), logger);
}
@@ -70,7 +70,7 @@ mapped_module* module_manager::map_local_module(const std::filesystem::path& fil
for (auto& mod : this->modules_ | std::views::values)
{
if (mod.path == file)
if (mod.path == local_file)
{
return &mod;
}

View File

@@ -12,7 +12,7 @@ class module_manager
using module_map = std::map<uint64_t, mapped_module>;
module_manager(emulator& emu, file_system& file_sys);
mapped_module* map_module(const std::filesystem::path& file, const logger& logger);
mapped_module* map_module(const windows_path& file, const logger& logger);
mapped_module* map_local_module(const std::filesystem::path& file, const logger& logger);
mapped_module* find_by_address(const uint64_t address)

View File

@@ -192,7 +192,7 @@ namespace
return STATUS_OBJECT_NAME_NOT_FOUND;
}
const std::wstring original_name(value->name.begin(), value->name.end());
const std::u16string original_name(value->name.begin(), value->name.end());
if (key_value_information_class == KeyValueBasicInformation)
{
@@ -2897,10 +2897,10 @@ namespace
const auto filename = read_unicode_string(
c.emu, emulator_object<UNICODE_STRING<EmulatorTraits<Emu64>>>{c.emu, attributes.ObjectName});
const auto u8_filename = u16_to_u8(filename);
const auto local_filename = c.win_emu.file_sys.translate(filename).string();
struct _stat64 file_stat{};
if (_stat64(u8_filename.c_str(), &file_stat) != 0)
if (_stat64(local_filename.c_str(), &file_stat) != 0)
{
return STATUS_OBJECT_NAME_NOT_FOUND;
}

View File

@@ -208,11 +208,6 @@ namespace
emu.reg<uint16_t>(x64_register::ss, 0x2B);
}
std::filesystem::path canonicalize_path(const std::filesystem::path& path)
{
return canonical(absolute(path)).make_preferred();
}
void setup_context(windows_emulator& win_emu, const emulator_settings& settings)
{
auto& emu = win_emu.emu();
@@ -220,7 +215,7 @@ namespace
setup_gdt(emu);
context.registry = registry_manager(settings.registry_directory);
context.registry = registry_manager(win_emu.root_directory / "registry");
context.kusd.setup(settings.use_relative_time);
@@ -260,7 +255,9 @@ namespace
allocator.copy_string(u"SystemRoot=C:\\WINDOWS");
allocator.copy_string(u"");
std::u16string command_line = u"\"" + settings.application.u16string() + u"\"";
const auto application_str = settings.application.u16string();
std::u16string command_line = u"\"" + application_str + u"\"";
for (const auto& arg : settings.arguments)
{
@@ -270,9 +267,8 @@ namespace
allocator.make_unicode_string(proc_params.CommandLine, command_line);
allocator.make_unicode_string(proc_params.CurrentDirectory.DosPath,
win_emu.file_sys.get_working_directory());
allocator.make_unicode_string(proc_params.ImagePathName,
canonicalize_path(settings.application).u16string());
win_emu.file_sys.get_working_directory().u16string());
allocator.make_unicode_string(proc_params.ImagePathName, application_str);
const auto total_length = allocator.get_next_address() - context.process_params.value();
@@ -824,11 +820,15 @@ std::unique_ptr<x64_emulator> create_default_x64_emulator()
windows_emulator::windows_emulator(const emulator_settings& settings, emulator_callbacks callbacks,
std::unique_ptr<x64_emulator> emu)
: windows_emulator(std::move(emu))
: windows_emulator(settings.root_filesystem, std::move(emu))
{
if (!settings.working_directory.empty())
{
this->file_sys.set_working_directory(settings.working_directory.u16string());
this->file_sys.set_working_directory(settings.working_directory);
}
else
{
this->file_sys.set_working_directory(settings.application.parent());
}
this->silent_until_main_ = settings.silent_until_main && !settings.disable_logging;
@@ -838,8 +838,9 @@ windows_emulator::windows_emulator(const emulator_settings& settings, emulator_c
this->setup_process(settings);
}
windows_emulator::windows_emulator(std::unique_ptr<x64_emulator> emu)
: file_sys(R"(C:\Users\mahe.WIBU\Desktop\emulator\src\tools\root)", u"C:\\"),
windows_emulator::windows_emulator(const std::filesystem::path& root_path, std::unique_ptr<x64_emulator> emu)
: root_directory{absolute(root_path)},
file_sys(root_directory / "filesys", u"C:\\"),
emu_(std::move(emu)),
process_(*emu_, file_sys)
{
@@ -855,7 +856,7 @@ void windows_emulator::setup_process(const emulator_settings& settings)
setup_context(*this, settings);
context.executable = context.mod_manager.map_local_module(settings.application, this->log);
context.executable = context.mod_manager.map_module(settings.application, this->log);
context.peb.access([&](PEB64& peb) {
peb.ImageBaseAddress = reinterpret_cast<std::uint64_t*>(context.executable->image_base); //

View File

@@ -27,9 +27,9 @@ struct emulator_callbacks
// TODO: Split up into application and emulator settings
struct emulator_settings
{
std::filesystem::path application{};
std::filesystem::path working_directory{};
std::filesystem::path registry_directory{"./registry"};
windows_path application{};
windows_path working_directory{};
std::filesystem::path root_filesystem{};
std::vector<std::u16string> arguments{};
bool disable_logging{false};
bool silent_until_main{false};
@@ -47,7 +47,8 @@ enum class apiset_location
class windows_emulator
{
public:
windows_emulator(std::unique_ptr<x64_emulator> emu = create_default_x64_emulator());
windows_emulator(const std::filesystem::path& root_path,
std::unique_ptr<x64_emulator> emu = create_default_x64_emulator());
windows_emulator(const emulator_settings& settings, emulator_callbacks callbacks = {},
std::unique_ptr<x64_emulator> emu = create_default_x64_emulator());
@@ -132,6 +133,7 @@ class windows_emulator
return this->callbacks_;
}
std::filesystem::path root_directory{};
file_system file_sys;
private:

View File

@@ -0,0 +1,236 @@
#pragma once
#include <list>
#include <optional>
#include <filesystem>
#include <utils/string.hpp>
#include <serialization.hpp>
namespace windows_path_detail
{
constexpr std::u16string_view unc_prefix = u"\\??\\";
inline std::u16string_view strip_unc_prefix(const std::u16string_view path)
{
if (!path.starts_with(unc_prefix))
{
return path;
}
return path.substr(unc_prefix.size());
}
inline bool is_slash(const char16_t chr)
{
return chr == u'\\' || chr == u'/';
}
}
class windows_path
{
public:
windows_path() = default;
windows_path(const std::filesystem::path& path)
{
const auto full_path = path.u16string();
const auto canonical_path = windows_path_detail::strip_unc_prefix(full_path);
std::u16string folder{};
for (const auto chr : canonical_path)
{
if (chr == u':' && this->folders_.empty() && !this->drive_.has_value() && folder.size() == 1)
{
this->drive_ = static_cast<char>(folder[0]);
folder.clear();
continue;
}
if (windows_path_detail::is_slash(chr))
{
if (folder.empty())
{
continue;
}
this->folders_.push_back(std::move(folder));
folder = {};
continue;
}
folder.push_back(chr);
}
if (!folder.empty())
{
this->folders_.push_back(folder);
}
this->canonicalize();
}
template <typename T>
requires(!std::is_same_v<T, windows_path> && !std::is_same_v<T, std::filesystem::path>)
windows_path(T&& path_like)
: windows_path(std::filesystem::path(std::forward<T>(path_like)))
{
}
windows_path(const std::optional<char> drive, std::list<std::u16string> folders)
: drive_(drive),
folders_(std::move(folders))
{
this->canonicalize();
}
bool is_absolute() const
{
return this->drive_.has_value();
}
bool is_relative() const
{
return !this->is_absolute();
}
std::u16string u16string() const
{
std::u16string path{};
if (this->drive_)
{
path.push_back(static_cast<char16_t>(*this->drive_));
path.push_back(u':');
}
for (const auto& folder : this->folders_)
{
if (!path.empty())
{
path.push_back(u'\\');
}
path.append(folder);
}
return path;
}
std::string string() const
{
return u16_to_u8(this->u16string());
}
std::u16string to_unc_path() const
{
if (this->is_relative())
{
return this->u16string();
}
return std::u16string(windows_path_detail::unc_prefix) + this->u16string();
}
std::filesystem::path to_portable_path() const
{
std::u16string path{};
if (this->drive_)
{
path.push_back(static_cast<char16_t>(*this->drive_));
}
for (const auto& folder : this->folders_)
{
if (!path.empty())
{
path.push_back(u'/');
}
path.append(folder);
}
return path;
}
windows_path operator/(const windows_path& path) const
{
if (path.is_absolute())
{
return path;
}
auto folders = this->folders_;
for (const auto& folder : path.folders_)
{
folders.push_back(folder);
}
return {this->drive_, std::move(folders)};
}
windows_path& operator/=(const windows_path& path)
{
*this = *this / path;
return *this;
}
windows_path parent() const
{
auto folders = this->folders_;
if (!folders.empty())
{
folders.pop_back();
}
return {this->drive_, std::move(folders)};
}
void serialize(utils::buffer_serializer& buffer) const
{
buffer.write_optional(this->drive_);
buffer.write_list(this->folders_);
}
void deserialize(utils::buffer_deserializer& buffer)
{
buffer.read_optional(this->drive_);
buffer.read_list(this->folders_);
}
bool operator==(const windows_path& other) const
{
return this->drive_ == other.drive_ && this->folders_ == other.folders_;
}
bool operator!=(const windows_path& other) const
{
return !this->operator==(other);
}
bool empty() const
{
return !this->is_relative() && this->folders_.empty();
}
private:
std::optional<char> drive_{};
std::list<std::u16string> folders_{};
void canonicalize()
{
if (this->drive_.has_value())
{
this->drive_ = utils::string::char_to_lower(*this->drive_);
}
for (auto& folder : this->folders_)
{
for (auto& chr : folder)
{
chr = utils::string::char_to_lower(chr);
}
}
}
};