A single-header C++ library for Windows to create hotpatchable images and apply hotpatches with 5 methods
Article at CodeProject: https://www.codeproject.com/Articles/1043089/HotPatching-Deep-Inside
- Build the executable in release mode from solution
- The solution is automatically configured to run the executable with parameter /postbuildpatch which updates itself with the patch information. It uses BeginUpdateResource and EndUpdateResource. Note that these are frequently stopped by antivirus. You can also use the included pig.exe which is a standalone app to read a file and its pdf, and put the hotpatch data inside it.
- Load the DLL from the executable and call an exported function (say, Patch()).
- Call hp.ApplyPatchFor() for each function you want to be patched:
hr = hp.ApplyPatchFor(hM, L"FOO::PatchableFunction1", PatchableFunction1, &xPatch);- Call hp.PrepareExecutableForCOMPatching();
- If embedding, start the COM server, specifying the patches and installing a message loop:
void EmbeddingStart() { hp.StartCOMServer(GUID_TEST, [](vector<HOTPATCH::NAMEANDPOINTER>& w) -> HRESULT { w.clear(); HOTPATCH::NAMEANDPOINTER nap; nap.n = L"PatchableFunction1"; nap.f = [](size_t*) -> size_t { MessageBox(0, L"Patch from COM Patcher", L"Patched", MB_ICONINFORMATION); return 0; }; w.push_back(nap); return S_OK; }, []() { // We are closing... PostThreadMessage(mtid, WM_QUIT, 0, 0); } ); } // main if (argc == 2 && (_wcsicmp(wargv[1], L"-embedding") == 0 || _wcsicmp(wargv[1], L"/embedding") == 0)) { mtid = GetCurrentThreadId(); EmbeddingStart(); MSG msg; while (GetMessage(&msg, 0, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } return 0; }
If main, start the patching process
hp.StartCOMPatching(); vector<wstring> pns; hp.AckGetPatchNames(pns); for (auto& aa : pns) { hp.ApplyCOMPatchFor(xPatch, GetModuleHandle(0), aa.c_str()); } ... // End app hp.FinishCOMPatching();
- Call hp.PrepareExecutableForUSMPatching();
- If patching mode, start the USM server, specifying the patches and installing a message loop:
void USMStart() { hp.StartUSMServer(GUID_TEST, [](vector<HOTPATCH::NAMEANDPOINTER>& w) -> HRESULT { HOTPATCH::NAMEANDPOINTER nap; nap.n = L"PatchableFunction1"; TCHAR cidx[1000] = { 0 }; StringFromGUID2(GUID_TEST, cidx, 1000); swprintf_s(cidx + wcslen(cidx), 1000 - wcslen(cidx), L"-%u", 0); nap.mu = (mutual*)new mutual(cidx, [&](unsigned long long) { MessageBox(0, L"Patch from USM Patcher", L"Patched", MB_ICONINFORMATION); return; }, 0, true); w.push_back(nap); return S_OK; }, []() { // We are closing... PostThreadMessage(mtid, WM_QUIT, 0, 0); } ); } // main if (argc == 2 && (_wcsicmp(wargv[1], L"-usm") == 0 || _wcsicmp(wargv[1], L"/usm") == 0)) { mtid = GetCurrentThreadId(); USMStart(); MSG msg; while (GetMessage(&msg, 0, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } return 0; }
If main, start the patching process
// a has path name to patcher wcscat_s(a, 1000, L" /usm"); // Run it with /USM STARTUPINFO sInfo = { 0 }; sInfo.cb = sizeof(sInfo); PROCESS_INFORMATION pi = { 0 }; if (!CreateProcess(0, a, 0, 0, 0, 0, 0, 0, &sInfo, &pi)) return 0; CloseHandle(pi.hThread); CloseHandle(pi.hProcess); // Get the names vector<wstring> pns; hp.AckGetPatchNames(pns); for (size_t i = 0; i < pns.size(); i++) { auto& aa = pns[i]; hp.ApplyUSMPatchFor(xPatch, GetModuleHandle(0), aa.c_str(), i); } ... // End app hp.FinishCOMPatching();
This method is based on my article at https://www.codeproject.com/Articles/1045674/Load-EXE-as-DLL-Mission-Possible , which describes how to load an EXE as a DLL. However this is a highly hackish method and should not be used.
The final method requires your app to be build itself as DLL. Therefore, the post-build event is now rundll32 .\test.dll,PostBuildPatch, the debugger run command is now rundll32 .\test.dll,dmain and you can have the program and the patcher inside the same DLL.