diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 22422081..1562f905 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -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 diff --git a/CMakeLists.txt b/CMakeLists.txt index 92c10865..1c0b8964 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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") + ########################################## diff --git a/cmake/compiler-env.cmake b/cmake/compiler-env.cmake index a0eebc1e..7430d2cd 100644 --- a/cmake/compiler-env.cmake +++ b/cmake/compiler-env.cmake @@ -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() ########################################## diff --git a/deps/unicorn b/deps/unicorn index 270d4716..9a6618ba 160000 --- a/deps/unicorn +++ b/deps/unicorn @@ -1 +1 @@ -Subproject commit 270d4716f4542d7caa6d2da597e5eab451debce7 +Subproject commit 9a6618baf899d515b8eccd22c1eec532bfbc7cd6 diff --git a/src/analyzer/main.cpp b/src/analyzer/main.cpp index 9b9adea2..e4468096 100644 --- a/src/analyzer/main.cpp +++ b/src/analyzer/main.cpp @@ -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) diff --git a/src/common/network/address.hpp b/src/common/network/address.hpp index d42604b0..52fe8018 100644 --- a/src/common/network/address.hpp +++ b/src/common/network/address.hpp @@ -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 diff --git a/src/common/platform/status.hpp b/src/common/platform/status.hpp index 9469bce4..59fbd48a 100644 --- a/src/common/platform/status.hpp +++ b/src/common/platform/status.hpp @@ -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) diff --git a/src/common/platform/unicode.hpp b/src/common/platform/unicode.hpp index d506ab6c..b5a895e1 100644 --- a/src/common/platform/unicode.hpp +++ b/src/common/platform/unicode.hpp @@ -1,6 +1,7 @@ #pragma once #include +#include template 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(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 diff --git a/src/common/platform/win_pefile.hpp b/src/common/platform/win_pefile.hpp index 20c1327a..24acb4ab 100644 --- a/src/common/platform/win_pefile.hpp +++ b/src/common/platform/win_pefile.hpp @@ -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 diff --git a/src/common/utils/path_key.hpp b/src/common/utils/path_key.hpp index 6165393e..617f0993 100644 --- a/src/common/utils/path_key.hpp +++ b/src/common/utils/path_key.hpp @@ -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); } diff --git a/src/emulator/serialization.hpp b/src/emulator/serialization.hpp index 7f6adf37..10da2567 100644 --- a/src/emulator/serialization.hpp +++ b/src/emulator/serialization.hpp @@ -1,5 +1,6 @@ #pragma once +#include #include #include #include @@ -205,6 +206,26 @@ namespace utils return result; } + template + void read_list(std::list& result) + { + const auto size = this->read(); + result.clear(); + + for (uint64_t i = 0; i < size; ++i) + { + result.emplace_back(this->read()); + } + } + + template + std::list read_list() + { + std::list result{}; + this->read_list(result); + return result; + } + template void read_map(Map& map) { @@ -397,6 +418,17 @@ namespace utils this->write_span(std::span(vec)); } + template + void write_list(const std::list vec) + { + this->write(static_cast(vec.size())); + + for (const auto& v : vec) + { + this->write(v); + } + } + template void write_string(const std::basic_string_view str) { diff --git a/src/fuzzer/main.cpp b/src/fuzzer/main.cpp index 9f8e7d8c..244b75b9 100644 --- a/src/fuzzer/main.cpp +++ b/src/fuzzer/main.cpp @@ -42,12 +42,12 @@ namespace struct fuzzer_executer : fuzzer::executer { - windows_emulator emu{}; + windows_emulator emu{"./"}; // TODO: Fix root directory std::span emulator_data{}; std::unordered_set visited_blocks{}; const std::function* handler{nullptr}; - fuzzer_executer(std::span data) + fuzzer_executer(const std::span data) : emulator_data(data) { emu.fuzzing = true; diff --git a/src/samples/test-sample/test.cpp b/src/samples/test-sample/test.cpp index efa056c8..83282f81 100644 --- a/src/samples/test-sample/test.cpp +++ b/src/samples/test-sample/test.cpp @@ -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(epoch_time).count()); } #define RUN_TEST(func, name) \ diff --git a/src/tools/.gitignore b/src/tools/.gitignore new file mode 100644 index 00000000..d484f3b3 --- /dev/null +++ b/src/tools/.gitignore @@ -0,0 +1,2 @@ +root +registry \ No newline at end of file diff --git a/src/tools/collect-dlls.bat b/src/tools/collect-dlls.bat deleted file mode 100644 index b4803989..00000000 --- a/src/tools/collect-dlls.bat +++ /dev/null @@ -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 diff --git a/src/tools/create-root.bat b/src/tools/create-root.bat new file mode 100644 index 00000000..5a89a64d --- /dev/null +++ b/src/tools/create-root.bat @@ -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 diff --git a/src/windows-emulator-test/emulation_test_utils.hpp b/src/windows-emulator-test/emulation_test_utils.hpp index 40fec6ea..f36ce716 100644 --- a/src/windows-emulator-test/emulation_test_utils.hpp +++ b/src/windows-emulator-test/emulation_test_utils.hpp @@ -1,5 +1,6 @@ #pragma once +#include #include #include @@ -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)}; } diff --git a/src/windows-emulator-test/serialization_test.cpp b/src/windows-emulator-test/serialization_test.cpp index 835e7857..73ca3e05 100644 --- a/src/windows-emulator-test/serialization_test.cpp +++ b/src/windows-emulator-test/serialization_test.cpp @@ -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); diff --git a/src/windows-emulator/file_system.hpp b/src/windows-emulator/file_system.hpp new file mode 100644 index 00000000..d76f3a52 --- /dev/null +++ b/src/windows-emulator/file_system.hpp @@ -0,0 +1,86 @@ +#pragma once +#include "std_include.hpp" +#include "windows_path.hpp" + +#include + +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 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_{}; +}; diff --git a/src/windows-emulator/handles.hpp b/src/windows-emulator/handles.hpp index cc99ac0d..7f0eca2e 100644 --- a/src/windows-emulator/handles.hpp +++ b/src/windows-emulator/handles.hpp @@ -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 @@ -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); diff --git a/src/windows-emulator/kusd_mmio.cpp b/src/windows-emulator/kusd_mmio.cpp index b6575ed6..5494f247 100644 --- a/src/windows-emulator/kusd_mmio.cpp +++ b/src/windows-emulator/kusd_mmio.cpp @@ -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; diff --git a/src/windows-emulator/module/module_manager.cpp b/src/windows-emulator/module/module_manager.cpp index 71baf230..935c2a43 100644 --- a/src/windows-emulator/module/module_manager.cpp +++ b/src/windows-emulator/module/module_manager.cpp @@ -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 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(); + 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); diff --git a/src/windows-emulator/module/module_manager.hpp b/src/windows-emulator/module/module_manager.hpp index 8fc19377..418c3d10 100644 --- a/src/windows-emulator/module/module_manager.hpp +++ b/src/windows-emulator/module/module_manager.hpp @@ -1,16 +1,19 @@ #pragma once -#include "mapped_module.hpp" #include +#include "mapped_module.hpp" +#include "../file_system.hpp" + class logger; class module_manager { public: using module_map = std::map; - 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_{}; diff --git a/src/windows-emulator/process_context.hpp b/src/windows-emulator/process_context.hpp index 52002de2..0a544474 100644 --- a/src/windows-emulator/process_context.hpp +++ b/src/windows-emulator/process_context.hpp @@ -108,17 +108,17 @@ struct mutant : ref_counted_object return true; } - uint32_t release() + std::pair 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 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 default_register_set{}; - uint32_t current_thread_id{0}; + uint32_t spawned_thread_count{0}; handle_store 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)); } }; diff --git a/src/windows-emulator/registry/registry_manager.cpp b/src/windows-emulator/registry/registry_manager.cpp index 62adfe1b..2b9fb544 100644 --- a/src/windows-emulator/registry/registry_manager.cpp +++ b/src/windows-emulator/registry/registry_manager.cpp @@ -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(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_manager::get_key(const std::filesystem::path& key) +std::optional 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_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_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_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; } diff --git a/src/windows-emulator/registry/registry_manager.hpp b/src/windows-emulator/registry/registry_manager.hpp index 1e598ac6..7b6f0c55 100644 --- a/src/windows-emulator/registry/registry_manager.hpp +++ b/src/windows-emulator/registry/registry_manager.hpp @@ -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 get_key(const std::filesystem::path& key); + std::optional get_key(const utils::path_key& key); std::optional get_value(const registry_key& key, std::string name); private: @@ -56,10 +56,10 @@ class registry_manager hive_map hives_{}; std::unordered_map 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(); }; diff --git a/src/windows-emulator/syscalls.cpp b/src/windows-emulator/syscalls.cpp index 69448eff..b739123a 100644 --- a/src/windows-emulator/syscalls.cpp +++ b/src/windows-emulator/syscalls.cpp @@ -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(old_count)); } - return STATUS_SUCCESS; + return succeeded ? STATUS_SUCCESS : STATUS_MUTANT_NOT_OWNED; } NTSTATUS handle_NtCreateMutant(const syscall_context& c, const emulator_object 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>*>(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 default_locale_id, const emulator_object /*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>>{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 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(old_count)); + } + + return succeeded ? STATUS_SUCCESS : STATUS_SEMAPHORE_LIMIT_EXCEEDED; + } + NTSTATUS handle_NtCreateSemaphore(const syscall_context& c, const emulator_object semaphore_handle, const ACCESS_MASK /*desired_access*/, const emulator_object>> 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& ha add_handler(NtUserModifyUserStartupInfoFlags); add_handler(NtUserGetDCEx); add_handler(NtUserGetDpiForCurrentProcess); + add_handler(NtReleaseSemaphore); #undef add_handler } diff --git a/src/windows-emulator/windows_emulator.cpp b/src/windows-emulator/windows_emulator.cpp index e7f7500c..479b40a4 100644 --- a/src/windows-emulator/windows_emulator.cpp +++ b/src/windows-emulator/windows_emulator.cpp @@ -145,13 +145,13 @@ namespace return buffer; } - std::vector obtain_api_set(apiset_location location) + std::vector 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(NtCurrentTeb64()->ProcessEnvironmentBlock->ApiSetMap); const auto* dataPtr = reinterpret_cast(apiSetMap); std::vector 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 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(*obtain_api_set(location).data())); + reinterpret_cast(*obtain_api_set(location, root).data())); } emulator_allocator create_allocator(emulator& emu, const size_t size) @@ -208,11 +209,6 @@ namespace emu.reg(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 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 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 emu) - : emu_(std::move(emu)), - process_(*emu_) +windows_emulator::windows_emulator(const std::filesystem::path& emulation_root, std::unique_ptr 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(context.executable->image_base); }); + context.peb.access([&](PEB64& peb) { + peb.ImageBaseAddress = reinterpret_cast(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(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([this] { return x64_emulator_wrapper{this->emu()}; }); + buffer.register_factory([this] { + return x64_emulator_wrapper{this->emu()}; // + }); - buffer.register_factory([this] { return windows_emulator_wrapper{*this}; }); + buffer.register_factory([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_; } diff --git a/src/windows-emulator/windows_emulator.hpp b/src/windows-emulator/windows_emulator.hpp index 2f524a07..e88a522f 100644 --- a/src/windows-emulator/windows_emulator.hpp +++ b/src/windows-emulator/windows_emulator.hpp @@ -8,6 +8,7 @@ #include "syscall_dispatcher.hpp" #include "process_context.hpp" #include "logger.hpp" +#include "file_system.hpp" std::unique_ptr 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 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 emu = create_default_x64_emulator()); - windows_emulator(emulator_settings settings, emulator_callbacks callbacks = {}, + windows_emulator(const std::filesystem::path& emulation_root, + std::unique_ptr emu = create_default_x64_emulator()); + windows_emulator(const emulator_settings& settings, emulator_callbacks callbacks = {}, std::unique_ptr 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}; diff --git a/src/windows-emulator/windows_path.hpp b/src/windows-emulator/windows_path.hpp new file mode 100644 index 00000000..eaf5a940 --- /dev/null +++ b/src/windows-emulator/windows_path.hpp @@ -0,0 +1,236 @@ +#pragma once + +#include +#include +#include +#include + +#include + +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(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 + requires(!std::is_same_v && !std::is_same_v) + windows_path(T&& path_like) + : windows_path(std::filesystem::path(std::forward(path_like))) + { + } + + windows_path(const std::optional drive, std::list 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(*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(*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 drive_{}; + std::list 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); + } + } + } +};