#include "providers.h" #include "object_api.h" #include "module_api.h" #include "resources.h" #include "memory.h" #include "msvcrt_api.h" #include "prefix.h" #include "profile_api.h" #include "registry.h" #include "desktop/api.h" #include "desktop/windows.h" #include "api.h" #include "ansi_api.h" #include "guest_allocator.h" #include "heap.h" #include "kernel_context.h" #include "paths.h" #include "x86_compat_api.h" #include "registry_words.h" #include "system_exports.h" #include "loader.h" #include "x86_loader.h" #include "x86_tls.h" #ifdef RAVEN_VULKAN_THUNK32 #include "graphics/vulkan_thunk32_engine.h" #endif #include "x86_system_api.h" #include "x86_d3d9.h" #include "x86_dsound.h" #include "x86_thread_api.h" #include #include #include #include #include #include #include #include #include #include #include namespace raven { namespace { // stdcall argument counts for every API the x86 process runner can dispatch. constexpr std::array, 110> kernel32_exports{{ {"CloseHandle", 1}, {"CreateEventA", 4}, {"CreateEventW", 4}, {"CreateThread", 6}, {"DeleteCriticalSection", 1}, {"EnterCriticalSection", 1}, {"ExitProcess", 1}, {"ExitThread", 1}, {"FindResourceA", 3}, {"FindResourceExA", 4}, {"FindResourceExW", 4}, {"FindResourceW", 3}, {"FreeLibrary", 1}, {"GetACP", 0}, {"GetCommandLineA", 0}, {"GetCommandLineW", 0}, {"GetCurrentProcessId", 0}, {"GetCurrentThreadId", 0}, {"GetExitCodeThread", 2}, {"GetFileType", 1}, {"GetLastError", 0}, {"GetModuleFileNameA", 3}, {"GetModuleHandleA", 1}, {"GetModuleHandleW", 1}, {"GetPrivateProfileIntW", 4}, {"GetPrivateProfileStringW", 6}, {"WritePrivateProfileStringW", 4}, {"LoadLibraryExA", 3}, {"LoadLibraryExW", 3}, {"GetProcAddress", 2}, {"GetProcessHeap", 0}, {"GetStartupInfoA", 1}, {"GetStartupInfoW", 1}, {"GetStdHandle", 1}, {"GetTickCount", 0}, {"ProcessIdToSessionId", 2}, {"HeapAlloc", 3}, {"HeapFree", 3}, {"HeapReAlloc", 4}, {"HeapSize", 3}, {"InitializeCriticalSection", 1}, {"LeaveCriticalSection", 1}, {"LoadLibraryA", 1}, {"LoadLibraryW", 1}, {"LoadResource", 2}, {"LockResource", 1}, {"OutputDebugStringA", 1}, {"ResetEvent", 1}, {"ResumeThread", 1}, {"SetEvent", 1}, {"SetLastError", 1}, {"SetThreadPriority", 2}, {"SizeofResource", 2}, {"Sleep", 1}, {"SwitchToThread", 0}, {"TryEnterCriticalSection", 1}, {"VirtualAlloc", 4}, // Services of the 32-bit CRT era: legacy file calls, global memory, code pages, environment and version queries. {"_lopen", 2}, {"_lcreat", 2}, {"_lread", 3}, {"_lwrite", 3}, {"_llseek", 3}, {"lstrcpyA", 2}, {"lstrlenA", 1}, {"GetWindowsDirectoryA", 2}, {"GlobalHandle", 1}, {"GetVersionExA", 1}, {"WinExec", 2}, {"GlobalUnlock", 1}, {"GlobalFree", 1}, {"GlobalAlloc", 2}, {"SetErrorMode", 1}, {"GlobalLock", 1}, {"GetCurrentProcess", 0}, {"SetHandleCount", 1}, {"GetOEMCP", 0}, {"GetCPInfo", 2}, {"WideCharToMultiByte", 8}, {"DeleteFileA", 1}, {"GetEnvironmentStrings", 0}, {"FreeEnvironmentStringsW", 1}, {"FreeEnvironmentStringsA", 1}, {"UnhandledExceptionFilter", 1}, {"RtlUnwind", 4}, {"HeapCreate", 3}, {"HeapDestroy", 1}, {"TerminateProcess", 2}, {"GetEnvironmentStringsW", 0}, {"GetVersion", 0}, {"GetFileAttributesA", 1}, // File and clock services of the C runtime component: the shared handlers read 32-bit words; a 64-bit offset takes two. {"RaiseException", 4}, {"CreateFileW", 7}, {"DeleteFileW", 1}, {"GetEnvironmentVariableA", 3}, {"GetFileInformationByHandle", 2}, {"MultiByteToWideChar", 6}, {"QueryPerformanceCounter", 1}, {"QueryPerformanceFrequency", 1}, {"ReadFile", 5}, {"SetFilePointer", 4}, {"SetFilePointerEx", 5}, {"GetFileSizeEx", 2}, {"FlushFileBuffers", 1}, {"VirtualFree", 3}, {"VirtualProtect", 4}, {"WaitForSingleObject", 2}, {"WriteFile", 5}, {"_lclose", 1}, {"lstrcpyW", 2}, {"lstrlenW", 1}, }}; // Shared-registry handlers that serve 32-bit programs as they are: every argument is one 32-bit word and no structure holds a pointer. constexpr std::array shared_exports{ NativeX86System::Export{"kernel32.dll", "CreateDirectoryW", 2}, NativeX86System::Export{"kernel32.dll", "CreateFileA", 7}, NativeX86System::Export{"kernel32.dll", "CreateFileMappingA", 6}, NativeX86System::Export{"kernel32.dll", "FormatMessageA", 7}, NativeX86System::Export{"kernel32.dll", "GetEnvironmentVariableW", 3}, NativeX86System::Export{"kernel32.dll", "GetModuleFileNameW", 3}, NativeX86System::Export{"kernel32.dll", "GetModuleHandleExA", 3}, NativeX86System::Export{"kernel32.dll", "GetSystemTimeAsFileTime", 1}, NativeX86System::Export{"kernel32.dll", "GetTempPathA", 2}, NativeX86System::Export{"kernel32.dll", "GetTickCount64", 0}, NativeX86System::Export{"kernel32.dll", "LocalFree", 1}, NativeX86System::Export{"kernel32.dll", "MapViewOfFile", 5}, NativeX86System::Export{"kernel32.dll", "UnmapViewOfFile", 1}, NativeX86System::Export{"user32.dll", "CallWindowProcA", 5}, NativeX86System::Export{"user32.dll", "DestroyCursor", 1}, NativeX86System::Export{"user32.dll", "EnumDisplayMonitors", 4}, NativeX86System::Export{"user32.dll", "EnumDisplaySettingsW", 3}, NativeX86System::Export{"user32.dll", "GetDCEx", 3}, NativeX86System::Export{"user32.dll", "GetDisplayConfigBufferSizes", 3}, NativeX86System::Export{"user32.dll", "GetForegroundWindow", 0}, NativeX86System::Export{"user32.dll", "GetMonitorInfoW", 2}, NativeX86System::Export{"user32.dll", "IsIconic", 1}, NativeX86System::Export{"user32.dll", "MonitorFromPoint", 3}, NativeX86System::Export{"user32.dll", "OffsetRect", 3}, NativeX86System::Export{"user32.dll", "QueryDisplayConfig", 6}, NativeX86System::Export{"gdi32.dll", "CreateBitmap", 5}, NativeX86System::Export{"gdi32.dll", "StretchBlt", 11}, }; // More shared handlers a 32-bit program may use as they are; their words come from the registry and their library from the public // export lists. Anything with a pointer-width structure, a 64-bit argument or a callback into x64 conventions is not here. constexpr std::string_view shared_allowed[] = { "SetDllDirectoryW", "SetDllDirectoryA", "SetConsoleTitleW", "SetConsoleTitleA", "OpenSCManagerW", "OpenSCManagerA", "OpenServiceW", "OpenServiceA", "CreateServiceW", "CreateServiceA", "CloseServiceHandle", "DeleteService", "ChangeServiceConfigW", "ChangeServiceConfig2W", "ChangeServiceConfig2A", "QueryServiceConfigW", "QueryServiceConfigA", "QueryServiceObjectSecurity", "SetServiceObjectSecurity", "RegQueryInfoKeyA", "RegQueryInfoKeyW", "LookupPrivilegeValueA", "QueryServiceStatus", "QueryServiceStatusEx", "StartServiceW", "ControlService", "StartServiceCtrlDispatcherW", "RegisterServiceCtrlHandlerW", "RegisterServiceCtrlHandlerExW", "SetServiceStatus", "WriteFile", "GetFileAttributesW", "GetFullPathNameA", "GetFullPathNameW", "GetCurrentDirectoryA", "GetCurrentDirectoryW", "SetCurrentDirectoryA", "SetCurrentDirectoryW", "FindFirstFileA", "FindFirstFileW", "FindNextFileA", "FindNextFileW", "FindClose", "CreateDirectoryA", "RemoveDirectoryA", "RemoveDirectoryW", "MoveFileA", "MoveFileW", "CopyFileA", "CopyFileW", "GetLocalTime", "GetSystemTime", "lstrcmpA", "lstrcmpW", "lstrcmpiA", "lstrcmpiW", "lstrcpynA", "lstrcatA", "OutputDebugStringW", "IsDebuggerPresent", "GetLogicalDrives", "GetDriveTypeA", "GetVolumeInformationA", "GetVolumeInformationW", "GetDiskFreeSpaceA", "MulDiv", "GetStringTypeW", "GetStringTypeA", "CompareStringA", "CompareStringW", "LCMapStringA", "LCMapStringW", "GetLocaleInfoA", "GetLocaleInfoW", "GetUserDefaultLCID", "GetSystemDefaultLCID", "GetUserDefaultLangID", "GetSystemDefaultLangID", "GetUserDefaultUILanguage", "GetSystemDefaultUILanguage", "GetThreadUILanguage", "IsValidCodePage", "SetEnvironmentVariableA", "SetEnvironmentVariableW", "ExpandEnvironmentStringsA", "ExpandEnvironmentStringsW", "CreateMutexA", "CreateMutexW", "ReleaseMutex", "GetFileTime", "SetFileTime", "FileTimeToLocalFileTime", "LocalFileTimeToFileTime", "FileTimeToDosDateTime", "DosDateTimeToFileTime", "GetFileSize", "SetEndOfFile", "IsWindowEnabled", "EnableWindow", "FindWindowExW", "SetForegroundWindow", "GetMessagePos", "OpenClipboard", "EmptyClipboard", "SetClipboardData", "CloseClipboard", "CharNextA", "CharNextW", "CharPrevA", "CharPrevW", "SystemParametersInfoW", "GetSystemMenu", "EnableMenuItem", "AppendMenuW", "CreatePopupMenu", "LoadBitmapW", "LoadImageW", "SHGetSpecialFolderLocation", "SHGetPathFromIDListW", "SHGetFileInfoW", "ShellExecuteW", "SHFileOperationW", "OleInitialize", "OleUninitialize", "CoTaskMemFree", "CoCreateInstance", "GetTempPathW", "CompareFileTime", "MoveFileExW", "lstrcatW", "lstrcpynW", "GetTempFileNameW", "GetDiskFreeSpaceW", "GetExitCodeProcess", "CreateProcessW", "OpenProcessToken", "LookupPrivilegeValueW", "AdjustTokenPrivileges", "SetFileSecurityW", "RegCloseKey", "RegCreateKeyExA", "RegCreateKeyExW", "RegOpenKeyA", "RegOpenKeyW", "RegOpenKeyExA", "RegOpenKeyExW", "RegQueryValueExA", "RegQueryValueExW", "RegSetValueExA", "RegSetValueExW", "RegDeleteKeyA", "RegDeleteKeyW", "RegDeleteValueA", "RegDeleteValueW", "RegEnumKeyA", "RegEnumKeyW", "RegEnumKeyExA", "RegEnumKeyExW", "RegEnumValueA", "RegEnumValueW", "GetWindowLongW", "SetWindowLongW", "GetFileVersionInfoA", "GetFileVersionInfoExW", "GetFileVersionInfoSizeA", "GetFileVersionInfoSizeExW", "GetFileVersionInfoSizeW", "GetFileVersionInfoW", "VerQueryValueA", "VerQueryValueW", "GetSystemDirectoryW", "GetSystemDirectoryA", "GetWindowsDirectoryW", "LocalAlloc", "LocalReAlloc", "GlobalReAlloc", "GlobalSize", "GetComputerNameA", "GetComputerNameW", "FileTimeToSystemTime", "SystemTimeToFileTime", "GetTimeZoneInformation", "GetCurrentProcessId", "SetFileAttributesA", "SetFileAttributesW", "GetLongPathNameA", "GetLongPathNameW", "GetShortPathNameA", "GetShortPathNameW", "SearchPathA", "SearchPathW", "GetModuleFileNameA", "IsDBCSLeadByte", "IsDBCSLeadByteEx", "GetCPInfoExA", "GetThreadLocale", "SetThreadLocale", "VerifyVersionInfoW", "GetUserNameA", "GetUserNameW", "FlsAlloc", "FlsFree", "FlsGetValue", "FlsSetValue", "SetDefaultDllDirectories", "AddDllDirectory", "RemoveDllDirectory", "CreateMutexW", "CreateMutexA", "CreateMutexExW", "OpenMutexW", "OpenMutexA", "ReleaseMutex", "CreateSemaphoreW", "CreateSemaphoreA", "CreateSemaphoreExW", "ReleaseSemaphore", }; struct NamedWords { std::string_view name; uint32_t words; }; // The names a module exports from the host, with the words each takes: the fixed table, the shared handlers and the system services. std::vector host_exports(std::string_view module) { std::vector result; for (const auto &item : shared_exports) if (item.module == module) result.push_back({item.name, item.words}); for (const auto &item : NativeX86System::exports()) if (item.module == module) result.push_back({item.name, item.words}); const auto published = system_export_names(module); for (const auto name : shared_allowed) { const auto words = registry_words(name); const auto duplicate = std::any_of(result.begin(), result.end(), [&](const auto &item) { return item.name == name; }); if (words != UINT32_MAX && !duplicate && std::find(published.begin(), published.end(), name) != published.end()) result.push_back({name, words}); } return result; } const std::vector &kernel32_table() { static const auto table = [] { std::vector result; for (const auto &[name, words] : kernel32_exports) result.push_back({name, words}); for (const auto &item : host_exports("kernel32.dll")) if (std::none_of(result.begin(), result.end(), [&](const auto &fixed) { return fixed.name == item.name; })) result.push_back(item); return result; }(); return table; } // stdcall argument counts of the desktop exports, keyed by export name. const std::map &desktop_arguments() { static const auto counts = [] { std::map counts{ {"AdjustWindowRect",3},{"AdjustWindowRectEx",4},{"BeginPaint",2},{"CallWindowProcW",5},{"ClientToScreen",2}, {"CopyIcon",1},{"CreateIconFromResourceEx",7},{"CreateWindowExW",12},{"DefWindowProcW",4},{"DestroyIcon",1}, {"DestroyMenu",1},{"DestroyWindow",1},{"DispatchMessageW",1},{"DrawIconEx",9},{"EndPaint",2},{"FillRect",3}, {"FrameRect",3},{"GetActiveWindow",0},{"GetClassInfoExW",3},{"GetClassInfoW",3},{"GetClassLongA",2}, {"GetClassLongPtrA",2},{"GetClassLongPtrW",2},{"GetClassLongW",2},{"GetClassNameA",3},{"GetClassNameW",3}, {"GetClientRect",2},{"GetDC",1},{"GetDlgCtrlID",1},{"GetDlgItem",2},{"GetFocus",0},{"GetIconInfo",2}, {"GetMenuItemCount",1},{"GetMenuItemID",2},{"GetMessageW",4},{"GetParent",1},{"GetSubMenu",2},{"GetSysColor",1}, {"GetSysColorBrush",1},{"GetSystemMetrics",1},{"GetUpdateRect",3},{"GetWindowDC",1},{"CheckDlgButton",3},{"MessageBoxIndirectW",1},{"TrackPopupMenu",7},{"DrawTextW",5},{"CreateDialogParamW",5},{"SendMessageTimeoutW",7}, {"CreateBrushIndirect",1},{"GetWindowLongA",2}, {"GetWindowLongPtrA",2},{"GetWindowLongPtrW",2},{"GetWindowLongW",2},{"GetWindowRect",2},{"GetWindowTextLengthW",1}, {"GetWindowTextW",3},{"InternalGetWindowText",3},{"InvalidateRect",3},{"IsChild",2},{"IsMenu",1},{"IsWindow",1}, {"IsWindowUnicode",1},{"IsWindowVisible",1},{"LoadMenuW",2},{"LoadStringW",4},{"LookupIconIdFromDirectoryEx",5}, {"MapWindowPoints",4},{"PeekMessageW",5},{"PostMessageW",4},{"PostQuitMessage",1},{"PrivateExtractIconsW",8}, {"RegisterClassExW",1},{"RegisterClassW",1},{"ReleaseDC",2},{"ScreenToClient",2},{"SendMessageW",4}, {"SetClassLongA",3},{"SetClassLongPtrA",3},{"SetClassLongPtrW",3},{"SetClassLongW",3},{"SetFocus",1}, {"SetWindowLongA",3},{"SetWindowLongPtrA",3},{"SetWindowLongPtrW",3},{"SetWindowLongW",3},{"SetWindowTextW",2}, {"ShowWindow",2},{"TranslateMessage",1},{"UnregisterClassW",2},{"UpdateWindow",1},{"ValidateRect",2}, {"BitBlt",9},{"CombineRgn",4},{"CreateCompatibleBitmap",3},{"CreateCompatibleDC",1},{"CreateDIBSection",6}, {"CreateFontIndirectW",1},{"CreatePen",3},{"CreateRectRgn",4},{"CreateSolidBrush",1},{"DPtoLP",3},{"DeleteDC",1}, {"DeleteObject",1},{"EqualRgn",2},{"ExcludeClipRect",5},{"ExtTextOutW",8},{"GdiFlush",0},{"GetBitmapBits",3}, {"GetBkColor",1},{"GetBkMode",1},{"GetClipBox",2},{"GetClipRgn",2},{"GetCurrentObject",2}, {"GetCurrentPositionEx",2},{"GetGraphicsMode",1},{"GetLayout",1},{"GetObjectA",3},{"GetObjectType",1}, {"GetObjectW",3},{"GetRgnBox",2},{"GetStockObject",1},{"GetTextColor",1},{"GetTextExtentExPointW",7}, {"GetTextExtentPoint32W",4},{"GetTextExtentPointW",4},{"GetTextMetricsW",2},{"GetViewportExtEx",2}, {"GetWindowExtEx",2},{"IntersectClipRect",5},{"LPtoDP",3},{"LineTo",3},{"MoveToEx",4},{"OffsetRgn",3}, {"PtInRegion",3},{"RectInRegion",2},{"SelectClipRgn",2},{"SelectObject",2},{"SetBkColor",2},{"SetBkMode",2}, {"SetRectRgn",5},{"SetTextColor",2},{"TextOutW",5},{"LoadIconWithScaleDown",5}, // Exports bound for real programs whose behavior is not implemented yet; each stops the process by name. {"CheckMenuItem",3},{"DialogBoxParamW",5},{"EndDialog",2},{"GetDesktopWindow",0},{"GetDlgItemInt",4}, {"GetDlgItemTextW",4},{"GetMenuItemRect",4},{"KillTimer",2},{"LoadAcceleratorsW",2},{"LoadCursorW",2}, {"LoadIconW",2},{"MessageBoxW",4},{"MoveWindow",6},{"PtInRect",3},{"ReleaseCapture",0},{"SetCapture",1}, {"SetDlgItemInt",4},{"SetDlgItemTextW",3},{"SetMenu",2},{"SetRect",5},{"SetTimer",4},{"TranslateAcceleratorW",3}, {"WinHelpW",4},{"wsprintfW",0},{"wsprintfA",0},{"GetDeviceCaps",2},{"SetDIBitsToDevice",12},{"SetLayout",2},{"SetPixel",4}, {"SetROP2",2}, // ANSI forms and cursor, key and text services served by the compat adapters or the shared registry. {"CreateWindowExA",12},{"DispatchMessageA",1},{"GetMessageA",4},{"PeekMessageA",5},{"RegisterClassA",1},{"RegisterClassExA",1}, {"UnregisterClassA",2},{"LoadCursorA",2},{"LoadIconA",2},{"GetAsyncKeyState",1},{"SetCursor",1},{"GetCursorPos",1},{"SetCursorPos",2}, {"DefWindowProcA",4},{"SendMessageA",4},{"PostMessageA",4},{"SetWindowTextA",2}, {"CreateFontA",14},{"ExtTextOutA",8},{"GetDIBits",7},{"GetTextExtentPoint32A",4},{"GetTextMetricsA",2},{"SetDIBits",7}, {"ExitWindowsEx",2},{"MessageBoxA",4},{"SetWindowPos",7},{"RealizePalette",1},{"SelectPalette",3},{"PatBlt",6},{"CreatePalette",1}, }; for (const auto module : {"user32.dll", "gdi32.dll"}) for (const auto &item : host_exports(module)) counts.emplace(item.name, item.words); // The display driver thunks each take one pointer to their parameter structure. for (const auto name : system_export_names("gdi32.dll")) if (name.starts_with("D3DKMT")) counts.emplace(name, 1); return counts; }(); return counts; } // Services of libraries Raven does not implement yet fail visibly instead of reporting success. bool unsupported_library(NativeProcess &process, NativeEnvironment &environment, std::string_view name) { // There is no kernel graphics subsystem behind the host's display: the thunks report that the request is not supported (STATUS_NOT_SUPPORTED). if (name.starts_with("D3DKMT")) { process.api_arguments(0); process.return_api(0xC00000BBU); return true; } const bool known = name == "PlaySoundW" || name == "ShellAboutW" || name == "InitCommonControlsEx"; if (!known) return false; process.api_arguments(0); environment.set_last_error(120); process.return_api(0); return true; } // Guest strings and small kernel32 services that need no shared state. bool kernel_string_api(NativeProcess &process, NativeEnvironment &environment, const PEModule &entry, const NativeLaunch &launch, std::string_view name) { if (name == "GetTickCount" || name == "timeGetTime") { const auto now = std::chrono::duration_cast(std::chrono::steady_clock::now().time_since_epoch()); process.return_api(uint32_t(now.count())); return true; } if (name == "ProcessIdToSessionId") { // Only the process itself is known, and it runs in the interactive session. const auto args = process.api_arguments(2); const auto own = process.read_memory(environment.teb() + 0x20, 4); const uint32_t own_id = own[0] | uint32_t(own[1]) << 8 | uint32_t(own[2]) << 16 | uint32_t(own[3]) << 24; if (uint32_t(args[0]) != own_id || !args[1]) { environment.set_last_error(87); process.return_api(0); return true; } const uint8_t session[4] = {1, 0, 0, 0}; process.write_memory(args[1], session); process.return_api(1); return true; } if (name == "timeBeginPeriod" || name == "timeEndPeriod") { process.api_arguments(1); process.return_api(0); return true; } if (name == "OutputDebugStringA") { process.api_arguments(1); process.return_api(0); return true; } if (name == "lstrlenW" || name == "lstrcpyW") { const bool copy = name == "lstrcpyW"; const auto args = process.api_arguments(copy ? 2 : 1); const uint64_t source = args[copy ? 1 : 0]; size_t length = 0; try { while (process.read_memory(source + length * 2, 2) != std::vector{0, 0}) { if (++length > 1024 * 1024) throw std::runtime_error("native string exceeds policy limit"); } if (copy) process.write_memory(args[0], process.read_memory(source, length * 2 + 2)); } catch (const std::runtime_error &failure) { const std::string_view message = failure.what(); if (message.find("readable") == message.npos && message.find("writable") == message.npos) throw; environment.set_last_error(998); process.return_api(0); return true; } process.return_api(copy ? args[0] : length); return true; } if (name == "GetModuleFileNameA") { const auto args = process.api_arguments(3); if (args[0] && args[0] != entry.loaded.base()) { environment.set_last_error(126); process.return_api(0); return true; } std::string path; for (const auto character : launch.image_path) path.push_back(character > 127 ? '?' : char(character)); const size_t size = uint32_t(args[2]); if (!size) { environment.set_last_error(122); process.return_api(0); return true; } const bool truncated = path.size() >= size; if (truncated) path.resize(size - 1); path.push_back('\0'); process.write_memory(args[1], std::span(reinterpret_cast(path.data()), path.size())); if (truncated) environment.set_last_error(122); process.return_api(path.size() - 1); return true; } return false; } uint64_t provider_base(const ModuleSet &modules, uint32_t size) { uint64_t base = 0x70000000ULL; for (;;) { bool overlap = false; for (const auto &[name, module] : modules.all()) { const uint64_t start = module->loaded.base(); const uint64_t end = start + module->loaded.data().size(); if (base < end && start < base + size) { base = (end + 65535) & ~uint64_t(65535); overlap = true; break; } } if (base >= 0xf0000000ULL - size) throw std::runtime_error("no address available for native x86 API provider"); if (!overlap) return base; } } } NativeResult run_native_process_x86(ModuleSet &modules, PEModule &entry, const std::string &bootstrap, std::chrono::milliseconds timeout, std::optional launch, DesktopPresentation presentation, std::function>(std::string_view)> source) { if (timeout.count() <= 0 || entry.image.is_64 || (entry.image.characteristics & 0x2000) || !std::any_of(modules.all().begin(), modules.all().end(), [&](const auto &item) { return item.second.get() == &entry; })) throw std::runtime_error("native x86 process requires a PE32 executable graph and positive timeout"); for (const auto &[name, module] : modules.all()) if (module->image.is_64) throw std::runtime_error("native x86 process cannot mix PE64 modules: " + name); // Libraries the program ships and imports statically join its graph; the runtime's own libraries answer for the system ones. if (source) { std::vector provided{"kernel32.dll", "user32.dll", "gdi32.dll", "advapi32.dll", "setupapi.dll", "msvcrt.dll", "winmm.dll", "dsound.dll", "vulkan-1.dll", "shell32.dll", "comctl32.dll", "ntdll.dll", "version.dll", "ole32.dll"}; if (!source("d3d9.dll")) provided.push_back("d3d9.dll"); // A library that is not there is left for the binder, which names every unresolved import at once. load_initial_dependencies(modules, source, provided, false, true); // Libraries loaded later, such as installer plug-ins, import the C runtime by name: its component answers for them. if (!modules.all().contains("ucrtbase.dll")) if (auto bytes = source("ucrtbase.dll")) { const PEImage image(*bytes); if (!image.is_64) modules.add("ucrtbase.dll", std::move(*bytes)); } } // What the graph imports from each system library by name. The runtime answers for all of it: a name that has a handler runs, a name that has // none fails visibly when the program calls it, so a program loads whether or not every function it imports is implemented yet. std::map> imported_names; std::map>> unknown_ordinals; std::set unknown_names; { std::set system_libraries(system_library_names().begin(), system_library_names().end()); system_libraries.insert("usp10.dll"); system_libraries.insert("ntdll.dll"); for (const auto &[module_name, module] : modules.all()) for (const auto &symbol : module->imports) { if (symbol.by_ordinal) { // A number with no name known for it still gets an export, so the program binds and the call it ends in names the number. auto library = symbol.module; for (auto &character : library) if (character >= 'A' && character <= 'Z') character = static_cast(character + ('a' - 'A')); if (library.find('.') == std::string::npos) library += ".dll"; const auto known = system_export_ordinals(library); if (system_libraries.contains(library) && !modules.all().contains(library) && std::none_of(known.begin(), known.end(), [&](const auto &item) { return item.second == symbol.ordinal; })) { const auto name = "Ordinal" + std::to_string(symbol.ordinal); imported_names[library].insert(name); unknown_ordinals[library].emplace_back(*unknown_names.insert(name).first, symbol.ordinal); } continue; } auto library = symbol.module; for (auto &character : library) if (character >= 'A' && character <= 'Z') character = static_cast(character + ('a' - 'A')); if (library.find('.') == std::string::npos) library += ".dll"; if (system_libraries.contains(library) && !modules.all().contains(library)) imported_names[library].insert(symbol.name); } } // A library's own list of names together with the ones the graph imports from it. const auto with_imports = [&](const std::string &library, std::span own) { std::vector merged(own.begin(), own.end()); // A library loaded later, such as an installer plug-in, imports names the initial graph never named: every public name binds. const auto published = system_export_names(library); merged.insert(merged.end(), published.begin(), published.end()); if (const auto found = imported_names.find(library); found != imported_names.end()) for (const auto &name : found->second) merged.push_back(name); std::sort(merged.begin(), merged.end()); merged.erase(std::unique(merged.begin(), merged.end()), merged.end()); return merged; }; // The stdcall argument words of a function: the library's own decorated symbol first, then what Raven's tables say. const auto words_of = [](const std::string &library, std::string_view name, uint32_t fallback) { const auto authoritative = system_export_words(library, name); if (authoritative != UINT32_MAX) return authoritative; return fallback; }; // A guest component that implements part of a system library receives those calls directly, as for 64-bit programs. const auto overlay = [&](PEModule &provider, std::string_view library) { struct Rule { const char *library, *component, *prefix; }; static constexpr Rule rules[] = {{"user32.dll", "raven-user32.dll", "raven-user32."}, {"comdlg32.dll", "raven-shell.dll", "raven-shell."}, {"ole32.dll", "raven-shell.dll", "raven-shell."}, {"shell32.dll", "raven-shell.dll", "raven-shell."}, {"msvcrt.dll", "ucrtbase.dll", "ucrtbase."}, {"shlwapi.dll", "raven-shell.dll", "raven-shell."}}; for (const auto &rule : rules) { if (library != rule.library) continue; const auto component = modules.all().find(rule.component); if (component == modules.all().end() || component->second->runtime_api) continue; // The startup and exception entry points of msvcrt.dll stay with the runtime, which knows the process. const auto host_crt = std::string_view(rule.library) == "msvcrt.dll" ? NativeCRT::exports() : std::span{}; for (auto &symbol : provider.exports) if (!symbol.names.empty() && std::find(host_crt.begin(), host_crt.end(), symbol.names.front()) == host_crt.end() && std::any_of(component->second->exports.begin(), component->second->exports.end(), [&](const auto &candidate) { return candidate.forwarder.empty() && std::find(candidate.names.begin(), candidate.names.end(), symbol.names.front()) != candidate.names.end(); })) symbol.forwarder = rule.prefix + symbol.names.front(); } }; std::map> traps; if (!modules.all().contains("kernel32.dll")) { std::vector own_names; for (const auto &item : kernel32_table()) own_names.push_back(item.name); const auto names = with_imports("kernel32.dll", own_names); const auto size = PEImage(runtime_provider("kernel32.dll", 0x70000000ULL, names, true)).image_size; const auto base = provider_base(modules, size); auto &provider = modules.add("kernel32.dll", runtime_provider("kernel32.dll", base, names, true), base); provider.runtime_api = true; for (const auto &symbol : provider.exports) { const auto &exported = symbol.names.at(0); const auto found = std::find_if(kernel32_table().begin(), kernel32_table().end(), [&](const auto &item) { return item.name == exported; }); const auto view = std::find(names.begin(), names.end(), exported); const uint32_t fixed = found != kernel32_table().end() ? found->words : registry_words(exported) != UINT32_MAX ? registry_words(exported) : 0; traps.emplace(provider.loaded.base() + symbol.address, std::pair{*view, words_of("kernel32.dll", exported, fixed) * 4}); } } const auto add_desktop = [&](const char *dll, std::span implemented, std::span pending) { if (modules.all().contains(dll)) return; std::vector own(implemented.begin(), implemented.end()); own.insert(own.end(), pending.begin(), pending.end()); const auto exports = with_imports(dll, own); const auto size = PEImage(runtime_provider(dll, 0x70000000ULL, exports, true)).image_size; const auto base = provider_base(modules, size); auto &provider = modules.add(dll, runtime_provider(dll, base, exports, true), base); provider.runtime_api = true; overlay(provider, dll); for (const auto &symbol : provider.exports) { if (!symbol.forwarder.empty()) continue; const auto &exported = symbol.names.at(0); const auto view = std::find(exports.begin(), exports.end(), exported); const auto found = desktop_arguments().find(exported); const uint32_t fixed = found != desktop_arguments().end() ? found->second : registry_words(exported) != UINT32_MAX ? registry_words(exported) : 0; traps.emplace(provider.loaded.base() + symbol.address, std::pair{*view, words_of(dll, exported, fixed) * 4}); } }; std::vector user_pending{"CheckMenuItem", "DialogBoxParamW", "EndDialog", "GetDesktopWindow", "GetDlgItemInt", "GetDlgItemTextW", "GetMenuItemRect", "KillTimer", "LoadAcceleratorsW", "LoadCursorW", "LoadIconW", "MessageBoxW", "MoveWindow", "PtInRect", "ReleaseCapture", "SetCapture", "SetDlgItemInt", "SetDlgItemTextW", "SetMenu", "SetRect", "SetTimer", "TranslateAcceleratorW", "WinHelpW", "wsprintfW", "wsprintfA", "CreateWindowExA", "DispatchMessageA", "GetMessageA", "PeekMessageA", "RegisterClassA", "RegisterClassExA", "UnregisterClassA", "LoadCursorA", "LoadIconA", "GetAsyncKeyState", "SetCursor", "GetCursorPos", "SetCursorPos", "DefWindowProcA", "SendMessageA", "PostMessageA", "SetWindowTextA", "ExitWindowsEx", "MessageBoxA", "SetWindowPos", "CheckDlgButton", "MessageBoxIndirectW", "TrackPopupMenu", "DrawTextW", "CreateDialogParamW", "SendMessageTimeoutW"}; std::vector gdi_pending{"CreateBrushIndirect", "GetDeviceCaps", "SetDIBitsToDevice", "SetLayout", "SetPixel", "SetROP2", "CreateFontA", "ExtTextOutA", "GetDIBits", "GetTextExtentPoint32A", "GetTextMetricsA", "SetDIBits", "RealizePalette", "SelectPalette", "PatBlt", "CreatePalette"}; for (const auto name : system_export_names("gdi32.dll")) if (name.starts_with("D3DKMT")) gdi_pending.push_back(name); for (const auto &item : host_exports("user32.dll")) user_pending.push_back(item.name); for (const auto &item : host_exports("gdi32.dll")) gdi_pending.push_back(item.name); add_desktop("user32.dll", NativeDesktop::user_exports(), user_pending); add_desktop("gdi32.dll", NativeDesktop::gdi_exports(), gdi_pending); // Libraries whose argument counts come from their own implementation or the unsupported-service table. const auto add_library = [&](const char *dll, std::span exports, std::span data, const std::function &count, std::span> ordinals = {}) { if (modules.all().contains(dll)) return; const auto merged = with_imports(dll, exports); const auto published_numbers = ordinals.empty() ? system_export_ordinals(dll) : ordinals; std::vector> numbered(published_numbers.begin(), published_numbers.end()); if (const auto extra = unknown_ordinals.find(dll); extra != unknown_ordinals.end()) numbered.insert(numbered.end(), extra->second.begin(), extra->second.end()); const auto size = PEImage(runtime_provider(dll, 0x70000000ULL, merged, true, data, numbered)).image_size; const auto base = provider_base(modules, size); auto &provider = modules.add(dll, runtime_provider(dll, base, merged, true, data, numbered), base); provider.runtime_api = true; overlay(provider, dll); for (const auto &symbol : provider.exports) { if (!symbol.forwarder.empty()) continue; // An export that has only an ordinal is the name the library lists for that ordinal. std::string exported; if (!symbol.names.empty()) { exported = symbol.names.front(); } else { const auto found = std::find_if(numbered.begin(), numbered.end(), [&](const auto &item) { return item.second == symbol.ordinal; }); // A slot that no name and no listed ordinal claims is padding between ordinals. if (found == numbered.end() || !symbol.address) continue; exported = found->first; } if (std::find(data.begin(), data.end(), exported) != data.end()) continue; const auto stable = std::find(merged.begin(), merged.end(), exported); if (stable == merged.end()) throw std::runtime_error("native x86 provider export is not in its list: " + std::string(dll) + "!" + exported); traps.emplace(provider.loaded.base() + symbol.address, std::pair{*stable, words_of(dll, exported, count(exported)) * 4}); } }; std::vector advapi_names; for (const auto &item : host_exports("advapi32.dll")) advapi_names.push_back(item.name); std::sort(advapi_names.begin(), advapi_names.end()); add_library("advapi32.dll", advapi_names, {}, [](std::string_view name) { for (const auto &item : host_exports("advapi32.dll")) if (item.name == name) return item.words; return uint32_t(0); }); std::vector setupapi_names; for (const auto &item : host_exports("setupapi.dll")) setupapi_names.push_back(item.name); std::sort(setupapi_names.begin(), setupapi_names.end()); add_library("setupapi.dll", setupapi_names, {}, [](std::string_view name) { for (const auto &item : host_exports("setupapi.dll")) if (item.name == name) return item.words; return uint32_t(0); }); // msvcrt.dll answers for the runtime's own entry points and, through forwarders, for everything the C runtime component implements. std::vector crt_names(NativeCRT::exports().begin(), NativeCRT::exports().end()); if (const auto component = modules.all().find("ucrtbase.dll"); component != modules.all().end()) for (const auto &symbol : component->second->exports) if (!symbol.names.empty() && symbol.forwarder.empty()) crt_names.push_back(symbol.names.front()); std::sort(crt_names.begin(), crt_names.end()); crt_names.erase(std::unique(crt_names.begin(), crt_names.end()), crt_names.end()); add_library("msvcrt.dll", crt_names, NativeCRT::data_exports(), [](std::string_view name) { return uint32_t(0); }); static constexpr std::array winmm_exports{"PlaySoundW", "timeBeginPeriod", "timeEndPeriod", "timeGetTime"}; static constexpr std::array shell_exports{"ShellAboutW"}; // File version resources are read by the shared handlers; the names are in byte order. static constexpr std::array version_exports{"GetFileVersionInfoA", "GetFileVersionInfoExW", "GetFileVersionInfoSizeA", "GetFileVersionInfoSizeExW", "GetFileVersionInfoSizeW", "GetFileVersionInfoW", "VerQueryValueA", "VerQueryValueW"}; static constexpr std::array controls_exports{"InitCommonControlsEx"}; add_library("winmm.dll", winmm_exports, {}, [](std::string_view name) { return uint32_t(name == "PlaySoundW" ? 3 : name == "timeGetTime" ? 0 : 1); }); const auto dsound_methods = NativeX86DirectSound::method_names(); add_library("dsound.dll", NativeX86DirectSound::entry_names(), {}, [](std::string_view name) { return NativeX86DirectSound::entry_words(name); }); add_library("raven-dsound.dll", dsound_methods, {}, [](std::string_view name) { return NativeX86DirectSound::method_words(name); }); // A Direct3D 9 library that ships beside the program is the program's choice (DXVK, for one); the built-in one answers otherwise. bool d3d9_beside = false; if (launch) { const auto probe_prefix = NativePrefix::from_environment(); const NativePaths probe(probe_prefix); const auto program = probe.resolve(launch->image_path); if (program && program->exists) for (const auto &item : std::filesystem::directory_iterator(program->host.parent_path())) { auto filename = item.path().filename().string(); for (auto &character : filename) if (character >= 'A' && character <= 'Z') character = static_cast(character + ('a' - 'A')); d3d9_beside |= filename == "d3d9.dll"; } } const auto d3d9_methods = NativeX86D3D9::method_names(); if (!d3d9_beside) { add_library("d3d9.dll", NativeX86D3D9::entry_names(), {}, [](std::string_view) { return uint32_t(1); }); add_library("raven-d3d9.dll", d3d9_methods, {}, [](std::string_view name) { return NativeX86D3D9::method_words(name); }); } #ifdef RAVEN_VULKAN_THUNK32 // The Vulkan loader for 32-bit programs: every command the bridge implements, taking 32-bit stdcall arguments. static const auto vulkan_exports = VulkanThunk32::exports(VulkanBridge::exports()); std::vector vulkan_names; for (const auto &item : vulkan_exports) vulkan_names.push_back(item.first); add_library("vulkan-1.dll", vulkan_names, {}, [](std::string_view name) { for (const auto &item : vulkan_exports) if (item.first == name) return item.second; return uint32_t(0); }); #endif add_library("version.dll", version_exports, {}, [](std::string_view name) { return registry_words(name); }); std::vector shell_names(shell_exports.begin(), shell_exports.end()); shell_names.push_back("SHBrowseForFolderW"); for (const auto &item : host_exports("shell32.dll")) shell_names.push_back(item.name); std::sort(shell_names.begin(), shell_names.end()); shell_names.erase(std::unique(shell_names.begin(), shell_names.end()), shell_names.end()); add_library("shell32.dll", shell_names, {}, [](std::string_view name) { if (name == "SHBrowseForFolderW") return uint32_t(1); for (const auto &item : host_exports("shell32.dll")) if (item.name == name) return item.words; return uint32_t(4); }); // The common controls come from the common controls component once it is built for 32 bits; until then these names resolve and fail when called. static constexpr std::array controls_names{"ImageList_AddMasked", "ImageList_Create", "ImageList_Destroy", "InitCommonControls", "InitCommonControlsEx"}; static constexpr std::array, 1> controls_ordinals{{{"InitCommonControls", 17}}}; add_library("comctl32.dll", controls_names, {}, [](std::string_view name) { return uint32_t(name == "ImageList_Create" ? 5 : name == "ImageList_AddMasked" ? 3 : name == "ImageList_Destroy" || name == "InitCommonControlsEx" ? 1 : 0); }, controls_ordinals); std::vector ole_names; for (const auto &item : host_exports("ole32.dll")) ole_names.push_back(item.name); std::sort(ole_names.begin(), ole_names.end()); add_library("ole32.dll", ole_names, {}, [](std::string_view name) { for (const auto &item : host_exports("ole32.dll")) if (item.name == name) return item.words; return uint32_t(0); }); // A system library the runtime has no provider for yet is served for the names the graph imports from it, with the shared handlers. std::set served; for (const auto &[library, library_names] : imported_names) { (void)library_names; served.insert(library); } for (const auto library : system_library_names()) served.insert(std::string(library)); for (const auto &library : served) { if (modules.all().contains(library)) continue; add_library(library.c_str(), {}, {}, [](std::string_view name) { return registry_words(name) != UINT32_MAX ? registry_words(name) : uint32_t(0); }, system_export_ordinals(library)); } alias_api_sets(modules); modules.bind_imports(); std::vector images; for (const auto &[name, module] : modules.all()) images.push_back(&module->loaded); const auto deadline = std::chrono::steady_clock::now() + timeout; NativeProcess process(images, bootstrap, timeout, NativeMode::x86); for (const auto &[address, api] : traps) process.add_api_trap(address, api.second); NativeEnvironment environment(process, entry.loaded.base(), uint32_t(::getpid()), 1); NativeX86D3D9 direct3d9(process); NativeX86DirectSound directsound(process); { std::map method_traps; for (const auto &[address, api] : traps) if (direct3d9.com().owns(api.first) || directsound.com().owns(api.first)) method_traps.emplace(std::string(api.first), address); direct3d9.com().set_trap_addresses(method_traps); directsound.com().set_trap_addresses(std::move(method_traps)); } if (!launch) { NativeLaunch initial; for (const char byte : entry.name) { const auto character = static_cast(byte); if (character > 127) throw std::runtime_error("native default launch identity requires ASCII"); initial.image_path.push_back(char16_t(character)); } initial.command_line = u"\"" + initial.image_path + u"\""; launch = std::move(initial); } NativeObjects kernel_objects; adopt_inherited(kernel_objects, launch->inherited); std::array standard_handles{UINT64_MAX, UINT64_MAX, UINT64_MAX}; for (size_t i = 0; i < 3; ++i) { if (launch->standard_fds[i] < 0) continue; const auto inherited = kernel_objects.inherit_file(launch->standard_fds[i]); if (inherited.status) throw std::runtime_error("cannot inherit native standard file"); standard_handles[i] = inherited.value; } environment.prepare_parameters(*launch, standard_handles); NativeObjectAPI object_api(process, environment, kernel_objects, deadline); const auto prefix = NativePrefix::from_environment(); NativeRegistry registry(prefix.registry_file()); NativeProfile profile(process, environment, prefix); NativeCRT crt(process, environment); crt.initialize([&](std::string_view name) { return modules.resolve({"msvcrt.dll", std::string(name), 0, false, 0}); }); NativeModuleAPI module_api(process, environment, modules, entry); NativePaths paths(prefix); if (!launch->directory.empty()) paths.set_current(launch->directory); std::function)> call_guest; NativeResources resources(process, environment, modules, entry); // What a program may commit: 512 MiB by default, as set by RAVEN_COMMIT_LIMIT_MB (large programs, a browser engine for one, need more). const size_t commit_limit=(std::getenv("RAVEN_COMMIT_LIMIT_MB") ? size_t(std::strtoull(std::getenv("RAVEN_COMMIT_LIMIT_MB"),nullptr,10)) : size_t(512))*1024*1024; NativeMemory memory(process,environment,commit_limit); register_images(memory, modules); // Declared first so that it outlives the threads, which release their blocks through it when they are torn down. NativeX86Tls static_tls(process); NativeThreads guest_threads(process, environment, memory, nullptr, deadline); NativeX86Threads thread_api(process, environment, kernel_objects, guest_threads); NativeX86System system_api(process, environment, kernel_objects, guest_threads, memory); static_tls.attach_main(environment.teb()); guest_threads.set_block_hooks([&](uint64_t teb) { static_tls.create_thread(teb); }, [&](uint64_t teb) { static_tls.release_thread(teb); }); object_api.set_cooperative(true); NativeX86Loader library_loader(process, environment, modules, paths, [&](uint64_t address, std::span arguments) { return call_guest(address, arguments); }); library_loader.set_tls(&static_tls); library_loader.set_mapped([&memory](PEModule &module) { register_image(memory, module); }); if (launch) { const auto separator = launch->image_path.find_last_of(u'\\'); if (separator != std::u16string::npos) library_loader.set_application_directory(launch->image_path.substr(0, separator)); } module_api.set_loader([&](std::string name) -> uint64_t { return library_loader.load(std::move(name)); }, [&](uint64_t handle) -> bool { if (library_loader.release(handle)) return true; const bool known = std::any_of(modules.all().begin(), modules.all().end(), [&](const auto &item) { return item.second->loaded.base() == handle; }); if (!known) environment.set_last_error(6); return known; }); module_api.set_path_lookup([&](const std::string &path) { return library_loader.find(path); }); DesktopObjects desktop_objects; NativeDesktop desktop(process, environment, desktop_objects, resources); // The built-in bitmaps, icons and cursors of user32 live in the component when it is there. if (const auto component = modules.all().find("raven-user32.dll"); component != modules.all().end() && !component->second->runtime_api) desktop.set_oem_module(component->second->loaded.base()); NativeWindows windows(process, environment, desktop_objects); // Registry handlers whose arguments carry no pointer-width structures also serve 32-bit programs. ApiRegistry shared_api; windows.register_api(shared_api); desktop.register_gdi_api(shared_api); desktop.register_text_api(shared_api); desktop.register_image_api(shared_api); NativeX86Compat compat(process, environment, windows); // Shared handlers whose arguments are 32-bit words and whose structures carry no pointers. std::set shared_names{"MoveWindow", "LoadCursorA", "LoadIconA", "GetAsyncKeyState", "SetCursor", "GetCursorPos", "SetCursorPos", "DefWindowProcA", "SendMessageA", "PostMessageA", "SetWindowTextA", "CreateFontA", "ExtTextOutA", "GetDIBits", "GetTextExtentPoint32A", "GetTextMetricsA", "SetDIBits", "MessageBoxA", "SetWindowPos", "SelectPalette", "PatBlt", "_lopen", "_lcreat", "_lread", "_lwrite", "_llseek", "lstrcpyA", "lstrlenA", "GetWindowsDirectoryA", "GlobalUnlock", "GlobalFree", "GlobalAlloc", "SetErrorMode", "GlobalLock", "GetOEMCP", "GetCPInfo", "WideCharToMultiByte", "DeleteFileA", "FreeEnvironmentStringsW", "GetEnvironmentStringsW", "GetVersion", "GetFileAttributesA", "CreateFileW", "DeleteFileW", "GetEnvironmentVariableA", "GetFileInformationByHandle", "MultiByteToWideChar", "QueryPerformanceCounter", "QueryPerformanceFrequency", "ReadFile", "SetFilePointer", "GetFileSizeEx", "FlushFileBuffers", "MonitorFromPoint"}; for (const auto &item : shared_exports) shared_names.insert(item.name); for (const auto name : shared_allowed) shared_names.insert(name); windows.set_deadline(deadline); #ifdef RAVEN_VULKAN_THUNK32 VulkanBridge vulkan(process, modules); vulkan.set_surface_source({[&windows](uint64_t window) -> std::optional> { const auto surface = windows.host_surface(window); if (!surface) return std::nullopt; return std::pair{surface->display, surface->surface}; }, [&windows](uint64_t window) { windows.release_host_surface(window); }, [&windows](uint64_t window) { return windows.client_size(window); }}); VulkanThunk32 vulkan32(process, vulkan); #endif windows.set_presentation(std::move(presentation)); desktop.set_updated([&](uint64_t window) { windows.drawing(window); }); struct ProcessExit { uint32_t code; uint64_t instruction; }; const auto remaining = [&] { return std::chrono::duration_cast(deadline - std::chrono::steady_clock::now()); }; // Runs one guest call, answering every API trap until the call returns or stops for another reason. const auto invoke = [&](uint64_t address, std::span arguments) { auto budget = remaining(); if (budget.count() <= 0) return NativeResult{NativeStop::timeout, 0, address, 0}; auto result = process.active() ? process.callback(address, arguments, budget) : process.call(address, arguments, budget); while (result.stop == NativeStop::api) { const auto found = traps.find(result.value); if (found == traps.end()) return result; const auto api = found->second.first; if (getenv("RAVEN_TRACE_X86")) { // Argument words only reflect stdcall exports; cdecl and variadic entries report none. std::cerr << "api " << api << '(' << std::hex; const auto count = found->second.second / 4; if (count) for (const auto value : process.api_arguments(count)) std::cerr << ' ' << value; std::cerr << std::dec << " )"; // RAVEN_TRACE_X86=2 also shows every argument that points at readable text, narrow or wide. if (const char *level = getenv("RAVEN_TRACE_X86"); level && level[0] == '2' && count) for (const auto value : process.api_arguments(count)) { if (value < 0x10000 || value > 0x7fffffffULL) continue; try { const auto bytes = process.read_memory(value, 120); std::string text; const bool wide = bytes[1] == 0 && bytes[0] >= 32 && bytes[0] < 127 && bytes[2] >= 32 && bytes[2] < 127 && bytes[3] == 0; for (size_t at = 0; at < bytes.size(); at += wide ? 2 : 1) { const auto character = bytes[at]; if (character == 0 && (!wide || bytes[at + 1] == 0)) break; if (character < 32 || character > 126 || (wide && bytes[at + 1])) { text.clear(); break; } text.push_back(char(character)); } if (text.size() >= 3) std::cerr << " <" << text << '>'; } catch (const std::runtime_error &) { } } // Window classes and texts are what a diagnosis of a window needs most. { static const std::map> wide_strings{{"SetWindowTextW", {1}}, {"SetDlgItemTextW", {2}}, {"CreateWindowExW", {1, 2}}, {"RegisterClassW", {}}, {"MessageBoxW", {1, 2}}, {"LoadStringW", {}}, {"CreateProcessW", {0, 1, 7}}, {"ShellExecuteW", {1, 2, 3}}, {"LoadLibraryW", {0}}, {"CreateFileW", {0}}, {"GetFileAttributesW", {0}}}; if (const auto entry = wide_strings.find(api); entry != wide_strings.end() && !entry->second.empty()) { const auto words = process.api_arguments(found->second.second / 4); for (const auto index : entry->second) { if (index >= words.size() || words[index] <= 0xffff) continue; try { const auto bytes = process.read_memory(words[index], 200); std::cerr << " [" << index << "]=\""; for (size_t at = 0; at + 1 < bytes.size() && (bytes[at] || bytes[at + 1]); at += 2) std::cerr << (bytes[at + 1] == 0 && bytes[at] >= 32 && bytes[at] < 127 ? char(bytes[at]) : '?'); std::cerr << '"'; } catch (...) {} } } } // The names a program resolves at run time are what a diagnosis needs most. if (api == "LoadLibraryA" || api == "LoadLibraryW" || api == "GetProcAddress") { const auto words = process.api_arguments(api == "GetProcAddress" ? 2 : 1); const auto text = words[api == "GetProcAddress" ? 1 : 0]; if (text > 0xffff) { try { const bool wide = api == "LoadLibraryW"; const auto bytes = process.read_memory(text, 64); std::cerr << " \""; for (size_t at = 0; at + (wide ? 1 : 0) < bytes.size() && bytes[at] && (!wide || at % 2 == 0); at += wide ? 2 : 1) std::cerr << char(bytes[at]); std::cerr << '"'; } catch (...) {} } } std::cerr << '\n'; } if (api == "VerSetConditionMask" || api == "VerifyVersionInfoW") { // The 64-bit condition mask is two words on the stack of a 32-bit program, and the handler takes it whole. const bool set = api == "VerSetConditionMask"; const auto words = process.api_arguments(4); const auto entry = shared_api.find(api); if (!entry) { std::cerr << "raven: unimplemented native x86 API " << api << '\n'; return result; } const uint64_t mask = (words[set ? 0 : 2] & 0xffffffffULL) | words[set ? 1 : 3] << 32; ApiCall inner(process, environment, set ? std::vector{mask, words[2], words[3]} : std::vector{words[0], words[1], mask}); entry->second(inner); process.return_api(inner.result); budget = remaining(); result = process.resume(budget); continue; } if (api == "SetFilePointerEx") { // The 64-bit distance arrives as two words; the shared handler takes it whole. const auto words = process.api_arguments(5); const auto entry = shared_api.find("SetFilePointerEx"); if (!entry) { std::cerr << "raven: unimplemented native x86 API " << api << '\n'; return result; } ApiCall inner(process, environment, {words[0], uint64_t(int64_t(int32_t(words[2])) << 32 | words[1]), words[3], words[4]}); entry->second(inner); process.return_api(inner.result); budget = remaining(); result = process.resume(budget); continue; } if (api == "RaiseException") { const auto words = process.api_arguments(4); std::cerr << "raven: unhandled exception 0x" << std::hex << uint32_t(words[0]) << std::dec << " raised by the program\n"; process.abandon_call(); throw ProcessExit{uint32_t(words[0]), result.instruction}; } if (api == "CreateFontA") { // The face name is an ANSI string; the shared handler takes the wide form of the same call. auto words = process.api_arguments(14); words[13] = compat.widen(words[13]); const auto entry = shared_api.find("CreateFontW"); if (!entry) { std::cerr << "raven: unimplemented native x86 API " << api << '\n'; return result; } ApiCall inner(process, environment, words); entry->second(inner); process.return_api(inner.result); budget = remaining(); result = process.resume(budget); continue; } if (api == "MonitorFromPoint") { // A POINT passed by value is two words here and one packed word in the shared handler. const auto words = process.api_arguments(3); const auto entry = shared_api.find("MonitorFromPoint"); if (!entry) { std::cerr << "raven: unimplemented native x86 API " << api << '\n'; return result; } ApiCall inner(process, environment, {uint64_t(uint32_t(words[1])) << 32 | uint32_t(words[0]), words[2]}); entry->second(inner); process.return_api(inner.result); budget = remaining(); result = process.resume(budget); continue; } if (api == "DuplicateHandle") { // A handle that crosses to or from another process goes through the shared handler, which owns the channels to other // processes; the pseudo handles are widened to their 64-bit values for it. auto words = process.api_arguments(7); if (words[0] != UINT32_MAX || words[2] != UINT32_MAX) { for (const size_t index : {0, 1, 2}) if (words[index] >= 0xfffffffaULL) words[index] |= 0xffffffff00000000ULL; const auto entry = shared_api.find("DuplicateHandle"); ApiCall inner(process, environment, words); entry->second(inner); process.return_api(inner.result); budget = remaining(); result = process.resume(budget); continue; } } if (api == "TerminateProcess") { const auto words = process.api_arguments(2); if (words[0] != UINT32_MAX) { const auto entry = shared_api.find("TerminateProcess"); ApiCall inner(process, environment, {uint64_t(words[0]), uint64_t(words[1])}); entry->second(inner); process.return_api(inner.result); budget = remaining(); result = process.resume(budget); continue; } process.abandon_call(); throw ProcessExit{uint32_t(words[1]), result.instruction}; } if (api == "ExitProcess" || api == "exit" || api == "_exit") { const auto code = uint32_t(process.api_arguments(1)[0]); process.abandon_call(); throw ProcessExit{code, result.instruction}; } if (!environment.dispatch(api) && !thread_api.dispatch(api) && !system_api.dispatch(api) && !compat.dispatch(api) && !direct3d9.dispatch(api) && !directsound.dispatch(api) && #ifdef RAVEN_VULKAN_THUNK32 !vulkan32.dispatch(api) && #endif !object_api.dispatch(api) && !module_api.dispatch(api) && !resources.dispatch(api) && !memory.dispatch(api) && !kernel_string_api(process, environment, entry, *launch, api) && !shared_api.dispatch(process, environment, api, true) && !desktop.dispatch(api) && !windows.dispatch(api) && !profile.dispatch(api) && !crt.dispatch(api) && !unsupported_library(process, environment, api)) { std::cerr << "raven: unimplemented native x86 API " << api << '\n'; // RAVEN_CONTINUE_UNIMPLEMENTED lists every missing function of a run: each call fails with ERROR_CALL_NOT_IMPLEMENTED instead. if (!std::getenv("RAVEN_CONTINUE_UNIMPLEMENTED")) return result; environment.set_last_error(120); process.return_api(0); } budget = remaining(); if (budget.count() <= 0) return NativeResult{NativeStop::timeout, 0, result.instruction, 0}; result = process.resume(budget); } if (result.stop == NativeStop::fault && getenv("RAVEN_TRACE_X86")) { // What a crash needs: the registers, the bytes at the instruction and the top of the stack. const auto context = process.context(); const auto &r = context.registers; std::cerr << std::hex << "fault: eip " << uint32_t(r.rip) << " eax " << uint32_t(r.rax) << " ebx " << uint32_t(r.rbx) << " ecx " << uint32_t(r.rcx) << " edx " << uint32_t(r.rdx) << " esi " << uint32_t(r.rsi) << " edi " << uint32_t(r.rdi) << " ebp " << uint32_t(r.rbp) << " esp " << uint32_t(r.rsp) << " address " << result.fault_address << "\ncode:"; try { for (const auto byte : process.read_memory(r.rip - 64, 96)) std::cerr << ' ' << unsigned(byte); std::cerr << "\nstack:"; for (const auto byte : process.read_memory(r.rsp, 256)) std::cerr << ' ' << unsigned(byte); } catch (...) {} std::cerr << std::dec << '\n'; } return result; }; windows.set_call(invoke); guest_threads.set_runner(invoke); // Threads announce themselves to the images that keep per-thread state: TLS callbacks first, then the library entry points. guest_threads.set_notifier([&](uint32_t reason) { const auto notify = [&](const PEModule &module) { for (const auto callback : static_tls.callbacks(module)) { const uint64_t arguments[] = {module.loaded.base(), reason, 0}; const auto result = invoke(callback, arguments); if (result.stop != NativeStop::returned) throw result; } if ((module.image.characteristics & 0x2000) && module.image.entry && !module.runtime_api && &module != &entry) { const uint64_t arguments[] = {module.loaded.base(), reason, 0}; const auto result = invoke(module.loaded.base() + module.image.entry, arguments); if (result.stop != NativeStop::returned) throw result; } }; if (reason == 2) { notify(entry); for (const auto &[name, module] : modules.all()) if (module.get() != &entry) notify(*module); } else { for (const auto &[name, module] : modules.all()) if (module.get() != &entry) notify(*module); notify(entry); } }); call_guest = invoke; // File, memory, text and system handlers of the shared registry take 32-bit words as well; they get the same process services. NativeHeap guest_heap(process, environment, modules, entry.image); GuestAllocator allocator(memory, guest_heap); compat.set_global_blocks([&allocator](uint64_t address) { return allocator.size(address); }, [&allocator](uint64_t address) { return allocator.release(address); }); KernelContext kernel{process, environment, memory, allocator, kernel_objects, guest_threads, paths, modules, entry, *launch, invoke}; guest_threads.set_watched([reactor = kernel.reactor](std::vector &descriptors) { reactor->watch(descriptors); }); register_file_api(shared_api, kernel); register_memory_api(shared_api, kernel); register_text_api(shared_api, kernel); register_system_api(shared_api, kernel); register_version_api(shared_api, kernel); register_registry_api(shared_api, kernel, registry); register_service_api(shared_api, kernel, registry); register_shell_api(shared_api, kernel); register_shlwapi_api(shared_api, kernel); register_platform_api(shared_api, kernel); register_process_api(shared_api, kernel); register_thread_api(shared_api, kernel); register_sync_api(shared_api, kernel); register_security_api(shared_api, kernel); register_child_process_api(shared_api, kernel); register_winsock_api(shared_api, kernel); register_host_api(shared_api, kernel); register_pipe_api(shared_api, kernel); register_job_api(shared_api, kernel); register_crypt32_api(shared_api, kernel); register_process_info_api(shared_api, kernel); register_font_service(shared_api, kernel); // A 32-bit program runs under WOW64 on the 64-bit system Raven presents. File system redirection is not done (one System32 exists), // so switching it off and back on changes nothing. shared_api.add("Wow64DisableWow64FsRedirection", 1, [](ApiCall &call) { if (call[0]) call.store(call[0], 0); call.ret(1); }); shared_api.add("Wow64RevertWow64FsRedirection", 1, [](ApiCall &call) { call.ret(1); }); register_ansi_adapters(shared_api); register_legacy_file_api(shared_api); windows.set_yield([&guest_threads] { return guest_threads.live() > 1 && guest_threads.yield(); }); crt.set_call(invoke); try { // Images of the startup graph with thread local storage get their blocks, and their callbacks hear about the process starting. for (const auto &[name, module] : modules.all()) static_tls.add(*module); // Dependencies first, the program last: each image's callbacks, then the entry point of a library that ships with the program. const auto start_image = [&](const PEModule &module) { for (const auto callback : static_tls.callbacks(module)) { const uint64_t arguments[] = {module.loaded.base(), 1, 0}; const auto started = invoke(callback, arguments); if (started.stop != NativeStop::returned) throw started; } }; auto order = initialization_order(modules, entry); // The user32 component registers the built-in classes in its entry point, before any library that subclasses them. if (const auto controls = modules.all().find("raven-user32.dll"); controls != modules.all().end() && !controls->second->runtime_api) { const auto found = std::find(order.begin(), order.end(), controls->second.get()); if (found != order.end()) { order.erase(found); order.insert(order.begin(), controls->second.get()); } } for (const auto *module : order) { if (module == &entry) continue; start_image(*module); if ((module->image.characteristics & 0x2000) && module->image.entry && !module->runtime_api) { const uint64_t arguments[] = {module->loaded.base(), 1, 0}; const auto attached = invoke(module->loaded.base() + module->image.entry, arguments); if (attached.stop != NativeStop::returned) throw attached; if (static_cast(attached.value) == 0) throw std::runtime_error("DLL_PROCESS_ATTACH failed: " + module->name); } } start_image(entry); const auto result = invoke(entry.loaded.base() + entry.image.entry, {}); windows.shutdown(); return result; } catch (const ProcessExit &request) { guest_threads.stop_time_slicing(); windows.shutdown(); return {NativeStop::exited, request.code, request.instruction, 0}; } catch (const NativeResult &stopped) { windows.shutdown(); return stopped; } } }