Window Contents Capturing using WM_PRINT Message for Win64.

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

Revision History

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”

Problem

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.

Test Program

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.

Original Solution Analysis

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.

New Hook Architecture

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!

Limitations

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.

Links

·         Original Feng Yuan article “Window Contents Capturing using WM_PRINT Message”.

·         Intel® Itanium® Architecture Software developer's manual.

·         wmpaint64 project page.

·         wmpaint64 project download area.

·         wmpaint64 project source code.

·         This document in docx format.

Contacts

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