/*** Copyright (C) 2021 J Reece Wilson (a/k/a "Reece"). All rights reserved. File: AuProcess.cpp Date: 2021-8-20 Author: Reece ***/ #include #include "Process.hpp" #if defined(AURORA_IS_POSIX_DERIVED) #include #include #include #include #include #endif #if defined(AURORA_PLATFORM_WIN32) #include #include #include #endif #if defined(AURORA_IS_MODERNNT_DERIVED) #include "AuProcessSectionView.NT.hpp" #endif #include #include #include "AuProcessMap.hpp" #if defined(AURORA_COMPILER_CLANG) // warning: enumeration values 'kEnumCount' and 'kEnumInvalid' not handled in switch [-Wswitch #pragma clang diagnostic ignored "-Wswitch" // Yea, I don't give a shit. #endif namespace Aurora::Process { // Because we're only protecting gModuleHandles in practice, and we cannot fuck up any iterator handles, we can use renterable mutexes. // We only use AuTryFind/AuTryInsert for the most part. // The only iterators created for gModuleHandles call out to apis that should not allow for further recursion. // So we should be fine. static AuCriticalSection gSpinLock; static AuList gClassPath; static AuHashMap gModuleHandles; static const bool kIsMainSigned = false; void LoadProcessSectionViewSymbol(); static constexpr const char *GetPlatformString(Build::EPlatform platform) { switch (platform) { case Build::EPlatform::ePlatformWin32: return ".Win32"; case Build::EPlatform::ePlatformLinux: return ".Linux"; case Build::EPlatform::ePlatformAndroid: return ".Android"; case Build::EPlatform::ePlatformAppleMacOS: return ".Mac"; case Build::EPlatform::ePlatformIos: return ".iOS"; case Build::EPlatform::eKernelBsd: return ".BSD"; default: return nullptr; } } static constexpr const char *GetPlatformExt(Build::EPlatform platform) { switch (platform) { case Build::EPlatform::ePlatformWin32: return ".dll"; case Build::EPlatform::ePlatformLinux: case Build::EPlatform::ePlatformAndroid: case Build::EPlatform::eKernelBsd: return ".so"; case Build::EPlatform::ePlatformAppleMacOS: case Build::EPlatform::ePlatformIos: return ".dylib"; default: return nullptr; } } static constexpr const char *GetArchString(Build::EArchitecture architecture) { switch (architecture) { case Build::EArchitecture::eX86_32: return ".x86_32"; case Build::EArchitecture::eX86_64: return ".x86_64"; case Build::EArchitecture::eAArch64: return ".arm"; default: return nullptr; } } static AuString ConstructAuDllSuffixUncached() { auto platform = GetPlatformString(Build::kCurrentPlatform); auto architecture = GetArchString(Build::kCurrentArchitecture); auto ext = GetPlatformExt(Build::kCurrentPlatform); AuString ret; #if defined(DEBUG) ret = ".Debug"; #elif defined(STAGING) ret = ".Stage"; #elif defined(SHIP) ret = ".Ship"; #endif if (platform) { ret += platform; } if (architecture) { ret += architecture; } if (ext) { ret += ext; } return ret; } AUKN_SYM AuROString GetViewOfAuroraDLLSuffix() { static AuString dllSuffixString {}; static AuInitOnce gInitOnce; gInitOnce.TryCall([] { try { dllSuffixString = ConstructAuDllSuffixUncached(); return true; } catch (...) { // I hate C++ strings so much return false; } }); return dllSuffixString; } AUKN_SYM AuROString GetViewOfDynamicLibraryExtensionSuffix() { return GetPlatformExt(Build::kCurrentPlatform); } AUKN_SYM AuROString GetViewOfArchitectureSuffix() { return GetArchString(Build::kCurrentArchitecture); } static AuString GetModuleNameFromFileName(const AuString &filename) { static const auto kStringSuffixA = GetViewOfAuroraDLLSuffix(); static const auto kStringSuffixB = GetPlatformExt(Build::kCurrentPlatform); if (AuEndsWith(filename, kStringSuffixA)) { return filename.substr(0, filename.size() - kStringSuffixA.size()); } if (AuEndsWith(filename, kStringSuffixB)) { return filename.substr(0, filename.size() - strlen(kStringSuffixB)); } return filename; } #if defined(AURORA_PLATFORM_WIN32) bool VerifyEmbeddedSignature(const wchar_t *path, HANDLE handle) { LONG lStatus; DWORD dwLastError; WINTRUST_FILE_INFO FileData; memset(&FileData, 0, sizeof(FileData)); FileData.cbStruct = sizeof(WINTRUST_FILE_INFO); FileData.pcwszFilePath = path; FileData.hFile = handle; FileData.pgKnownSubject = NULL; GUID WVTPolicyGUID = WINTRUST_ACTION_GENERIC_VERIFY_V2; WINTRUST_DATA WinTrustData; // Initialize the WinVerifyTrust input data structure. // Default all fields to 0. memset(&WinTrustData, 0, sizeof(WinTrustData)); WinTrustData.cbStruct = sizeof(WinTrustData); // Use default code signing EKU. WinTrustData.pPolicyCallbackData = NULL; // No data to pass to SIP. WinTrustData.pSIPClientData = NULL; // Disable WVT UI. WinTrustData.dwUIChoice = WTD_UI_NONE; // Revocation checking.constexpr WinTrustData.fdwRevocationChecks = WTD_REVOKE_WHOLECHAIN; // Verify an embedded signature on a file. WinTrustData.dwUnionChoice = WTD_CHOICE_FILE; // Verify action. WinTrustData.dwStateAction = WTD_STATEACTION_VERIFY; // Verification sets this value. WinTrustData.hWVTStateData = NULL; // Not used. WinTrustData.pwszURLReference = NULL; // This is not applicable if there is no UI because it changes // the UI to accommodate running applications instead of // installing applications. WinTrustData.dwUIContext = 0; // Set pFile. WinTrustData.pFile = &FileData; if (!pWinVerifyTrust) { return gRuntimeConfig.debug.bWin32VerifyTrustFailMissingAPI; } // WinVerifyTrust verifies signatures as specified by the GUID // and Wintrust_Data. lStatus = pWinVerifyTrust( NULL, &WVTPolicyGUID, &WinTrustData); bool status {}; switch (lStatus) { case ERROR_SUCCESS: status = true; break; case TRUST_E_NOSIGNATURE: // The file was not signed or had a signature // that was not valid. // Get the reason for no signature. dwLastError = GetLastError(); if (TRUST_E_NOSIGNATURE == dwLastError || TRUST_E_SUBJECT_FORM_UNKNOWN == dwLastError || TRUST_E_PROVIDER_UNKNOWN == dwLastError) { SysPushErrorCrypt("The file is not signed."); } else { SysPushErrorCrypt("An unknown error occurred trying to verify the signature of the file."); } break; case TRUST_E_EXPLICIT_DISTRUST: SysPushErrorCrypt("The signature is present, but specifically disallowed."); break; case TRUST_E_SUBJECT_NOT_TRUSTED: SysPushErrorCrypt("The signature is present, but not trusted."); break; case CRYPT_E_SECURITY_SETTINGS: SysPushErrorCrypt("CRYPT_E_SECURITY_SETTINGS - The hash " "representing the subject or the publisher wasn't " "explicitly trusted by the admin and admin policy " "has disabled user trust. No signature, publisher " "or timestamp errors."); break; default: SysPushErrorCrypt("Dependency Injection Error"); break; } WinTrustData.dwStateAction = WTD_STATEACTION_CLOSE; if (!pWinVerifyTrust) { return gRuntimeConfig.debug.bWin32VerifyTrustFailMissingAPI; } lStatus = pWinVerifyTrust( NULL, &WVTPolicyGUID, &WinTrustData); return status; } #endif static void *LoadModule(const AuROString &name, const AuROString &path) { void *pFound {}; if (AuTryFind(gModuleHandles, path, pFound)) { return pFound; } #if defined(AURORA_IS_MODERNNT_DERIVED) if (!pLoadLibraryW) { return {}; } auto handle = pLoadLibraryW(Locale::ConvertFromUTF8(path).c_str()); if (handle == INVALID_HANDLE_VALUE) { SysPushErrorNested("Could't link dynamic library {}", path); return {}; } #else auto handle = dlopen(AuString(path).c_str(), RTLD_DEEPBIND); if (handle == nullptr) { SysPushErrorNested("Could't link dynamic library {}", path); return {}; } #endif auto pRet = (void *)handle; gModuleHandles.insert(AuMakePair(AuMove(AuString(name)), AuConstReference(pRet))); gModuleHandles.insert(AuMakePair(AuMove(AuString(path)), AuConstReference(pRet))); return pRet; } static void *TryLoadModule(const AuString &path, #if defined(AURORA_IS_MODERNNT_DERIVED) const AuString &abs, #endif const ModuleLoadRequest &request, bool &fail ) { fail = false; auto pathNrml = AuIOFS::NormalizePathRet(path); if (pathNrml.empty() && path.size()) { SysPushErrorMemory(); return nullptr; } #if defined(AURORA_IS_MODERNNT_DERIVED) auto widePath = Locale::ConvertFromUTF8(pathNrml); if (widePath.empty() && pathNrml.size()) { SysPushErrorMemory(); return nullptr; } auto mitigateTimeOfUse = Win32Open(widePath.c_str(), GENERIC_READ, FILE_SHARE_READ, false, OPEN_EXISTING, 0, 0); if (mitigateTimeOfUse == INVALID_HANDLE_VALUE) { SysPushErrorNested("Couldn't open existing file. Race exploit?"); fail = true; return {}; } auto absPath = AuIOFS::NormalizePathRet(abs); if (absPath.empty() && abs.size()) { SysPushErrorMemory(); return nullptr; } #endif if (request.verify || ((kIsMainSigned || request.forceMitigations) && request.enableMitigations)) { #if defined(AURORA_PLATFORM_WIN32) if (!VerifyEmbeddedSignature(widePath.c_str(), mitigateTimeOfUse)) { SysPushErrorNested("Couldn't verify file {}", path); fail = true; AuWin32CloseHandle(mitigateTimeOfUse); return {}; } #else AuLogWarn("Can't verify {} on this platform", path); #endif } #if defined(AURORA_IS_POSIX_DERIVED) if (request.enableMitigations && request.unixCheckPlusX) { struct stat sb; if (stat(pathNrml.data(), &sb) != 0) { SysPushErrorNested("Couldn't open existing file. Race exploit?"); fail = true; return {}; } if ((sb.st_mode & S_IXUSR) == 0) { fail = true; return {}; } } #endif #if defined(AURORA_IS_MODERNNT_DERIVED) auto returnValue = LoadModule(request.mod, absPath); #else auto returnValue = LoadModule(request.mod, pathNrml); #endif #if defined(AURORA_IS_MODERNNT_DERIVED) // Still doesnt stop hotswaps, and idc // Glitching would require software to time the attack // ...requiring a certain degree of access (code execution) + effort AuWin32CloseHandle(mitigateTimeOfUse); #endif return returnValue; } static void *TryLoadModuleNTC(const AuString &path, const AuString &auDll, const AuString &genericDll, const ModuleLoadRequest &request, bool &fail) { AuString a = AuString(path) + AuString({ AuFS::kPathSplitter }) + AuString(auDll); AuString b = AuString(path) + AuString({ AuFS::kPathSplitter }) + AuString(genericDll); #if defined(AURORA_IS_MODERNNT_DERIVED) #if defined(AURORA_PLATFORM_WIN32) if (gRuntimeConfig.win32Config.bProcessCheckWinLdrForModNameFirst) { auto aW = AuLocale::ConvertFromUTF8(genericDll); auto bW = AuLocale::ConvertFromUTF8(auDll); auto pDLL = ::GetModuleHandleW(aW.c_str()); if (!pDLL) { pDLL = ::GetModuleHandleW(bW.c_str()); } if (pDLL) { gModuleHandles.insert(AuMakePair(request.mod, (void *)pDLL)); return pDLL; } } #endif AuString aAbs = AuString(path) + /*SOO save us pls*/ AuString({ AuFS::kPathSplitter }) + auDll + "."; AuString bAbs = AuString(path) + /*SOO save us pls*/ AuString({ AuFS::kPathSplitter }) + genericDll + "."; #endif if (AuIOFS::FileExists(a)) { return TryLoadModule(a, #if defined(AURORA_IS_MODERNNT_DERIVED) aAbs, #endif request, fail ); } if (AuIOFS::FileExists(b)) { return TryLoadModule(b, #if defined(AURORA_IS_MODERNNT_DERIVED) bAbs, #endif request, fail ); } return {}; } static void *TryLoadModule(const AuString &path, const AuString &auDll, const AuString &genericDll, const ModuleLoadRequest &request, bool &fail) { // TODO: need safe string container try { return TryLoadModuleNTC(path, auDll, genericDll, request, fail); } catch (...) { return {}; } } static void *TryLoadModule(EModulePath path, const AuString &auDll, const AuString &genericDll, const ModuleLoadRequest &request) { AuString pathA, pathB; AuList *pArrayPaths {}; switch (path) { case EModulePath::eClassPath: { pArrayPaths = &gClassPath; break; } case EModulePath::eModulePathCWD: { if (!Process::GetWorkingDirectory(pathA)) { return {}; } break; } case EModulePath::eProcessDirectory: { if (auto pProcPath = Process::GetProcessDirectory()) { pathA = *pProcPath; break; } else { return {}; } } case EModulePath::eModulePathSystemDir: { pathA = AuIOFS::GetSystemLibPath().value_or(AuString {}); pathB = AuIOFS::GetSystemLibPath2().value_or(AuString {}); if (pathA.empty()) { return {}; } break; } case EModulePath::eModulePathUserDir: { pathA = AuIOFS::GetUserLibPath().value_or(AuString {}); pathB = AuIOFS::GetUserLibPath2().value_or(AuString {}); if (pathA.empty()) { return {}; } break; } //case EModulePath::eOSSpecified: // // break; case EModulePath::eSpecified: break; } bool fail {}; if (pathA.size()) { if (auto pRet = TryLoadModule(pathA, auDll, genericDll, request, fail)) { return pRet; } if (fail) { return {}; } } if (pathB.size()) { if (auto pRet = TryLoadModule(pathB, auDll, genericDll, request, fail)) { return pRet; } if (fail) { return {}; } } if (pArrayPaths) { AU_LOCK_GLOBAL_GUARD(gSpinLock); for (const auto &dir : *pArrayPaths) { if (auto pRet = TryLoadModule(dir, auDll, genericDll, request, fail)) { return pRet; } if (fail) { return {}; } } } if (path == EModulePath::eSpecified && request.specifiedSearchPaths) { for (const auto &val : *request.specifiedSearchPaths) { if (auto pRet = TryLoadModule(val, auDll, genericDll, request, fail)) { return pRet; } if (fail) { return {}; } } } return {}; } AUKN_SYM bool LoadModule(const ModuleLoadRequest &request) { return bool(LoadModuleEx(request)); } AUKN_SYM void *LoadModuleEx(const ModuleLoadRequest &request) { AU_LOCK_GLOBAL_GUARD(gSpinLock); void *pFound {}; if (AuTryFind(gModuleHandles, request.mod, pFound)) { return pFound; } // TODO: need safe string container AuString base, au; const char *ext {}; try { base = AuString(request.mod); au = base + AuString(GetViewOfAuroraDLLSuffix()); ext = GetPlatformExt(Build::kCurrentPlatform); if (ext && !Build::kIsNtDerived) { base += ext; } if (request.version.size()) { au += AuString(".") + AuString(request.version); base += AuString(".") + AuString(request.version); } if (ext && Build::kIsNtDerived) { base += ext; } } catch (...) { // stupid unsafe strings SysPushErrorMemory(); return {}; } auto searchPath = request.searchPath ? request.searchPath : &kUserOverloadableSearchPath; for (EModulePath path : *searchPath) { if (auto pRet = TryLoadModule(path, au, base, request)) { return pRet; } } return {}; } AUKN_SYM AuMach GetProcAddressEx(void *pHandle, const AuRONString &symbol) { AuMach ret {}; #if defined(AURORA_IS_MODERNNT_DERIVED) if (!pGetProcAddress) { return {}; } if (pHandle) { ret = reinterpret_cast(pGetProcAddress(reinterpret_cast(pHandle), symbol.c_str())); } else { #if defined(AURORA_PLATFORM_WIN32) ret = reinterpret_cast(pGetProcAddress(::GetModuleHandleW(nullptr), symbol.c_str())); if (!ret) { HMODULE hHandle; if (GetModuleHandleExW(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, reinterpret_cast(&GetProcAddressEx), &hHandle)) { ret = reinterpret_cast(pGetProcAddress(hHandle, symbol.c_str())); } } if (!ret) #endif { AU_LOCK_GLOBAL_GUARD(gSpinLock); for (const auto &[string, hHandle] : gModuleHandles) { ret = reinterpret_cast(pGetProcAddress(reinterpret_cast(hHandle), symbol.c_str())); if (ret) { break; } } } } #else if (pHandle) { ret = reinterpret_cast(dlsym(pHandle, symbol.c_str())); } else { #if defined(RTLD_DEFAULT) ret = reinterpret_cast(dlsym(RTLD_DEFAULT, symbol.c_str())); if (!ret) #endif { AU_LOCK_GLOBAL_GUARD(gSpinLock); for (const auto &[string, hHandle] : gModuleHandles) { ret = reinterpret_cast(dlsym(hHandle, symbol.c_str())); if (ret) { break; } } } } #endif return ret; } AUKN_SYM void *GetProcHandle(const AuROString &name) { AU_LOCK_GLOBAL_GUARD(gSpinLock); void *pFound {}; if (!AuTryFind(gModuleHandles, name, pFound)) { SysPushErrorGen("Module {} is not loaded", name); return {}; } return pFound; } AUKN_SYM AuMach GetProcAddress(const AuROString &mod, const AuRONString &symbol) { if (mod.empty()) { return GetProcAddressEx(nullptr, symbol); } if (auto pHandle = GetProcHandle(mod)) { auto ret = GetProcAddressEx(pHandle, symbol); if (!ret) { SysPushErrorGen("Couldn't resolve symbol in module {} of mangled name '{}'", mod, symbol); } return ret; } return {}; } AUKN_SYM void Exit(AuUInt32 exitcode) { Aurora::RuntimeShutdown(); #if defined(AURORA_IS_MODERNNT_DERIVED) TerminateProcess(GetCurrentProcess(), exitcode); Win32Terminate(); #elif defined(AURORA_IS_POSIX_DERIVED) // TODO: if main thread, long jump back, and return exit code ::kill(getpgrp(), SIGKILL); while (true) { ::sched_yield(); } #else // ??? *(AuUInt32 *)0 = exitcode; *(AuUInt32 *)0xFFFF = exitcode; *(AuUInt32 *)0xFFFFFFFF = exitcode; #endif } static void PreloadDLLsDoOnce(const AuList &dirs) { AU_DEBUG_MEMCRUNCH; AuList cookieList; #if defined(AURORA_PLATFORM_WIN32) for (const auto &dir : dirs) { void *pCookie {}; // Yay I love required nevis era plugin load fighting // MSDN still states this function should be dynamically loaded for some KB that probably wont work on Vista or 7. // Also, GNU LibC - the architectures ld linker, cannot keep track of preload directory lists. // So for older Win32 targets, and POSIX, we have to.... if (pRemoveDllDirectory) { if (pAddDllDirectory) { pCookie = pAddDllDirectory(AuLocale::ConvertFromUTF8(dir).c_str()); } } cookieList.push_back(pCookie); } #endif // ...just try to probe every file manually. // ...and later on, try to load each file again manually by full real path // This makes sure we have future basic dependencies loaded of LoadModuleXXX calls, at the very least, we hope. for (const auto &dir : dirs) { AuList files; AuList sharedObjects; if (!AuFS::FilesInDirectory(dir, files)) { SysPushErrorIO("Couldn't readdir: {}", dir); continue; } auto endingPattern = GetViewOfAuroraDLLSuffix(); auto endingAlt = GetPlatformExt(Build::kCurrentPlatform); for (const auto &file : files) { if (!(AuEndsWith(file, endingPattern) || AuEndsWith(file, endingAlt))) { continue; } sharedObjects.push_back(file); } { AuUInt uSuccess {}; do { uSuccess = 0; for (auto itr = sharedObjects.begin(); itr != sharedObjects.end(); ) { auto &sharedDLL = *itr; AuString a = dir + AuString({ AuFS::kPathSplitter }) + sharedDLL; #if defined(AURORA_IS_MODERNNT_DERIVED) AuString aAbs = dir + AuString({ AuFS::kPathSplitter }) + sharedDLL + "."; #endif #if defined(AURORA_IS_MODERNNT_DERIVED) if (GetProcHandle(aAbs)) #else if (GetProcHandle(a)) #endif { itr++; continue; } auto modName = GetModuleNameFromFileName(sharedDLL); //AuLogDbg("[attempt] Loading shared object: {} (path: {})", modName, sharedDLL); //This got really spammy some time //Best just keep dynamic loads as a singular verbose, "hey dipshit, this file actually managed to get into our address space" //... bool fail {}; ModuleLoadRequest request { AuList{ dir }, modName }; if (TryLoadModule(a, #if defined(AURORA_IS_MODERNNT_DERIVED) aAbs, #endif request, fail )) { if (!fail) { //..here AuLogDbg("[attempt] Loaded shared object: {} (path: {})", modName, sharedDLL); //get hacked retard uSuccess++; itr = sharedObjects.erase(itr); } else { itr++; } } else { itr++; } } } while (uSuccess); } } #if defined(AURORA_PLATFORM_WIN32) for (const auto pCookie : AuExchange(cookieList, {})) { if (pRemoveDllDirectory) { if (pCookie) { pRemoveDllDirectory(pCookie); } } } #endif } static void PreloadDLLs(const AuROString &dir) { AU_DEBUG_MEMCRUNCH; if (gRuntimeConfig.processConfig.bAlwaysPreloadEntireClassPath) { PreloadDLLsDoOnce(gClassPath); } else { PreloadDLLsDoOnce({ AuString(dir) }); } } AUKN_SYM bool SetBinaryClassPath(const AuList &list, AuOptional optBoolPreloadAll) { AU_DEBUG_MEMCRUNCH; AU_LOCK_GLOBAL_GUARD(gSpinLock); AuExchange(gClassPath, list); if (((optBoolPreloadAll.ValueOr(false) && gRuntimeConfig.processConfig.bEnablePreload)) || (gRuntimeConfig.processConfig.bForcePreload)) { if (gRuntimeConfig.processConfig.bAlwaysPreloadEntireClassPath) { PreloadDLLsDoOnce(gClassPath); } else { PreloadDLLsDoOnce(list); } } return true; } AUKN_SYM bool AddBinaryClassPath(const AuROString &dir, AuOptional optBoolPreloadAll) { AU_LOCK_GLOBAL_GUARD(gSpinLock); if (!AuTryInsert(gClassPath, AuString(dir))) { return false; } if (((optBoolPreloadAll.ValueOr(false) && gRuntimeConfig.processConfig.bEnablePreload)) || (gRuntimeConfig.processConfig.bForcePreload)) { PreloadDLLs(dir); } return true; } AUKN_SYM AuList GetBinaryClassPath() { try { AU_LOCK_GLOBAL_GUARD(gSpinLock); return gClassPath; } catch (...) { SysPushErrorMemory(); return {}; } } void InitProcess() { // TODO (reece): test if self is signed -> xref kIsMainSigned InitProcessMap(); #if defined(AURORA_IS_MODERNNT_DERIVED) LoadProcessSectionViewSymbol(); #endif } void DeinitProcess() { DeinitProcessMap(); } }