Skip to content

Commit

Permalink
Fix TlsGetValue & more (#48)
Browse files Browse the repository at this point in the history
`TlsGetValue` disambiguates 0 and an error by relying on `GetLastError`. Depending on the program state, `GetLastError` could be non-0, even though `TlsGetValue` succeeded. Resolve this by always setting `wibo::lastError`. This matches the behavior described by the documentation.

Additionally, when reading resources, later versions of mwcc and mwld call `GetModuleHandleA` with the program path, and then call `LoadStringA` on that handle. Support this behavior by _actually_ loading the PE at the path passed in to `GetModuleHandleA`, instead of assuming it's the current program.

(This is especially useful because sjiswrap relies on overriding `GetModuleFileNameA`, so the wrapped program reads its own resources, rather than sjiswrap's.)

Other small changes:
- Add ms-win-crt `exit` & run atexit funcs
- Implements vcruntime `memmove`
- Implements kernel32 `GetModuleFileNameA`
  • Loading branch information
encounter authored Oct 2, 2023
1 parent 8a6aacb commit c4de059
Show file tree
Hide file tree
Showing 7 changed files with 145 additions and 31 deletions.
20 changes: 18 additions & 2 deletions common.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#include <sys/stat.h>
#include <unistd.h>
#include <vector>
#include <memory>

// 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.
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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;
Expand All @@ -115,6 +117,20 @@ namespace wibo {
return fromRVA<T>((uint32_t) rva);
}
};
struct ModuleInfo {
std::string name;
const wibo::Module* module = nullptr;
std::unique_ptr<wibo::Executable> 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
18 changes: 17 additions & 1 deletion dll/crt.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
#include "common.h"

#include <vector>

typedef void (*_PVFV)();
typedef int (*_PIFV)();

Expand All @@ -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) {
Expand Down Expand Up @@ -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;
}

Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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;
}

Expand Down
68 changes: 53 additions & 15 deletions dll/kernel32.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand All @@ -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;
Expand Down Expand Up @@ -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;
}
}
Expand Down Expand Up @@ -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<wibo::ModuleInfo *>(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
Expand Down
9 changes: 6 additions & 3 deletions dll/user32.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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;
}
Expand Down
4 changes: 4 additions & 0 deletions dll/vcruntime.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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;
}

Expand Down
22 changes: 19 additions & 3 deletions loader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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!");
Expand All @@ -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);
Expand All @@ -179,6 +190,11 @@ bool wibo::Executable::loadPE(FILE *file) {
}
}

if (!exec) {
// No need to resolve imports
return true;
}

// Handle imports
PEImportDirectoryEntry *dir = fromRVA<PEImportDirectoryEntry>(header32.importTable.virtualAddress);

Expand Down
35 changes: 28 additions & 7 deletions main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ void wibo::debug_log(const char *fmt, ...) {

vfprintf(stderr, fmt, args);
}

va_end(args);
}

Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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<wibo::ModuleInfo *>(module);
if (!info->executable) {
DEBUG_LOG("wibo::executableFromModule: loading %s\n", info->name.c_str());
auto executable = std::make_unique<wibo::Executable>();
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;
Expand Down Expand Up @@ -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)
Expand Down

0 comments on commit c4de059

Please sign in to comment.