Add cross platform filesystem support (#94)

This commit is contained in:
Maurice Heumann
2025-01-25 08:42:31 +01:00
committed by GitHub
30 changed files with 899 additions and 288 deletions

View File

@@ -28,22 +28,57 @@ jobs:
clang-format-version: '19'
check-path: 'src'
dump-registry:
name: Dump Registry
runs-on: windows-latest
dump-root:
name: Dump Root FS
runs-on: ${{ matrix.runner }}
needs: [build]
strategy:
fail-fast: false
matrix:
platform:
- Windows 2025
- Windows 2022
- Windows 2019
include:
- platform: Windows 2025
runner: windows-2025
- platform: Windows 2022
runner: windows-2022
- platform: Windows 2019
runner: windows-2019
steps:
- name: Checkout Source
uses: actions/checkout@v4
- name: Dump Registry
run: src/tools/grab-registry.bat
- name: Download DirectX Runtime
run: curl -L -o directx_Jun2010_redist.exe https://download.microsoft.com/download/8/4/A/84A35BF1-DAFE-4AE8-82AF-AD2AE20B6B14/directx_Jun2010_redist.exe
- name: Extract DirectX Runtime
run: ./directx_Jun2010_redist.exe /Q /T:"${{github.workspace}}/dxrt"
- name: Install DirectX Runtime
run: "cmd /c \"start /wait .\\dxrt\\dxsetup.exe /silent\""
- name: Download Windows Artifacts
uses: pyTooling/download-artifact@v4
with:
name: Windows Release Artifacts
path: build/release/artifacts
- name: Dump Root FS
run: src/tools/create-root.bat
- name: Dump API Set
run: cd root && ../build/release/artifacts/dump-apiset.exe
- name: Upload Artifacts
uses: actions/upload-artifact@v4
uses: pyTooling/upload-artifact@v4
with:
name: Temp Registry Dump
path: |
registry/*
name: ${{ matrix.platform }} Root FS
path: "*"
working-directory: root
retention-days: 1
build:
name: Build
@@ -55,7 +90,8 @@ jobs:
- Windows
- Linux GCC
- Linux Clang
- macOS
- macOS arm64
- macOS x86_64
- Android x86_64
- Android arm64-v8a
configuration:
@@ -73,8 +109,10 @@ jobs:
- platform: Linux Clang
runner: ubuntu-24.04
clang-version: 18
- platform: macOS
- platform: macOS arm64
runner: macos-latest
- platform: macOS x86_64
runner: macos-13
- platform: Android x86_64
runner: ubuntu-24.04
abi: x86_64
@@ -127,35 +165,36 @@ jobs:
if: ${{ !startsWith(matrix.platform, 'Android') }}
- name: Upload Artifacts
uses: actions/upload-artifact@v4
uses: pyTooling/upload-artifact@v4
with:
name: ${{ matrix.platform }} ${{matrix.configuration}} Artifacts
path: |
build/${{matrix.preset}}/artifacts/*
working-directory: build/${{matrix.preset}}/artifacts/
path: "*"
- name: Upload Test Configuration
uses: actions/upload-artifact@v4
uses: pyTooling/upload-artifact@v4
with:
name: Temp ${{ matrix.platform }} ${{matrix.configuration}} Test Config
path: |
build/${{matrix.preset}}/**/CTestTestfile.cmake
path: "**/CTestTestfile.cmake"
working-directory: build/${{matrix.preset}}
test:
name: Test
runs-on: ${{ matrix.runner }}
needs: [dump-registry, build]
needs: [dump-root, build]
strategy:
fail-fast: false
matrix:
platform:
# TODO: Move different windows platforms into registry dump matrix
filesystem:
- Windows 2025
- Windows 2022
- Windows 2019
platform:
- Windows
- Linux GCC
- Linux Clang
- macOS
- macOS arm64
- macOS x86_64
configuration:
- Debug
- Release
@@ -164,58 +203,54 @@ jobs:
preset: debug
- configuration: Release
preset: release
- platform: Windows 2025
build-platform: Windows
runner: windows-2025
- platform: Windows 2022
build-platform: Windows
runner: windows-2022
- platform: Windows 2019
build-platform: Windows
runner: windows-2019
- platform: Windows
runner: windows-latest
- platform: Linux GCC
build-platform: Linux GCC
runner: ubuntu-24.04
- platform: Linux Clang
build-platform: Linux Clang
runner: ubuntu-24.04
- platform: macOS
build-platform: macOS
- platform: macOS arm64
runner: macos-latest
- platform: macOS x86_64
runner: macos-13
steps:
- name: Download Test Config
uses: actions/download-artifact@v4
uses: pyTooling/download-artifact@v4
with:
name: Temp ${{ matrix.build-platform }} ${{matrix.configuration}} Test Config
name: Temp ${{ matrix.platform }} ${{matrix.configuration}} Test Config
path: build/${{matrix.preset}}
- name: Download Artifacts
uses: actions/download-artifact@v4
uses: pyTooling/download-artifact@v4
with:
name: ${{ matrix.build-platform }} ${{matrix.configuration}} Artifacts
name: ${{ matrix.platform }} ${{matrix.configuration}} Artifacts
path: build/${{matrix.preset}}/artifacts
- name: Download Windows Artifacts
uses: actions/download-artifact@v4
if: "${{ matrix.build-platform != 'Windows' }}"
uses: pyTooling/download-artifact@v4
if: "${{ matrix.platform != 'Windows' }}"
with:
name: Windows ${{matrix.configuration}} Artifacts
path: build/${{matrix.preset}}/artifacts
- name: Download Registry Dump
uses: actions/download-artifact@v4
- name: Download Root FS
uses: pyTooling/download-artifact@v4
with:
name: Temp Registry Dump
path: build/${{matrix.preset}}/artifacts/registry
name: ${{ 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
if: "${{ matrix.build-platform == 'Windows' }}"
env:
EMULATOR_ROOT: ${{github.workspace}}/build/${{matrix.preset}}/artifacts/root
summary:
name: Pipeline Summary
runs-on: ubuntu-24.04
needs: [dump-registry, build, test, verify-formatting]
needs: [dump-root, build, test, verify-formatting]
if: always()
steps:
- uses: geekyeggo/delete-artifact@v5

View File

@@ -24,7 +24,11 @@ set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_OSX_DEPLOYMENT_TARGET 11.0)
set(CMAKE_OSX_ARCHITECTURES "arm64;x86_64")
# Prevent unicorn from generating universal binaries on macOS
# It doesn't support it, even if it thinks it does...
set(ENV{ARCHFLAGS} "nope")
##########################################

View File

@@ -26,7 +26,10 @@ set(CMAKE_INTERPROCEDURAL_OPTIMIZATION ON)
##########################################
if(UNIX)
momo_add_c_and_cxx_compile_options(-fvisibility=hidden)
momo_add_c_and_cxx_compile_options(
-fvisibility=hidden
-ftrivial-auto-var-init=zero
)
endif()
##########################################
@@ -91,6 +94,8 @@ if(MSVC)
momo_add_release_link_options(
#/LTCG
)
add_compile_definitions(_CRT_SECURE_NO_WARNINGS)
endif()
##########################################

2
deps/unicorn vendored

View File

@@ -11,7 +11,9 @@ namespace
{
bool use_gdb{false};
bool concise_logging{false};
bool verbose_logging{false};
std::string registry_path{"./registry"};
std::string emulation_root{};
};
void watch_system_objects(windows_emulator& win_emu, const bool cache_logging)
@@ -107,6 +109,7 @@ namespace
emulator_settings settings{
.application = args[0],
.registry_directory = options.registry_path,
.emulation_root = options.emulation_root,
.arguments = parse_arguments(args),
.silent_until_main = options.concise_logging,
};
@@ -116,7 +119,7 @@ namespace
(void)&watch_system_objects;
watch_system_objects(win_emu, options.concise_logging);
win_emu.buffer_stdout = true;
// win_emu.verbose_calls = true;
win_emu.verbose_calls = options.verbose_logging;
const auto& exe = *win_emu.process().executable;
@@ -200,10 +203,23 @@ namespace
{
options.use_gdb = true;
}
else if (arg == "-v")
{
options.verbose_logging = true;
}
else if (arg == "-c")
{
options.concise_logging = true;
}
else if (arg == "-e")
{
if (args.size() < 2)
{
throw std::runtime_error("No emulation root path provided after -e");
}
arg_it = args.erase(arg_it);
options.emulation_root = args[0];
}
else if (arg == "-r")
{
if (args.size() < 2)

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

@@ -31,6 +31,8 @@ using NTSTATUS = std::uint32_t;
#define STATUS_ACCESS_DENIED ((NTSTATUS)0xC0000022L)
#define STATUS_BUFFER_TOO_SMALL ((NTSTATUS)0xC0000023L)
#define STATUS_OBJECT_NAME_NOT_FOUND ((NTSTATUS)0xC0000034L)
#define STATUS_MUTANT_NOT_OWNED ((NTSTATUS)0xC0000046L)
#define STATUS_SEMAPHORE_LIMIT_EXCEEDED ((NTSTATUS)0xC0000047L)
#define STATUS_NO_TOKEN ((NTSTATUS)0xC000007CL)
#define STATUS_FILE_INVALID ((NTSTATUS)0xC0000098L)
#define STATUS_MEMORY_NOT_ALLOCATED ((NTSTATUS)0xC00000A0L)

View File

@@ -1,6 +1,7 @@
#pragma once
#include <string>
#include <filesystem>
template <typename Traits>
struct UNICODE_STRING
@@ -62,9 +63,9 @@ inline std::string w_to_u8(const std::wstring_view w_view)
}
#ifndef OS_WINDOWS
inline int open_unicode(FILE** handle, const std::u16string& fileName, const std::u16string& mode)
inline int open_unicode(FILE** handle, const std::filesystem::path& fileName, const std::u16string& mode)
{
*handle = fopen(u16_to_u8(fileName).c_str(), u16_to_u8(mode).c_str());
*handle = fopen(fileName.string().c_str(), u16_to_u8(mode).c_str());
return errno;
}
#else
@@ -73,8 +74,8 @@ inline std::wstring u16_to_w(const std::u16string& u16str)
return std::wstring(reinterpret_cast<const wchar_t*>(u16str.data()), u16str.size());
}
inline auto open_unicode(FILE** handle, const std::u16string& fileName, const std::u16string& mode)
inline auto open_unicode(FILE** handle, const std::filesystem::path& fileName, const std::u16string& mode)
{
return _wfopen_s(handle, u16_to_w(fileName).c_str(), u16_to_w(mode).c_str());
return _wfopen_s(handle, fileName.wstring().c_str(), u16_to_w(mode).c_str());
}
#endif

View File

@@ -278,7 +278,7 @@ typedef struct _IMAGE_BASE_RELOCATION
{
DWORD VirtualAddress;
DWORD SizeOfBlock;
WORD TypeOffset[1];
// WORD TypeOffset[1];
} IMAGE_BASE_RELOCATION, *PIMAGE_BASE_RELOCATION;
#endif

View File

@@ -39,7 +39,10 @@ namespace utils
static std::filesystem::path canonicalize_path(const std::filesystem::path& key)
{
auto path = key.lexically_normal().wstring();
auto key_string = key.u16string();
std::ranges::replace(key_string, u'\\', '/');
auto path = std::filesystem::path(key_string).lexically_normal().wstring();
return utils::string::to_lower_consume(path);
}

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

@@ -290,7 +290,7 @@ bool test_native_exceptions()
void print_time()
{
const auto epoch_time = std::chrono::system_clock::now().time_since_epoch();
printf("Time: %lld\n", epoch_time.count());
printf("Time: %lld\n", std::chrono::duration_cast<std::chrono::nanoseconds>(epoch_time).count());
}
#define RUN_TEST(func, name) \

2
src/tools/.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
root
registry

View File

@@ -1,89 +0,0 @@
@ECHO OFF
:: Host system directories
SET SYSDIR="%WINDIR%\System32"
:: Qiling rootfs directories
SET QL_WINDIR="Windows"
SET QL_SYSDIR="%QL_WINDIR%\System32"
MKDIR %QL_WINDIR%
MKDIR %QL_SYSDIR%
:: Collect 32-bit DLL files
CALL :collect_dll advapi32.dll
CALL :collect_dll bcrypt.dll
CALL :collect_dll cfgmgr32.dll
CALL :collect_dll ci.dll
CALL :collect_dll combase.dll
CALL :collect_dll comctl32.dll
CALL :collect_dll comdlg32.dll
CALL :collect_dll crypt32.dll
CALL :collect_dll cryptbase.dll
CALL :collect_dll gdi32.dll
CALL :collect_dll hal.dll
CALL :collect_dll iphlpapi.dll
CALL :collect_dll kdcom.dll
CALL :collect_dll kernel32.dll
CALL :collect_dll KernelBase.dll
CALL :collect_dll mpr.dll
CALL :collect_dll mscoree.dll
CALL :collect_dll msvcp_win.dll
CALL :collect_dll msvcp60.dll
CALL :collect_dll msvcr120_clr0400.dll, msvcr110.dll
CALL :collect_dll msvcrt.dll
CALL :collect_dll netapi32.dll
CALL :collect_dll ntdll.dll
CALL :collect_dll ole32.dll
CALL :collect_dll oleaut32.dll
CALL :collect_dll psapi.dll
CALL :collect_dll rpcrt4.dll
CALL :collect_dll sechost.dll
CALL :collect_dll setupapi.dll
CALL :collect_dll shell32.dll
CALL :collect_dll shlwapi.dll
CALL :collect_dll sspicli.dll
CALL :collect_dll ucrtbase.dll
CALL :collect_dll ucrtbased.dll
CALL :collect_dll urlmon.dll
CALL :collect_dll user32.dll
CALL :collect_dll userenv.dll
CALL :collect_dll uxtheme.dll
CALL :collect_dll vcruntime140.dll
CALL :collect_dll vcruntime140d.dll
CALL :collect_dll vcruntime140_1.dll
CALL :collect_dll vcruntime140_1d.dll
CALL :collect_dll version.dll
CALL :collect_dll win32u.dll
CALL :collect_dll winhttp.dll
CALL :collect_dll wininet.dll
CALL :collect_dll winmm.dll
CALL :collect_dll ws2_32.dll
CALL :collect_dll wsock32.dll
CALL :collect_dll locale.nls
:: Collect extras
CALL :collect %SYSDIR64%, ntoskrnl.exe, %QL_SYSDIR32%
:: All done!
EXIT /B 0
:: Functions definitions
:normpath
SET %1=%~dpfn2
EXIT /B
:collect
CALL :normpath SRC, %~1\%~2
CALL :normpath DST, %~3\%~4
IF EXIST %SRC% (
ECHO %SRC% -^> %DST%
COPY /B /Y "%SRC%" "%DST%" >NUL
)
EXIT /B
:collect_dll
CALL :collect %SYSDIR%, %~1, %QL_SYSDIR%, %~2
EXIT /B

136
src/tools/create-root.bat Normal file
View File

@@ -0,0 +1,136 @@
@ECHO OFF
NET SESSIONS > NUL 2>&1
IF %ERRORLEVEL% NEQ 0 (
ECHO Error: This script requires administrative privileges.
EXIT /B 1
)
SET SYSDIR="%WINDIR%\System32"
:: Qiling rootfs directories
SET EMU_ROOT=root
SET EMU_FILESYS=%EMU_ROOT%\filesys
SET EMU_WINDIR=%EMU_FILESYS%\c\windows
SET EMU_SYSDIR=%EMU_WINDIR%\system32
SET EMU_REGDIR=%EMU_ROOT%\registry
MKDIR %EMU_SYSDIR%
MKDIR %EMU_REGDIR%
REG SAVE HKLM\SYSTEM %EMU_REGDIR%\SYSTEM /Y
REG SAVE HKLM\SECURITY %EMU_REGDIR%\SECURITY /Y
REG SAVE HKLM\SOFTWARE %EMU_REGDIR%\SOFTWARE /Y
REG SAVE HKLM\HARDWARE %EMU_REGDIR%\HARDWARE /Y
REG SAVE HKLM\SAM %EMU_REGDIR%\SAM /Y
COPY /B /Y C:\Users\Default\NTUSER.DAT "%EMU_REGDIR%\NTUSER.DAT"
CALL :collect_dll advapi32.dll
CALL :collect_dll bcrypt.dll
CALL :collect_dll cfgmgr32.dll
CALL :collect_dll ci.dll
CALL :collect_dll combase.dll
CALL :collect_dll comctl32.dll
CALL :collect_dll comdlg32.dll
CALL :collect_dll crypt32.dll
CALL :collect_dll cryptbase.dll
CALL :collect_dll gdi32.dll
CALL :collect_dll hal.dll
CALL :collect_dll iphlpapi.dll
CALL :collect_dll kdcom.dll
CALL :collect_dll kernel32.dll
CALL :collect_dll kernelbase.dll
CALL :collect_dll mpr.dll
CALL :collect_dll mscoree.dll
CALL :collect_dll msvcp_win.dll
CALL :collect_dll msvcp60.dll
CALL :collect_dll msvcr120_clr0400.dll
CALL :collect_dll msvcrt.dll
CALL :collect_dll netapi32.dll
CALL :collect_dll ntdll.dll
CALL :collect_dll ole32.dll
CALL :collect_dll oleaut32.dll
CALL :collect_dll psapi.dll
CALL :collect_dll rpcrt4.dll
CALL :collect_dll sechost.dll
CALL :collect_dll setupapi.dll
CALL :collect_dll shell32.dll
CALL :collect_dll shlwapi.dll
CALL :collect_dll sspicli.dll
CALL :collect_dll ucrtbase.dll
CALL :collect_dll ucrtbased.dll
CALL :collect_dll urlmon.dll
CALL :collect_dll user32.dll
CALL :collect_dll userenv.dll
CALL :collect_dll uxtheme.dll
CALL :collect_dll vcruntime140.dll
CALL :collect_dll vcruntime140d.dll
CALL :collect_dll vcruntime140_1.dll
CALL :collect_dll vcruntime140_1d.dll
CALL :collect_dll version.dll
CALL :collect_dll win32u.dll
CALL :collect_dll winhttp.dll
CALL :collect_dll wininet.dll
CALL :collect_dll winmm.dll
CALL :collect_dll ws2_32.dll
CALL :collect_dll wsock32.dll
CALL :collect_dll msvcp140.dll
CALL :collect_dll msvcp140d.dll
CALL :collect_dll d3d11.dll
CALL :collect_dll d3d9.dll
CALL :collect_dll d3d12.dll
CALL :collect_dll d3dcompiler_47.dll
CALL :collect_dll dxgi.dll
CALL :collect_dll dsound.dll
CALL :collect_dll dwmapi.dll
CALL :collect_dll hid.dll
CALL :collect_dll imm32.dll
CALL :collect_dll uiautomationcore.dll
CALL :collect_dll opengl32.dll
CALL :collect_dll normaliz.dll
CALL :collect_dll wintrust.dll
CALL :collect_dll wldap32.dll
CALL :collect_dll wtsapi32.dll
CALL :collect_dll x3daudio1_7.dll
CALL :collect_dll xapofx1_5.dll
CALL :collect_dll xinput1_3.dll
CALL :collect_dll xinput9_1_0.dll
CALL :collect_dll cryptsp.dll
CALL :collect_dll resampledmo.dll
CALL :collect_dll powrprof.dll
CALL :collect_dll winmmbase.dll
CALL :collect_dll gdi32full.dll
CALL :collect_dll glu32.dll
CALL :collect_dll msdmo.dll
CALL :collect_dll dxcore.dll
CALL :collect_dll mfplat.dll
CALL :collect_dll wer.dll
CALL :collect_dll dbghelp.dll
CALL :collect_dll mscms.dll
CALL :collect_dll ktmw32.dll
CALL :collect_dll shcore.dll
CALL :collect_dll diagnosticdatasettings.dll
CALL :collect_dll locale.nls
:: All done!
EXIT /B 0
:: Functions definitions
:normpath
SET %1=%~dpfn2
EXIT /B
:collect
CALL :normpath SRC, %~1\%~2
CALL :normpath DST, %~3\%~2
IF EXIST %SRC% (
ECHO %SRC% -^> %DST%
COPY /B /Y "%SRC%" "%DST%" >NUL
)
EXIT /B
:collect_dll
CALL :collect %SYSDIR%, %~1, %EMU_SYSDIR%
EXIT /B

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.emulation_root = 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

@@ -0,0 +1,86 @@
#pragma once
#include "std_include.hpp"
#include "windows_path.hpp"
#include <platform/compiler.hpp>
class file_system
{
public:
file_system(std::filesystem::path root, windows_path working_dir = "C:\\")
: root_(std::move(root)),
working_dir_(std::move(working_dir))
{
}
std::filesystem::path translate(const windows_path& win_path) const
{
const auto& full_path = win_path.is_absolute() //
? win_path
: (this->working_dir_ / win_path);
#ifdef OS_WINDOWS
if (this->root_.empty())
{
return full_path.u16string();
}
#endif
// TODO: Sanitize path to prevent traversal!
return this->root_ / full_path.to_portable_path();
}
void set_working_directory(windows_path working_dir)
{
this->working_dir_ = std::move(working_dir);
}
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)};
}
void serialize(utils::buffer_serializer& buffer) const
{
buffer.write(this->working_dir_);
}
void deserialize(utils::buffer_deserializer& buffer)
{
buffer.read(this->working_dir_);
}
private:
std::filesystem::path root_{};
windows_path working_dir_{};
};

