From 836f485d660e6a5dd8db470973392b518df2b21e Mon Sep 17 00:00:00 2001 From: Luke Street Date: Fri, 26 Sep 2025 00:55:35 -0600 Subject: [PATCH] Initial external DLL support --- CMakeLists.txt | 1 + README.md | 2 +- common.h | 45 ++- dll/crt.cpp | 140 ++++++- dll/kernel32.cpp | 211 ++++++++-- loader.cpp | 81 +++- main.cpp | 156 +------- module_registry.cpp | 815 +++++++++++++++++++++++++++++++++++++++ test/Makefile | 20 + test/external_exports.c | 22 ++ test/test_external_dll.c | 32 ++ 11 files changed, 1322 insertions(+), 203 deletions(-) create mode 100644 module_registry.cpp create mode 100644 test/Makefile create mode 100644 test/external_exports.c create mode 100644 test/test_external_dll.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 6c89538..755a4cb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -31,6 +31,7 @@ add_executable(wibo files.cpp handles.cpp loader.cpp + module_registry.cpp main.cpp processes.cpp strutil.cpp diff --git a/README.md b/README.md index 586adec..c98ff23 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ Rough to-do list: - Do something intelligent with Windows `HANDLE`s - Convert paths in environment variables (and the structure of `PATH` itself, maybe) to Windows format - Implement PE relocations rather than just failing unceremoniously -- Make the PE loader work for DLLs as well in case we ever want to load some +- Land external DLL loading support (module registry + search order + export resolution) --- diff --git a/common.h b/common.h index 0ac0157..8d56669 100644 --- a/common.h +++ b/common.h @@ -3,11 +3,14 @@ #include #include #include +#include +#include +#include #include +#include #include #include #include -#include // On Windows, the incoming stack is aligned to a 4 byte boundary. // force_align_arg_pointer will realign the stack to match GCC's 16 byte alignment. @@ -57,7 +60,9 @@ typedef unsigned char BYTE; #define ERROR_INVALID_PARAMETER 87 #define ERROR_BUFFER_OVERFLOW 111 #define ERROR_INSUFFICIENT_BUFFER 122 +#define ERROR_MOD_NOT_FOUND 126 #define ERROR_NEGATIVE_SEEK 131 +#define ERROR_BAD_EXE_FORMAT 193 #define ERROR_ALREADY_EXISTS 183 #define INVALID_SET_FILE_POINTER ((DWORD)-1) @@ -94,7 +99,18 @@ namespace wibo { ResolveByName byName; ResolveByOrdinal byOrdinal; }; - extern const Module *modules[]; + struct ModuleInfo; + void initializeModuleRegistry(); + void shutdownModuleRegistry(); + ModuleInfo *moduleInfoFromHandle(HMODULE module); + void setDllDirectoryOverride(const std::filesystem::path &path); + void clearDllDirectoryOverride(); + std::optional dllDirectoryOverride(); + HMODULE findLoadedModule(const char *name); + void registerOnExitTable(void *table); + void addOnExitFunction(void *table, void (*func)()); + void executeOnExitTable(void *table); + void runPendingOnExit(ModuleInfo &info); HMODULE loadModule(const char *name); void freeModule(HMODULE module); @@ -110,6 +126,12 @@ namespace wibo { size_t imageSize; void *entryPoint; void *rsrcBase; + uintptr_t preferredImageBase; + intptr_t relocationDelta; + uint32_t exportDirectoryRVA; + uint32_t exportDirectorySize; + uint32_t relocationDirectoryRVA; + uint32_t relocationDirectorySize; template T *fromRVA(uint32_t rva) { @@ -122,9 +144,24 @@ namespace wibo { } }; struct ModuleInfo { - std::string name; - const wibo::Module* module = nullptr; + std::string originalName; + std::string normalizedName; + std::filesystem::path resolvedPath; + const wibo::Module *module = nullptr; std::unique_ptr executable; + void *entryPoint = nullptr; + void *imageBase = nullptr; + size_t imageSize = 0; + unsigned int refCount = 0; + bool dataFile = false; + bool processAttachCalled = false; + bool processAttachSucceeded = false; + bool dontResolveReferences = false; + uint32_t exportOrdinalBase = 0; + std::vector exportsByOrdinal; + std::unordered_map exportNameToOrdinal; + bool exportsInitialized = false; + std::vector onExitFunctions; }; extern Executable *mainModule; diff --git a/dll/crt.cpp b/dll/crt.cpp index 28822f0..99d6259 100644 --- a/dll/crt.cpp +++ b/dll/crt.cpp @@ -1,9 +1,18 @@ #include "common.h" +#include +#include +#include +#include +#include #include +#include typedef void (*_PVFV)(); typedef int (*_PIFV)(); +typedef void (*_invalid_parameter_handler)(const wchar_t *, const wchar_t *, const wchar_t *, unsigned int, uintptr_t); + +extern char **environ; typedef enum _crt_app_type { _crt_unknown_app, @@ -20,8 +29,10 @@ typedef enum _crt_argv_mode { namespace crt { int _commode = 0; +int _fmode = 0; std::vector<_PVFV> atexitFuncs; +_invalid_parameter_handler invalidParameterHandler = nullptr; void WIN_ENTRY _initterm(const _PVFV *ppfn, const _PVFV *end) { do { @@ -45,12 +56,15 @@ int WIN_ENTRY _initterm_e(const _PIFV *ppfn, const _PIFV *end) { void WIN_ENTRY _set_app_type(_crt_app_type type) { DEBUG_LOG("STUB: _set_app_type(%i)\n", type); } int WIN_ENTRY _set_fmode(int mode) { - DEBUG_LOG("STUB: _set_fmode(%i)\n", mode); + DEBUG_LOG("_set_fmode(%i)\n", mode); + _fmode = mode; return 0; } int *WIN_ENTRY __p__commode() { return &_commode; } +int *WIN_ENTRY __p__fmode() { return &_fmode; } + int WIN_ENTRY _crt_atexit(void (*func)()) { DEBUG_LOG("_crt_atexit(%p)\n", func); atexitFuncs.push_back(func); @@ -62,6 +76,13 @@ int WIN_ENTRY _configure_narrow_argv(_crt_argv_mode mode) { return 0; } +_invalid_parameter_handler WIN_ENTRY _set_invalid_parameter_handler(_invalid_parameter_handler newHandler) { + DEBUG_LOG("STUB: _set_invalid_parameter_handler(%p)\n", newHandler); + _invalid_parameter_handler oldHandler = invalidParameterHandler; + invalidParameterHandler = newHandler; + return oldHandler; +} + int WIN_ENTRY _controlfp_s(unsigned int *currentControl, unsigned int newControl, unsigned int mask) { DEBUG_LOG("STUB: _controlfp_s(%p, %u, %u)\n", currentControl, newControl, mask); return 0; @@ -84,12 +105,49 @@ int WIN_ENTRY _set_new_mode(int newhandlermode) { char **WIN_ENTRY _get_initial_narrow_environment() { return environ; } +char ***WIN_ENTRY __p__environ() { return &environ; } + char ***WIN_ENTRY __p___argv() { return &wibo::argv; } int *WIN_ENTRY __p___argc() { return &wibo::argc; } size_t WIN_ENTRY strlen(const char *str) { return ::strlen(str); } +int WIN_ENTRY strncmp(const char *lhs, const char *rhs, size_t count) { return ::strncmp(lhs, rhs, count); } + +void *WIN_ENTRY malloc(size_t size) { return ::malloc(size); } + +void *WIN_ENTRY calloc(size_t count, size_t size) { return ::calloc(count, size); } + +void *WIN_ENTRY realloc(void *ptr, size_t newSize) { return ::realloc(ptr, newSize); } + +void WIN_ENTRY free(void *ptr) { ::free(ptr); } + +void *WIN_ENTRY memcpy(void *dest, const void *src, size_t count) { return std::memcpy(dest, src, count); } + +int WIN_ENTRY __setusermatherr(void *handler) { + DEBUG_LOG("STUB: __setusermatherr(%p)\n", handler); + return 0; +} + +int WIN_ENTRY _initialize_onexit_table(void *table) { + DEBUG_LOG("STUB: _initialize_onexit_table(%p)\n", table); + wibo::registerOnExitTable(table); + return 0; +} + +int WIN_ENTRY _register_onexit_function(void *table, void (*func)()) { + DEBUG_LOG("STUB: _register_onexit_function(%p, %p)\n", table, func); + wibo::addOnExitFunction(table, func); + return 0; +} + +int WIN_ENTRY _execute_onexit_table(void *table) { + DEBUG_LOG("STUB: _execute_onexit_table(%p)\n", table); + wibo::executeOnExitTable(table); + return 0; +} + void WIN_ENTRY exit(int status) { DEBUG_LOG("exit(%i)\n", status); for (auto it = atexitFuncs.rbegin(); it != atexitFuncs.rend(); ++it) { @@ -99,6 +157,42 @@ void WIN_ENTRY exit(int status) { ::exit(status); } +void WIN_ENTRY _cexit(void) { + DEBUG_LOG("_cexit()\n"); + for (auto it = atexitFuncs.rbegin(); it != atexitFuncs.rend(); ++it) { + DEBUG_LOG("Calling atexit function %p\n", *it); + (*it)(); + } +} + +void WIN_ENTRY _exit(int status) { + DEBUG_LOG("_exit(%i)\n", status); + ::_exit(status); +} + +void WIN_ENTRY abort(void) { + DEBUG_LOG("abort()\n"); + std::abort(); +} + +using signal_handler = void (*)(int); + +signal_handler WIN_ENTRY signal(int signum, signal_handler handler) { return std::signal(signum, handler); } + +void *WIN_ENTRY __acrt_iob_func(unsigned int index) { + if (index == 0) + return stdin; + if (index == 1) + return stdout; + if (index == 2) + return stderr; + return nullptr; +} + +int WIN_ENTRY __stdio_common_vfprintf(unsigned long long /*options*/, FILE *stream, const char *format, void * /*locale*/, va_list args) { + return vfprintf(stream, format, args); +} + } // namespace crt static void *resolveByName(const char *name) { @@ -112,10 +206,14 @@ static void *resolveByName(const char *name) { return (void *)crt::_set_fmode; if (strcmp(name, "__p__commode") == 0) return (void *)crt::__p__commode; + if (strcmp(name, "__p__fmode") == 0) + return (void *)crt::__p__fmode; if (strcmp(name, "_crt_atexit") == 0) return (void *)crt::_crt_atexit; if (strcmp(name, "_configure_narrow_argv") == 0) return (void *)crt::_configure_narrow_argv; + if (strcmp(name, "_set_invalid_parameter_handler") == 0) + return (void *)crt::_set_invalid_parameter_handler; if (strcmp(name, "_controlfp_s") == 0) return (void *)crt::_controlfp_s; if (strcmp(name, "_configthreadlocale") == 0) @@ -126,14 +224,48 @@ static void *resolveByName(const char *name) { return (void *)crt::_set_new_mode; if (strcmp(name, "_get_initial_narrow_environment") == 0) return (void *)crt::_get_initial_narrow_environment; + if (strcmp(name, "__p__environ") == 0) + return (void *)crt::__p__environ; if (strcmp(name, "__p___argv") == 0) return (void *)crt::__p___argv; if (strcmp(name, "__p___argc") == 0) return (void *)crt::__p___argc; if (strcmp(name, "strlen") == 0) return (void *)crt::strlen; + if (strcmp(name, "strncmp") == 0) + return (void *)crt::strncmp; + if (strcmp(name, "malloc") == 0) + return (void *)crt::malloc; + if (strcmp(name, "calloc") == 0) + return (void *)crt::calloc; + if (strcmp(name, "realloc") == 0) + return (void *)crt::realloc; + if (strcmp(name, "free") == 0) + return (void *)crt::free; + if (strcmp(name, "memcpy") == 0) + return (void *)crt::memcpy; if (strcmp(name, "exit") == 0) return (void *)crt::exit; + if (strcmp(name, "_cexit") == 0) + return (void *)crt::_cexit; + if (strcmp(name, "_exit") == 0) + return (void *)crt::_exit; + if (strcmp(name, "abort") == 0) + return (void *)crt::abort; + if (strcmp(name, "signal") == 0) + return (void *)crt::signal; + if (strcmp(name, "__acrt_iob_func") == 0) + return (void *)crt::__acrt_iob_func; + if (strcmp(name, "__stdio_common_vfprintf") == 0) + return (void *)crt::__stdio_common_vfprintf; + if (strcmp(name, "__setusermatherr") == 0) + return (void *)crt::__setusermatherr; + if (strcmp(name, "_initialize_onexit_table") == 0) + return (void *)crt::_initialize_onexit_table; + if (strcmp(name, "_register_onexit_function") == 0) + return (void *)crt::_register_onexit_function; + if (strcmp(name, "_execute_onexit_table") == 0) + return (void *)crt::_execute_onexit_table; return nullptr; } @@ -149,6 +281,12 @@ wibo::Module lib_crt = { "api-ms-win-crt-stdio-l1-1-0.dll", "api-ms-win-crt-string-l1-1-0", "api-ms-win-crt-string-l1-1-0.dll", + "api-ms-win-crt-environment-l1-1-0", + "api-ms-win-crt-environment-l1-1-0.dll", + "api-ms-win-crt-math-l1-1-0", + "api-ms-win-crt-math-l1-1-0.dll", + "api-ms-win-crt-private-l1-1-0", + "api-ms-win-crt-private-l1-1-0.dll", nullptr, }, resolveByName, diff --git a/dll/kernel32.cpp b/dll/kernel32.cpp index b39d018..40a5ad3 100644 --- a/dll/kernel32.cpp +++ b/dll/kernel32.cpp @@ -1565,8 +1565,9 @@ namespace kernel32 { return wibo::mainModule->imageBuffer; } - // wibo::lastError = 0; - return wibo::loadModule(lpModuleName); + HMODULE module = wibo::findLoadedModule(lpModuleName); + wibo::lastError = module ? ERROR_SUCCESS : ERROR_MOD_NOT_FOUND; + return module; } HMODULE WIN_FUNC GetModuleHandleW(LPCWSTR lpModuleName) { @@ -1592,7 +1593,16 @@ namespace kernel32 { const auto absPath = std::filesystem::absolute(exePath); path = files::pathToWindows(absPath); } else { - path = static_cast(hModule)->name; + auto *info = wibo::moduleInfoFromHandle(hModule); + if (!info) { + wibo::lastError = ERROR_INVALID_PARAMETER; + return 0; + } + if (!info->resolvedPath.empty()) { + path = files::pathToWindows(info->resolvedPath); + } else { + path = info->originalName; + } } const size_t len = path.size(); if (nSize == 0) { @@ -1627,17 +1637,32 @@ namespace kernel32 { const auto absPath = std::filesystem::absolute(exePath); path = files::pathToWindows(absPath); } else { - path = static_cast(hModule)->name; + auto *info = wibo::moduleInfoFromHandle(hModule); + if (!info) { + wibo::lastError = ERROR_INVALID_PARAMETER; + return 0; + } + if (!info->resolvedPath.empty()) { + path = files::pathToWindows(info->resolvedPath); + } else { + path = info->originalName; + } } - const size_t len = path.size(); if (nSize == 0) { wibo::lastError = ERROR_INSUFFICIENT_BUFFER; return 0; } - const size_t copyLen = std::min(len, nSize - 1); - memcpy(lpFilename, stringToWideString(path.c_str()).data(), copyLen * 2); - if (copyLen < nSize) { + auto wide = stringToWideString(path.c_str()); + if (wide.empty()) { + wide.push_back(0); + } + const size_t len = wide.size() - 1; + const size_t copyLen = std::min(len, static_cast(nSize - 1)); + for (size_t i = 0; i < copyLen; i++) { + lpFilename[i] = wide[i]; + } + if (copyLen < static_cast(nSize)) { lpFilename[copyLen] = 0; } if (copyLen < len) { @@ -1649,38 +1674,58 @@ namespace kernel32 { return copyLen; } - void* WIN_FUNC FindResourceA(void* hModule, const char* lpName, const char* lpType) { - DEBUG_LOG("FindResourceA %p %s %s\n", hModule, lpName, lpType); - return (void*)0x100002; + static std::string resource_identifier_to_string(const char *id) { + if (!id) { + return ""; + } + if ((uintptr_t)id >> 16 == 0) { + return std::to_string(static_cast((uintptr_t)id)); + } + return id; } - // https://github.com/reactos/reactos/blob/master/dll/win32/kernelbase/wine/loader.c#L1090 - // https://github.com/wine-mirror/wine/blob/master/dlls/kernelbase/loader.c#L1097 - void* WIN_FUNC FindResourceW(void* hModule, const uint16_t* lpName, const uint16_t* lpType) { - DEBUG_LOG("FindResourceW %p\n", hModule); - std::string name, type; - - if(!hModule) hModule = GetModuleHandleW(0); - - if((uintptr_t)lpName >> 16 == 0){ - name = std::to_string((unsigned int)(uintptr_t)lpName); + static std::string resource_identifier_to_string(const uint16_t *id) { + if (!id) { + return ""; } - else { - name = wideStringToString(lpName); - } - - if((uintptr_t)lpType >> 16 == 0){ - type = std::to_string((unsigned int)(uintptr_t)lpType); - } - else { - type = wideStringToString(lpType); + if ((uintptr_t)id >> 16 == 0) { + return std::to_string(static_cast((uintptr_t)id)); } + return wideStringToString(id); + } + static FILE *open_resource_stream(const std::string &type, const std::string &name) { char path[512]; snprintf(path, sizeof(path), "resources/%s/%s.res", type.c_str(), name.c_str()); DEBUG_LOG("Created path %s\n", path); return fopen(path, "rb"); - // return (void*)0x100002; + } + + void *WIN_FUNC FindResourceA(void *hModule, const char *lpName, const char *lpType) { + DEBUG_LOG("FindResourceA %p %s %s\n", hModule, lpName, lpType); + + if (!hModule) { + hModule = GetModuleHandleA(nullptr); + } + + const std::string name = resource_identifier_to_string(lpName); + const std::string type = resource_identifier_to_string(lpType); + + return open_resource_stream(type, name); + } + + // https://github.com/reactos/reactos/blob/master/dll/win32/kernelbase/wine/loader.c#L1090 + // https://github.com/wine-mirror/wine/blob/master/dlls/kernelbase/loader.c#L1097 + void *WIN_FUNC FindResourceW(void *hModule, const uint16_t *lpName, const uint16_t *lpType) { + DEBUG_LOG("FindResourceW %p\n", hModule); + + if (!hModule) + hModule = GetModuleHandleW(0); + + const std::string name = resource_identifier_to_string(lpName); + const std::string type = resource_identifier_to_string(lpType); + + return open_resource_stream(type, name); } void* WIN_FUNC LoadResource(void* hModule, void* res) { @@ -1813,19 +1858,34 @@ namespace kernel32 { return (void *) 0x100006; } + static int translateProtect(DWORD flProtect) { + switch (flProtect) { + case 0x01: /* PAGE_NOACCESS */ + return PROT_NONE; + case 0x02: /* PAGE_READONLY */ + return PROT_READ; + case 0x04: /* PAGE_READWRITE */ + return PROT_READ | PROT_WRITE; + case 0x08: /* PAGE_WRITECOPY */ + return PROT_READ | PROT_WRITE; + case 0x10: /* PAGE_EXECUTE */ + return PROT_EXEC; + case 0x20: /* PAGE_EXECUTE_READ */ + return PROT_READ | PROT_EXEC; + case 0x40: /* PAGE_EXECUTE_READWRITE */ + return PROT_READ | PROT_WRITE | PROT_EXEC; + case 0x80: /* PAGE_EXECUTE_WRITECOPY */ + return PROT_READ | PROT_WRITE | PROT_EXEC; + default: + DEBUG_LOG("Unhandled flProtect: %u, defaulting to RW\n", flProtect); + return PROT_READ | PROT_WRITE; + } + } + void *WIN_FUNC VirtualAlloc(void *lpAddress, unsigned int dwSize, unsigned int flAllocationType, unsigned int flProtect) { DEBUG_LOG("VirtualAlloc %p %u %u %u\n", lpAddress, dwSize, flAllocationType, flProtect); - int prot = PROT_READ | PROT_WRITE; - if (flProtect == 0x04 /* PAGE_READWRITE */) { - prot = PROT_READ | PROT_WRITE; - } else if (flProtect == 0x02 /* PAGE_READONLY */) { - prot = PROT_READ; - } else if (flProtect == 0x40 /* PAGE_EXECUTE_READWRITE */) { - prot = PROT_READ | PROT_WRITE | PROT_EXEC; - } else { - DEBUG_LOG("Unhandled flProtect: %u, defaulting to RW\n", flProtect); - } + int prot = translateProtect(flProtect); int flags = MAP_PRIVATE | MAP_ANONYMOUS; // MAP_ANONYMOUS ensures the memory is zeroed out if (lpAddress != NULL) { @@ -1869,6 +1929,51 @@ namespace kernel32 { return 1; } + BOOL WIN_FUNC VirtualProtect(LPVOID lpAddress, SIZE_T dwSize, DWORD flNewProtect, PDWORD lpflOldProtect) { + DEBUG_LOG("VirtualProtect %p %zu %u\n", lpAddress, dwSize, flNewProtect); + if (!lpAddress || dwSize == 0) { + wibo::lastError = ERROR_INVALID_PARAMETER; + return FALSE; + } + if (lpflOldProtect) + *lpflOldProtect = flNewProtect; + size_t pageSize = static_cast(sysconf(_SC_PAGESIZE)); + uintptr_t base = reinterpret_cast(lpAddress) & ~(pageSize - 1); + size_t length = ((reinterpret_cast(lpAddress) + dwSize) - base + pageSize - 1) & ~(pageSize - 1); + int prot = translateProtect(flNewProtect); + if (mprotect(reinterpret_cast(base), length, prot) != 0) { + perror("VirtualProtect/mprotect"); + return FALSE; + } + return TRUE; + } + + typedef struct _MEMORY_BASIC_INFORMATION { + void *BaseAddress; + void *AllocationBase; + DWORD AllocationProtect; + size_t RegionSize; + DWORD State; + DWORD Protect; + DWORD Type; + } MEMORY_BASIC_INFORMATION, *PMEMORY_BASIC_INFORMATION; + + SIZE_T WIN_FUNC VirtualQuery(const void *lpAddress, PMEMORY_BASIC_INFORMATION lpBuffer, SIZE_T dwLength) { + DEBUG_LOG("VirtualQuery %p %zu\n", lpAddress, dwLength); + if (!lpBuffer || dwLength < sizeof(MEMORY_BASIC_INFORMATION)) { + return 0; + } + memset(lpBuffer, 0, sizeof(MEMORY_BASIC_INFORMATION)); + lpBuffer->BaseAddress = const_cast(lpAddress); + lpBuffer->AllocationBase = lpBuffer->BaseAddress; + lpBuffer->AllocationProtect = 0x04; // PAGE_READWRITE + lpBuffer->RegionSize = static_cast(sysconf(_SC_PAGESIZE)); + lpBuffer->State = 0x1000; // MEM_COMMIT + lpBuffer->Protect = 0x04; // PAGE_READWRITE + lpBuffer->Type = 0x20000; // MEM_PRIVATE + return sizeof(MEMORY_BASIC_INFORMATION); + } + unsigned int WIN_FUNC GetProcessWorkingSetSize(void *hProcess, unsigned int *lpMinimumWorkingSetSize, unsigned int *lpMaximumWorkingSetSize) { DEBUG_LOG("GetProcessWorkingSetSize\n"); // A pointer to a variable that receives the minimum working set size of the specified process, in bytes. @@ -1966,6 +2071,11 @@ namespace kernel32 { return uNumber + 10; } + void WIN_FUNC Sleep(DWORD dwMilliseconds) { + DEBUG_LOG("Sleep(%u)\n", dwMilliseconds); + usleep(static_cast(dwMilliseconds) * 1000); + } + unsigned int WIN_FUNC GetACP() { DEBUG_LOG("GetACP\n"); // return 65001; // UTF-8 @@ -2177,7 +2287,21 @@ namespace kernel32 { } BOOL WIN_FUNC SetDllDirectoryA(LPCSTR lpPathName) { - DEBUG_LOG("STUB: SetDllDirectoryA(%s)\n", lpPathName); + DEBUG_LOG("SetDllDirectoryA(%s)\n", lpPathName); + if (!lpPathName || lpPathName[0] == '\0') { + wibo::clearDllDirectoryOverride(); + wibo::lastError = ERROR_SUCCESS; + return TRUE; + } + + auto hostPath = files::pathFromWindows(lpPathName); + if (hostPath.empty() || !std::filesystem::exists(hostPath)) { + wibo::lastError = ERROR_PATH_NOT_FOUND; + return FALSE; + } + + wibo::setDllDirectoryOverride(std::filesystem::absolute(hostPath)); + wibo::lastError = ERROR_SUCCESS; return TRUE; } @@ -2591,6 +2715,9 @@ static void *resolveByName(const char *name) { if (strcmp(name, "EncodePointer") == 0) return (void *) kernel32::EncodePointer; if (strcmp(name, "DecodePointer") == 0) return (void *) kernel32::DecodePointer; if (strcmp(name, "SetDllDirectoryA") == 0) return (void *) kernel32::SetDllDirectoryA; + if (strcmp(name, "Sleep") == 0) return (void *) kernel32::Sleep; + if (strcmp(name, "VirtualProtect") == 0) return (void *) kernel32::VirtualProtect; + if (strcmp(name, "VirtualQuery") == 0) return (void *) kernel32::VirtualQuery; // processenv.h if (strcmp(name, "GetCommandLineA") == 0) return (void *) kernel32::GetCommandLineA; diff --git a/loader.cpp b/loader.cpp index bd194fe..5d5b80d 100644 --- a/loader.cpp +++ b/loader.cpp @@ -92,6 +92,14 @@ struct PEHintNameTableEntry { char name[1]; // variable length }; +struct PEBaseRelocationBlock { + uint32_t virtualAddress; + uint32_t sizeOfBlock; +}; + +constexpr uint16_t IMAGE_REL_BASED_ABSOLUTE = 0; +constexpr uint16_t IMAGE_REL_BASED_HIGHLOW = 3; + uint16_t read16(FILE *file) { uint16_t v = 0; fread(&v, 2, 1, file); @@ -109,6 +117,12 @@ wibo::Executable::Executable() { imageSize = 0; entryPoint = nullptr; rsrcBase = 0; + preferredImageBase = 0; + relocationDelta = 0; + exportDirectoryRVA = 0; + exportDirectorySize = 0; + relocationDirectoryRVA = 0; + relocationDirectorySize = 0; } wibo::Executable::~Executable() { @@ -150,20 +164,29 @@ bool wibo::Executable::loadPE(FILE *file, bool exec) { long pageSize = sysconf(_SC_PAGE_SIZE); DEBUG_LOG("Page size: %x\n", (unsigned int)pageSize); + preferredImageBase = header32.imageBase; + exportDirectoryRVA = header32.exportTable.virtualAddress; + exportDirectorySize = header32.exportTable.size; + relocationDirectoryRVA = header32.baseRelocationTable.virtualAddress; + relocationDirectorySize = header32.baseRelocationTable.size; + // Build buffer imageSize = header32.sizeOfImage; - if (exec) { - imageBuffer = mmap((void *)header32.imageBase, header32.sizeOfImage, PROT_READ | PROT_WRITE | PROT_EXEC, - MAP_ANONYMOUS | MAP_FIXED | MAP_PRIVATE, -1, 0); - } else { - imageBuffer = mmap(nullptr, header32.sizeOfImage, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); + int prot = PROT_READ | PROT_WRITE; + if (exec) + prot |= PROT_EXEC; + void *preferredBase = (void *)(uintptr_t)header32.imageBase; + imageBuffer = mmap(preferredBase, header32.sizeOfImage, prot, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); + if (imageBuffer == MAP_FAILED) { + imageBuffer = mmap(nullptr, header32.sizeOfImage, prot, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); } - memset(imageBuffer, 0, header32.sizeOfImage); if (imageBuffer == MAP_FAILED) { perror("Image mapping failed!"); - imageBuffer = 0; + imageBuffer = nullptr; return false; } + relocationDelta = (intptr_t)((uintptr_t)imageBuffer - (uintptr_t)header32.imageBase); + memset(imageBuffer, 0, header32.sizeOfImage); // Read the sections fseek(file, offsetToPE + sizeof header + header.sizeOfOptionalHeader, SEEK_SET); @@ -191,6 +214,48 @@ bool wibo::Executable::loadPE(FILE *file, bool exec) { } } + if (exec && relocationDelta != 0) { + if (relocationDirectoryRVA == 0 || relocationDirectorySize == 0) { + DEBUG_LOG("Relocation required but no relocation directory present\n"); + munmap(imageBuffer, imageSize); + imageBuffer = nullptr; + return false; + } + + uint8_t *relocCursor = fromRVA(relocationDirectoryRVA); + uint8_t *relocEnd = relocCursor + relocationDirectorySize; + while (relocCursor < relocEnd) { + auto *block = reinterpret_cast(relocCursor); + if (block->sizeOfBlock < sizeof(PEBaseRelocationBlock) || block->sizeOfBlock > static_cast(relocEnd - relocCursor)) { + break; + } + if (block->sizeOfBlock == sizeof(PEBaseRelocationBlock)) { + break; + } + size_t entryCount = (block->sizeOfBlock - sizeof(PEBaseRelocationBlock)) / sizeof(uint16_t); + auto *entries = reinterpret_cast(relocCursor + sizeof(PEBaseRelocationBlock)); + for (size_t i = 0; i < entryCount; ++i) { + uint16_t entry = entries[i]; + uint16_t type = entry >> 12; + uint16_t offset = entry & 0x0FFF; + if (type == IMAGE_REL_BASED_ABSOLUTE) + continue; + uintptr_t target = reinterpret_cast(imageBuffer) + block->virtualAddress + offset; + switch (type) { + case IMAGE_REL_BASED_HIGHLOW: { + auto *addr = reinterpret_cast(target); + *addr += static_cast(relocationDelta); + break; + } + default: + DEBUG_LOG("Unhandled relocation type %u at %08x\n", type, block->virtualAddress + offset); + break; + } + } + relocCursor += block->sizeOfBlock; + } + } + if (!exec) { // No need to resolve imports return true; @@ -222,8 +287,6 @@ bool wibo::Executable::loadPE(FILE *file, bool exec) { ++lookupTable; ++addressTable; } - freeModule(module); - ++dir; } diff --git a/main.cpp b/main.cpp index 9955493..93c4f53 100644 --- a/main.cpp +++ b/main.cpp @@ -1,15 +1,15 @@ #include "common.h" #include "files.h" -#include -#include -#include #include "strutil.h" -#include -#include -#include -#include +#include #include #include +#include +#include +#include +#include +#include +#include uint32_t wibo::lastError = 0; char** wibo::argv; @@ -34,145 +34,6 @@ void wibo::debug_log(const char *fmt, ...) { va_end(args); } -#define FOR_256_3(a, b, c, d) FOR_ITER((a << 6 | b << 4 | c << 2 | d)) -#define FOR_256_2(a, b) \ - FOR_256_3(a, b, 0, 0) FOR_256_3(a, b, 0, 1) FOR_256_3(a, b, 0, 2) FOR_256_3(a, b, 0, 3) \ - FOR_256_3(a, b, 1, 0) FOR_256_3(a, b, 1, 1) FOR_256_3(a, b, 1, 2) FOR_256_3(a, b, 1, 3) \ - FOR_256_3(a, b, 2, 0) FOR_256_3(a, b, 2, 1) FOR_256_3(a, b, 2, 2) FOR_256_3(a, b, 2, 3) \ - FOR_256_3(a, b, 3, 0) FOR_256_3(a, b, 3, 1) FOR_256_3(a, b, 3, 2) FOR_256_3(a, b, 3, 3) -#define FOR_256 \ - FOR_256_2(0, 0) FOR_256_2(0, 1) FOR_256_2(0, 2) FOR_256_2(0, 3) \ - FOR_256_2(1, 0) FOR_256_2(1, 1) FOR_256_2(1, 2) FOR_256_2(1, 3) \ - FOR_256_2(2, 0) FOR_256_2(2, 1) FOR_256_2(2, 2) FOR_256_2(2, 3) \ - FOR_256_2(3, 0) FOR_256_2(3, 1) FOR_256_2(3, 2) FOR_256_2(3, 3) \ - -static int stubIndex = 0; -static char stubDlls[0x100][0x100]; -static char stubFuncNames[0x100][0x100]; - -static void stubBase(int index) { - printf("Unhandled function %s (%s)\n", stubFuncNames[index], stubDlls[index]); - exit(1); -} - -void (*stubFuncs[0x100])(void) = { -#define FOR_ITER(i) []() { stubBase(i); }, -FOR_256 -#undef FOR_ITER -}; - -#undef FOR_256_3 -#undef FOR_256_2 -#undef FOR_256 - -static void *resolveMissingFuncName(const char *dllName, const char *funcName) { - DEBUG_LOG("Missing function: %s (%s)\n", dllName, funcName); - assert(stubIndex < 0x100); - assert(strlen(dllName) < 0x100); - assert(strlen(funcName) < 0x100); - strcpy(stubFuncNames[stubIndex], funcName); - strcpy(stubDlls[stubIndex], dllName); - return (void *)stubFuncs[stubIndex++]; -} - -static void *resolveMissingFuncOrdinal(const char *dllName, uint16_t ordinal) { - char buf[16]; - sprintf(buf, "%d", ordinal); - return resolveMissingFuncName(dllName, buf); -} - -extern const wibo::Module lib_advapi32; -extern const wibo::Module lib_bcrypt; -extern const wibo::Module lib_crt; -extern const wibo::Module lib_kernel32; -extern const wibo::Module lib_lmgr; -extern const wibo::Module lib_mscoree; -extern const wibo::Module lib_msvcrt; -extern const wibo::Module lib_ntdll; -extern const wibo::Module lib_ole32; -extern const wibo::Module lib_user32; -extern const wibo::Module lib_vcruntime; -extern const wibo::Module lib_version; -const wibo::Module * wibo::modules[] = { - &lib_advapi32, - &lib_bcrypt, - &lib_crt, - &lib_kernel32, - &lib_lmgr, - &lib_mscoree, - &lib_msvcrt, - &lib_ntdll, - &lib_ole32, - &lib_user32, - &lib_vcruntime, - &lib_version, - nullptr, -}; - -HMODULE wibo::loadModule(const char *dllName) { - auto *result = new ModuleInfo; - result->name = dllName; - for (int i = 0; modules[i]; i++) { - for (int j = 0; modules[i]->names[j]; j++) { - if (strcasecmp(dllName, modules[i]->names[j]) == 0) { - result->module = modules[i]; - return result; - } - } - } - return result; -} - -void wibo::freeModule(HMODULE module) { delete static_cast(module); } - -void *wibo::resolveFuncByName(HMODULE module, const char *funcName) { - auto *info = static_cast(module); - assert(info); - if (info->module && info->module->byName) { - void *func = info->module->byName(funcName); - if (func) - return func; - } - return resolveMissingFuncName(info->name.c_str(), funcName); -} - -void *wibo::resolveFuncByOrdinal(HMODULE module, uint16_t ordinal) { - auto *info = static_cast(module); - assert(info); - if (info->module && info->module->byOrdinal) { - void *func = info->module->byOrdinal(ordinal); - if (func) - return func; - } - return resolveMissingFuncOrdinal(info->name.c_str(), ordinal); -} - -wibo::Executable *wibo::executableFromModule(HMODULE module) { - if (wibo::isMainModule(module)) { - return wibo::mainModule; - } - - auto info = static_cast(module); - if (!info->executable) { - DEBUG_LOG("wibo::executableFromModule: loading %s\n", info->name.c_str()); - auto executable = std::make_unique(); - const auto path = files::pathFromWindows(info->name.c_str()); - FILE *f = fopen(path.c_str(), "rb"); - if (!f) { - perror("wibo::executableFromModule"); - return nullptr; - } - bool result = executable->loadPE(f, false); - fclose(f); - if (!result) { - DEBUG_LOG("wibo::executableFromModule: failed to load %s\n", path.c_str()); - return nullptr; - } - info->executable = std::move(executable); - } - return info->executable.get(); -} - struct UNICODE_STRING { unsigned short Length; unsigned short MaximumLength; @@ -410,6 +271,8 @@ int main(int argc, char **argv) { wibo::argv = argv + 1; wibo::argc = argc - 1; + wibo::initializeModuleRegistry(); + wibo::Executable exec; wibo::mainModule = &exec; @@ -432,6 +295,7 @@ int main(int argc, char **argv) { : "r"(tibSegment), "r"(exec.entryPoint) ); DEBUG_LOG("We came back\n"); + wibo::shutdownModuleRegistry(); return 1; } diff --git a/module_registry.cpp b/module_registry.cpp new file mode 100644 index 0000000..f835eed --- /dev/null +++ b/module_registry.cpp @@ -0,0 +1,815 @@ +#include "common.h" +#include "files.h" +#include "strutil.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +extern const wibo::Module lib_advapi32; +extern const wibo::Module lib_bcrypt; +extern const wibo::Module lib_crt; +extern const wibo::Module lib_kernel32; +extern const wibo::Module lib_lmgr; +extern const wibo::Module lib_mscoree; +extern const wibo::Module lib_msvcrt; +extern const wibo::Module lib_ntdll; +extern const wibo::Module lib_ole32; +extern const wibo::Module lib_user32; +extern const wibo::Module lib_vcruntime; +extern const wibo::Module lib_version; + +namespace { + +constexpr DWORD DLL_PROCESS_DETACH = 0; +constexpr DWORD DLL_PROCESS_ATTACH = 1; + +struct PEExportDirectory { + uint32_t characteristics; + uint32_t timeDateStamp; + uint16_t majorVersion; + uint16_t minorVersion; + uint32_t name; + uint32_t base; + uint32_t numberOfFunctions; + uint32_t numberOfNames; + uint32_t addressOfFunctions; + uint32_t addressOfNames; + uint32_t addressOfNameOrdinals; +}; + +#define FOR_256_3(a, b, c, d) FOR_ITER((a << 6 | b << 4 | c << 2 | d)) +#define FOR_256_2(a, b) \ + FOR_256_3(a, b, 0, 0) \ + FOR_256_3(a, b, 0, 1) \ + FOR_256_3(a, b, 0, 2) FOR_256_3(a, b, 0, 3) FOR_256_3(a, b, 1, 0) FOR_256_3(a, b, 1, 1) FOR_256_3(a, b, 1, 2) \ + FOR_256_3(a, b, 1, 3) FOR_256_3(a, b, 2, 0) FOR_256_3(a, b, 2, 1) FOR_256_3(a, b, 2, 2) FOR_256_3(a, b, 2, 3) \ + FOR_256_3(a, b, 3, 0) FOR_256_3(a, b, 3, 1) FOR_256_3(a, b, 3, 2) FOR_256_3(a, b, 3, 3) +#define FOR_256 \ + FOR_256_2(0, 0) \ + FOR_256_2(0, 1) \ + FOR_256_2(0, 2) FOR_256_2(0, 3) FOR_256_2(1, 0) FOR_256_2(1, 1) FOR_256_2(1, 2) FOR_256_2(1, 3) FOR_256_2(2, 0) \ + FOR_256_2(2, 1) FOR_256_2(2, 2) FOR_256_2(2, 3) FOR_256_2(3, 0) FOR_256_2(3, 1) FOR_256_2(3, 2) \ + FOR_256_2(3, 3) + +static int stubIndex = 0; +static char stubDlls[0x100][0x100]; +static char stubFuncNames[0x100][0x100]; + +static void stubBase(int index) { + printf("Unhandled function %s (%s)\n", stubFuncNames[index], stubDlls[index]); + exit(1); +} + +void (*stubFuncs[0x100])(void) = { +#define FOR_ITER(i) []() { stubBase(i); }, + FOR_256 +#undef FOR_ITER +}; + +#undef FOR_256_3 +#undef FOR_256_2 +#undef FOR_256 + +void *resolveMissingFuncName(const char *dllName, const char *funcName) { + DEBUG_LOG("Missing function: %s (%s)\n", dllName, funcName); + assert(stubIndex < 0x100); + assert(strlen(dllName) < 0x100); + assert(strlen(funcName) < 0x100); + strcpy(stubFuncNames[stubIndex], funcName); + strcpy(stubDlls[stubIndex], dllName); + return (void *)stubFuncs[stubIndex++]; +} + +void *resolveMissingFuncOrdinal(const char *dllName, uint16_t ordinal) { + char buf[16]; + sprintf(buf, "%d", ordinal); + return resolveMissingFuncName(dllName, buf); +} + +} // namespace + +namespace { + +using ModulePtr = std::unique_ptr; + +struct ModuleRegistry { + std::recursive_mutex mutex; + std::unordered_map modulesByKey; + std::unordered_map modulesByAlias; + std::optional dllDirectory; + bool initialized = false; + std::unordered_map onExitTables; +}; + +ModuleRegistry ®istry() { + static ModuleRegistry reg; + return reg; +} + +std::string toLowerCopy(const std::string &value) { + std::string out = value; + std::transform(out.begin(), out.end(), out.begin(), + [](unsigned char c) { return static_cast(std::tolower(c)); }); + return out; +} + +std::string normalizeAlias(const std::string &value) { + std::string out = value; + std::replace(out.begin(), out.end(), '/', '\\'); + std::transform(out.begin(), out.end(), out.begin(), + [](unsigned char c) { return static_cast(std::tolower(c)); }); + return out; +} + +struct ParsedModuleName { + std::string original; + std::string directory; // Windows-style directory component (may be empty) + std::string base; + bool hasExtension = false; + bool endsWithDot = false; +}; + +ParsedModuleName parseModuleName(const std::string &name) { + ParsedModuleName parsed; + parsed.original = name; + parsed.base = name; + std::string sanitized = name; + std::replace(sanitized.begin(), sanitized.end(), '/', '\\'); + auto sep = sanitized.find_last_of('\\'); + if (sep != std::string::npos) { + parsed.directory = sanitized.substr(0, sep); + parsed.base = sanitized.substr(sep + 1); + } else { + parsed.base = sanitized; + } + parsed.endsWithDot = !parsed.base.empty() && parsed.base.back() == '.'; + parsed.hasExtension = (!parsed.endsWithDot) && parsed.base.find('.') != std::string::npos; + return parsed; +} + +std::vector candidateModuleNames(const ParsedModuleName &parsed) { + std::vector names; + if (!parsed.base.empty()) { + names.push_back(parsed.base); + if (!parsed.hasExtension && !parsed.endsWithDot) { + names.push_back(parsed.base + ".dll"); + } + } + return names; +} + +std::string normalizedBaseKey(const ParsedModuleName &parsed) { + if (parsed.base.empty()) { + return std::string(); + } + std::string base = parsed.base; + if (!parsed.hasExtension && !parsed.endsWithDot) { + base += ".dll"; + } + return normalizeAlias(base); +} + +std::optional findCaseInsensitiveFile(const std::filesystem::path &directory, + const std::string &filename) { + std::error_code ec; + if (directory.empty()) { + return std::nullopt; + } + if (!std::filesystem::exists(directory, ec) || !std::filesystem::is_directory(directory, ec)) { + return std::nullopt; + } + const std::string lower = toLowerCopy(filename); + for (const auto &entry : std::filesystem::directory_iterator(directory, ec)) { + if (ec) { + break; + } + const std::string candidate = toLowerCopy(entry.path().filename().string()); + if (candidate == lower) { + return std::filesystem::canonical(entry.path(), ec); + } + } + auto direct = directory / filename; + if (std::filesystem::exists(direct, ec)) { + return std::filesystem::canonical(direct, ec); + } + return std::nullopt; +} + +std::optional combineAndFind(const std::filesystem::path &directory, + const std::string &filename) { + if (filename.empty()) { + return std::nullopt; + } + if (directory.empty()) { + return std::nullopt; + } + return findCaseInsensitiveFile(directory, filename); +} + +std::filesystem::path canonicalPath(const std::filesystem::path &path) { + std::error_code ec; + auto canonical = std::filesystem::weakly_canonical(path, ec); + if (!ec) { + return canonical; + } + return std::filesystem::absolute(path); +} + +std::vector collectSearchDirectories(bool alteredSearchPath) { + std::vector dirs; + std::unordered_set seen; + auto addDirectory = [&](const std::filesystem::path &dir) { + if (dir.empty()) + return; + std::error_code ec; + auto canonical = std::filesystem::weakly_canonical(dir, ec); + if (ec) { + canonical = std::filesystem::absolute(dir, ec); + } + if (ec) + return; + if (!std::filesystem::exists(canonical, ec) || ec) + return; + std::string key = toLowerCopy(canonical.string()); + if (seen.insert(key).second) { + dirs.push_back(canonical); + } + }; + + auto ® = registry(); + + if (wibo::argv && wibo::argc > 0 && wibo::argv[0]) { + std::filesystem::path mainBinary = std::filesystem::absolute(wibo::argv[0]); + if (mainBinary.has_parent_path()) { + addDirectory(mainBinary.parent_path()); + } + } + + if (reg.dllDirectory.has_value()) { + addDirectory(*reg.dllDirectory); + } + + addDirectory(files::pathFromWindows("Z:/Windows/System32")); + addDirectory(files::pathFromWindows("Z:/Windows")); + + if (!alteredSearchPath) { + addDirectory(std::filesystem::current_path()); + } + + if (const char *envPath = std::getenv("PATH")) { + std::string pathList = envPath; + size_t start = 0; + while (start <= pathList.size()) { + size_t end = pathList.find_first_of(":;", start); + if (end == std::string::npos) { + end = pathList.size(); + } + if (end > start) { + auto piece = pathList.substr(start, end - start); + if (!piece.empty()) { + std::filesystem::path candidate(piece); + if (piece.find(':') != std::string::npos || piece.find('\\') != std::string::npos) { + auto converted = files::pathFromWindows(piece.c_str()); + if (!converted.empty()) { + candidate = converted; + } + } + addDirectory(candidate); + } + } + if (end == pathList.size()) { + break; + } + start = end + 1; + } + } + + return dirs; +} +std::optional resolveModuleOnDisk(const std::string &requestedName, bool alteredSearchPath) { + ParsedModuleName parsed = parseModuleName(requestedName); + auto names = candidateModuleNames(parsed); + + if (!parsed.directory.empty()) { + for (const auto &candidate : names) { + auto combined = parsed.directory + "\\" + candidate; + auto posixPath = files::pathFromWindows(combined.c_str()); + if (!posixPath.empty()) { + auto resolved = findCaseInsensitiveFile(std::filesystem::path(posixPath).parent_path(), + std::filesystem::path(posixPath).filename().string()); + if (resolved) { + return canonicalPath(*resolved); + } + } + } + return std::nullopt; + } + + auto dirs = collectSearchDirectories(alteredSearchPath); + for (const auto &dir : dirs) { + for (const auto &candidate : names) { + auto resolved = combineAndFind(dir, candidate); + if (resolved) { + return canonicalPath(*resolved); + } + } + } + + return std::nullopt; +} + +std::string storageKeyForPath(const std::filesystem::path &path) { + return normalizeAlias(files::pathToWindows(canonicalPath(path))); +} + +std::string storageKeyForBuiltin(const std::string &normalizedName) { return normalizedName; } + +wibo::ModuleInfo *findByAlias(const std::string &alias) { + auto ® = registry(); + auto it = reg.modulesByAlias.find(alias); + if (it != reg.modulesByAlias.end()) { + return it->second; + } + return nullptr; +} + +void registerAlias(const std::string &alias, wibo::ModuleInfo *info) { + if (alias.empty() || !info) { + return; + } + auto ® = registry(); + if (reg.modulesByAlias.find(alias) == reg.modulesByAlias.end()) { + reg.modulesByAlias[alias] = info; + } +} + +void registerBuiltinModule(const wibo::Module *module) { + if (!module) { + return; + } + ModulePtr entry = std::make_unique(); + entry->module = module; + entry->refCount = UINT_MAX; + entry->originalName = module->names[0] ? module->names[0] : ""; + entry->normalizedName = normalizedBaseKey(parseModuleName(entry->originalName)); + entry->exportsInitialized = true; + auto storageKey = storageKeyForBuiltin(entry->normalizedName); + auto raw = entry.get(); + auto ® = registry(); + reg.modulesByKey[storageKey] = std::move(entry); + + for (size_t i = 0; module->names[i]; ++i) { + registerAlias(normalizeAlias(module->names[i]), raw); + ParsedModuleName parsed = parseModuleName(module->names[i]); + registerAlias(normalizedBaseKey(parsed), raw); + } +} + +void callDllMain(wibo::ModuleInfo &info, DWORD reason) { + if (!info.entryPoint || info.module) { + return; + } + using DllMainFunc = BOOL(WIN_FUNC *)(HMODULE, DWORD, LPVOID); + auto dllMain = reinterpret_cast(info.entryPoint); + if (!dllMain) { + return; + } + if (reason == 1) { + if (info.processAttachCalled) { + return; + } + info.processAttachCalled = true; + BOOL result = dllMain(reinterpret_cast(info.imageBase), reason, nullptr); + info.processAttachSucceeded = result != 0; + } else if (reason == 0) { + if (info.processAttachCalled && info.processAttachSucceeded) { + dllMain(reinterpret_cast(info.imageBase), reason, nullptr); + } + } +} + +void ensureInitialized() { + auto ® = registry(); + if (reg.initialized) { + return; + } + reg.initialized = true; + + const wibo::Module *builtins[] = { + &lib_advapi32, &lib_bcrypt, &lib_crt, &lib_kernel32, &lib_lmgr, &lib_mscoree, &lib_msvcrt, + &lib_ntdll, &lib_ole32, &lib_user32, &lib_vcruntime, &lib_version, nullptr, + }; + + for (const wibo::Module **module = builtins; *module; ++module) { + registerBuiltinModule(*module); + } +} + +void registerExternalModuleAliases(const std::string &requestedName, const std::filesystem::path &resolvedPath, + wibo::ModuleInfo *info) { + ParsedModuleName parsed = parseModuleName(requestedName); + registerAlias(normalizedBaseKey(parsed), info); + registerAlias(normalizeAlias(requestedName), info); + registerAlias(storageKeyForPath(resolvedPath), info); +} + +wibo::ModuleInfo *moduleFromAddress(void *addr) { + if (!addr) + return nullptr; + auto ® = registry(); + for (auto &pair : reg.modulesByKey) { + wibo::ModuleInfo *info = pair.second.get(); + if (!info) + continue; + uint8_t *base = nullptr; + size_t size = 0; + if (info->imageBase && info->imageSize) { + base = static_cast(info->imageBase); + size = info->imageSize; + } else if (info->executable) { + base = static_cast(info->executable->imageBuffer); + size = info->executable->imageSize; + } + if (!base || size == 0) + continue; + uint8_t *ptr = static_cast(addr); + if (ptr >= base && ptr < base + size) { + return info; + } + } + return nullptr; +} + +void ensureExportsInitialized(wibo::ModuleInfo &info) { + if (info.module || info.exportsInitialized) + return; + if (!info.executable) + return; + auto *exe = info.executable.get(); + if (!exe->exportDirectoryRVA || !exe->exportDirectorySize) { + info.exportsInitialized = true; + return; + } + + auto *dir = exe->fromRVA(exe->exportDirectoryRVA); + info.exportOrdinalBase = dir->base; + uint32_t functionCount = dir->numberOfFunctions; + info.exportsByOrdinal.assign(functionCount, nullptr); + if (functionCount) { + auto *functions = exe->fromRVA(dir->addressOfFunctions); + for (uint32_t i = 0; i < functionCount; ++i) { + uint32_t rva = functions[i]; + if (!rva) { + continue; + } + if (rva >= exe->exportDirectoryRVA && rva < exe->exportDirectoryRVA + exe->exportDirectorySize) { + const char *forward = exe->fromRVA(rva); + info.exportsByOrdinal[i] = resolveMissingFuncName(info.originalName.c_str(), forward); + } else { + info.exportsByOrdinal[i] = exe->fromRVA(rva); + } + } + } + + uint32_t nameCount = dir->numberOfNames; + if (nameCount) { + auto *names = exe->fromRVA(dir->addressOfNames); + auto *ordinals = exe->fromRVA(dir->addressOfNameOrdinals); + for (uint32_t i = 0; i < nameCount; ++i) { + uint16_t index = ordinals[i]; + uint16_t ordinal = static_cast(dir->base + index); + if (index < info.exportsByOrdinal.size()) { + const char *namePtr = exe->fromRVA(names[i]); + info.exportNameToOrdinal[std::string(namePtr)] = ordinal; + } + } + } + info.exportsInitialized = true; +} + +} // namespace + +namespace wibo { + +void initializeModuleRegistry() { + std::lock_guard lock(registry().mutex); + ensureInitialized(); +} + +void shutdownModuleRegistry() { + std::lock_guard lock(registry().mutex); + for (auto &pair : registry().modulesByKey) { + ModuleInfo *info = pair.second.get(); + if (!info || info->module) { + continue; + } + runPendingOnExit(*info); + if (info->processAttachCalled && info->processAttachSucceeded) { + callDllMain(*info, DLL_PROCESS_DETACH); + } + } + registry().modulesByKey.clear(); + registry().modulesByAlias.clear(); + registry().dllDirectory.reset(); + registry().initialized = false; + registry().onExitTables.clear(); +} + +ModuleInfo *moduleInfoFromHandle(HMODULE module) { return static_cast(module); } + +void setDllDirectoryOverride(const std::filesystem::path &path) { + auto canonical = canonicalPath(path); + std::lock_guard lock(registry().mutex); + registry().dllDirectory = canonical; +} + +void clearDllDirectoryOverride() { + std::lock_guard lock(registry().mutex); + registry().dllDirectory.reset(); +} + +std::optional dllDirectoryOverride() { + std::lock_guard lock(registry().mutex); + return registry().dllDirectory; +} + +void registerOnExitTable(void *table) { + if (!table) + return; + std::lock_guard lock(registry().mutex); + ensureInitialized(); + auto ® = registry(); + if (reg.onExitTables.find(table) == reg.onExitTables.end()) { + if (auto *info = moduleFromAddress(table)) { + reg.onExitTables[table] = info; + } + } +} + +void addOnExitFunction(void *table, void (*func)()) { + if (!func) + return; + std::lock_guard lock(registry().mutex); + auto ® = registry(); + ModuleInfo *info = nullptr; + auto it = reg.onExitTables.find(table); + if (it != reg.onExitTables.end()) { + info = it->second; + } else if (table) { + info = moduleFromAddress(table); + if (info) + reg.onExitTables[table] = info; + } + if (info) { + info->onExitFunctions.push_back(reinterpret_cast(func)); + } +} + +void runPendingOnExit(ModuleInfo &info) { + for (auto it = info.onExitFunctions.rbegin(); it != info.onExitFunctions.rend(); ++it) { + auto fn = reinterpret_cast(*it); + if (fn) { + fn(); + } + } + info.onExitFunctions.clear(); +} + +void executeOnExitTable(void *table) { + std::lock_guard lock(registry().mutex); + auto ® = registry(); + ModuleInfo *info = nullptr; + if (table) { + auto it = reg.onExitTables.find(table); + if (it != reg.onExitTables.end()) { + info = it->second; + reg.onExitTables.erase(it); + } else { + info = moduleFromAddress(table); + } + } + if (info) { + runPendingOnExit(*info); + } +} + +HMODULE findLoadedModule(const char *name) { + if (!name) { + return nullptr; + } + std::lock_guard lock(registry().mutex); + ensureInitialized(); + ParsedModuleName parsed = parseModuleName(name); + std::string alias = normalizedBaseKey(parsed); + ModuleInfo *info = findByAlias(alias); + if (!info) { + info = findByAlias(normalizeAlias(name)); + } + return info; +} + +HMODULE loadModule(const char *dllName) { + if (!dllName) { + lastError = ERROR_INVALID_PARAMETER; + return nullptr; + } + std::string requested(dllName); +DEBUG_LOG("loadModule(%s)\n", requested.c_str()); + + std::lock_guard lock(registry().mutex); + ensureInitialized(); + + ParsedModuleName parsed = parseModuleName(requested); + std::string alias = normalizedBaseKey(parsed); + ModuleInfo *existing = findByAlias(alias); + if (!existing) { + existing = findByAlias(normalizeAlias(requested)); + } + if (existing) { + DEBUG_LOG(" found existing module alias %s\n", alias.c_str()); + if (existing->refCount != UINT_MAX) { + existing->refCount++; + } + lastError = ERROR_SUCCESS; + return existing; + } + + auto resolvedPath = resolveModuleOnDisk(requested, false); + if (!resolvedPath) { + DEBUG_LOG(" module not found on disk\n"); + lastError = ERROR_MOD_NOT_FOUND; + return nullptr; + } + + std::string key = storageKeyForPath(*resolvedPath); + auto ® = registry(); + auto it = reg.modulesByKey.find(key); + if (it != reg.modulesByKey.end()) { + ModuleInfo *info = it->second.get(); + info->refCount++; + registerExternalModuleAliases(requested, *resolvedPath, info); + lastError = ERROR_SUCCESS; + return info; + } + + FILE *file = fopen(resolvedPath->c_str(), "rb"); + if (!file) { + perror("loadModule"); + lastError = ERROR_MOD_NOT_FOUND; + return nullptr; + } + + auto executable = std::make_unique(); + if (!executable->loadPE(file, true)) { + DEBUG_LOG(" loadPE failed for %s\n", resolvedPath->c_str()); + fclose(file); + lastError = ERROR_BAD_EXE_FORMAT; + return nullptr; + } + fclose(file); + + ModulePtr info = std::make_unique(); + info->module = nullptr; + info->originalName = requested; + info->normalizedName = alias; + info->resolvedPath = *resolvedPath; + info->executable = std::move(executable); + info->entryPoint = info->executable->entryPoint; + info->imageBase = info->executable->imageBuffer; + info->imageSize = info->executable->imageSize; + info->refCount = 1; + info->dataFile = false; + info->dontResolveReferences = false; + + ModuleInfo *raw = info.get(); + reg.modulesByKey[key] = std::move(info); + registerExternalModuleAliases(requested, *resolvedPath, raw); + ensureExportsInitialized(*raw); + + callDllMain(*raw, DLL_PROCESS_ATTACH); + lastError = ERROR_SUCCESS; + + return raw; +} + +void freeModule(HMODULE module) { + if (!module) { + return; + } + std::lock_guard lock(registry().mutex); + ModuleInfo *info = moduleInfoFromHandle(module); + if (!info || info->refCount == UINT_MAX) { + return; + } + if (info->refCount == 0) { + return; + } + info->refCount--; + if (info->refCount == 0) { + auto ® = registry(); + for (auto it = reg.onExitTables.begin(); it != reg.onExitTables.end();) { + if (it->second == info) { + it = reg.onExitTables.erase(it); + } else { + ++it; + } + } + runPendingOnExit(*info); + callDllMain(*info, DLL_PROCESS_DETACH); + std::string key = info->resolvedPath.empty() ? storageKeyForBuiltin(info->normalizedName) + : storageKeyForPath(info->resolvedPath); + reg.modulesByKey.erase(key); + for (auto it = reg.modulesByAlias.begin(); it != reg.modulesByAlias.end();) { + if (it->second == info) { + it = reg.modulesByAlias.erase(it); + } else { + ++it; + } + } + } +} + +void *resolveFuncByName(HMODULE module, const char *funcName) { + ModuleInfo *info = moduleInfoFromHandle(module); + if (!info) { + return nullptr; + } + if (info->module && info->module->byName) { + void *func = info->module->byName(funcName); + if (func) { + return func; + } + } + ensureExportsInitialized(*info); + if (!info->module) { + auto it = info->exportNameToOrdinal.find(funcName); + if (it != info->exportNameToOrdinal.end()) { + return resolveFuncByOrdinal(module, it->second); + } + } + return resolveMissingFuncName(info->originalName.c_str(), funcName); +} + +void *resolveFuncByOrdinal(HMODULE module, uint16_t ordinal) { + ModuleInfo *info = moduleInfoFromHandle(module); + if (!info) { + return nullptr; + } + if (info->module && info->module->byOrdinal) { + void *func = info->module->byOrdinal(ordinal); + if (func) { + return func; + } + } + if (!info->module) { + ensureExportsInitialized(*info); + if (!info->exportsByOrdinal.empty() && ordinal >= info->exportOrdinalBase) { + size_t index = static_cast(ordinal - info->exportOrdinalBase); + if (index < info->exportsByOrdinal.size()) { + void *addr = info->exportsByOrdinal[index]; + if (addr) { + return addr; + } + } + } + } + return resolveMissingFuncOrdinal(info->originalName.c_str(), ordinal); +} + +Executable *executableFromModule(HMODULE module) { + if (isMainModule(module)) { + return mainModule; + } + ModuleInfo *info = moduleInfoFromHandle(module); + if (!info) { + return nullptr; + } + if (!info->executable && !info->resolvedPath.empty()) { + FILE *file = fopen(info->resolvedPath.c_str(), "rb"); + if (!file) { + perror("executableFromModule"); + return nullptr; + } + auto executable = std::make_unique(); + if (!executable->loadPE(file, false)) { + DEBUG_LOG("executableFromModule: failed to load %s\n", info->resolvedPath.c_str()); + fclose(file); + return nullptr; + } + fclose(file); + info->executable = std::move(executable); + } + return info->executable.get(); +} + +} // namespace wibo diff --git a/test/Makefile b/test/Makefile new file mode 100644 index 0000000..2b4d545 --- /dev/null +++ b/test/Makefile @@ -0,0 +1,20 @@ +CC = i686-w64-mingw32-gcc +CFLAGS = -Wall -Wextra -O2 + +DLL_SRC = external_exports.c +EXE_SRC = test_external_dll.c +DLL = external_exports.dll +EXE = test_external_dll.exe + +all: $(DLL) $(EXE) + +$(DLL): $(DLL_SRC) + $(CC) $(CFLAGS) -shared -o $@ $< + +$(EXE): $(EXE_SRC) + $(CC) $(CFLAGS) -o $@ $< + +clean: + rm -f $(DLL) $(EXE) + +.PHONY: all clean diff --git a/test/external_exports.c b/test/external_exports.c new file mode 100644 index 0000000..8057edf --- /dev/null +++ b/test/external_exports.c @@ -0,0 +1,22 @@ +#include + +static int attached = 0; + +BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpReserved) { + (void) hinstDLL; + (void) lpReserved; + if (fdwReason == DLL_PROCESS_ATTACH) { + attached = 1; + } else if (fdwReason == DLL_PROCESS_DETACH) { + attached = 2; + } + return TRUE; +} + +__declspec(dllexport) int __stdcall add_numbers(int a, int b) { + return a + b; +} + +__declspec(dllexport) int __stdcall was_attached(void) { + return attached; +} diff --git a/test/test_external_dll.c b/test/test_external_dll.c new file mode 100644 index 0000000..65d21cb --- /dev/null +++ b/test/test_external_dll.c @@ -0,0 +1,32 @@ +#include +#include + +int main(void) { +typedef int (__stdcall *add_numbers_fn)(int, int); +typedef int (__stdcall *was_attached_fn)(void); + + HMODULE mod = LoadLibraryA("external_exports.dll"); + if (!mod) { + printf("LoadLibraryA failed: %lu\n", GetLastError()); + return 1; + } + + add_numbers_fn add_numbers = (add_numbers_fn)GetProcAddress(mod, "add_numbers@8"); + was_attached_fn was_attached = (was_attached_fn)GetProcAddress(mod, "was_attached@0"); + if (!add_numbers || !was_attached) { + printf("GetProcAddress failed: %lu\n", GetLastError()); + return 1; + } + + int sum = add_numbers(2, 40); + int attached = was_attached(); + + printf("sum=%d attached=%d\n", sum, attached); + + if (!FreeLibrary(mod)) { + printf("FreeLibrary failed: %lu\n", GetLastError()); + return 1; + } + + return (sum == 42 && attached == 1) ? 0 : 2; +}