Running Acronis Backup in Windows PE is pretty easy nowadays thanks to the included support for Windows PE, but there’s one major drawback: Closing Acronis Backup automatically reboots Windows PE. While this propably won’t bug you when your Windows PE image doesn’t contain anything else it really sucks when you use it as a universal recovery/repair OS. I don’t unterstand why Acronis even added this „feature“, they could have just added WPEINIT and Acronis to WINPESHL.INI and Windows PE would reboot when Acronis is closed. Happily I found a way to disable that behavior.
I’ve never done any C++ coding before, special thanks to Patrick Hahn for his articles about API Hooking and DLL injection and Tsuda Kageyu for sharing his MinHook library.
Finding the API Acronis uses to reboot Windows PE
I used the profiler that’s built into Dependency Walker to trace all API calls made by Acronis Backup. To do so you have open TrueImage.exe
in Dependency Walker (ignore any errors) and start profiling by pressing F7
.
Close Acronis and watch the output, before rebooting we can see it calls NtShutdownSystem
from NTDLL.DLL
– this is the API call we need to disable!
Hooking/Overriding NtShutdownSystem
For simplicity I chose to build a small DLL using Visual C++ with the only purpose to replace NtShutdownSystem with a dummy method using MinHook. Please note that this is my first C++ application ever, so there’s propably a lot of room for improvements – feel free to to submit any better solutions:
#include <Windows.h> #include <iostream> #include <iomanip> #include "MinHook.h" typedef VOID(WINAPI *INITIATESHUTDOWN)(LPTSTR, LPTSTR, DWORD, DWORD, DWORD); typedef enum _SHUTDOWN_ACTION { ShutdownNoReboot, ShutdownReboot, ShutdownPowerOff } SHUTDOWN_ACTION, *PSHUTDOWN_ACTION; typedef VOID(WINAPI *NTSHUTDOWNSYSTEM)(SHUTDOWN_ACTION Action); NTSHUTDOWNSYSTEM fpNtShutdownSystem = NULL; VOID WINAPI DetourNtShutdownSystem(SHUTDOWN_ACTION Action) { //MessageBoxA(NULL, "NtShutdownSysten intercepted!", "NtShutdownSystemOverride", MB_OK | MB_ICONINFORMATION); } BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) { switch (ul_reason_for_call) { case DLL_PROCESS_ATTACH: // Initialize MinHook if (MH_Initialize() != MH_OK) { MessageBoxA(nullptr, "Failed to initialize MinHook.", "NtShutdownSystemOverride", MB_OK | MB_ICONERROR); } else { // Load NTDLL.DLL HINSTANCE hNTDLL = LoadLibraryA("NTDLL.DLL"); if (hNTDLL) { // Create NtShutdownSystem Hook LPVOID pfna = GetProcAddress(hNTDLL, "NtShutdownSystem"); if (MH_CreateHook(pfna, &DetourNtShutdownSystem, reinterpret_cast<void**>((LPVOID)&fpNtShutdownSystem)) != MH_OK) { MessageBoxA(NULL, "Failed to create NtShutdownSystem hooked.", "NtShutdownSystemOverride", MB_OK | MB_ICONERROR); } else { // Enable NtShutdownSystem Hook if (MH_EnableHook(MH_ALL_HOOKS) != MH_OK) { MessageBoxA(NULL, "Failed to enable NtShutdownSystem hook!", "NtShutdownSystemOverride", MB_OK | MB_ICONERROR); } } } else { MessageBoxA(NULL, "Couldn't load NTDLL.DLL!", "NtShutdownSystemOverride", MB_OK | MB_ICONERROR); } } break; case DLL_THREAD_ATTACH: break; case DLL_THREAD_DETACH: break; case DLL_PROCESS_DETACH: MH_DisableHook(MH_ALL_HOOKS); MH_Uninitialize(); break; } return TRUE; }To inject this DLL into Acronis Backup I’m using the injector from Patrick Hahn, I only modified it to run Acronis so I don’t have to run it and find the PID myself:
#include <Windows.h> #include <iostream> void Usage() { std::cout << "Usage: DLLInjector.exe [Executable] [DLL]\n"; } void EnableDebugPrivileges() { HANDLE hToken; LUID SeDebugNameValue; TOKEN_PRIVILEGES TokenPrivileges; if (OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken)) { if (LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &SeDebugNameValue)) { TokenPrivileges.PrivilegeCount = 1; TokenPrivileges.Privileges[0].Luid = SeDebugNameValue; TokenPrivileges.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; if (AdjustTokenPrivileges(hToken, FALSE, &TokenPrivileges, sizeof(TOKEN_PRIVILEGES), NULL, NULL)) { CloseHandle(hToken); } else { CloseHandle(hToken); throw std::runtime_error("Couldn't adjust token privileges!"); } } else { CloseHandle(hToken); throw std::runtime_error("Couldn't look up privilege value!"); } } else { throw std::runtime_error("Couldn't open process token!"); } } int LaunchExecutable(LPCWSTR lpApplicationName) { STARTUPINFO sInfo; PROCESS_INFORMATION pInfo; int pid = NULL; ZeroMemory(&sInfo, sizeof(sInfo)); sInfo.cb = sizeof(sInfo); ZeroMemory(&pInfo, sizeof(pInfo)); if (CreateProcess(lpApplicationName, NULL, NULL, NULL, false, CREATE_NEW_CONSOLE, NULL, NULL, &sInfo, &pInfo)) { pid = pInfo.dwProcessId; CloseHandle(pInfo.hThread); CloseHandle(pInfo.hProcess); } return pid; } wchar_t *ConvertCharArrayToLPCWSTR(const char* charArray) { wchar_t* wString = new wchar_t[4096]; MultiByteToWideChar(CP_ACP, 0, charArray, -1, wString, 4096); return wString; } int main(int argc, char *argv[]) { if (argc != 3) { Usage(); return EXIT_FAILURE; } // Enable Debug Privileges try { EnableDebugPrivileges(); } catch (std::runtime_error& e) { std::cerr << e.what() << "\n"; return EXIT_FAILURE; } // Launch EXE int pidTarget = LaunchExecutable(ConvertCharArrayToLPCWSTR(argv[1])); if (pidTarget != NULL) { std::cout << "Target PID: " << pidTarget << "\n"; std::string dll = argv[2]; HANDLE remoteProcessHandle = OpenProcess(PROCESS_ALL_ACCESS, false, pidTarget); if (remoteProcessHandle == nullptr) { int error = GetLastError(); std::cout << "Failed to open process. (Error " << error << ")\n"; return EXIT_FAILURE; } // Allocate memory in the target's address space void* pathmem = VirtualAllocEx(remoteProcessHandle, nullptr, dll.size() + 1, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); if (pathmem == nullptr) { int error = GetLastError(); std::cout << "Failed to allocate memory. (Error " << error << ")\n"; std::cin.ignore(std::cin.rdbuf()->in_avail()); return EXIT_FAILURE; } // Write the path of the dll in the target's address space if (!WriteProcessMemory(remoteProcessHandle, pathmem, dll.c_str(), dll.size() + 1, nullptr)) { int error = GetLastError(); std::cout << "Failed to write into target's memory. (Error " << error << ")\n"; std::cin.ignore(std::cin.rdbuf()->in_avail()); return EXIT_FAILURE; } // Create a remote thread in the other process' address space HANDLE remoteThreadHandle = CreateRemoteThread(remoteProcessHandle, nullptr, 0, (LPTHREAD_START_ROUTINE)GetProcAddress(GetModuleHandleA("kernel32.dll"), "LoadLibraryA"), pathmem, CREATE_SUSPENDED, nullptr); if (remoteThreadHandle == nullptr) { int error = GetLastError(); std::cout << "Failed to run the thread. (Error " << error << ")\n"; std::cin.ignore(std::cin.rdbuf()->in_avail()); return EXIT_FAILURE; } // Let the process run ResumeThread(remoteThreadHandle); // Wait for it WaitForSingleObject(remoteThreadHandle, INFINITE); std::cout << "DLL successfully injected!\n"; } else { std::cout << "Error launching taget application.\n"; return EXIT_FAILURE; } return EXIT_SUCCESS; }Usage
DLLInjector.exe [Executable] [DLL]
A quick sample on how to run Acronis using the injector:
@ECHO OFF NET START MMS IF "%PROCESSOR_ARCHITECTURE%"=="AMD64" DLLInjector_x64.exe "X:\Program Files\Acronis\BackupAndRecovery\TrueImage.exe" "NtShutdownSystemOverride_x64.dll" IF NOT "%PROCESSOR_ARCHITECTURE%"=="AMD64" DLLInjector_x86.exe "X:\Program Files\Acronis\BackupAndRecovery\TrueImage.exe" "NtShutdownSystemOverride_x86.dll"Download Binaries
Download: NtShutdownSystemOverride Binaries (32/64 Bit) (35 KB)
Don’t forget to copy
msvcp120.dll
andmsvcr120.dll
to Windows PE.
You are the KING !
Thank for all