diff --git a/common.h b/common.h index e274462..7e26a0e 100644 --- a/common.h +++ b/common.h @@ -7,6 +7,7 @@ #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. @@ -53,6 +54,7 @@ typedef unsigned char BYTE; #define ERROR_HANDLE_EOF 38 #define ERROR_NOT_SUPPORTED 50 #define ERROR_INVALID_PARAMETER 87 +#define ERROR_INSUFFICIENT_BUFFER 122 #define ERROR_NEGATIVE_SEEK 131 #define ERROR_ALREADY_EXISTS 183 @@ -98,7 +100,7 @@ namespace wibo { struct Executable { Executable(); ~Executable(); - bool loadPE(FILE *file); + bool loadPE(FILE *file, bool exec); void *imageBuffer; size_t imageSize; @@ -115,6 +117,20 @@ namespace wibo { return fromRVA((uint32_t) rva); } }; + struct ModuleInfo { + std::string name; + const wibo::Module* module = nullptr; + std::unique_ptr executable; + }; extern Executable *mainModule; -} + Executable *executableFromModule(HMODULE module); + + /** + * HMODULE will be `nullptr` or `mainModule->imageBuffer` if it's the main module, + * otherwise it will be a pointer to a `wibo::ModuleInfo`. + */ + inline bool isMainModule(HMODULE hModule) { + return hModule == nullptr || hModule == mainModule->imageBuffer; + } +} // namespace wibo diff --git a/dll/crt.cpp b/dll/crt.cpp index 61ff89f..28822f0 100644 --- a/dll/crt.cpp +++ b/dll/crt.cpp @@ -1,5 +1,7 @@ #include "common.h" +#include + typedef void (*_PVFV)(); typedef int (*_PIFV)(); @@ -19,6 +21,8 @@ namespace crt { int _commode = 0; +std::vector<_PVFV> atexitFuncs; + void WIN_ENTRY _initterm(const _PVFV *ppfn, const _PVFV *end) { do { if (_PVFV pfn = *++ppfn) { @@ -48,7 +52,8 @@ int WIN_ENTRY _set_fmode(int mode) { int *WIN_ENTRY __p__commode() { return &_commode; } int WIN_ENTRY _crt_atexit(void (*func)()) { - DEBUG_LOG("STUB: _crt_atexit(%p)\n", func); + DEBUG_LOG("_crt_atexit(%p)\n", func); + atexitFuncs.push_back(func); return 0; } @@ -85,6 +90,15 @@ int *WIN_ENTRY __p___argc() { return &wibo::argc; } size_t WIN_ENTRY strlen(const char *str) { return ::strlen(str); } +void WIN_ENTRY exit(int status) { + DEBUG_LOG("exit(%i)\n", status); + for (auto it = atexitFuncs.rbegin(); it != atexitFuncs.rend(); ++it) { + DEBUG_LOG("Calling atexit function %p\n", *it); + (*it)(); + } + ::exit(status); +} + } // namespace crt static void *resolveByName(const char *name) { @@ -118,6 +132,8 @@ static void *resolveByName(const char *name) { return (void *)crt::__p___argc; if (strcmp(name, "strlen") == 0) return (void *)crt::strlen; + if (strcmp(name, "exit") == 0) + return (void *)crt::exit; return nullptr; } diff --git a/dll/kernel32.cpp b/dll/kernel32.cpp index e4e213c..519c78f 100644 --- a/dll/kernel32.cpp +++ b/dll/kernel32.cpp @@ -250,14 +250,14 @@ namespace kernel32 { unsigned int WIN_FUNC WaitForSingleObject(void *hHandle, unsigned int dwMilliseconds) { DEBUG_LOG("WaitForSingleObject (%u)\n", dwMilliseconds); - + // TODO - wait on other objects? // TODO: wait for less than forever - assert(dwMilliseconds == 0xffffffff); + assert(dwMilliseconds == 0xffffffff); processes::Process* process = processes::processFromHandle(hHandle, false); - + int status; waitpid(process->pid, &status, 0); @@ -268,7 +268,7 @@ namespace kernel32 { // Specific exit codes don't really map onto any of these situations - we just know it's bad. // Specify a non-zero exit code to alert our parent process something's gone wrong. DEBUG_LOG("WaitForSingleObject: Child process exited abnormally - returning exit code 1."); - process->exitCode = 1; + process->exitCode = 1; } return 0; @@ -366,30 +366,42 @@ namespace kernel32 { } } DEBUG_LOG("...returning nothing\n"); - return 0xFFFFFFFF; + wibo::lastError = 1; + return 0xFFFFFFFF; // TLS_OUT_OF_INDEXES } + unsigned int WIN_FUNC TlsFree(unsigned int dwTlsIndex) { DEBUG_LOG("TlsFree(%u)\n", dwTlsIndex); if (dwTlsIndex >= 0 && dwTlsIndex < MAX_TLS_VALUES && tlsValuesUsed[dwTlsIndex]) { tlsValuesUsed[dwTlsIndex] = false; return 1; } else { + wibo::lastError = 1; return 0; } } + void *WIN_FUNC TlsGetValue(unsigned int dwTlsIndex) { - // DEBUG_LOG("TlsGetValue(%u)\n", dwTlsIndex); - if (dwTlsIndex >= 0 && dwTlsIndex < MAX_TLS_VALUES && tlsValuesUsed[dwTlsIndex]) - return tlsValues[dwTlsIndex]; - else - return 0; + // DEBUG_LOG("TlsGetValue(%u)", dwTlsIndex); + void *result = nullptr; + if (dwTlsIndex >= 0 && dwTlsIndex < MAX_TLS_VALUES && tlsValuesUsed[dwTlsIndex]) { + result = tlsValues[dwTlsIndex]; + // See https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-TlsGetValue#return-value + wibo::lastError = ERROR_SUCCESS; + } else { + wibo::lastError = 1; + } + // DEBUG_LOG(" -> %p\n", result); + return result; } + unsigned int WIN_FUNC TlsSetValue(unsigned int dwTlsIndex, void *lpTlsValue) { // DEBUG_LOG("TlsSetValue(%u, %p)\n", dwTlsIndex, lpTlsValue); if (dwTlsIndex >= 0 && dwTlsIndex < MAX_TLS_VALUES && tlsValuesUsed[dwTlsIndex]) { tlsValues[dwTlsIndex] = lpTlsValue; return 1; } else { + wibo::lastError = 1; return 0; } } @@ -1303,16 +1315,42 @@ namespace kernel32 { } } - unsigned int WIN_FUNC GetModuleFileNameA(void* hModule, char* lpFilename, unsigned int nSize) { + DWORD WIN_FUNC GetModuleFileNameA(HMODULE hModule, LPSTR lpFilename, DWORD nSize) { DEBUG_LOG("GetModuleFileNameA (hModule=%p, nSize=%i)\n", hModule, nSize); + if (lpFilename == nullptr) { + wibo::lastError = ERROR_INVALID_PARAMETER; + return 0; + } - *lpFilename = 0; // just NUL terminate + std::string path; + if (wibo::isMainModule(hModule)) { + const auto exePath = files::pathFromWindows(wibo::argv[0]); + const auto absPath = std::filesystem::absolute(exePath); + path = files::pathToWindows(absPath); + } else { + path = static_cast(hModule)->name; + } + const size_t len = path.size(); + if (nSize == 0) { + wibo::lastError = ERROR_INSUFFICIENT_BUFFER; + return 0; + } - wibo::lastError = 0; - return 0; + const size_t copyLen = std::min(len, nSize - 1); + memcpy(lpFilename, path.c_str(), copyLen); + if (copyLen < nSize) { + lpFilename[copyLen] = 0; + } + if (copyLen < len) { + wibo::lastError = ERROR_INSUFFICIENT_BUFFER; + return nSize; + } + + wibo::lastError = ERROR_SUCCESS; + return copyLen; } - unsigned int WIN_FUNC GetModuleFileNameW(void* hModule, uint16_t* lpFilename, unsigned int nSize) { + DWORD WIN_FUNC GetModuleFileNameW(HMODULE hModule, LPWSTR lpFilename, DWORD nSize) { DEBUG_LOG("GetModuleFileNameW (hModule=%p, nSize=%i)\n", hModule, nSize); *lpFilename = 0; // just NUL terminate diff --git a/dll/user32.cpp b/dll/user32.cpp index fc9c1b8..eda8744 100644 --- a/dll/user32.cpp +++ b/dll/user32.cpp @@ -57,8 +57,7 @@ namespace user32 { return (unsigned int*)(rsrcBase + langEntry); } - static const char *getStringFromTable(unsigned int uID) { - wibo::Executable *mod = wibo::mainModule; + static const char *getStringFromTable(wibo::Executable *mod, unsigned int uID) { unsigned int tableID = (uID >> 4) + 1; unsigned int entryID = uID & 15; unsigned int* stringTable = getResourceByID(mod, 6, tableID, 1033); @@ -82,7 +81,11 @@ namespace user32 { int WIN_FUNC LoadStringA(void* hInstance, unsigned int uID, char* lpBuffer, int cchBufferMax) { DEBUG_LOG("LoadStringA %p %d %d\n", hInstance, uID, cchBufferMax); - const char* s = getStringFromTable(uID); + wibo::Executable *mod = wibo::executableFromModule(hInstance); + if (!mod) { + return 0; + } + const char* s = getStringFromTable(mod, uID); if (!s) { return 0; } diff --git a/dll/vcruntime.cpp b/dll/vcruntime.cpp index ed31789..f1e3ad5 100644 --- a/dll/vcruntime.cpp +++ b/dll/vcruntime.cpp @@ -8,6 +8,8 @@ void *WIN_ENTRY memset(void *dest, int ch, size_t count) { return ::memset(dest, int WIN_ENTRY memcmp(const void *buf1, const void *buf2, size_t count) { return ::memcmp(buf1, buf2, count); } +void *WIN_ENTRY memmove(void *dest, const void *src, size_t count) { return ::memmove(dest, src, count); } + } // namespace vcruntime static void *resolveByName(const char *name) { @@ -17,6 +19,8 @@ static void *resolveByName(const char *name) { return (void *)vcruntime::memset; if (strcmp(name, "memcmp") == 0) return (void *)vcruntime::memcmp; + if (strcmp(name, "memmove") == 0) + return (void *)vcruntime::memmove; return nullptr; } diff --git a/loader.cpp b/loader.cpp index a8766f4..cbcf898 100644 --- a/loader.cpp +++ b/loader.cpp @@ -116,7 +116,13 @@ wibo::Executable::~Executable() { } } -bool wibo::Executable::loadPE(FILE *file) { +/** + * Load a PE file into memory. + * + * @param file The file to load. + * @param exec Whether to make the loaded image executable. + */ +bool wibo::Executable::loadPE(FILE *file, bool exec) { // Skip to PE header fseek(file, 0x3C, SEEK_SET); uint32_t offsetToPE = read32(file); @@ -145,7 +151,12 @@ bool wibo::Executable::loadPE(FILE *file) { // Build buffer imageSize = header32.sizeOfImage; - imageBuffer = mmap((void *) header32.imageBase, header32.sizeOfImage, PROT_READ|PROT_WRITE|PROT_EXEC, MAP_ANONYMOUS|MAP_FIXED|MAP_PRIVATE, -1, 0); + 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); + } memset(imageBuffer, 0, header32.sizeOfImage); if (imageBuffer == MAP_FAILED) { perror("Image mapping failed!"); @@ -165,7 +176,7 @@ bool wibo::Executable::loadPE(FILE *file) { name[8] = 0; DEBUG_LOG("Section %d: name=%s addr=%x size=%x (raw=%x) ptr=%x\n", i, name, section.virtualAddress, section.virtualSize, section.sizeOfRawData, section.pointerToRawData); - void *sectionBase = (void *) (header32.imageBase + section.virtualAddress); + void *sectionBase = (void *) ((uintptr_t) imageBuffer + section.virtualAddress); if (section.pointerToRawData > 0 && section.sizeOfRawData > 0) { // Grab this data long savePos = ftell(file); @@ -179,6 +190,11 @@ bool wibo::Executable::loadPE(FILE *file) { } } + if (!exec) { + // No need to resolve imports + return true; + } + // Handle imports PEImportDirectoryEntry *dir = fromRVA(header32.importTable.virtualAddress); diff --git a/main.cpp b/main.cpp index 5a29b55..91d75b9 100644 --- a/main.cpp +++ b/main.cpp @@ -31,7 +31,7 @@ void wibo::debug_log(const char *fmt, ...) { vfprintf(stderr, fmt, args); } - + va_end(args); } @@ -110,11 +110,6 @@ const wibo::Module * wibo::modules[] = { nullptr, }; -struct ModuleInfo { - std::string name; - const wibo::Module* module = nullptr; -}; - HMODULE wibo::loadModule(const char *dllName) { auto *result = new ModuleInfo; result->name = dllName; @@ -151,6 +146,32 @@ void *wibo::resolveFuncByOrdinal(HMODULE module, uint16_t ordinal) { 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; @@ -295,7 +316,7 @@ int main(int argc, char **argv) { return 1; } - exec.loadPE(f); + exec.loadPE(f, true); fclose(f); // 32-bit windows only reserves the lowest 2GB of memory for use by a process (https://www.tenouk.com/WinVirtualAddressSpace.html)