o/ (FEB13/19:12)

HOWTO: Stop Acronis Backup from rebooting Windows PE


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!

Profiling TrueImage.exe using Dependency Walker

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 and msvcr120.dll to Windows PE.


Ein Kommentar


Schreibe einen Kommentar


  1. « OpenELEC – Umstieg von USB als Bootdevice auf Netzwerk

OCT1/20:46 - iTunes plays:
The Prodigy - Wild Frontier

Kontakt Impressum Martin Karer 2008-2024