Skip to content

Commit

Permalink
Version 1
Browse files Browse the repository at this point in the history
  • Loading branch information
homo-programmatis committed Dec 12, 2018
1 parent 447141a commit 6905f2b
Show file tree
Hide file tree
Showing 4 changed files with 478 additions and 0 deletions.
28 changes: 28 additions & 0 deletions PatchRunningCode.sln
Original file line number Diff line number Diff line change
@@ -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
271 changes: 271 additions & 0 deletions PatchRunningCode/PatchRunningCode.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,271 @@
#include <windows.h>

#include <conio.h>
#include <intrin.h>
#include <process.h>
#include <stdio.h>

// 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;
}
Loading

0 comments on commit 6905f2b

Please sign in to comment.