From 6905f2be078df7014f21aeed9410c1fddff0fe9e Mon Sep 17 00:00:00 2001 From: Alexandr Miloslavskiy Date: Wed, 12 Dec 2018 15:14:05 +0100 Subject: [PATCH] Version 1 --- PatchRunningCode.sln | 28 ++ PatchRunningCode/PatchRunningCode.cpp | 271 ++++++++++++++++++ PatchRunningCode/PatchRunningCode.vcxproj | 157 ++++++++++ .../PatchRunningCode.vcxproj.filters | 22 ++ 4 files changed, 478 insertions(+) create mode 100644 PatchRunningCode.sln create mode 100644 PatchRunningCode/PatchRunningCode.cpp create mode 100644 PatchRunningCode/PatchRunningCode.vcxproj create mode 100644 PatchRunningCode/PatchRunningCode.vcxproj.filters diff --git a/PatchRunningCode.sln b/PatchRunningCode.sln new file mode 100644 index 0000000..52bbbfd --- /dev/null +++ b/PatchRunningCode.sln @@ -0,0 +1,28 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 2013 +VisualStudioVersion = 12.0.40629.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "PatchRunningCode", "PatchRunningCode\PatchRunningCode.vcxproj", "{2C42EB0D-D706-429D-9461-FBE1808995F3}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Debug|x64 = Debug|x64 + Release|Win32 = Release|Win32 + Release|x64 = Release|x64 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {2C42EB0D-D706-429D-9461-FBE1808995F3}.Debug|Win32.ActiveCfg = Debug|Win32 + {2C42EB0D-D706-429D-9461-FBE1808995F3}.Debug|Win32.Build.0 = Debug|Win32 + {2C42EB0D-D706-429D-9461-FBE1808995F3}.Debug|x64.ActiveCfg = Debug|x64 + {2C42EB0D-D706-429D-9461-FBE1808995F3}.Debug|x64.Build.0 = Debug|x64 + {2C42EB0D-D706-429D-9461-FBE1808995F3}.Release|Win32.ActiveCfg = Release|Win32 + {2C42EB0D-D706-429D-9461-FBE1808995F3}.Release|Win32.Build.0 = Release|Win32 + {2C42EB0D-D706-429D-9461-FBE1808995F3}.Release|x64.ActiveCfg = Release|x64 + {2C42EB0D-D706-429D-9461-FBE1808995F3}.Release|x64.Build.0 = Release|x64 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/PatchRunningCode/PatchRunningCode.cpp b/PatchRunningCode/PatchRunningCode.cpp new file mode 100644 index 0000000..7cbba76 --- /dev/null +++ b/PatchRunningCode/PatchRunningCode.cpp @@ -0,0 +1,271 @@ +#include + +#include +#include +#include +#include + +// Force console - for those who compile this source without project +#pragma comment(linker, "/SUBSYSTEM:console /ENTRY:mainCRTStartup") + +#pragma comment(lib, "Winmm.lib") // for timeBeginPeriod() + +/////////////////////////////////////////////////////////////////////////////// +// Testing variables + +const HANDLE g_EventStartMethod = CreateEvent(0, FALSE, FALSE, 0); +const HANDLE g_EventMethodExited = CreateEvent(0, FALSE, FALSE, 0); +DWORD g_NumErrors = 0; + +/////////////////////////////////////////////////////////////////////////////// +// Method that will be patched while it's running + +const BYTE SAMPLE_METHOD[] = +{ + 0xC3, // 0x00: Ret + 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, // 0x01: align(cache_line_size = 64), just to be extra sure to not cause unwanted effects in this cache line + 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, // 0x08: ^ + 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, // 0x10: ^ + 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, // 0x18: ^ + 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, // 0x20: ^ + 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, // 0x28: ^ + 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, // 0x30: ^ + 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, // 0x38: ^ + 0x89, 0x84, 0x24, 0x00, 0x90, 0xFF, 0xFF, // 0x40: Patchable entry point used by OpenJDK: 'mov dword ptr [rsp-7000h],eax' + 0xE9, 0xF4, 0xFF, 0xFF, 0xFF, // 0x47: 'jmp' back to 0x40, making infinite loop +}; + +const size_t SAMPLE_METHOD_ENTRY_OFFSET = 0x40; +const size_t SAMPLE_METHOD_TARGET_OFFSET = 0x00; +typedef void (*METHOD)(); + +BYTE* g_Method = (BYTE*)VirtualAlloc(0, sizeof(SAMPLE_METHOD), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE); + +/////////////////////////////////////////////////////////////////////////////// +// Patching code similar to code used by OpenJDK + +// Similar to OpenJDK's 'AbstractICache::_flush_icache_stub' +void FlushCache(const void* a_Address) +{ + _mm_mfence(); + _mm_clflush(a_Address); + _mm_mfence(); +} + +// Similar to OpenJDK's 'NativeJump::patch_verified_entry' +void PatchCodeWithJmp(BYTE* a_Where, BYTE* a_Target) +{ + const DWORD jmpOffset = (DWORD)(a_Target - (a_Where + 5)); + + BYTE patch[5]; + patch[0] = 0xE9; // jmp opcode + *(DWORD*)(patch + 1) = jmpOffset; // jmp offset + + // Replace bytes [0]...[3] with a temporary endless loop, + // to guard byte [4] from being executed while we're patching. + *(DWORD*)a_Where = 0xFEEBFEEB; + FlushCache(a_Where); + + // Patch byte [4] + a_Where[4] = patch[4]; + FlushCache(a_Where); + + // Finally, patch bytes [0]...[3] + *(DWORD*)a_Where = *(DWORD*)patch; + FlushCache(a_Where); + + // Verify patch. The bug will already be detected here. + if (0 != memcmp(a_Where, patch, sizeof(patch))) + { + g_NumErrors++; + + printf + ( + "ERROR: Patch failed:\n" + " Expected = %02X%02X%02X%02X%02X\n" + " Actual = %02X%02X%02X%02X%02X\n", + patch[0], patch[1], patch[2], patch[3], patch[4], + a_Where[0], a_Where[1], a_Where[2], a_Where[3], a_Where[4] + ); + } +} + +/////////////////////////////////////////////////////////////////////////////// +// Testing environment + +void* GetCrashLocation(const EXCEPTION_POINTERS* a_ExceptionInfo) +{ +#ifdef _M_X64 + return (void*)a_ExceptionInfo->ContextRecord->Rip; +#else + return (void*)a_ExceptionInfo->ContextRecord->Eip; +#endif +} + +int ExceptionFilter(const EXCEPTION_POINTERS* a_ExceptionInfo) +{ + g_NumErrors++; + + printf + ( + "ERROR: Crashed in method: CrashedAt=%p Method=%p\n", + GetCrashLocation(a_ExceptionInfo), + g_Method + ); + + return EXCEPTION_EXECUTE_HANDLER; +} + +void ForceStackAllocation() +{ + // Allocate enough stack to prepare for 'mov dword ptr [rsp-7000h],eax' + alloca(0x10000); +} + +// Secondary thread that runs method while it's patched +void __cdecl ThreadRunMethod(void*) +{ + ForceStackAllocation(); + + const METHOD method = (METHOD)(g_Method + SAMPLE_METHOD_ENTRY_OFFSET); + for (;;) + { + // Wait for method body to be generated... + WaitForSingleObject(g_EventStartMethod, INFINITE); + + // Call method. This will hang until method is patched. + __try + { + method(); + } + __except(ExceptionFilter(GetExceptionInformation())) + { + // Any exceptions will be processed in 'ExceptionFilter'. + } + + // Done! + SetEvent(g_EventMethodExited); + } +} + +// Secondary thread that hammers method's body with read operations while it's patched +void __cdecl ThreadReadMethod(void*) +{ + volatile BYTE* methodEntry = g_Method + SAMPLE_METHOD_ENTRY_OFFSET; + const BYTE* methodEnd = g_Method + sizeof(SAMPLE_METHOD); + + static volatile DWORD s_dontOptimizeMe = 0; + + for (;;) + { + // Wait for method body to be generated... + WaitForSingleObject(g_EventStartMethod, INFINITE); + + // Hammer method's entry point with read operations until it's patched. + // Do a few extra loops after it's patched - just in case. + for (DWORD patchedIterations = 0; patchedIterations < 1024; ) + { + // Read method's entry point + DWORD dontOptimizeMe = 0; + for (volatile BYTE* pos = methodEntry; pos < methodEnd; pos++) + { + dontOptimizeMe += *pos; + } + + s_dontOptimizeMe += dontOptimizeMe; + + // Is method patched already? + // Note: On bugged CPU's any bytes could be changed, so I test them all to prevent endless test. + if (0 != memcmp((const void*)methodEntry, SAMPLE_METHOD + SAMPLE_METHOD_ENTRY_OFFSET, 5)) + patchedIterations++; + } + + // Done! + SetEvent(g_EventMethodExited); + } +} + +// Secondary thread that doesn't touch the method. +void __cdecl ThreadDoNothing(void*) +{ + for (;;) + { + // Wait for method body to be generated... + WaitForSingleObject(g_EventStartMethod, INFINITE); + + // Done! + SetEvent(g_EventMethodExited); + } +} + +// The thread that patches method +void __cdecl ThreadPatchMethod(void*) +{ + // Request higher timer frequency to allow more tests per seconds + timeBeginPeriod(1); + + DWORD lastReportTicks = GetTickCount(); + BYTE* methodEntry = g_Method + SAMPLE_METHOD_ENTRY_OFFSET; + BYTE* patchTarget = g_Method + SAMPLE_METHOD_TARGET_OFFSET; + + for (DWORD numTests = 0; ; numTests++) + { + // Prepare method body. + memcpy_s(g_Method, sizeof(SAMPLE_METHOD), SAMPLE_METHOD, sizeof(SAMPLE_METHOD)); + + // Allow 'Run' thread to start. + SetEvent(g_EventStartMethod); + + // Let 'Run' thread spin a little... + Sleep(1); + + // Patch. This will break method's infinite loop and cause it to exit via 'ret'. + PatchCodeWithJmp(methodEntry, patchTarget); + + // Wait for 'Run' thread... + WaitForSingleObject(g_EventMethodExited, INFINITE); + + // Give some stats + if (GetTickCount() > lastReportTicks + 1000) + { + lastReportTicks = GetTickCount(); + printf("... Tests=%d Errors=%d\n", numTests, g_NumErrors); + } + } +} + +void RunTest() +{ + _beginthread(ThreadRunMethod, 0, 0); + ThreadPatchMethod(0); +} + +int main() +{ + printf("Select test:\n"); + printf("1. Patch while code is running\n"); + printf("2. Patch while code is being read\n"); + printf("3. Patch while code is not used\n"); + + for (;;) + { + switch (_getch()) + { + case '1': + _beginthread(ThreadRunMethod, 0, 0); + ThreadPatchMethod(0); + return 0; + case '2': + _beginthread(ThreadReadMethod, 0, 0); + ThreadPatchMethod(0); + return 0; + case '3': + _beginthread(ThreadDoNothing, 0, 0); + ThreadPatchMethod(0); + return 0; + } + } + + RunTest(); + return 0; +} \ No newline at end of file diff --git a/PatchRunningCode/PatchRunningCode.vcxproj b/PatchRunningCode/PatchRunningCode.vcxproj new file mode 100644 index 0000000..521ab13 --- /dev/null +++ b/PatchRunningCode/PatchRunningCode.vcxproj @@ -0,0 +1,157 @@ + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + {2C42EB0D-D706-429D-9461-FBE1808995F3} + Win32Proj + PatchRunningCode + + + + Application + true + v120 + Unicode + + + Application + true + v120 + Unicode + + + Application + false + v120 + true + Unicode + + + Application + false + v120 + true + Unicode + + + + + + + + + + + + + + + + + + + true + $(SolutionDir)!Bin\$(PlatformShortName)_$(Configuration)\ + $(SolutionDir)!Obj\$(PlatformShortName)_$(Configuration)\$(ProjectName)\ + + + true + $(SolutionDir)!Bin\$(PlatformShortName)_$(Configuration)\ + $(SolutionDir)!Obj\$(PlatformShortName)_$(Configuration)\$(ProjectName)\ + + + false + $(SolutionDir)!Bin\$(PlatformShortName)_$(Configuration)\ + $(SolutionDir)!Obj\$(PlatformShortName)_$(Configuration)\$(ProjectName)\ + + + false + $(SolutionDir)!Bin\$(PlatformShortName)_$(Configuration)\ + $(SolutionDir)!Obj\$(PlatformShortName)_$(Configuration)\$(ProjectName)\ + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions) + + + Console + true + + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions) + + + Console + true + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions) + MultiThreaded + + + Console + true + true + true + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions) + MultiThreaded + + + Console + true + true + true + + + + + + + + + \ No newline at end of file diff --git a/PatchRunningCode/PatchRunningCode.vcxproj.filters b/PatchRunningCode/PatchRunningCode.vcxproj.filters new file mode 100644 index 0000000..6a66ee1 --- /dev/null +++ b/PatchRunningCode/PatchRunningCode.vcxproj.filters @@ -0,0 +1,22 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Source Files + + + \ No newline at end of file