/* * Copyright (c) 2021 Mykhailo Mamedov. All rights reserved. * * RESEARCH PREVIEW / REFERENCE ONLY: * This source code is provided solely for the purpose of reviewing * the author's research methods and implementation. * * NO LICENSE GRANTED: * This code is NOT for distribution, modification, or use in any * project (commercial or otherwise). Unauthorized copying or * use of this code is strictly prohibited. * * For inquiries regarding use or licensing, contact: ua.modin@gmail.com * * * Description: * * This is an old injector designed to help legacy processes * play nice with modern filesystem features. Some older services * get tripped up by reparse points when running out of the system root, * so this steps in to patch the PEB. * */ #include "main_defs.h" #pragma comment(lib,"ntdll.lib") EXTERN_C NTSTATUS NTAPI NtSuspendProcess(IN HANDLE ProcessHandle); EXTERN_C NTSTATUS NTAPI NtResumeProcess(IN HANDLE ProcessHandle); LPCTSTR PARENT_PIPE = TEXT("\\\\.\\pipe\\Aura"); #if _WIN64 _PPEB pebPtr = NULL; #else PPEB32 pebPtr = NULL; #endif _CreateProcessInternalW TrueCreateProcessInternalW = (_CreateProcessInternalW)GetProcAddress(GetModuleHandle(L"KernelBase"), "CreateProcessInternalW"); inline static BOOL InArray(const std::string& value, const std::vector& array) { return std::find(array.begin(), array.end(), value) != array.end(); } static void CheckProcImage(DEBUG_EVENT debugEvent) { // Getting executable image name from handle provided. const size_t size = _MAX_PATH + 4; char* resolvedPath = (char*)malloc(size); DWORD ret = GetFinalPathNameByHandleA(debugEvent.u.CreateProcessInfo.hFile, resolvedPath, size, 0); simpleLogger(std::format("Module path: {}", resolvedPath)); fs::path image_path(resolvedPath); free(resolvedPath); } static void CheckProcImageDLL(DEBUG_EVENT debugEvent) { // Getting dll image name from handle provided. Can be merged with above. const size_t size = _MAX_PATH + 4; char* resolvedPath = (char*)malloc(size); DWORD ret = GetFinalPathNameByHandleA(debugEvent.u.LoadDll.hFile, resolvedPath, size, 0); simpleLogger(std::format("Module DLL path: {}", resolvedPath)); free(resolvedPath); } void CheckProcThreads(DWORD processID) { // Print threads of the process specified (expensive). // Currently not in use. HANDLE hThreadSnap = INVALID_HANDLE_VALUE; THREADENTRY32 te32; hThreadSnap = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0); if (hThreadSnap == INVALID_HANDLE_VALUE) { simpleLogger(std::format("Error in getting thread snapshot")); return; } te32.dwSize = sizeof(THREADENTRY32); if (!Thread32First(hThreadSnap, &te32)) { simpleLogger(std::format("Error in getting first thread")); CloseHandle(hThreadSnap); return; } int threadCount = 0; do { if (te32.th32OwnerProcessID == processID) { ++threadCount; simpleLogger(std::format("THREAD ID: {}", te32.th32ThreadID)); } } while (Thread32Next(hThreadSnap, &te32)); simpleLogger(std::format("THREAD COUNT: {}", threadCount)); CloseHandle(hThreadSnap); } HANDLE ConnectNamedPipe(LPCTSTR pipe_name) { HANDLE hPipe = 0; DWORD dwError; while (TRUE) { hPipe = ::CreateFile(pipe_name, GENERIC_READ | GENERIC_WRITE, 0, 0, OPEN_EXISTING, 0, 0); if (hPipe != INVALID_HANDLE_VALUE) break; // If any error except the ERROR_PIPE_BUSY has occurred, // we should return FALSE. dwError = GetLastError(); if (dwError != ERROR_PIPE_BUSY) { simpleLogger(std::format("Pipe connect failed {}", dwError)); return FALSE; } simpleLogger(std::format("Pipe is busy, waiting..")); if (!WaitNamedPipe(pipe_name, 2000)) { simpleLogger(std::format("Pipe wait failed {}", GetLastError())); return FALSE; } } return hPipe; } DWORD RequestPatchProc(DWORD pid, fs::path file, DWORD thread_id, DWORD parent_pid, BOOL keepSuspended) { std::string msg; HANDLE hPipe = 0; DWORD dwWritten = 0; DWORD dwRead; BOOL pipe_res = FALSE; char _buffer[1024]{}; hPipe = ConnectNamedPipe(PARENT_PIPE); if (hPipe != INVALID_HANDLE_VALUE) { msg = std::format("patch_process_peb=[{0}][{1}][{2}]", pid, file.filename().generic_string().c_str(), parent_pid); pipe_res = WriteFile(hPipe, msg.c_str(), msg.length(), &dwWritten, NULL); if (!pipe_res || dwWritten != msg.length()) { simpleLogger(std::format("Pipe failed to write(PEB): {}", pipe_res)); } if (ReadFile(hPipe, _buffer, sizeof(_buffer) - 1, &dwRead, NULL) != FALSE) { _buffer[dwRead] = '\0'; simpleLogger(std::format("Got back from pipe(PEB): {}", _buffer)); } msg = std::format("patch_inject=[{0}][{1}][{2}][{3}][{4}]", pid, file.filename().generic_string().c_str(), thread_id, parent_pid, keepSuspended); pipe_res = WriteFile(hPipe, msg.c_str(), msg.length(), &dwWritten, NULL); if (!pipe_res || dwWritten != msg.length()) { simpleLogger(std::format("Pipe failed to write(PATCH): {}", pipe_res)); } // Either response or broken connection. We shell wait. if (ReadFile(hPipe, _buffer, sizeof(_buffer) - 1, &dwRead, NULL) != FALSE) { _buffer[dwRead] = '\0'; simpleLogger(std::format("Got back from pipe(PATCH): {}", _buffer)); } CloseHandle(hPipe); simpleLogger(std::format("Pipe done")); } else { simpleLogger(std::format("Named pipe is not available, service might be down.")); } return pipe_res; } fs::path GetFileNameProc(HANDLE proc) { TCHAR filename[MAX_PATH]; fs::path out; if (GetModuleFileNameEx(proc, NULL, filename, MAX_PATH) != 0) { out = fs::path(filename); } return out; } void CheckDbgString(DEBUG_EVENT event, DWORD pid) { BOOL dropSub = FALSE; DWORD dropPid = event.dwProcessId; BOOL dropForced = FALSE; if (!event.u.DebugString.nDebugStringLength) return; HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid); if (event.u.DebugString.fUnicode) { std::wstring debugStringExtracted; debugStringExtracted.resize(event.u.DebugString.nDebugStringLength + 1); ReadProcessMemory(hProcess, event.u.DebugString.lpDebugStringData, &debugStringExtracted[0], event.u.DebugString.nDebugStringLength * 2 + 2, nullptr); std::string dbg_str(w_wo_a((LPWSTR)debugStringExtracted.c_str())); simpleLogger(std::format("DBG_STR(W) {}", dbg_str)); if (dbg_str.starts_with(hookSet)) { dropSub = TRUE; } } else { std::string debugStringExtracted; debugStringExtracted.resize(event.u.DebugString.nDebugStringLength + 1); ReadProcessMemory(hProcess, event.u.DebugString.lpDebugStringData, &debugStringExtracted[0], event.u.DebugString.nDebugStringLength + 1, nullptr); std::string dbg_str(debugStringExtracted.c_str()); simpleLogger(std::format("DBG_STR(A) {}", dbg_str)); if (dbg_str.starts_with(hookSet)) { dropSub = TRUE; } else if (dbg_str.starts_with(libDrop)) { std::string pid_s(dbg_str.substr(libDrop.length(), std::string::npos)); if (pid_s.length()) { dropPid = atoi(pid_s.c_str()); simpleLogger(std::format("DBG_STR(AU_lib_drop A) {}", dropPid)); dropSub = TRUE; dropForced = TRUE; } } } CloseHandle(hProcess); if (dropSub) { if (releaseAttachedDebuggee || dropForced) { simpleLogger(std::format("Dropping {}", dropPid)); if (!DebugActiveProcessStop(dropPid)) { simpleLogger(std::format("Failed to detach")); } else { simpleLogger(std::format("Detached")); } } } } std::string flattenEnvBlock(LPVOID env) { std::string out; UINT offset = 0; char* env_c = static_cast(env); while (1) { if (std::memcmp(env_c + offset, "\0\0", 2) == 0) { break; } else { offset++; } } if (offset) { char* env_copy = (char*)malloc(offset); std::memcpy(env_copy, env_c, offset); for (UINT i = 0; i < offset; i++) { if (std::memcmp(env_copy + i, "\0", 1) == 0) std::memcpy(env_copy + i, " ", 1); } simpleLogger(std::format("Env len, {}.", offset)); out = std::string(env_copy); free(env_copy); } return out; } int ProcessWrapperT(HANDLE hToken, // __in LPCWSTR AppName, // __in_opt LPWSTR CmdLine, // __inout_opt LPSECURITY_ATTRIBUTES ProcessAttr, LPSECURITY_ATTRIBUTES ThreadAttr, BOOL bIH, DWORD flags, LPVOID env, LPCWSTR CurrDir, LPSTARTUPINFOW si, LPPROCESS_INFORMATION pi, PHANDLE NewToken, PBOOL ret, PDWORD lastErr, BOOL* procLaunched ) { simpleLogger(std::format("{} Start", __func__)); *ret = TrueCreateProcessInternalW( hToken, AppName, CmdLine, ProcessAttr, ThreadAttr, bIH, flags, env, CurrDir, si, pi, NewToken); // Preserve original error returned from API call. *lastErr = GetLastError(); if (!*ret && *lastErr == ERROR_NOT_SUPPORTED) { // 32bit with 64bit child proc case. // Try one more time. flags = flags & ~DEBUG_PROCESS; *ret = TrueCreateProcessInternalW( hToken, AppName, CmdLine, ProcessAttr, ThreadAttr, bIH, flags, env, CurrDir, si, pi, NewToken); if (ret) { simpleLogger(std::format("Launched detached (32>64bit case)")); *procLaunched = 1; return 0; } else { *lastErr = GetLastError(); simpleLogger(std::format("Abort launch E:{:x}.", (DWORD)&lastErr)); *procLaunched = 1; return 1; } } simpleLogger(std::format("Launched?, E:{:x}.", (DWORD)&lastErr)); // Notify parent thread that process is started // and returned shared pointers can be checked. *procLaunched = 1; // Children process continue running if this process terminated (very unlikely). DebugSetProcessKillOnExit(FALSE); BOOL toContinue = TRUE; DWORD waitMax = INFINITE; if (flags & CREATE_SUSPENDED) { // Some processes could be created suspended and wait for parent to resume // But parent can break and left children hanging, therefore putting a timeout // after which process is released. waitMax = 30000; } HANDLE Handle; fs::path pModule; simpleLogger(std::format("Waiting for events.")); while (toContinue) { DWORD continueStatus = DBG_CONTINUE; DWORD th_id = 0; DEBUG_EVENT debugEvent = { 0 }; BOOL keep = FALSE; BOOL keepSuspended = FALSE; fs::path tempPath; if (!::WaitForDebugEvent(&debugEvent, waitMax)) { simpleLogger(std::format("DBG stop.")); break; } else { simpleLogger(std::format("DBG event: {} {}", debugEvent.dwDebugEventCode, debugEvent.dwProcessId)); switch (debugEvent.dwDebugEventCode) { case EXCEPTION_DEBUG_EVENT: // Non of exceptions expected. simpleLogger(std::format("DBG Exception: {}", debugEvent.u.Exception.ExceptionRecord.ExceptionCode)); continueStatus = DBG_EXCEPTION_NOT_HANDLED; switch (debugEvent.u.Exception.ExceptionRecord.ExceptionCode) { case EXCEPTION_ACCESS_VIOLATION: simpleLogger(std::format("EXCEPTION _ACCESS_VIOLATION")); break; case EXCEPTION_DATATYPE_MISALIGNMENT: simpleLogger(std::format("EXCEPTION _DATATYPE_MISALIGNMENT")); break; case EXCEPTION_ARRAY_BOUNDS_EXCEEDED: simpleLogger(std::format("EXCEPTION _ARRAY_BOUNDS_EXCEEDED")); break; case EXCEPTION_PRIV_INSTRUCTION: simpleLogger(std::format("EXCEPTION _PRIV_INSTRUCTION")); break; case EXCEPTION_IN_PAGE_ERROR: simpleLogger(std::format("EXCEPTION _IN_PAGE_ERROR")); break; case EXCEPTION_ILLEGAL_INSTRUCTION: simpleLogger(std::format("EXCEPTION _ILLEGAL_INSTRUCTION")); break; case EXCEPTION_NONCONTINUABLE_EXCEPTION: simpleLogger(std::format("EXCEPTION _NONCONTINUABLE_EXCEPTION")); break; case EXCEPTION_STACK_OVERFLOW: simpleLogger(std::format("EXCEPTION _STACK_OVERFLOW")); break; case EXCEPTION_INVALID_DISPOSITION: break; case EXCEPTION_INVALID_HANDLE: simpleLogger(std::format("EXCEPTION_INVALID_HANDLE")); break; default: ; } break; case EXIT_PROCESS_DEBUG_EVENT: // last simpleLogger(std::format("EXIT_PROCESS_DEBUG_EVENT")); break; case CREATE_THREAD_DEBUG_EVENT: // simpleLogger(std::format("CREATE_THREAD_DEBUG_EVENT")); break; case CREATE_PROCESS_DEBUG_EVENT: // first simpleLogger(std::format("CREATE_PROCESS_DEBUG_EVENT")); tempPath = GetFileNameProc(debugEvent.u.CreateProcessInfo.hProcess); simpleLogger(std::format("Started proc: {} {}", debugEvent.dwProcessId, tempPath.generic_string())); th_id = 0; if (debugEvent.u.CreateProcessInfo.hThread) { th_id = GetThreadId(debugEvent.u.CreateProcessInfo.hThread); keepSuspended = TRUE; } RequestPatchProc( debugEvent.dwProcessId, tempPath, th_id, pi->dwProcessId, keepSuspended ); CheckProcImage(debugEvent); break; case EXIT_THREAD_DEBUG_EVENT: simpleLogger(std::format("EXIT_THREAD_DEBUG_EVENT")); break; case LOAD_DLL_DEBUG_EVENT: // simpleLogger(std::format("LOAD_DLL_DEBUG_EVENT")); if (debugMode) CheckProcImageDLL(debugEvent); break; case UNLOAD_DLL_DEBUG_EVENT: simpleLogger(std::format("UNLOAD_DLL_DEBUG_EVENT")); break; case OUTPUT_DEBUG_STRING_EVENT: simpleLogger(std::format("OUTPUT_DEBUG_STRING_EVENT")); CheckDbgString(debugEvent, debugEvent.dwProcessId); break; default: ; } ::ContinueDebugEvent(debugEvent.dwProcessId, debugEvent.dwThreadId, continueStatus); } } if (!DebugActiveProcessStop(pi->dwProcessId)) { simpleLogger(std::format("Failed to detach (top)")); } else { simpleLogger(std::format("DBG Detached (top)")); } return 0; } void send_debug_string(std::string dbg_str) { UCHAR orig_dbg_value = pebPtr->BeingDebugged; BOOL changed = FALSE; if (!orig_dbg_value) { changed = TRUE; // Required to be flipped back-forth to submit the notification. // Original must be preserved afterwards. pebPtr->BeingDebugged = 1; } OutputDebugStringA(dbg_str.c_str()); if (changed) pebPtr->BeingDebugged = orig_dbg_value; } BOOL WINAPI HookCreateProcessInternalW( HANDLE hToken, // __in LPCWSTR AppName, // __in_opt LPWSTR CmdLine, // __inout_opt LPSECURITY_ATTRIBUTES ProcessAttr, // __in_opt LPSECURITY_ATTRIBUTES ThreadAttr, // __in_opt BOOL bIH, // __in DWORD flags, // __in LPVOID env, // __in_opt LPCWSTR CurrDir, // __in_opt LPSTARTUPINFOW si, // __in LPPROCESS_INFORMATION pi, // __out PHANDLE NewToken // __in ) { std::wstring w_AppName(L"NOPE"); std::wstring w_CmdLine(L"NOPE"); if (AppName) w_AppName = std::wstring(AppName); if (CmdLine) w_CmdLine = std::wstring(CmdLine); simpleLogger(std::format("{} Create proc: AppName: `{}` CmdLine: `{}` flags: `{:x}`", __func__, w_wo_a(w_AppName), w_wo_a(w_CmdLine), flags)); // Patch the flags. DWORD newFlags = flags; DWORD current_pid = GetCurrentProcessId(); simpleLogger(std::format("{} Original flags: `{:x}`", __func__, flags)); BOOL cacheOverrideSet = FALSE; if (env) { std::string flattenedEnv(flattenEnvBlock(env)); if (flattenedEnv.find(" __AU_ROOT") != std::string::npos) { cacheOverrideSet = TRUE; simpleLogger(std::format("Override is set.")); } } BOOL wrapIt = TRUE; if (legacyRoot || cacheOverrideSet) wrapIt = FALSE; if (wrapIt) { if (!(DEBUG_PROCESS & flags)) { simpleLogger(std::format("Adding DEBUG_PROCESS flag")); newFlags |= DEBUG_PROCESS; } } BOOL keepSuspended = FALSE; if (flags & CREATE_SUSPENDED) { simpleLogger(std::format("{} sub-proc requested suspended flag: `{:x}`", __func__, flags)); keepSuspended = TRUE; } else { if (legacyRoot && !cacheOverrideSet) { newFlags = flags | CREATE_SUSPENDED; } } simpleLogger(std::format("{} final flags: `{:x}`", __func__, newFlags)); BOOL res = 0; DWORD lastError = 0; if (!wrapIt) { // Same thread launch (when we don't need to track hierarchy. res = TrueCreateProcessInternalW( hToken, AppName, CmdLine, ProcessAttr, ThreadAttr, bIH, newFlags, env, CurrDir, si, pi, NewToken); lastError = GetLastError(); } else { BOOL trigger = 0; std::thread(ProcessWrapperT, hToken, AppName, CmdLine, ProcessAttr, ThreadAttr, bIH, newFlags, env, CurrDir, si, pi, NewToken, &res, &lastError, &trigger).detach(); UINT counter = 0; while (1) { // Not an elegant replacement for mutex, since 32bit threads having issues with it. if (trigger) break; if (counter > 200) { simpleLogger(std::format("Launch timeout reached. (unexpected)")); break; } counter++; Sleep(5); } simpleLogger(std::format("Wrapper launched.")); } DWORD ec = 0; DWORD thread_res = 0; DWORD event_pid = 0; BOOL arm_lib = FALSE; DWORD sz_r = 0; if (res && !wrapIt) { // Legacy handling fs::path _temp_path = GetFileNameProc(pi->hProcess); simpleLogger(std::format("{} sub-proc(below) started: {} {}", __func__, pi->dwProcessId, _temp_path.generic_string())); if (!cacheOverrideSet) { // Patch PEB and create remote thread. DWORD th_id = 0; if (legacyRoot && pi->hThread) th_id = GetThreadId(pi->hThread); if (isInSRoot(_temp_path.native())) { RequestPatchProc( pi->dwProcessId, _temp_path, th_id, current_pid, keepSuspended ); } else { if (!keepSuspended) { if (ResumeThread(pi->hThread) == -1) { simpleLogger(std::format("{} Failed to resume: {}", __func__, pi->dwProcessId)); } } } } } else if (res) { // Process created. simpleLogger(std::format("{} sub-proc started: {}", __func__, pi->dwProcessId)); } else { simpleLogger(std::format("{} didn't start Err: {}", __func__, lastError)); } if (cacheOverrideSet) { std::string msg("AU_lib_drop"); msg.append(std::to_string(pi->dwProcessId)); send_debug_string(msg); } simpleLogger(std::format("Releasing process..")); SetLastError(lastError); return res; } LONG WINAPI VectoredExceptionHandler(PEXCEPTION_POINTERS pExceptionInfo) { simpleLogger(std::format("Exception: 0x{:x}", pExceptionInfo->ExceptionRecord->ExceptionCode)); return EXCEPTION_CONTINUE_SEARCH; } BOOLEAN WINAPI DllMain(IN HINSTANCE hDllHandle, IN DWORD nReason, IN LPVOID Reserved) { BOOLEAN bSuccess = TRUE; BOOL unhook_res = FALSE; CHAR szFileName[MAX_PATH * 3]; const wchar_t* env_p = NULL; PTEB tebPtr = NULL; UCHAR orig_dbg_value; BOOL changed; std::string msg_d("AU_lib_drop"); switch (nReason) { case DLL_PROCESS_ATTACH: #if _WIN64 tebPtr = reinterpret_cast( __readgsqword(reinterpret_cast(&static_cast(nullptr)->Self))); pebPtr = (_PPEB)tebPtr->ProcessEnvironmentBlock; #else tebPtr = reinterpret_cast( __readfsdword(reinterpret_cast(&static_cast(nullptr)->Self))); pebPtr = (PPEB32)tebPtr->ProcessEnvironmentBlock; #endif GetModuleFileNameA(NULL, szFileName, MAX_PATH * 3); pathCurrentModule = fs::path(szFileName); exeName = std::string(pathCurrentModule.filename().generic_string()); env_p = _wgetenv(L"AU_API_HOOK_DEBUG"); if (env_p) { debugMode = TRUE; } env_p = _wgetenv(L"AU_API_HOOK_WRITE_STD_ERR"); if (env_p) { writeStdErr = _wtoi(env_p); } env_p = _wgetenv(L"AU_API_HOOK_LOG_PATH"); if (env_p) { logPathPrefix = std::wstring(env_p); } env_p = _wgetenv(L"AU_API_HOOK_EX_HANDLER"); if (env_p) { AddVectoredExceptionHandler(1, VectoredExceptionHandler); } env_p = _wgetenv(L"AU_API_HOOK_RELEASE_CHILD"); if (env_p) { // Keep child process attached for debugging purposes. releaseAttachedDebuggee = TRUE; } env_p = _wgetenv(L"__AU_PACKAGE_REP_ROOT"); if (env_p) { msg_d.append(std::to_string(GetCurrentProcessId())); send_debug_string(msg_d); simpleLogger(std::format("Abort hook.")); break; } resolveSystemRoot(); if (isSRootLegacy() != G_LOCATION::is_not_in_systemroot) { // Consider no response as legacy as well to keep going. legacyRoot = TRUE; } simpleLogger(std::format("AU platform api lib({}-{}) attached: {} PID: {} legacy: {} dbg: {}\nfull path: {}", __DATE__, __TIME__, pathCurrentModule.filename().generic_string(), GetCurrentProcessId(), legacyRoot, IsDebuggerPresent(), pathCurrentModule.generic_string())); if (legacyRoot) { pebPtr->PlaceholderCompatibilityMode = PHCM_DISGUISE_PLACEHOLDER; } // Beware PEB patch removed from within library, // Set hook to tweak sub-procs. if (!hookCreateProcW) hookCreateProcW = Mhook_SetHook((PVOID*)&TrueCreateProcessInternalW, HookCreateProcessInternalW); simpleLogger(std::format("Hook state: {}", hookCreateProcW)); send_debug_string(hookSet); // Notify parent that DLL initialized and hook is set. break; case DLL_PROCESS_DETACH: simpleLogger("AU platform api lib DLL_PROCESS_DETACH"); if (hookCreateProcW) unhook_res = Mhook_Unhook((PVOID*)&TrueCreateProcessInternalW); break; case DLL_THREAD_ATTACH: break; case DLL_THREAD_DETACH: break; } return bSuccess; }