Welcome to the second and final installment of the series on WoW64 syscall hooking. This part builds on the background provided by the first part and shows what is required to build a working implementation of a syscall hook under WoW64 for x64 Windows 8.1. The code presented here should be trivially portable across different versions of Windows by simply changing syscall numbers, which again can be found on this useful table mentioned in the first part. The code provided will be a basic syscall hook that allows for filtering of one particular syscall. There is not much stopping it from being extended to support hooking an arbitrary amount of syscalls.
Getting Started
From the first part in the series, we see that each thread holds a pointer to a block of code responsible for making the switch from x86 to x64 code execution. This is the pathway that WoW64 uses to perform a syscall, and the pointer to this code resides within the thread local storage region allotted to each thread. Since the value (FS:[0xC0]) in all threads points to the same location, the easiest way to go about hooking syscalls is to replace that jump with an inline hook to our filtering code. This code can then check the syscall number, which is stored in EAX, against the desired syscall to hook. If they match, our hook gets called, otherwise execution will continue on as normal.
The process begins by getting this address. With the aid of inline assembly, this is very straightforward:
const DWORD_PTR __declspec(naked) GetWow64Address() { __asm { mov eax, dword ptr fs:[0xC0] ret } } |
The return value of this function, stored in EAX, will hold the address of the block of code responsible for performing the jump to x64 bit mode. As previously mentioned, this address is the one that will be replaced with a jump to the filtering code. This is achieved with a simple overwrite of the instruction bytes at the address:
const void EnableWow64Redirect(const DWORD_PTR dwWow64Address, const LPVOID lpNewJumpLocation) { unsigned char trampolineBytes[] = { 0x68, 0xDD, 0xCC, 0xBB, 0xAA, /*push 0xAABBCCDD*/ 0xC3, /*ret*/ 0xCC, 0xCC, 0xCC /*padding*/ }; memcpy(&trampolineBytes[1], &lpNewJumpLocation, sizeof(DWORD_PTR)); WriteJump(dwWow64Address, trampolineBytes, sizeof(trampolineBytes)); } const void WriteJump(const DWORD_PTR dwWow64Address, const void *pBuffer, size_t ulSize) { DWORD dwOldProtect = 0; (void)VirtualProtect((LPVOID)dwWow64Address, PAGE_SIZE, PAGE_EXECUTE_READWRITE, &dwOldProtect); (void)memcpy((void *)dwWow64Address, pBuffer, ulSize); (void)VirtualProtect((LPVOID)dwWow64Address, PAGE_SIZE, dwOldProtect, &dwOldProtect); } |
You can see the hook being installed in a before/after form:
Above are the bytes of the original code. This carries out the inter-segment jump to x64 bit mode as is standard for all WoW64 calls.
This is the code with the hook is shown below. Here a jump to 0x13411B0 is made, which is the location of the filtering code.
This filtering code is just an implementation of what was described above, and performs a check of the syscall number against the desired target and acts accordingly
void __declspec(naked) Wow64Trampoline() { __asm { cmp eax, SYSCALL_INTERCEPT jz NtWriteVirtualMemoryHook jmp lpJmpRealloc } } |
In the case that the syscall number is not the one to filter, execution continues as normal by the jump to lpJmpRealloc, which is just an executable region of memory that contains the bytes of the original inter-segment jump (the jmp 0033:77BE1D84 instruction above). If the syscall number does match, then our hooking function, NtWriteVirtualMemoryHook, gets invoked. The example code just prints a message denoting that the call happened then continues on to performing the syscall. There is also a pushad/popad to properly preserve the stack in the event that another syscall is made from the syscall hook.
void __declspec(naked) NtWriteVirtualMemoryHook() { __asm pushad fprintf(stderr, "NtWriteVirtualMemory called.\n"); __asm { popad jmp lpJmpRealloc } } |
And that is all that there is to it as far as the example code goes. It can quickly be verified by seeing if the hook gets invoked for a call to NtWriteVirtualMemory.
//Test syscall int i = 0x123; int j = 0x678; SIZE_T ulBytesWritten = 0; fprintf(stderr, "i = 0x%X\n", i); NtWriteVirtualMemory(GetCurrentProcess(), &i, &j, sizeof(int), &ulBytesWritten); fprintf(stderr, "i = 0x%X\n", i); |
The output below shows everything working successfully
i = 0x123 NtWriteVirtualMemory called. i = 0x678
Get the Code
The Visual Studio 2015 RC project for this example can be found here. The source code is viewable on Github here.
Follow on Twitter for more updates.