View File

@@ -114,7 +114,7 @@ namespace handle_detail
struct generic_handle_store
{
virtual ~generic_handle_store() = default;
virtual bool erase(const handle h) = 0;
virtual bool erase(handle h) = 0;
};
template <handle_types::type Type, typename T, uint32_t IndexShift = 0>
@@ -205,7 +205,7 @@ class handle_store : public generic_handle_store
{
if (!T::deleter(entry->second))
{
return false;
return true;
}
}
@@ -354,6 +354,8 @@ constexpr auto BASE_NAMED_OBJECTS_DIRECTORY = make_pseudo_handle(0x2, handle_typ
constexpr auto KNOWN_DLLS_SYMLINK = make_pseudo_handle(0x1, handle_types::symlink);
constexpr auto SHARED_SECTION = make_pseudo_handle(0x1, handle_types::section);
constexpr auto WER_PORT_READY = make_pseudo_handle(0x1, handle_types::event);
constexpr auto CONSOLE_HANDLE = make_pseudo_handle(0x1, handle_types::file);
constexpr auto STDOUT_HANDLE = make_pseudo_handle(0x2, handle_types::file);
constexpr auto STDIN_HANDLE = make_pseudo_handle(0x3, handle_types::file);

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

@@ -3,21 +3,7 @@
#include "module_mapping.hpp"
#include "windows-emulator/logger.hpp"
namespace
{
std::filesystem::path canonicalize_module_path(const std::filesystem::path& file)
{
constexpr std::u16string_view nt_prefix = u"\\??\\";
const auto wide_file = file.u16string();
if (!wide_file.starts_with(nt_prefix))
{
return canonical(absolute(file));
}
return canonicalize_module_path(wide_file.substr(nt_prefix.size()));
}
}
#include <serialization_helper.hpp>
namespace utils
{
@@ -39,8 +25,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);
@@ -52,8 +38,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);
@@ -64,26 +50,32 @@ namespace utils
}
}
module_manager::module_manager(emulator& emu)
: emu_(&emu)
module_manager::module_manager(emulator& emu, file_system& file_sys)
: emu_(&emu),
file_sys_(&file_sys)
{
}
mapped_module* module_manager::map_module(const std::filesystem::path& file, logger& logger)
mapped_module* module_manager::map_module(const windows_path& file, const logger& logger)
{
auto canonical_file = canonicalize_module_path(file);
return this->map_local_module(this->file_sys_->translate(file), logger);
}
for (auto& mod : this->modules_)
mapped_module* module_manager::map_local_module(const std::filesystem::path& file, const logger& logger)
{
auto local_file = canonical(absolute(file));
for (auto& mod : this->modules_ | std::views::values)
{
if (mod.second.path == canonical_file)
if (mod.path == local_file)
{
return &mod.second;
return &mod;
}
}
try
{
auto mod = map_module_from_file(*this->emu_, std::move(canonical_file));
auto mod = map_module_from_file(*this->emu_, std::move(local_file));
logger.log("Mapped %s at 0x%" PRIx64 "\n", mod.path.generic_string().c_str(), mod.image_base);

View File

@@ -1,16 +1,19 @@
#pragma once
#include "mapped_module.hpp"
#include <emulator.hpp>
#include "mapped_module.hpp"
#include "../file_system.hpp"
class logger;
class module_manager
{
public:
using module_map = std::map<uint64_t, mapped_module>;
module_manager(emulator& emu);
module_manager(emulator& emu, file_system& file_sys);
mapped_module* map_module(const std::filesystem::path& file, 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)
{
@@ -45,6 +48,7 @@ class module_manager
private:
emulator* emu_{};
file_system* file_sys_{};
module_map modules_{};

View File

@@ -108,17 +108,17 @@ struct mutant : ref_counted_object
return true;
}
uint32_t release()
std::pair<uint32_t, bool> release(const uint32_t thread_id)
{
const auto old_count = this->locked_count;
if (this->locked_count <= 0)
if (this->locked_count <= 0 || this->owning_thread_id != thread_id)
{
return old_count;
return {old_count, false};
}
--this->locked_count;
return old_count;
return {old_count, true};
}
void serialize(utils::buffer_serializer& buffer) const
@@ -239,9 +239,34 @@ struct section
struct semaphore : ref_counted_object
{
std::u16string name{};
volatile uint32_t current_count{};
uint32_t current_count{};
uint32_t max_count{};
bool try_lock()
{
if (this->current_count > 0)
{
--this->current_count;
return true;
}
return false;
}
std::pair<uint32_t, bool> release(const uint32_t release_count)
{
const auto old_count = this->current_count;
if (this->current_count + release_count > this->max_count)
{
return {old_count, false};
}
this->current_count += release_count;
return {old_count, true};
}
void serialize(utils::buffer_serializer& buffer) const
{
buffer.write(this->name);
@@ -504,12 +529,12 @@ class emulator_thread : ref_counted_object
struct process_context
{
process_context(x64_emulator& emu)
process_context(x64_emulator& emu, file_system& file_sys)
: base_allocator(emu),
peb(emu),
process_params(emu),
kusd(emu, *this),
mod_manager(emu)
mod_manager(emu, file_sys)
{
}
@@ -550,7 +575,7 @@ struct process_context
std::vector<std::byte> default_register_set{};
uint32_t current_thread_id{0};
uint32_t spawned_thread_count{0};
handle_store<handle_types::thread, emulator_thread> threads{};
emulator_thread* active_thread{nullptr};
@@ -587,7 +612,7 @@ struct process_context
buffer.write_map(this->atoms);
buffer.write_vector(this->default_register_set);
buffer.write(this->current_thread_id);
buffer.write(this->spawned_thread_count);
buffer.write(this->threads);
buffer.write(this->threads.find_handle(this->active_thread).bits);
@@ -630,7 +655,7 @@ struct process_context
buffer.read_map(this->atoms);
buffer.read_vector(this->default_register_set);
buffer.read(this->current_thread_id);
buffer.read(this->spawned_thread_count);
buffer.read(this->threads);
@@ -640,7 +665,7 @@ struct process_context
handle create_thread(x64_emulator& emu, const uint64_t start_address, const uint64_t argument,
const uint64_t stack_size)
{
emulator_thread t{emu, *this, start_address, argument, stack_size, ++this->current_thread_id};
emulator_thread t{emu, *this, start_address, argument, stack_size, ++this->spawned_thread_count};
return this->threads.store(std::move(t));
}
};

View File

@@ -7,14 +7,14 @@
namespace
{
bool is_subpath(const std::filesystem::path& root, const std::filesystem::path& p)
bool is_subpath(const utils::path_key& root, const utils::path_key& p)
{
auto root_it = root.begin();
auto p_it = p.begin();
auto root_it = root.get().begin();
auto p_it = p.get().begin();
for (; root_it != root.end(); ++root_it, ++p_it)
for (; root_it != root.get().end(); ++root_it, ++p_it)
{
if (p_it == p.end() || *root_it != *p_it)
if (p_it == p.get().end() || *root_it != *p_it)
{
return false;
}
@@ -23,8 +23,7 @@ namespace
return true;
}
void register_hive(registry_manager::hive_map& hives, const std::filesystem::path& key,
const std::filesystem::path& file)
void register_hive(registry_manager::hive_map& hives, const utils::path_key& key, const std::filesystem::path& file)
{
hives[key] = std::make_unique<hive_parser>(file);
}
@@ -56,7 +55,7 @@ void registry_manager::setup()
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");
register_hive(this->hives_, root / "user", this->hive_path_ / "NTUSER.DAT");
this->add_path_mapping(machine / "system" / "CurrentControlSet", machine / "system" / "ControlSet001");
}
@@ -72,7 +71,7 @@ void registry_manager::deserialize(utils::buffer_deserializer& buffer)
this->setup();
}
std::filesystem::path registry_manager::normalize_path(const std::filesystem::path& path) const
utils::path_key registry_manager::normalize_path(const utils::path_key& path) const
{
const utils::path_key canonical_path = path;
@@ -87,16 +86,16 @@ std::filesystem::path registry_manager::normalize_path(const std::filesystem::pa
return canonical_path.get();
}
void registry_manager::add_path_mapping(const std::filesystem::path& key, const std::filesystem::path& value)
void registry_manager::add_path_mapping(const utils::path_key& key, const utils::path_key& value)
{
this->path_mapping_[key] = value;
}
std::optional<registry_key> registry_manager::get_key(const std::filesystem::path& key)
std::optional<registry_key> registry_manager::get_key(const utils::path_key& key)
{
const auto normal_key = this->normalize_path(key);
if (is_subpath(normal_key, "\\registry\\machine"))
if (is_subpath(normal_key, utils::path_key{"\\registry\\machine"}))
{
registry_key reg_key{};
reg_key.hive = normal_key;
@@ -111,14 +110,14 @@ std::optional<registry_key> registry_manager::get_key(const std::filesystem::pat
registry_key reg_key{};
reg_key.hive = iterator->first.get();
reg_key.path = normal_key.lexically_relative(reg_key.hive);
reg_key.path = normal_key.get().lexically_relative(reg_key.hive.get());
if (reg_key.path.empty())
if (reg_key.path.get().empty())
{
return {std::move(reg_key)};
}
const auto entry = iterator->second->get_sub_key(reg_key.path);
const auto entry = iterator->second->get_sub_key(reg_key.path.get());
if (!entry)
{
return std::nullopt;
@@ -137,7 +136,7 @@ std::optional<registry_value> registry_manager::get_value(const registry_key& ke
return std::nullopt;
}
auto* entry = iterator->second->get_value(key.path, name);
auto* entry = iterator->second->get_value(key.path.get(), name);
if (!entry)
{
return std::nullopt;
@@ -151,11 +150,11 @@ std::optional<registry_value> registry_manager::get_value(const registry_key& ke
return v;
}
registry_manager::hive_map::iterator registry_manager::find_hive(const std::filesystem::path& key)
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)
{
if (is_subpath(i->first.get(), key))
if (is_subpath(i->first, key))
{
return i;
}

View File

@@ -6,8 +6,8 @@
struct registry_key
{
std::filesystem::path hive{};
std::filesystem::path path{};
utils::path_key hive{};
utils::path_key path{};
void serialize(utils::buffer_serializer& buffer) const
{
@@ -48,7 +48,7 @@ class registry_manager
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_key> get_key(const utils::path_key& key);
std::optional<registry_value> get_value(const registry_key& key, std::string name);
private:
@@ -56,10 +56,10 @@ class registry_manager
hive_map hives_{};
std::unordered_map<utils::path_key, utils::path_key> 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);
utils::path_key normalize_path(const utils::path_key& path) const;
void add_path_mapping(const utils::path_key& key, const utils::path_key& value);
hive_map::iterator find_hive(const std::filesystem::path& key);
hive_map::iterator find_hive(const utils::path_key& key);
void setup();
};

View File

@@ -77,13 +77,13 @@ namespace
return STATUS_INVALID_HANDLE;
}
const std::filesystem::path full_path = parent_handle->hive / parent_handle->path / key;
const std::filesystem::path full_path = parent_handle->hive.get() / parent_handle->path.get() / key;
key = full_path.u16string();
}
c.win_emu.log.print(color::dark_gray, "--> Registry key: %s\n", u16_to_u8(key).c_str());
auto entry = c.proc.registry.get_key(key);
auto entry = c.proc.registry.get_key({key});
if (!entry.has_value())
{
return STATUS_OBJECT_NAME_NOT_FOUND;
@@ -115,8 +115,8 @@ namespace
if (key_information_class == KeyNameInformation)
{
auto key_name = (key->hive / key->path).wstring();
while (key_name.ends_with('/') || key_name.ends_with('\\'))
auto key_name = (key->hive.get() / key->path.get()).u16string();
while (key_name.ends_with(u'/') || key_name.ends_with(u'\\'))
{
key_name.pop_back();
}
@@ -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)
{
@@ -449,14 +449,14 @@ namespace
return STATUS_INVALID_HANDLE;
}
const auto old_count = mutant->release();
const auto [old_count, succeeded] = mutant->release(c.win_emu.current_thread().id);
if (previous_count)
{
previous_count.write(static_cast<LONG>(old_count));
}
return STATUS_SUCCESS;
return succeeded ? STATUS_SUCCESS : STATUS_MUTANT_NOT_OWNED;
}
NTSTATUS handle_NtCreateMutant(const syscall_context& c, const emulator_object<handle> mutant_handle,
@@ -477,10 +477,12 @@ namespace
if (!name.empty())
{
for (const auto& mutant : c.proc.mutants | std::views::values)
for (auto& entry : c.proc.mutants)
{
if (mutant.name == name)
if (entry.second.name == name)
{
++entry.second.ref_count;
mutant_handle.write(c.proc.mutants.make_handle(entry.first));
return STATUS_OBJECT_NAME_EXISTS;
}
}
@@ -518,10 +520,12 @@ namespace
if (!name.empty())
{
for (const auto& event : c.proc.events | std::views::values)
for (auto& entry : c.proc.events)
{
if (event.name == name)
if (entry.second.name == name)
{
++entry.second.ref_count;
event_handle.write(c.proc.events.make_handle(entry.first));
return STATUS_OBJECT_NAME_EXISTS;
}
}
@@ -549,6 +553,12 @@ namespace
const auto name =
read_unicode_string(c.emu, reinterpret_cast<UNICODE_STRING<EmulatorTraits<Emu64>>*>(attributes.ObjectName));
if (name == u"\\KernelObjects\\SystemErrorPortReady")
{
event_handle.write(WER_PORT_READY.bits);
return STATUS_SUCCESS;
}
for (auto& entry : c.proc.events)
{
if (entry.second.name == name)
@@ -1520,7 +1530,7 @@ namespace
if (!f->enumeration_state || query_flags & SL_RESTART_SCAN)
{
f->enumeration_state.emplace(file_enumeration_state{});
f->enumeration_state->files = scan_directory(f->name);
f->enumeration_state->files = scan_directory(c.win_emu.file_sys().translate(f->name));
}
auto& enum_state = *f->enumeration_state;
@@ -2575,7 +2585,8 @@ namespace
const emulator_object<LCID> default_locale_id,
const emulator_object<LARGE_INTEGER> /*default_casing_table_size*/)
{
const auto locale_file = utils::io::read_file(R"(C:\Windows\System32\locale.nls)");
const auto locale_file =
utils::io::read_file(c.win_emu.file_sys().translate(R"(C:\Windows\System32\locale.nls)"));
if (locale_file.empty())
{
return STATUS_FILE_INVALID;
@@ -2815,14 +2826,14 @@ namespace
if (create_disposition & FILE_CREATE)
{
std::error_code ec{};
std::filesystem::create_directory(f.name, ec);
create_directory(c.win_emu.file_sys().translate(f.name), ec);
if (ec)
{
return STATUS_ACCESS_DENIED;
}
}
else if (!std::filesystem::is_directory(f.name))
else if (!std::filesystem::is_directory(c.win_emu.file_sys().translate(f.name)))
{
return STATUS_OBJECT_NAME_NOT_FOUND;
}
@@ -2844,7 +2855,7 @@ namespace
FILE* file{};
const auto error = open_unicode(&file, f.name, mode);
const auto error = open_unicode(&file, c.win_emu.file_sys().translate(f.name), mode);
if (!file)
{
@@ -2886,10 +2897,13 @@ 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);
c.win_emu.log.print(color::dark_gray, "--> Querying file attributes: %s\n", u16_to_u8(filename).c_str());
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;
}
@@ -3016,6 +3030,32 @@ namespace
return STATUS_OBJECT_NAME_NOT_FOUND;
}
NTSTATUS handle_NtReleaseSemaphore(const syscall_context& c, const handle semaphore_handle,
const ULONG release_count, const emulator_object<LONG> previous_count)
{
if (semaphore_handle.value.type != handle_types::semaphore)
{
c.win_emu.log.error("Bad handle type for NtReleaseSemaphore\n");
c.emu.stop();
return STATUS_NOT_SUPPORTED;
}
auto* mutant = c.proc.semaphores.get(semaphore_handle);
if (!mutant)
{
return STATUS_INVALID_HANDLE;
}
const auto [old_count, succeeded] = mutant->release(release_count);
if (previous_count)
{
previous_count.write(static_cast<LONG>(old_count));
}
return succeeded ? STATUS_SUCCESS : STATUS_SEMAPHORE_LIMIT_EXCEEDED;
}
NTSTATUS handle_NtCreateSemaphore(const syscall_context& c, const emulator_object<handle> semaphore_handle,
const ACCESS_MASK /*desired_access*/,
const emulator_object<OBJECT_ATTRIBUTES<EmulatorTraits<Emu64>>> object_attributes,
@@ -3037,10 +3077,12 @@ namespace
if (!s.name.empty())
{
for (const auto& semaphore : c.proc.semaphores | std::views::values)
for (auto& entry : c.proc.semaphores)
{
if (semaphore.name == s.name)
if (entry.second.name == s.name)
{
++entry.second.ref_count;
semaphore_handle.write(c.proc.semaphores.make_handle(entry.first));
return STATUS_OBJECT_NAME_EXISTS;
}
}
@@ -3216,8 +3258,9 @@ namespace
bool is_awaitable_object_type(const handle h)
{
return h.value.type == handle_types::thread //
|| h.value.type == handle_types::mutant //
return h.value.type == handle_types::thread //
|| h.value.type == handle_types::mutant //
|| h.value.type == handle_types::semaphore //
|| h.value.type == handle_types::event;
}
@@ -3531,6 +3574,7 @@ void syscall_dispatcher::add_handlers(std::map<std::string, syscall_handler>& ha
add_handler(NtUserModifyUserStartupInfoFlags);
add_handler(NtUserGetDCEx);
add_handler(NtUserGetDpiForCurrentProcess);
add_handler(NtReleaseSemaphore);
#undef add_handler
}

View File

@@ -145,13 +145,13 @@ namespace
return buffer;
}
std::vector<uint8_t> obtain_api_set(apiset_location location)
std::vector<uint8_t> obtain_api_set(const apiset_location location, const std::filesystem::path& root)
{
switch (location)
{
#ifdef OS_WINDOWS
case apiset_location::host: {
auto apiSetMap =
const auto apiSetMap =
reinterpret_cast<const API_SET_NAMESPACE*>(NtCurrentTeb64()->ProcessEnvironmentBlock->ApiSetMap);
const auto* dataPtr = reinterpret_cast<const uint8_t*>(apiSetMap);
std::vector<uint8_t> buffer(dataPtr, dataPtr + apiSetMap->Size);
@@ -162,7 +162,7 @@ namespace
throw std::runtime_error("The APISET host location is not supported on this platform");
#endif
case apiset_location::file: {
auto apiset = utils::io::read_file("api-set.bin");
const auto apiset = utils::io::read_file(root / "api-set.bin");
if (apiset.empty())
throw std::runtime_error("Failed to read file api-set.bin");
return decompress_apiset(apiset);
@@ -181,10 +181,11 @@ namespace
}
emulator_object<API_SET_NAMESPACE> build_api_set_map(x64_emulator& emu, emulator_allocator& allocator,
apiset_location location = apiset_location::host)
const apiset_location location = apiset_location::host,
const std::filesystem::path& root = {})
{
return clone_api_set_map(emu, allocator,
reinterpret_cast<const API_SET_NAMESPACE&>(*obtain_api_set(location).data()));
reinterpret_cast<const API_SET_NAMESPACE&>(*obtain_api_set(location, root).data()));
}
emulator_allocator create_allocator(emulator& emu, const size_t size)
@@ -208,11 +209,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 +216,9 @@ namespace
setup_gdt(emu);
context.registry = registry_manager(settings.registry_directory);
context.registry =
registry_manager(win_emu.get_emulation_root().empty() ? settings.registry_directory
: win_emu.get_emulation_root() / "registry");
context.kusd.setup(settings.use_relative_time);
@@ -260,7 +258,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)
{
@@ -268,20 +268,10 @@ namespace
command_line.append(arg);
}
std::u16string current_folder{};
if (!settings.working_directory.empty())
{
current_folder = canonicalize_path(settings.working_directory).u16string() + u"\\";
}
else
{
current_folder = canonicalize_path(settings.application).parent_path().u16string() + u"\\";
}
allocator.make_unicode_string(proc_params.CommandLine, command_line);
allocator.make_unicode_string(proc_params.CurrentDirectory.DosPath, current_folder);
allocator.make_unicode_string(proc_params.ImagePathName,
canonicalize_path(settings.application).u16string());
allocator.make_unicode_string(proc_params.CurrentDirectory.DosPath,
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();
@@ -290,17 +280,21 @@ namespace
proc_params.MaximumLength = proc_params.Length;
});
// TODO: make this configurable
apiset_location apiset_loc = apiset_location::file;
if (win_emu.get_emulation_root().empty())
{
#ifdef OS_WINDOWS
apiset_location apiset_loc = apiset_location::host;
apiset_loc = apiset_location::host;
#else
apiset_location apiset_loc = apiset_location::default_windows_11;
apiset_loc = apiset_location::default_windows_11;
#endif
}
context.peb.access([&](PEB64& peb) {
peb.ImageBaseAddress = nullptr;
peb.ProcessParameters = context.process_params.ptr();
peb.ApiSetMap = build_api_set_map(emu, allocator, apiset_loc).ptr();
peb.ApiSetMap = build_api_set_map(emu, allocator, apiset_loc, win_emu.get_emulation_root()).ptr();
peb.ProcessHeap = nullptr;
peb.ProcessHeaps = nullptr;
@@ -634,6 +628,11 @@ namespace
break;
case handle_types::event: {
if (h.value.is_pseudo)
{
return true;
}
auto* e = c.events.get(h);
if (e)
{
@@ -653,6 +652,16 @@ namespace
break;
}
case handle_types::semaphore: {
auto* s = c.semaphores.get(h);
if (s)
{
return s->try_lock();
}
break;
}
case handle_types::thread: {
const auto* t = c.threads.get(h);
if (t)
@@ -816,10 +825,19 @@ std::unique_ptr<x64_emulator> create_default_x64_emulator()
return unicorn::create_x64_emulator();
}
windows_emulator::windows_emulator(emulator_settings settings, emulator_callbacks callbacks,
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.emulation_root, std::move(emu))
{
if (!settings.working_directory.empty())
{
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;
this->use_relative_time_ = settings.use_relative_time;
this->log.disable_output(settings.disable_logging || this->silent_until_main_);
@@ -827,10 +845,19 @@ windows_emulator::windows_emulator(emulator_settings settings, emulator_callback
this->setup_process(settings);
}
windows_emulator::windows_emulator(std::unique_ptr<x64_emulator> emu)
: emu_(std::move(emu)),
process_(*emu_)
windows_emulator::windows_emulator(const std::filesystem::path& emulation_root, std::unique_ptr<x64_emulator> emu)
: emulation_root_{emulation_root.empty() ? emulation_root : absolute(emulation_root)},
file_sys_(emulation_root_.empty() ? emulation_root_ : emulation_root_ / "filesys"),
emu_(std::move(emu)),
process_(*emu_, file_sys_)
{
#ifndef OS_WINDOWS
if (this->get_emulation_root().empty())
{
throw std::runtime_error("Emulation root directory can not be empty!");
}
#endif
this->setup_hooks();
}
@@ -839,14 +866,15 @@ void windows_emulator::setup_process(const emulator_settings& settings)
auto& emu = this->emu();
auto& context = this->process();
context.mod_manager = module_manager(emu); // TODO: Cleanup module manager
context.mod_manager = module_manager(emu, this->file_sys()); // TODO: Cleanup module manager
setup_context(*this, settings);
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); });
context.peb.access([&](PEB64& peb) {
peb.ImageBaseAddress = reinterpret_cast<std::uint64_t*>(context.executable->image_base); //
});
context.ntdll = context.mod_manager.map_module(R"(C:\Windows\System32\ntdll.dll)", this->log);
context.win32u = context.mod_manager.map_module(R"(C:\Windows\System32\win32u.dll)", this->log);
@@ -930,9 +958,13 @@ void windows_emulator::on_instruction_execution(const uint64_t address)
const auto export_entry = binary->address_names.find(address);
if (export_entry != binary->address_names.end())
{
const auto rsp = this->emu().read_stack_pointer();
const auto return_address = this->emu().read_memory<uint64_t>(rsp);
const auto* mod_name = this->process().mod_manager.find_name(return_address);
log.print(is_interesting_call ? color::yellow : color::dark_gray,
"Executing function: %s - %s (0x%" PRIx64 ")\n", binary->name.c_str(),
export_entry->second.c_str(), address);
"Executing function: %s - %s (0x%" PRIx64 ") via (0x%" PRIx64 ") %s\n", binary->name.c_str(),
export_entry->second.c_str(), address, return_address, mod_name);
}
else if (address == binary->entry_point)
{
@@ -1096,6 +1128,7 @@ void windows_emulator::start(std::chrono::nanoseconds timeout, size_t count)
void windows_emulator::serialize(utils::buffer_serializer& buffer) const
{
buffer.write(this->use_relative_time_);
this->file_sys().serialize(buffer);
this->emu().serialize(buffer);
this->process_.serialize(buffer);
this->dispatcher_.serialize(buffer);
@@ -1103,11 +1136,16 @@ void windows_emulator::serialize(utils::buffer_serializer& buffer) const
void windows_emulator::deserialize(utils::buffer_deserializer& buffer)
{
buffer.register_factory<x64_emulator_wrapper>([this] { return x64_emulator_wrapper{this->emu()}; });
buffer.register_factory<x64_emulator_wrapper>([this] {
return x64_emulator_wrapper{this->emu()}; //
});
buffer.register_factory<windows_emulator_wrapper>([this] { return windows_emulator_wrapper{*this}; });
buffer.register_factory<windows_emulator_wrapper>([this] {
return windows_emulator_wrapper{*this}; //
});
buffer.read(this->use_relative_time_);
this->file_sys().deserialize(buffer);
this->emu().deserialize(buffer);
this->process_.deserialize(buffer);
@@ -1119,6 +1157,7 @@ void windows_emulator::save_snapshot()
this->emu().save_snapshot();
utils::buffer_serializer serializer{};
this->file_sys().serialize(serializer);
this->process_.serialize(serializer);
this->process_snapshot_ = serializer.move_buffer();
@@ -1138,6 +1177,7 @@ void windows_emulator::restore_snapshot()
this->emu().restore_snapshot();
utils::buffer_deserializer deserializer{this->process_snapshot_};
this->file_sys().deserialize(deserializer);
this->process_.deserialize(deserializer);
// this->process_ = *this->process_snapshot_;
}

View File

@@ -8,6 +8,7 @@
#include "syscall_dispatcher.hpp"
#include "process_context.hpp"
#include "logger.hpp"
#include "file_system.hpp"
std::unique_ptr<x64_emulator> create_default_x64_emulator();
@@ -26,16 +27,17 @@ struct emulator_callbacks
// TODO: Split up into application and emulator settings
struct emulator_settings
{
std::filesystem::path application{};
std::filesystem::path working_directory{};
windows_path application{};
windows_path working_directory{};
std::filesystem::path registry_directory{"./registry"};
std::filesystem::path emulation_root{};
std::vector<std::u16string> arguments{};
bool disable_logging{false};
bool silent_until_main{false};
bool use_relative_time{false};
};
enum class apiset_location
enum class apiset_location : uint8_t
{
host,
file,
@@ -46,8 +48,9 @@ enum class apiset_location
class windows_emulator
{
public:
windows_emulator(std::unique_ptr<x64_emulator> emu = create_default_x64_emulator());
windows_emulator(emulator_settings settings, emulator_callbacks callbacks = {},
windows_emulator(const std::filesystem::path& emulation_root,
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());
windows_emulator(windows_emulator&&) = delete;
@@ -131,7 +134,25 @@ class windows_emulator
return this->callbacks_;
}
file_system& file_sys()
{
return this->file_sys_;
}
const file_system& file_sys() const
{
return this->file_sys_;
}
const std::filesystem::path& get_emulation_root()
{
return this->emulation_root_;
}
private:
std::filesystem::path emulation_root_{};
file_system file_sys_;
emulator_callbacks callbacks_{};
bool use_relative_time_{false};
bool silent_until_main_{false};

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);
}
}
}
};