Copyleft (cc) 2012 by Andriy
Golovnya
This article is distributed under the terms of Creative Commons License.
Please make a reference to this article if you use any materials from it.
Version 1.1
Version (x.y) |
Date (Asian format) |
Changes |
1.0 |
2012-01-15 |
Initial release |
1.1 |
2012-01-17 |
Add new chapters
“Limitations” and “Contacts” |
In year
2000-2004 Feng Yuan has written a very important article “Window Contents Capturing
using WM_PRINT Message” which describes a method of handling WM_PRINTCLIENT
message for 3-rd party code on Windows 32-bit.
This
article describes a way how to adopt Feng Yuan’s idea for Windows 64-bit.
Feng Yuan has
written an example program for his original article to demonstrate his ideas. I
took this example code and improved it:
1.
to
make it compile and work in modern Visual Studio 2010;
2.
to
make it compile and work in modern Windows 32- and 64-bit.
The new
example program is located in Git
repository. You can download binaries for Windows 32- and 64-bit from project
download page.
The method
proposed by Feng Yuan is based on hooking two system functions located in
user32.dll: BeginPaint() and EndPaint(). These functions are very simple and do
only a trap to kernel. The very important here is to know that trap code
fragment are really small and differs only in one parameter – system call ID.
The bad thing here is that for each new version Windows the system call ID
value and function code itself can be changed without notice. Microsoft is not
willing to disclose this information so we only can guess what comes in next
Windows. Knowing this fact Feng Yuan has written a hooking procedure that
stores original system call ID and uses it later when original code fragment is
called. Please refer to original article for more information.
The
original method of hooking lacks in some points:
1.
The
hooking code is not prepared for 64-bit Windows.
2.
The
hooking code is complicated and uses inline assembler fragments.
3.
The
inline assembler code causes additional problems on 64-bit Windows where inline
assembly is forbidden.
4.
The
inline assembler code makes it not possible to compile example program with
another C/C++ compiler (e.g. g++).
Taking all
this in account I decided to modify the way hooking is done to rid of inline
assembler code. After this change it’s easy to port resulting code to Windows
x64 and even other CPU architectures.
The
original code form Feng Yuan corrupts/modifies only beginning of the hooked
function and calls the rest of it from our substitution function. Please refer
to original Feng Yuan’s article for details.
In the new
hook implementation I decided to copy original code of hooked function
completely to a new place and call it from there later. As far as it is known
that functions BeginPaint() and EndPaint() are no longer than 32 bytes we can
copy a fixed size buffer of 32 bytes long to a reserved buffer and corrupt a
function code in original location as we want. This makes hook code simple and
clear.
// We define a copy buffer size as 32
bytes for x86 and x64 architectures and as 16 bytes for ia64.
#if defined(_M_IX86) || defined(_M_X64)
#define COPY_FUNC_SIZE 32
#elif defined(_M_IA64)
#define COPY_FUNC_SIZE sizeof(PLABEL_DESCRIPTOR)
#else
#error No code for your platform!
#endif
// We define a function prototype and
reserve a copy buffer for BeginPaint()
typedef HDC (__stdcall * CopyBeginPaintPtr)(HWND
hWnd, LPPAINTSTRUCT lpPaint);
static BYTE
CopyBeginPaintBuf[COPY_FUNC_SIZE] = {0};
static CopyBeginPaintPtr CopyBeginPaint
= (CopyBeginPaintPtr)&CopyBeginPaintBuf;
// We define a function prototype and
reserve a copy buffer for EndPaint()
typedef BOOL (__stdcall *
CopyEndPaintPtr)(HWND hWnd, const PAINTSTRUCT* lpPaint);
static BYTE
CopyEndPaintBuf[COPY_FUNC_SIZE] = {0};
static CopyEndPaintPtr CopyEndPaint =
(CopyEndPaintPtr)&CopyEndPaintBuf;
// This is a hooking function. It copies
an original function to a copy buffers and
// patches a code in original location
to redirect an execution to our substitution function.
bool Hook(const TCHAR * module, const
TCHAR * proc, void * pCopyProc, const void * pNewProc)
{
HINSTANCE hMod = GetModuleHandle(module); // We loading required DLL.
BYTE * pProc;
pProc = (BYTE *) GetProcAddress(hMod, proc); // Now we try to get an address of required function
by its name.
#if defined(_M_IX86) // Code for x86 32-bit architecture.
if ( (pProc[0] == 0xB8) && (((BYTE*) pCopyProc)[0] == 0x00) ) // Here we check if a function we found
has format we expect.
// It must begin with B8 XX XX XX XX
-> mov eax, SystemCallNr
{
DWORD flOldProtect;
if (!VirtualProtect(pCopyProc, COPY_FUNC_SIZE, PAGE_EXECUTE_READWRITE,
&flOldProtect)) // Now we
fix permissions for copy buffer.
return false;
memcpy(pCopyProc, pProc, COPY_FUNC_SIZE); // Copy the original function code to the copy buffer.
if (!VirtualProtect(pProc, 5, PAGE_EXECUTE_READWRITE,
&flOldProtect)) // Fix
permissions for original code fragment.
return false;
// Now we are
going to patch an original code to redirect execution to place we want.
pProc[0] = 0xE9; // E9 XX XX XX XX -> jmp OurHookFunction
* (unsigned *) (pProc+1) = (unsigned) pNewProc - (unsigned) (pProc+5);
return true;
}
else
return false;
#elif defined(_M_X64) // Code for x64 (AMD64) architecture.
if ( (* (unsigned *) (&pProc[0]) == 0xB8D18B4C) && (((BYTE*)
pCopyProc)[0] == 0x00) ) // Here we check if a function we found has format we expect.
// It must begin with 4C 8B D1 -> mov
r10,rcx
// B8 XX XX XX XX -> mov
eax, SystemCallNr
{
DWORD flOldProtect;
if (!VirtualProtect(pCopyProc, COPY_FUNC_SIZE, PAGE_EXECUTE_READWRITE,
&flOldProtect)) // Now
we fix permissions for copy buffer.
return false;
memcpy(pCopyProc, pProc, COPY_FUNC_SIZE); // Copy the original function code to the copy
buffer.
if (!VirtualProtect(pProc, 12, PAGE_EXECUTE_READWRITE,
&flOldProtect)) // Fix
permissions for original code fragment.
return false;
// Now we are going to patch an original code to redirect execution to
place we want.
pProc[0] = 0x48; // 48 B8 XX XX XX XX XX XX XX XX -> mov rax, OurHookFunction
pProc[1] = 0xb8;
* (__int64 *) (pProc+2) = (__int64) pNewProc;
pProc[10] = 0xff; // FF E0 -> jmp rax
pProc[11] = 0xe0;
return true;
}
else
return false;
#elif defined(_M_IA64) // Code for ia64 (Itanium) architecture.
if ( (((BYTE*) pCopyProc)[0] == 0x00) ) // No guard check here, we only check if copy buffer
is empty.
{
DWORD flOldProtect;
// For Itanium
we should not patch any code directly. Our goal is to patch only so called link
pointers.
// Please refer to Intel Itanium Architecture Software developer's
manual for more information.
PLABEL_DESCRIPTOR *pLabelDescriptor = (PLABEL_DESCRIPTOR *) pProc; // original branch descriptor
PLABEL_DESCRIPTOR *pCopyLabelDescriptor = (PLABEL_DESCRIPTOR *)
pCopyProc; // copy branch
descriptor
PLABEL_DESCRIPTOR *pNewLabelDescriptor = (PLABEL_DESCRIPTOR *) pNewProc;
// hook f() branch
descriptor
if (!VirtualProtect(pCopyProc, sizeof(PLABEL_DESCRIPTOR),
PAGE_EXECUTE_READWRITE, &flOldProtect)) // Now we fix permissions for copy buffer.
return false;
memcpy(pCopyLabelDescriptor, pLabelDescriptor,
sizeof(PLABEL_DESCRIPTOR)); // Copy the original function pointer to the copy buffer.
if (!VirtualProtect(pProc, sizeof(PLABEL_DESCRIPTOR),
PAGE_EXECUTE_READWRITE, &flOldProtect)) // Fix permissions for original code pointer.
return false;
memcpy(pLabelDescriptor, pNewLabelDescriptor,
sizeof(PLABEL_DESCRIPTOR)); // Copy the substitution function pointer to the original location.
return true;
}
else
return false;
#else
#error No code for your platform!
#endif
}
// This is our substitution function for
BeginPaint().
HDC WINAPI CPaintHook::MyBeginPaint(HWND
hWnd, LPPAINTSTRUCT lpPaint)
{
...
if ( pThis->Within_WM_PRINT() )
{
...
}
else
{
return CopyBeginPaint(hWnd, lpPaint); // As you can see no assembler code is here!
}
}
// This is our substitution function for
EndPaint().
BOOL WINAPI CPaintHook::MyEndPaint(HWND
hWnd, LPPAINTSTRUCT lpPaint)
{
...
if ( pThis->Within_WM_PRINT() )
...
else
{
return CopyEndPaint(hWnd, lpPaint); // No assembler here too!
}
}
// Hooking class constructor.
CPaintHook::CPaintHook()
{
static bool s_hooked = false;
if ( ! s_hooked )
{
// Here we
hook our two functions to achieve necessary functionality.
// Note that hooking process is not thread safe itself.
// No calling of BeginPaint() and EndPaint() is allowed until hooking
process is not finished.
Hook("USER32.DLL", "BeginPaint", CopyBeginPaint,
MyBeginPaint);
Hook("USER32.DLL", "EndPaint", CopyEndPaint, MyEndPaint);
s_hooked = true;
}
...
}
// Substitute default window event
processing procedure.
void CPaintHook::SubClass(HWND hWnd)
{
SetProp(hWnd, thisPtrProperty, this);
#if defined(_M_IX86) // Code for 32-bit architecture.
m_OldWndProc = (WNDPROC) GetWindowLongPtrA(hWnd, GWL_WNDPROC);
SetWindowLongPtrA(hWnd, GWL_WNDPROC, (LONG)NewWndProc);
#elif defined(_M_X64) ||
defined(_M_IA64) // Code
for 64-bit architecture.
m_OldWndProc = (WNDPROC) GetWindowLongPtrA(hWnd, GWLP_WNDPROC);
SetWindowLongPtrA(hWnd, GWLP_WNDPROC, (LONG_PTR)NewWndProc);
#else
#error No code for your platform!
#endif
}
So be it!
:-) The new method of hooking is implemented for x64, x64 and ia64
architectures now. The code has no inline assembled fragments and can be easily
extended with other architectures (e.g. ARM).
Thanks for
reading up till here! You are really brave!
On x86 and
x64 platform the new hooking mechanism expects that hooked functions are not
longer than 32 bytes and copies only this fragment of code to the new location.
This can be a limitation if you are hooking other functions and hooked function
is longer than this predefined value. The hooked function should also not
contain any relative branches out of this 32 bytes fragment so it could
flawlessly execute in new location.
·
Original
Feng Yuan article “Window
Contents Capturing using WM_PRINT Message”.
·
Intel®
Itanium® Architecture Software developer's manual.
·
wmpaint64
project download area.
·
wmpaint64
project source code.
·
This document in docx format.
Please
contact me if you have any questions, suggestions or simply fun to talk about
theme described in this article.
·
andrew_golovnia[at]users.sourceforge.net
·
ag[at]embedded.org.ru