#include "common.h" #include "files.h" #include #include #include #include "strutil.h" #include #include #include #include #include #include uint32_t wibo::lastError = 0; char** wibo::argv; int wibo::argc; char *wibo::executableName; char *wibo::commandLine; std::vector wibo::commandLineW; wibo::Executable *wibo::mainModule = 0; bool wibo::debugEnabled = false; unsigned int wibo::debugIndent = 0; void wibo::debug_log(const char *fmt, ...) { va_list args; va_start(args, fmt); if (wibo::debugEnabled) { for (size_t i = 0; i < wibo::debugIndent; i++) fprintf(stderr, "\t"); vfprintf(stderr, fmt, args); } 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; uint16_t *Buffer; }; // Run Time Library (RTL) struct RTL_USER_PROCESS_PARAMETERS { char Reserved1[16]; void *Reserved2[10]; UNICODE_STRING ImagePathName; UNICODE_STRING CommandLine; }; // Windows Process Environment Block (PEB) struct PEB { char Reserved1[2]; char BeingDebugged; char Reserved2[1]; void *Reserved3[2]; void *Ldr; RTL_USER_PROCESS_PARAMETERS *ProcessParameters; char Reserved4[104]; void *Reserved5[52]; void *PostProcessInitRoutine; char Reserved6[128]; void *Reserved7[1]; unsigned int SessionId; }; // Windows Thread Information Block (TIB) struct TIB { /* 0x00 */ void *sehFrame; /* 0x04 */ void *stackBase; /* 0x08 */ void *stackLimit; /* 0x0C */ void *subSystemTib; /* 0x10 */ void *fiberData; /* 0x14 */ void *arbitraryDataSlot; /* 0x18 */ TIB *tib; /* */ char pad[0x14]; /* 0x30 */ PEB *peb; /* */ char pad2[0x1000]; }; // Make this global to ease debugging TIB tib; const size_t MAPS_BUFFER_SIZE = 0x10000; /** * Read /proc/self/maps into a buffer. * * While reading /proc/self/maps, we need to be extremely careful not to allocate any memory, * as that could cause libc to modify memory mappings while we're attempting to fill them. * To accomplish this, we use Linux syscalls directly. * * @param buffer The buffer to read into. * @return The number of bytes read. */ static size_t readMaps(char* buffer) { int fd = open("/proc/self/maps", O_RDONLY); if (fd == -1) { perror("Failed to open /proc/self/maps"); exit(1); } char *cur = buffer; char *bufferEnd = buffer + MAPS_BUFFER_SIZE; while (cur < bufferEnd) { int ret = read(fd, cur, static_cast(bufferEnd - cur)); if (ret == -1) { if (errno == EINTR) { continue; } perror("Failed to read /proc/self/maps"); exit(1); } else if (ret == 0) { break; } cur += ret; } close(fd); if (cur == bufferEnd) { fprintf(stderr, "Buffer too small while reading /proc/self/maps\n"); exit(1); } *cur = '\0'; return static_cast(cur - buffer); } /** * Map the upper 2GB of memory to prevent libc from allocating there. * * This is necessary because 32-bit windows only reserves the lowest 2GB of memory for use by a process * (https://www.tenouk.com/WinVirtualAddressSpace.html). Linux, on the other hand, will happily allow * nearly the entire 4GB address space to be used. Some Windows programs rely on heap allocations to be * in the lower 2GB of memory, otherwise they misbehave or crash. * * Between reading /proc/self/maps and mmap-ing the upper 2GB, we must be extremely careful not to allocate * any memory, as that could cause libc to modify memory mappings while we're attempting to fill them. */ static void blockUpper2GB() { const unsigned int FILL_MEMORY_ABOVE = 0x80000000; // 2GB DEBUG_LOG("Blocking upper 2GB address space\n"); // Buffer lives on the stack to avoid heap allocation char buffer[MAPS_BUFFER_SIZE]; size_t len = readMaps(buffer); std::string_view procLine(buffer, len); unsigned int lastMapEnd = 0; while (true) { size_t newline = procLine.find('\n'); if (newline == std::string::npos) { break; } unsigned int mapStart = 0; auto result = std::from_chars(procLine.data(), procLine.data() + procLine.size(), mapStart, 16); if (result.ec != std::errc()) { break; } unsigned int mapEnd = 0; result = std::from_chars(result.ptr + 1, procLine.data() + procLine.size(), mapEnd, 16); if (result.ec != std::errc()) { break; } // The empty space we want to map out is now between lastMapEnd and mapStart unsigned int holdingMapStart = lastMapEnd; unsigned int holdingMapEnd = mapStart; if ((holdingMapEnd - holdingMapStart) != 0 && holdingMapEnd > FILL_MEMORY_ABOVE) { holdingMapStart = std::max(holdingMapStart, FILL_MEMORY_ABOVE); // DEBUG_LOG("Mapping %08x-%08x\n", holdingMapStart, holdingMapEnd); void* holdingMap = mmap((void*) holdingMapStart, holdingMapEnd - holdingMapStart, PROT_READ | PROT_WRITE, MAP_ANONYMOUS|MAP_FIXED|MAP_PRIVATE, -1, 0); if (holdingMap == MAP_FAILED) { perror("Failed to create holding map"); exit(1); } } lastMapEnd = mapEnd; procLine = procLine.substr(newline + 1); } } int main(int argc, char **argv) { if (argc <= 1) { printf("Usage: ./wibo program.exe ...\n"); return 1; } if (getenv("WIBO_DEBUG")) { wibo::debugEnabled = true; } if (getenv("WIBO_DEBUG_INDENT")) { wibo::debugIndent = std::stoul(getenv("WIBO_DEBUG_INDENT")); } blockUpper2GB(); files::init(); // Create TIB memset(&tib, 0, sizeof(tib)); tib.tib = &tib; tib.peb = (PEB*)calloc(sizeof(PEB), 1); tib.peb->ProcessParameters = (RTL_USER_PROCESS_PARAMETERS*)calloc(sizeof(RTL_USER_PROCESS_PARAMETERS), 1); struct user_desc tibDesc; memset(&tibDesc, 0, sizeof tibDesc); tibDesc.entry_number = 0; tibDesc.base_addr = (unsigned int) &tib; tibDesc.limit = 0x1000; tibDesc.seg_32bit = 1; tibDesc.contents = 0; // hopefully this is ok tibDesc.read_exec_only = 0; tibDesc.limit_in_pages = 0; tibDesc.seg_not_present = 0; tibDesc.useable = 1; if (syscall(SYS_modify_ldt, 1, &tibDesc, sizeof tibDesc) != 0) { perror("Failed to modify LDT\n"); return 1; } // Build a command line std::string cmdLine; for (int i = 1; i < argc; i++) { std::string arg; if (i == 1) { arg = files::pathToWindows(std::filesystem::absolute(argv[1])); } else { cmdLine += ' '; arg = argv[i]; } bool needQuotes = arg.find_first_of("\\\" \t\n") != std::string::npos; if (needQuotes) cmdLine += '"'; int backslashes = 0; for (const char *p = arg.c_str(); ; p++) { char c = *p; if (c == '\\') { backslashes++; continue; } // Backslashes are doubled *before quotes* for (int j = 0; j < backslashes; j++) { cmdLine += '\\'; if (c == '\0' || c == '"') cmdLine += '\\'; } backslashes = 0; if (c == '\0') break; if (c == '\"') cmdLine += '\\'; cmdLine += c; } if (needQuotes) cmdLine += '"'; } cmdLine += '\0'; wibo::commandLine = cmdLine.data(); wibo::commandLineW = stringToWideString(wibo::commandLine); DEBUG_LOG("Command line: %s\n", wibo::commandLine); wibo::executableName = argv[0]; wibo::argv = argv + 1; wibo::argc = argc - 1; wibo::Executable exec; wibo::mainModule = &exec; char* pe_path = argv[1]; FILE *f = fopen(pe_path, "rb"); if (!f) { std::string mesg = std::string("Failed to open file ") + pe_path; perror(mesg.c_str()); return 1; } exec.loadPE(f, true); fclose(f); uint16_t tibSegment = (tibDesc.entry_number << 3) | 7; // Invoke the damn thing asm( "movw %0, %%fs; call *%1" : : "r"(tibSegment), "r"(exec.entryPoint) ); DEBUG_LOG("We came back\n"); return 1; }