From c099a1b5776377782b2080d1dc939fa2072505f4 Mon Sep 17 00:00:00 2001 From: Luke Street Date: Wed, 1 Oct 2025 16:56:51 -0600 Subject: [PATCH] Improve current thread handles --- common.h | 4 + dll/advapi32.cpp | 522 +++++++++++++++++++++++++++++++++++++++++++- dll/kernel32.cpp | 380 +++++++++++++++++++++++++++++--- handles.h | 4 +- module_registry.cpp | 47 ++-- 5 files changed, 895 insertions(+), 62 deletions(-) diff --git a/common.h b/common.h index b346d48..6e0439b 100644 --- a/common.h +++ b/common.h @@ -60,6 +60,10 @@ typedef size_t SIZE_T; typedef SIZE_T *PSIZE_T; typedef unsigned char BYTE; typedef unsigned int UINT; +typedef void *HKEY; +typedef HKEY *PHKEY; +typedef DWORD REGSAM; +typedef LONG LSTATUS; typedef struct _OVERLAPPED { ULONG_PTR Internal; diff --git a/dll/advapi32.cpp b/dll/advapi32.cpp index 0fed5dc..aa09d13 100644 --- a/dll/advapi32.cpp +++ b/dll/advapi32.cpp @@ -4,10 +4,142 @@ #include "strutil.h" #include +#include +#include +#include +#include #include +#include +#include #include namespace { + constexpr DWORD REG_OPTION_OPEN_LINK = 0x00000008; + constexpr DWORD KEY_WOW64_64KEY = 0x00000100; + constexpr DWORD KEY_WOW64_32KEY = 0x00000200; + + struct RegistryKeyHandleData { + std::u16string canonicalPath; + bool predefined = false; + }; + + struct PredefinedKeyInfo { + uintptr_t value; + const char16_t *name; + }; + + static constexpr PredefinedKeyInfo predefinedKeyInfos[] = { + {0x80000000u, u"HKEY_CLASSES_ROOT"}, + {0x80000001u, u"HKEY_CURRENT_USER"}, + {0x80000002u, u"HKEY_LOCAL_MACHINE"}, + {0x80000003u, u"HKEY_USERS"}, + {0x80000004u, u"HKEY_PERFORMANCE_DATA"}, + {0x80000005u, u"HKEY_CURRENT_CONFIG"}, + }; + + static constexpr size_t predefinedKeyCount = sizeof(predefinedKeyInfos) / sizeof(predefinedKeyInfos[0]); + + static std::mutex registryMutex; + static bool predefinedHandlesInitialized = false; + static RegistryKeyHandleData predefinedHandles[predefinedKeyCount]; + static bool registryInitialized = false; + static std::unordered_set existingKeys; + struct Luid; + static std::mutex privilegeMapMutex; + static std::unordered_map privilegeLuidCache; + + static std::u16string canonicalizeKeySegment(const std::u16string &input) { + std::u16string result; + result.reserve(input.size()); + bool lastWasSlash = false; + for (char16_t ch : input) { + char16_t normalized = (ch == u'/') ? u'\\' : ch; + if (normalized == u'\\') { + if (!result.empty() && !lastWasSlash) { + result.push_back(u'\\'); + } + lastWasSlash = true; + continue; + } + lastWasSlash = false; + uint16_t lowered = wcharToLower(static_cast(normalized)); + result.push_back(static_cast(lowered)); + } + while (!result.empty() && result.back() == u'\\') { + result.pop_back(); + } + auto it = result.begin(); + while (it != result.end() && *it == u'\\') { + it = result.erase(it); + } + return result; + } + + static std::u16string canonicalizeKeySegment(const uint16_t *input) { + if (!input) { + return {}; + } + std::u16string wide(reinterpret_cast(input), wstrlen(input)); + return canonicalizeKeySegment(wide); + } + + static void initializePredefinedHandlesLocked() { + if (predefinedHandlesInitialized) { + return; + } + for (size_t i = 0; i < predefinedKeyCount; ++i) { + predefinedHandles[i].canonicalPath = canonicalizeKeySegment(std::u16string(predefinedKeyInfos[i].name)); + predefinedHandles[i].predefined = true; + } + predefinedHandlesInitialized = true; + } + + static RegistryKeyHandleData *predefinedHandleForValue(uintptr_t value) { + for (size_t i = 0; i < predefinedKeyCount; ++i) { + if (predefinedKeyInfos[i].value == value) { + return &predefinedHandles[i]; + } + } + return nullptr; + } + + static RegistryKeyHandleData *handleDataFromHKeyLocked(HKEY hKey) { + uintptr_t raw = reinterpret_cast(hKey); + if (raw == 0) { + return nullptr; + } + initializePredefinedHandlesLocked(); + if (auto *predefined = predefinedHandleForValue(raw)) { + return predefined; + } + auto data = handles::dataFromHandle(hKey, false); + if (data.type != handles::TYPE_REGISTRY_KEY || data.ptr == nullptr) { + return nullptr; + } + return static_cast(data.ptr); + } + + static bool isPredefinedKeyHandle(HKEY hKey) { + uintptr_t raw = reinterpret_cast(hKey); + for (size_t i = 0; i < predefinedKeyCount; ++i) { + if (predefinedKeyInfos[i].value == raw) { + return true; + } + } + return false; + } + + static void ensureRegistryInitializedLocked() { + if (registryInitialized) { + return; + } + initializePredefinedHandlesLocked(); + for (size_t i = 0; i < predefinedKeyCount; ++i) { + existingKeys.insert(predefinedHandles[i].canonicalPath); + } + registryInitialized = true; + } + using ALG_ID = unsigned int; constexpr ALG_ID CALG_MD5 = 0x00008003; @@ -17,6 +149,20 @@ namespace { constexpr DWORD HP_HASHVAL = 0x00000002; constexpr DWORD HP_HASHSIZE = 0x00000004; + constexpr DWORD SECURITY_DESCRIPTOR_REVISION = 1; + constexpr uint16_t SE_DACL_PRESENT = 0x0004; + constexpr uint16_t SE_DACL_DEFAULTED = 0x0008; + + struct SecurityDescriptor { + uint8_t Revision = 0; + uint8_t Sbz1 = 0; + uint16_t Control = 0; + void *Owner = nullptr; + void *Group = nullptr; + void *Sacl = nullptr; + void *Dacl = nullptr; + }; + struct HashObject { ALG_ID algid = 0; std::vector data; @@ -83,6 +229,11 @@ namespace { int32_t HighPart = 0; }; + struct LuidAndAttributes { + Luid value; + DWORD Attributes = 0; + }; + struct TokenStatisticsData { Luid tokenId; Luid authenticationId; @@ -287,9 +438,93 @@ namespace { } namespace advapi32 { - unsigned int WIN_FUNC RegOpenKeyExA(void *hKey, const char *lpSubKey, unsigned int ulOptions, void *samDesired, void **phkResult) { - DEBUG_LOG("STUB: RegOpenKeyExA(%p, %s, ...)\n", hKey, lpSubKey); - return 1; // screw them for now + LSTATUS WIN_FUNC RegOpenKeyExW(HKEY hKey, const uint16_t *lpSubKey, DWORD ulOptions, REGSAM samDesired, PHKEY phkResult) { + std::string subKeyString = lpSubKey ? wideStringToString(lpSubKey) : std::string("(null)"); + DEBUG_LOG("RegOpenKeyExW(%p, %s, %u, 0x%x, %p)\n", hKey, subKeyString.c_str(), ulOptions, samDesired, phkResult); + if (!phkResult) { + wibo::lastError = ERROR_INVALID_PARAMETER; + return ERROR_INVALID_PARAMETER; + } + *phkResult = nullptr; + if ((ulOptions & ~REG_OPTION_OPEN_LINK) != 0) { + wibo::lastError = ERROR_INVALID_PARAMETER; + return ERROR_INVALID_PARAMETER; + } + if (ulOptions & REG_OPTION_OPEN_LINK) { + DEBUG_LOG("RegOpenKeyExW: ignoring REG_OPTION_OPEN_LINK\n"); + } + REGSAM sanitizedAccess = samDesired & ~(KEY_WOW64_64KEY | KEY_WOW64_32KEY); + if (sanitizedAccess != samDesired) { + DEBUG_LOG("RegOpenKeyExW: ignoring WOW64 access mask 0x%x\n", samDesired ^ sanitizedAccess); + } + (void) sanitizedAccess; + std::lock_guard lock(registryMutex); + ensureRegistryInitializedLocked(); + RegistryKeyHandleData *baseHandle = handleDataFromHKeyLocked(hKey); + if (!baseHandle) { + wibo::lastError = ERROR_INVALID_HANDLE; + return ERROR_INVALID_HANDLE; + } + std::u16string targetPath = baseHandle->canonicalPath; + if (lpSubKey && lpSubKey[0] != 0) { + std::u16string subComponent = canonicalizeKeySegment(lpSubKey); + if (!subComponent.empty()) { + if (!targetPath.empty()) { + targetPath.push_back(u'\\'); + } + targetPath.append(subComponent); + } + } + if (targetPath.empty()) { + wibo::lastError = ERROR_INVALID_HANDLE; + return ERROR_INVALID_HANDLE; + } + if (existingKeys.find(targetPath) == existingKeys.end()) { + wibo::lastError = ERROR_FILE_NOT_FOUND; + return ERROR_FILE_NOT_FOUND; + } + if (!lpSubKey || lpSubKey[0] == 0) { + if (baseHandle->predefined) { + *phkResult = hKey; + wibo::lastError = ERROR_SUCCESS; + return ERROR_SUCCESS; + } + } + auto *handleData = new RegistryKeyHandleData; + handleData->canonicalPath = targetPath; + handleData->predefined = false; + auto handle = handles::allocDataHandle({handles::TYPE_REGISTRY_KEY, handleData, sizeof(*handleData)}); + *phkResult = reinterpret_cast(handle); + wibo::lastError = ERROR_SUCCESS; + return ERROR_SUCCESS; + } + + LSTATUS WIN_FUNC RegOpenKeyExA(HKEY hKey, const char *lpSubKey, DWORD ulOptions, REGSAM samDesired, PHKEY phkResult) { + DEBUG_LOG("RegOpenKeyExA(%p, %s, %u, 0x%x, %p)\n", hKey, lpSubKey ? lpSubKey : "(null)", ulOptions, samDesired, phkResult); + const uint16_t *widePtr = nullptr; + std::vector wideStorage; + if (lpSubKey) { + wideStorage = stringToWideString(lpSubKey); + widePtr = wideStorage.data(); + } + return RegOpenKeyExW(hKey, widePtr, ulOptions, samDesired, phkResult); + } + + LSTATUS WIN_FUNC RegCloseKey(HKEY hKey) { + DEBUG_LOG("RegCloseKey(%p)\n", hKey); + if (isPredefinedKeyHandle(hKey)) { + wibo::lastError = ERROR_SUCCESS; + return ERROR_SUCCESS; + } + auto data = handles::dataFromHandle(hKey, true); + if (data.type != handles::TYPE_REGISTRY_KEY || data.ptr == nullptr) { + wibo::lastError = ERROR_INVALID_HANDLE; + return ERROR_INVALID_HANDLE; + } + auto *handleData = static_cast(data.ptr); + delete handleData; + wibo::lastError = ERROR_SUCCESS; + return ERROR_SUCCESS; } BOOL WIN_FUNC CryptReleaseContext(void* hProv, unsigned int dwFlags) { @@ -496,6 +731,7 @@ namespace advapi32 { constexpr unsigned int TokenUserClass = 1; // TokenUser constexpr unsigned int TokenStatisticsClass = 10; // TokenStatistics constexpr unsigned int TokenElevationClass = 20; // TokenElevation + constexpr unsigned int TokenPrimaryGroupClass = 5; // TokenPrimaryGroup if (TokenInformationClass == TokenUserClass) { constexpr size_t sidSize = sizeof(Sid); constexpr size_t tokenUserSize = sizeof(TokenUserData); @@ -545,6 +781,28 @@ namespace advapi32 { wibo::lastError = ERROR_SUCCESS; return TRUE; } + if (TokenInformationClass == TokenPrimaryGroupClass) { + struct TokenPrimaryGroupStub { + Sid *PrimaryGroup; + }; + constexpr size_t sidSize = sizeof(Sid); + constexpr size_t headerSize = sizeof(TokenPrimaryGroupStub); + const unsigned int required = static_cast(headerSize + sidSize); + *ReturnLength = required; + if (!TokenInformation || TokenInformationLength < required) { + wibo::lastError = ERROR_INSUFFICIENT_BUFFER; + return FALSE; + } + auto *groupInfo = reinterpret_cast(TokenInformation); + auto *sid = reinterpret_cast(reinterpret_cast(TokenInformation) + headerSize); + sid->Revision = 1; + sid->SubAuthorityCount = 1; + sid->IdentifierAuthority = {{0, 0, 0, 0, 0, 5}}; + sid->SubAuthority[0] = 18; // SECURITY_LOCAL_SYSTEM_RID + groupInfo->PrimaryGroup = sid; + wibo::lastError = ERROR_SUCCESS; + return TRUE; + } wibo::lastError = ERROR_NOT_SUPPORTED; return FALSE; } @@ -583,10 +841,260 @@ namespace advapi32 { wibo::lastError = ERROR_SUCCESS; return TRUE; } + + static Luid generateDeterministicLuid(const std::string &normalizedName) { + uint32_t hash = 2166136261u; + for (unsigned char ch : normalizedName) { + hash ^= ch; + hash *= 16777619u; + } + if (hash == 0) { + hash = 1; + } + Luid result{}; + result.LowPart = hash; + result.HighPart = 0; + return result; + } + + static std::string normalizePrivilegeName(const std::string &name) { + std::string normalized; + normalized.reserve(name.size()); + for (unsigned char ch : name) { + if (ch == '\r' || ch == '\n' || ch == '\t') { + continue; + } + normalized.push_back(static_cast(std::tolower(ch))); + } + return normalized; + } + + static Luid lookupOrGeneratePrivilegeLuid(const std::string &normalizedName) { + std::lock_guard lock(privilegeMapMutex); + auto cached = privilegeLuidCache.find(normalizedName); + if (cached != privilegeLuidCache.end()) { + return cached->second; + } + static const std::unordered_map predefined = { + {"secreatepagefileprivilege", 0x00000002}, + {"seshutdownprivilege", 0x00000003}, + {"sebackupprivilege", 0x00000004}, + {"serestoreprivilege", 0x00000005}, + {"sechangenotifyprivilege", 0x00000006}, + {"seassignprimarytokenprivilege", 0x00000007}, + {"seincreasequotaprivilege", 0x00000008}, + {"seincreasebasepriorityprivilege", 0x00000009}, + {"seloaddriverprivilege", 0x0000000a}, + {"setakeownershipprivilege", 0x0000000b}, + {"sesystemtimeprivilege", 0x0000000c}, + {"sesystemenvironmentprivilege", 0x0000000d}, + {"setcbprivilege", 0x0000000e}, + {"sedebugprivilege", 0x0000000f}, + {"semanagevolumeprivilege", 0x00000010}, + {"seimpersonateprivilege", 0x00000011}, + {"secreateglobalprivilege", 0x00000012}, + {"sesecurityprivilege", 0x00000013}, + {"selockmemoryprivilege", 0x00000014}, + {"seundockprivilege", 0x00000015}, + {"seremoteshutdownprivilege", 0x00000016} + }; + auto known = predefined.find(normalizedName); + Luid luid{}; + if (known != predefined.end()) { + luid.LowPart = known->second; + luid.HighPart = 0; + } else { + luid = generateDeterministicLuid(normalizedName); + } + privilegeLuidCache.emplace(normalizedName, luid); + return luid; + } + + BOOL WIN_FUNC LookupPrivilegeValueA(const char *lpSystemName, const char *lpName, Luid *lpLuid) { + DEBUG_LOG("LookupPrivilegeValueA(%s, %s, %p)\n", + lpSystemName ? lpSystemName : "", + lpName ? lpName : "", + lpLuid); + if (!lpName || !lpLuid) { + wibo::lastError = ERROR_INVALID_PARAMETER; + return FALSE; + } + if (lpSystemName && lpSystemName[0] != '\0') { + DEBUG_LOG("-> remote system unsupported\n"); + wibo::lastError = ERROR_NOT_SUPPORTED; + return FALSE; + } + std::string normalized = normalizePrivilegeName(lpName); + if (normalized.empty()) { + wibo::lastError = ERROR_INVALID_PARAMETER; + return FALSE; + } + Luid luid = lookupOrGeneratePrivilegeLuid(normalized); + *lpLuid = luid; + wibo::lastError = ERROR_SUCCESS; + return TRUE; + } + + BOOL WIN_FUNC LookupPrivilegeValueW(const uint16_t *lpSystemName, const uint16_t *lpName, Luid *lpLuid) { + DEBUG_LOG("LookupPrivilegeValueW(%ls, %ls, %p)\n", + lpSystemName ? reinterpret_cast(lpSystemName) : L"", + lpName ? reinterpret_cast(lpName) : L"", + lpLuid); + if (!lpName || !lpLuid) { + wibo::lastError = ERROR_INVALID_PARAMETER; + return FALSE; + } + if (lpSystemName && lpSystemName[0] != 0) { + std::string host = wideStringToString(lpSystemName); + if (!host.empty()) { + wibo::lastError = ERROR_NOT_SUPPORTED; + return FALSE; + } + } + std::string ansiName = wideStringToString(lpName); + return LookupPrivilegeValueA(nullptr, ansiName.c_str(), lpLuid); + } + + struct TokenPrivilegesHeader { + DWORD PrivilegeCount = 0; + }; + + BOOL WIN_FUNC AdjustTokenPrivileges(HANDLE TokenHandle, BOOL DisableAllPrivileges, void *NewState, DWORD BufferLength, + void *PreviousState, PDWORD ReturnLength) { + DEBUG_LOG("AdjustTokenPrivileges(%p, %d, %p, %u, %p, %p)\n", TokenHandle, DisableAllPrivileges, + NewState, BufferLength, PreviousState, ReturnLength); + (void) DisableAllPrivileges; + (void) NewState; + auto data = handles::dataFromHandle(TokenHandle, false); + if (data.type != handles::TYPE_TOKEN) { + wibo::lastError = ERROR_INVALID_HANDLE; + return FALSE; + } + if (PreviousState) { + if (BufferLength < sizeof(TokenPrivilegesHeader)) { + if (ReturnLength) { + *ReturnLength = sizeof(TokenPrivilegesHeader); + } + wibo::lastError = ERROR_INSUFFICIENT_BUFFER; + return FALSE; + } + auto *header = reinterpret_cast(PreviousState); + header->PrivilegeCount = 0; + if (ReturnLength) { + *ReturnLength = sizeof(TokenPrivilegesHeader); + } + } else if (ReturnLength) { + *ReturnLength = 0; + } + wibo::lastError = ERROR_SUCCESS; + return TRUE; + } + + BOOL WIN_FUNC InitializeSecurityDescriptor(void *pSecurityDescriptor, DWORD dwRevision) { + DEBUG_LOG("InitializeSecurityDescriptor(%p, %u)\n", pSecurityDescriptor, dwRevision); + if (!pSecurityDescriptor) { + wibo::lastError = ERROR_INVALID_PARAMETER; + return FALSE; + } + if (dwRevision != SECURITY_DESCRIPTOR_REVISION) { + wibo::lastError = ERROR_INVALID_PARAMETER; + return FALSE; + } + auto *descriptor = static_cast(pSecurityDescriptor); + descriptor->Revision = static_cast(dwRevision); + descriptor->Sbz1 = 0; + descriptor->Control = 0; + descriptor->Owner = nullptr; + descriptor->Group = nullptr; + descriptor->Sacl = nullptr; + descriptor->Dacl = nullptr; + wibo::lastError = ERROR_SUCCESS; + return TRUE; + } + + BOOL WIN_FUNC SetSecurityDescriptorDacl(void *pSecurityDescriptor, BOOL bDaclPresent, void *pDacl, BOOL bDaclDefaulted) { + DEBUG_LOG("SetSecurityDescriptorDacl(%p, %u, %p, %u)\n", pSecurityDescriptor, bDaclPresent, pDacl, bDaclDefaulted); + if (!pSecurityDescriptor) { + wibo::lastError = ERROR_INVALID_PARAMETER; + return FALSE; + } + auto *descriptor = static_cast(pSecurityDescriptor); + if (descriptor->Revision != SECURITY_DESCRIPTOR_REVISION) { + wibo::lastError = ERROR_INVALID_PARAMETER; + return FALSE; + } + uint16_t control = static_cast(descriptor->Control & ~(SE_DACL_PRESENT | SE_DACL_DEFAULTED)); + if (bDaclPresent) { + control = static_cast(control | SE_DACL_PRESENT); + if (bDaclDefaulted) { + control = static_cast(control | SE_DACL_DEFAULTED); + } + descriptor->Dacl = pDacl; + } else { + descriptor->Dacl = nullptr; + } + descriptor->Control = control; + wibo::lastError = ERROR_SUCCESS; + return TRUE; + } + + BOOL WIN_FUNC GetUserNameA(char *lpBuffer, DWORD *pcbBuffer) { + DEBUG_LOG("GetUserNameA(%p, %p)\n", lpBuffer, pcbBuffer); + if (!pcbBuffer) { + wibo::lastError = ERROR_INVALID_PARAMETER; + return FALSE; + } + const char *name = "SYSTEM"; + size_t needed = std::strlen(name) + 1; + if (!lpBuffer || *pcbBuffer < needed) { + *pcbBuffer = static_cast(needed); + wibo::lastError = ERROR_INSUFFICIENT_BUFFER; + return FALSE; + } + std::memcpy(lpBuffer, name, needed); + *pcbBuffer = static_cast(needed); + wibo::lastError = ERROR_SUCCESS; + return TRUE; + } + + BOOL WIN_FUNC GetUserNameW(uint16_t *lpBuffer, DWORD *pcbBuffer) { + DEBUG_LOG("GetUserNameW(%p, %p)\n", lpBuffer, pcbBuffer); + if (!pcbBuffer) { + wibo::lastError = ERROR_INVALID_PARAMETER; + return FALSE; + } + const char16_t name[] = {u'S', u'Y', u'S', u'T', u'E', u'M', u'\0'}; + size_t needed = (std::size(name)); + if (!lpBuffer || *pcbBuffer < needed) { + *pcbBuffer = static_cast(needed); + wibo::lastError = ERROR_INSUFFICIENT_BUFFER; + return FALSE; + } + std::memcpy(lpBuffer, name, needed * sizeof(uint16_t)); + *pcbBuffer = static_cast(needed); + wibo::lastError = ERROR_SUCCESS; + return TRUE; + } + + BOOL WIN_FUNC SetTokenInformation(HANDLE TokenHandle, unsigned int TokenInformationClass, void *TokenInformation, DWORD TokenInformationLength) { + DEBUG_LOG("STUB: SetTokenInformation(%p, %u, %p, %u)\n", TokenHandle, TokenInformationClass, TokenInformation, TokenInformationLength); + (void) TokenInformationClass; + (void) TokenInformation; + (void) TokenInformationLength; + auto data = handles::dataFromHandle(TokenHandle, false); + if (data.type != handles::TYPE_TOKEN) { + wibo::lastError = ERROR_INVALID_HANDLE; + return FALSE; + } + wibo::lastError = ERROR_SUCCESS; + return TRUE; + } } static void *resolveByName(const char *name) { if (strcmp(name, "RegOpenKeyExA") == 0) return (void *) advapi32::RegOpenKeyExA; + if (strcmp(name, "RegOpenKeyExW") == 0) return (void *) advapi32::RegOpenKeyExW; + if (strcmp(name, "RegCloseKey") == 0) return (void *) advapi32::RegCloseKey; if (strcmp(name, "CryptReleaseContext") == 0) return (void*) advapi32::CryptReleaseContext; if (strcmp(name, "CryptAcquireContextW") == 0) return (void*) advapi32::CryptAcquireContextW; if (strcmp(name, "CryptGenRandom") == 0) return (void*) advapi32::CryptGenRandom; @@ -597,6 +1105,14 @@ static void *resolveByName(const char *name) { if (strcmp(name, "OpenProcessToken") == 0) return (void*) advapi32::OpenProcessToken; if (strcmp(name, "GetTokenInformation") == 0) return (void*) advapi32::GetTokenInformation; if (strcmp(name, "LookupAccountSidW") == 0) return (void*) advapi32::LookupAccountSidW; + if (strcmp(name, "InitializeSecurityDescriptor") == 0) return (void*) advapi32::InitializeSecurityDescriptor; + if (strcmp(name, "SetSecurityDescriptorDacl") == 0) return (void*) advapi32::SetSecurityDescriptorDacl; + if (strcmp(name, "LookupPrivilegeValueA") == 0) return (void*) advapi32::LookupPrivilegeValueA; + if (strcmp(name, "LookupPrivilegeValueW") == 0) return (void*) advapi32::LookupPrivilegeValueW; + if (strcmp(name, "AdjustTokenPrivileges") == 0) return (void*) advapi32::AdjustTokenPrivileges; + if (strcmp(name, "GetUserNameA") == 0) return (void*) advapi32::GetUserNameA; + if (strcmp(name, "GetUserNameW") == 0) return (void*) advapi32::GetUserNameW; + if (strcmp(name, "SetTokenInformation") == 0) return (void*) advapi32::SetTokenInformation; return nullptr; } diff --git a/dll/kernel32.cpp b/dll/kernel32.cpp index 196bc40..718a576 100644 --- a/dll/kernel32.cpp +++ b/dll/kernel32.cpp @@ -303,6 +303,34 @@ namespace kernel32 { } } + struct SemaphoreObject { + pthread_mutex_t mutex; + pthread_cond_t cond; + LONG count = 0; + LONG maxCount = 0; + std::u16string name; + int refCount = 1; + }; + + static std::mutex semaphoreRegistryLock; + static std::unordered_map namedSemaphores; + + static void releaseSemaphoreObject(SemaphoreObject *obj) { + if (!obj) { + return; + } + std::lock_guard lock(semaphoreRegistryLock); + obj->refCount--; + if (obj->refCount == 0) { + if (!obj->name.empty()) { + namedSemaphores.erase(obj->name); + } + pthread_cond_destroy(&obj->cond); + pthread_mutex_destroy(&obj->mutex); + delete obj; + } + } + typedef DWORD (WIN_FUNC *LPTHREAD_START_ROUTINE)(LPVOID); struct ThreadObject { @@ -310,6 +338,7 @@ namespace kernel32 { bool finished = false; bool joined = false; bool detached = false; + bool synthetic = false; DWORD exitCode = 0; int refCount = 1; pthread_mutex_t mutex; @@ -332,15 +361,17 @@ namespace kernel32 { delete obj; } + static ThreadObject *retainThreadObject(ThreadObject *obj); static void releaseThreadObject(ThreadObject *obj); static void *threadTrampoline(void *param); + static ThreadObject *ensureCurrentThreadObject(); static thread_local ThreadObject *currentThreadObject = nullptr; - static constexpr uintptr_t PSEUDO_CURRENT_THREAD_HANDLE_VALUE = 0x100007u; + static constexpr uintptr_t PSEUDO_CURRENT_THREAD_HANDLE_VALUE = static_cast(-2); static ThreadObject *threadObjectFromHandle(HANDLE hThread) { auto raw = reinterpret_cast(hThread); - if (raw == PSEUDO_CURRENT_THREAD_HANDLE_VALUE || raw == static_cast(-2)) { - return currentThreadObject; + if (raw == PSEUDO_CURRENT_THREAD_HANDLE_VALUE) { + return ensureCurrentThreadObject(); } if (raw == static_cast(-1) || raw == 0) { return nullptr; @@ -352,6 +383,26 @@ namespace kernel32 { return reinterpret_cast(data.ptr); } + static ThreadObject *ensureCurrentThreadObject() { + ThreadObject *obj = currentThreadObject; + if (obj) { + return obj; + } + obj = new ThreadObject(); + obj->thread = pthread_self(); + obj->finished = false; + obj->joined = false; + obj->detached = true; + obj->synthetic = false; + obj->exitCode = STILL_ACTIVE; + obj->refCount = 0; + obj->suspendCount = 0; + pthread_mutex_init(&obj->mutex, nullptr); + pthread_cond_init(&obj->cond, nullptr); + currentThreadObject = obj; + return obj; + } + static std::u16string makeMutexName(LPCWSTR name) { if (!name) { return std::u16string(); @@ -378,6 +429,16 @@ namespace kernel32 { } } + static ThreadObject *retainThreadObject(ThreadObject *obj) { + if (!obj) { + return nullptr; + } + pthread_mutex_lock(&obj->mutex); + obj->refCount++; + pthread_mutex_unlock(&obj->mutex); + return obj; + } + static void releaseThreadObject(ThreadObject *obj) { if (!obj) { return; @@ -388,14 +449,16 @@ namespace kernel32 { bool finished = false; bool joined = false; bool detached = false; + bool synthetic = false; pthread_mutex_lock(&obj->mutex); obj->refCount--; finished = obj->finished; joined = obj->joined; detached = obj->detached; + synthetic = obj->synthetic; thread = obj->thread; if (obj->refCount == 0) { - if (finished) { + if (finished || synthetic) { shouldDelete = true; } else if (!detached) { obj->detached = true; @@ -405,13 +468,15 @@ namespace kernel32 { } pthread_mutex_unlock(&obj->mutex); - if (shouldDetach) { + if (shouldDetach && !synthetic) { pthread_detach(thread); } if (shouldDelete) { - if (!joined && !detached) { - pthread_join(thread, nullptr); + if (!synthetic) { + if (!joined && !detached) { + pthread_join(thread, nullptr); + } } destroyThreadObject(obj); } @@ -577,6 +642,32 @@ namespace kernel32 { return TRUE; } + BOOL WIN_FUNC IsWow64Process(HANDLE hProcess, PBOOL Wow64Process) { + DEBUG_LOG("IsWow64Process(%p, %p)\n", hProcess, Wow64Process); + if (!Wow64Process) { + wibo::lastError = ERROR_INVALID_PARAMETER; + return FALSE; + } + + uintptr_t rawHandle = reinterpret_cast(hProcess); + bool isPseudoHandle = rawHandle == static_cast(-1); + if (!isPseudoHandle) { + if (!hProcess) { + wibo::lastError = ERROR_INVALID_HANDLE; + return FALSE; + } + auto data = handles::dataFromHandle(hProcess, false); + if (data.type != handles::TYPE_PROCESS || data.ptr == nullptr) { + wibo::lastError = ERROR_INVALID_HANDLE; + return FALSE; + } + } + + *Wow64Process = FALSE; + wibo::lastError = ERROR_SUCCESS; + return TRUE; + } + void WIN_FUNC RaiseException(DWORD dwExceptionCode, DWORD dwExceptionFlags, DWORD nNumberOfArguments, const ULONG_PTR *lpArguments) { DEBUG_LOG("RaiseException(0x%x, 0x%x, %u, %p)\n", dwExceptionCode, dwExceptionFlags, nNumberOfArguments, lpArguments); (void)lpArguments; @@ -991,6 +1082,22 @@ namespace kernel32 { wibo::lastError = ERROR_SUCCESS; return 0; } + case handles::TYPE_SEMAPHORE: { + auto *obj = reinterpret_cast(data.ptr); + if (dwMilliseconds != 0xffffffff) { + DEBUG_LOG("WaitForSingleObject: timeout for semaphore not supported\n"); + wibo::lastError = ERROR_NOT_SUPPORTED; + return 0xFFFFFFFF; + } + pthread_mutex_lock(&obj->mutex); + while (obj->count == 0) { + pthread_cond_wait(&obj->cond, &obj->mutex); + } + obj->count--; + pthread_mutex_unlock(&obj->mutex); + wibo::lastError = ERROR_SUCCESS; + return 0; + } case handles::TYPE_MUTEX: { auto *obj = reinterpret_cast(data.ptr); if (dwMilliseconds != 0xffffffff) { @@ -1372,27 +1479,123 @@ namespace kernel32 { unsigned int bInheritHandle, unsigned int dwOptions) { DEBUG_LOG("DuplicateHandle(%p, %p, %p, %p, %x, %d, %x)\n", hSourceProcessHandle, hSourceHandle, hTargetProcessHandle, lpTargetHandle, dwDesiredAccess, bInheritHandle, dwOptions); - assert(hSourceProcessHandle == (void *)0xFFFFFFFF); // current process - assert(hTargetProcessHandle == (void *)0xFFFFFFFF); // current process (void)dwDesiredAccess; (void)bInheritHandle; (void)dwOptions; + if (!lpTargetHandle) { + wibo::lastError = ERROR_INVALID_PARAMETER; + return 0; + } + + auto validateProcessHandle = [&](void *handle) -> bool { + uintptr_t raw = reinterpret_cast(handle); + if (raw == static_cast(-1)) { + return true; + } + auto data = handles::dataFromHandle(handle, false); + if (data.type != handles::TYPE_PROCESS || data.ptr == nullptr) { + return false; + } + auto *proc = reinterpret_cast(data.ptr); + return proc && proc->pid == getpid(); + }; + + if (!validateProcessHandle(hSourceProcessHandle) || !validateProcessHandle(hTargetProcessHandle)) { + DEBUG_LOG("DuplicateHandle: unsupported process handle combination (source=%p target=%p)\n", + hSourceProcessHandle, hTargetProcessHandle); + wibo::lastError = ERROR_INVALID_HANDLE; + return 0; + } + auto file = files::fileHandleFromHandle(hSourceHandle); if (file && (file->fp == stdin || file->fp == stdout || file->fp == stderr)) { - // we never close standard handles so they are fine to duplicate void *handle = files::duplicateFileHandle(file, false); - DEBUG_LOG("-> %p\n", handle); + DEBUG_LOG("DuplicateHandle: duplicated std handle -> %p\n", handle); *lpTargetHandle = handle; + wibo::lastError = ERROR_SUCCESS; return 1; } - // other handles are more problematic; fail for now - printf("failed to duplicate handle\n"); - assert(0); + + uintptr_t sourceHandleRaw = reinterpret_cast(hSourceHandle); + if (sourceHandleRaw == static_cast(-1)) { + void *handle = processes::allocProcessHandle(getpid()); + processes::Process *proc = processes::processFromHandle(handle, false); + if (proc) { + proc->exitCode = STILL_ACTIVE; + proc->forcedExitCode = STILL_ACTIVE; + proc->terminationRequested = false; + } + DEBUG_LOG("DuplicateHandle: created process handle for current process -> %p\n", handle); + *lpTargetHandle = handle; + wibo::lastError = ERROR_SUCCESS; + return 1; + } + if (sourceHandleRaw == PSEUDO_CURRENT_THREAD_HANDLE_VALUE) { + ThreadObject *obj = ensureCurrentThreadObject(); + if (obj) { + retainThreadObject(obj); + void *handle = handles::allocDataHandle({handles::TYPE_THREAD, obj, 0}); + DEBUG_LOG("DuplicateHandle: duplicated pseudo current thread -> %p\n", handle); + *lpTargetHandle = handle; + wibo::lastError = ERROR_SUCCESS; + return 1; + } + ThreadObject *syntheticObj = new ThreadObject(); + syntheticObj->thread = pthread_self(); + syntheticObj->finished = false; + syntheticObj->joined = false; + syntheticObj->detached = true; + syntheticObj->synthetic = true; + syntheticObj->exitCode = 0; + syntheticObj->refCount = 1; + syntheticObj->suspendCount = 0; + pthread_mutex_init(&syntheticObj->mutex, nullptr); + pthread_cond_init(&syntheticObj->cond, nullptr); + void *handle = handles::allocDataHandle({handles::TYPE_THREAD, syntheticObj, 0}); + DEBUG_LOG("DuplicateHandle: created synthetic thread handle -> %p\n", handle); + *lpTargetHandle = handle; + wibo::lastError = ERROR_SUCCESS; + return 1; + } + + handles::Data data = handles::dataFromHandle(hSourceHandle, false); + if (data.type == handles::TYPE_PROCESS && data.ptr) { + auto *original = reinterpret_cast(data.ptr); + void *handle = processes::allocProcessHandle(original->pid); + auto *copy = processes::processFromHandle(handle, false); + if (copy) { + *copy = *original; + } + DEBUG_LOG("DuplicateHandle: duplicated process handle -> %p\n", handle); + *lpTargetHandle = handle; + wibo::lastError = ERROR_SUCCESS; + return 1; + } + if (data.type == handles::TYPE_THREAD && data.ptr) { + auto *threadObj = reinterpret_cast(data.ptr); + if (!retainThreadObject(threadObj)) { + wibo::lastError = ERROR_INVALID_HANDLE; + return 0; + } + void *handle = handles::allocDataHandle({handles::TYPE_THREAD, threadObj, 0}); + DEBUG_LOG("DuplicateHandle: duplicated thread handle -> %p\n", handle); + *lpTargetHandle = handle; + wibo::lastError = ERROR_SUCCESS; + return 1; + } + DEBUG_LOG("DuplicateHandle: unsupported handle type for %p\n", hSourceHandle); + wibo::lastError = ERROR_INVALID_HANDLE; + return 0; } BOOL WIN_FUNC CloseHandle(HANDLE hObject) { DEBUG_LOG("CloseHandle(%p)\n", hObject); auto data = handles::dataFromHandle(hObject, true); + if (data.type == handles::TYPE_UNUSED || data.ptr == nullptr) { + wibo::lastError = ERROR_INVALID_HANDLE; + return FALSE; + } + bool success = true; if (data.type == handles::TYPE_FILE) { auto file = reinterpret_cast(data.ptr); if (file) { @@ -1401,15 +1604,24 @@ namespace kernel32 { fclose(file->fp); } delete file; + } else { + success = false; } } else if (data.type == handles::TYPE_MAPPED) { auto *mapping = reinterpret_cast(data.ptr); if (mapping) { mapping->closed = true; tryReleaseMapping(mapping); + } else { + success = false; } } else if (data.type == handles::TYPE_PROCESS) { - delete (processes::Process *)data.ptr; + auto *proc = reinterpret_cast(data.ptr); + if (proc) { + delete proc; + } else { + success = false; + } } else if (data.type == handles::TYPE_TOKEN) { advapi32::releaseToken(data.ptr); } else if (data.type == handles::TYPE_MUTEX) { @@ -1418,7 +1630,16 @@ namespace kernel32 { releaseEventObject(reinterpret_cast(data.ptr)); } else if (data.type == handles::TYPE_THREAD) { releaseThreadObject(reinterpret_cast(data.ptr)); + } else if (data.type == handles::TYPE_SEMAPHORE) { + releaseSemaphoreObject(reinterpret_cast(data.ptr)); + } else { + success = false; } + if (!success) { + wibo::lastError = ERROR_INVALID_HANDLE; + return FALSE; + } + wibo::lastError = ERROR_SUCCESS; return TRUE; } @@ -4895,8 +5116,11 @@ namespace kernel32 { } HANDLE WIN_FUNC GetCurrentThread() { - DEBUG_LOG("STUB: GetCurrentThread\n"); - return reinterpret_cast(PSEUDO_CURRENT_THREAD_HANDLE_VALUE); + ThreadObject *obj = ensureCurrentThreadObject(); + (void)obj; + HANDLE pseudoHandle = reinterpret_cast(PSEUDO_CURRENT_THREAD_HANDLE_VALUE); + DEBUG_LOG("GetCurrentThread() -> %p\n", pseudoHandle); + return pseudoHandle; } DWORD_PTR WIN_FUNC SetThreadAffinityMask(HANDLE hThread, DWORD_PTR dwThreadAffinityMask) { @@ -4908,7 +5132,6 @@ namespace kernel32 { uintptr_t rawThreadHandle = reinterpret_cast(hThread); bool isPseudoHandle = rawThreadHandle == PSEUDO_CURRENT_THREAD_HANDLE_VALUE || - rawThreadHandle == static_cast(-2) || rawThreadHandle == 0 || rawThreadHandle == static_cast(-1); if (!isPseudoHandle) { @@ -5060,25 +5283,11 @@ namespace kernel32 { wibo::lastError = ERROR_INVALID_PARAMETER; return FALSE; } - if (reinterpret_cast(hThread) == PSEUDO_CURRENT_THREAD_HANDLE_VALUE) { - ThreadObject *obj = currentThreadObject; - if (obj) { - pthread_mutex_lock(&obj->mutex); - DWORD code = obj->finished ? obj->exitCode : STILL_ACTIVE; - pthread_mutex_unlock(&obj->mutex); - *lpExitCode = code; - } else { - *lpExitCode = STILL_ACTIVE; - } - wibo::lastError = ERROR_SUCCESS; - return TRUE; - } - auto data = handles::dataFromHandle(hThread, false); - if (data.type != handles::TYPE_THREAD) { + ThreadObject *obj = threadObjectFromHandle(hThread); + if (!obj) { wibo::lastError = ERROR_INVALID_HANDLE; return FALSE; } - ThreadObject *obj = reinterpret_cast(data.ptr); pthread_mutex_lock(&obj->mutex); DWORD code = obj->finished ? obj->exitCode : STILL_ACTIVE; pthread_mutex_unlock(&obj->mutex); @@ -5222,6 +5431,100 @@ namespace kernel32 { return CreateEventW(lpEventAttributes, bManualReset, bInitialState, lpName ? reinterpret_cast(wideName.data()) : nullptr); } + HANDLE WIN_FUNC CreateSemaphoreW(void *lpSemaphoreAttributes, LONG lInitialCount, LONG lMaximumCount, + LPCWSTR lpName) { + DEBUG_LOG("CreateSemaphoreW(%p, %ld, %ld, %ls)\n", lpSemaphoreAttributes, static_cast(lInitialCount), + static_cast(lMaximumCount), lpName ? reinterpret_cast(lpName) : L""); + (void)lpSemaphoreAttributes; + SemaphoreObject *obj = nullptr; + bool alreadyExists = false; + std::u16string name = makeMutexName(lpName); + { + std::lock_guard lock(semaphoreRegistryLock); + if (!name.empty()) { + auto it = namedSemaphores.find(name); + if (it != namedSemaphores.end()) { + obj = it->second; + obj->refCount++; + alreadyExists = true; + } + } + if (!obj) { + if (lMaximumCount <= 0 || lInitialCount < 0 || lInitialCount > lMaximumCount) { + wibo::lastError = ERROR_INVALID_PARAMETER; + return nullptr; + } + obj = new SemaphoreObject(); + pthread_mutex_init(&obj->mutex, nullptr); + pthread_cond_init(&obj->cond, nullptr); + obj->count = lInitialCount; + obj->maxCount = lMaximumCount; + obj->name = name; + obj->refCount = 1; + if (!name.empty()) { + namedSemaphores[name] = obj; + } + } + } + void *handle = handles::allocDataHandle({handles::TYPE_SEMAPHORE, obj, 0}); + wibo::lastError = alreadyExists ? ERROR_ALREADY_EXISTS : ERROR_SUCCESS; + return handle; + } + + HANDLE WIN_FUNC CreateSemaphoreA(void *lpSemaphoreAttributes, LONG lInitialCount, LONG lMaximumCount, + LPCSTR lpName) { + DEBUG_LOG("CreateSemaphoreA -> "); + std::vector wideName; + if (lpName) { + wideName = stringToWideString(lpName); + } + return CreateSemaphoreW(lpSemaphoreAttributes, lInitialCount, lMaximumCount, + lpName ? reinterpret_cast(wideName.data()) : nullptr); + } + + BOOL WIN_FUNC ReleaseSemaphore(HANDLE hSemaphore, LONG lReleaseCount, PLONG lpPreviousCount) { + DEBUG_LOG("ReleaseSemaphore(%p, %ld, %p)\n", hSemaphore, static_cast(lReleaseCount), lpPreviousCount); + if (lReleaseCount <= 0) { + wibo::lastError = ERROR_INVALID_PARAMETER; + return FALSE; + } + auto data = handles::dataFromHandle(hSemaphore, false); + if (data.type != handles::TYPE_SEMAPHORE || data.ptr == nullptr) { + wibo::lastError = ERROR_INVALID_HANDLE; + return FALSE; + } + auto *obj = reinterpret_cast(data.ptr); + pthread_mutex_lock(&obj->mutex); + if (lpPreviousCount) { + *lpPreviousCount = obj->count; + } + if (lReleaseCount > obj->maxCount - obj->count) { + pthread_mutex_unlock(&obj->mutex); + wibo::lastError = ERROR_INVALID_PARAMETER; + return FALSE; + } + obj->count += lReleaseCount; + pthread_mutex_unlock(&obj->mutex); + pthread_cond_broadcast(&obj->cond); + wibo::lastError = ERROR_SUCCESS; + return TRUE; + } + + BOOL WIN_FUNC SetThreadPriority(HANDLE hThread, int nPriority) { + DEBUG_LOG("STUB: SetThreadPriority(%p, %d)\n", hThread, nPriority); + (void) hThread; + (void) nPriority; + wibo::lastError = ERROR_SUCCESS; + return TRUE; + } + + int WIN_FUNC GetThreadPriority(HANDLE hThread) { + DEBUG_LOG("STUB: GetThreadPriority(%p)\n", hThread); + (void) hThread; + wibo::lastError = ERROR_SUCCESS; + return 0; // THREAD_PRIORITY_NORMAL + } + BOOL WIN_FUNC SetEvent(HANDLE hEvent) { DEBUG_LOG("SetEvent(%p)\n", hEvent); auto data = handles::dataFromHandle(hEvent, false); @@ -5271,8 +5574,7 @@ namespace kernel32 { } bool isPseudoCurrentThread = reinterpret_cast(hThread) == PSEUDO_CURRENT_THREAD_HANDLE_VALUE || - hThread == (HANDLE)0xFFFFFFFE || hThread == (HANDLE)0 || - hThread == (HANDLE)0xFFFFFFFF; + hThread == (HANDLE)0 || hThread == (HANDLE)0xFFFFFFFF; if (!isPseudoCurrentThread) { DEBUG_LOG("GetThreadTimes: unsupported handle %p\n", hThread); wibo::lastError = ERROR_INVALID_HANDLE; @@ -6340,6 +6642,7 @@ static void *resolveByName(const char *name) { if (strcmp(name, "IsBadWritePtr") == 0) return (void *) kernel32::IsBadWritePtr; if (strcmp(name, "Wow64DisableWow64FsRedirection") == 0) return (void *) kernel32::Wow64DisableWow64FsRedirection; if (strcmp(name, "Wow64RevertWow64FsRedirection") == 0) return (void *) kernel32::Wow64RevertWow64FsRedirection; + if (strcmp(name, "IsWow64Process") == 0) return (void *) kernel32::IsWow64Process; if (strcmp(name, "RaiseException") == 0) return (void *) kernel32::RaiseException; if (strcmp(name, "AddVectoredExceptionHandler") == 0) return (void *) kernel32::AddVectoredExceptionHandler; @@ -6404,11 +6707,16 @@ static void *resolveByName(const char *name) { if (strcmp(name, "CreateMutexW") == 0) return (void *) kernel32::CreateMutexW; if (strcmp(name, "CreateEventA") == 0) return (void *) kernel32::CreateEventA; if (strcmp(name, "CreateEventW") == 0) return (void *) kernel32::CreateEventW; + if (strcmp(name, "CreateSemaphoreA") == 0) return (void *) kernel32::CreateSemaphoreA; + if (strcmp(name, "CreateSemaphoreW") == 0) return (void *) kernel32::CreateSemaphoreW; if (strcmp(name, "SetEvent") == 0) return (void *) kernel32::SetEvent; if (strcmp(name, "ResetEvent") == 0) return (void *) kernel32::ResetEvent; if (strcmp(name, "ReleaseMutex") == 0) return (void *) kernel32::ReleaseMutex; + if (strcmp(name, "ReleaseSemaphore") == 0) return (void *) kernel32::ReleaseSemaphore; if (strcmp(name, "SetThreadAffinityMask") == 0) return (void *) kernel32::SetThreadAffinityMask; if (strcmp(name, "ResumeThread") == 0) return (void *) kernel32::ResumeThread; + if (strcmp(name, "SetThreadPriority") == 0) return (void *) kernel32::SetThreadPriority; + if (strcmp(name, "GetThreadPriority") == 0) return (void *) kernel32::GetThreadPriority; // winbase.h if (strcmp(name, "GlobalAlloc") == 0) return (void *) kernel32::GlobalAlloc; diff --git a/handles.h b/handles.h index e2bdcb1..8c05506 100644 --- a/handles.h +++ b/handles.h @@ -13,8 +13,10 @@ namespace handles { TYPE_TOKEN, TYPE_MUTEX, TYPE_EVENT, + TYPE_SEMAPHORE, TYPE_THREAD, - TYPE_HEAP + TYPE_HEAP, + TYPE_REGISTRY_KEY }; struct Data { diff --git a/module_registry.cpp b/module_registry.cpp index 24bc1a9..23bec32 100644 --- a/module_registry.cpp +++ b/module_registry.cpp @@ -268,33 +268,36 @@ std::vector collectSearchDirectories(ModuleRegistry ®, addDirectory(std::filesystem::current_path()); } - if (const char *envPath = std::getenv("WIBO_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; + const auto addFromEnv = [&](const char *envVar) { + if (const char *envPath = std::getenv(envVar)) { + 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()) { + auto candidate = files::pathFromWindows(piece.c_str()); + if (!candidate.empty()) { + addDirectory(candidate); + } else { + addDirectory(std::filesystem::path(piece)); } } - addDirectory(candidate); } + if (end == pathList.size()) { + break; + } + start = end + 1; } - if (end == pathList.size()) { - break; - } - start = end + 1; } - } + }; + + addFromEnv("WIBO_PATH"); + addFromEnv("WINEPATH"); // Wine compatibility return dirs; }