Thunk的基本原理是分配一段内存,然后将窗口过程设置为这段内存。这段内存的作用是将窗口过程的第一个参数(窗口句柄)替换成类的This指针,并jump到类的WinProc函数中。这样就完成了窗口过程到类的成员函数的一个转换。
ATL采用一种称为thunk的机制,简单来说,公共的窗口过程依然是类的一个静态函数,但只负责窗口的第一个消息WM_NCCREATE,其目的是在堆上开辟一小块数据区,称为thunk,这小块数据区其实是一段机器码,把这个地址换成窗口的过程函数地址,通过调用SetWindowLongPtr实现。因此以后真正的窗口过程是thunk,thunk负责把hwnd替换为对象的地址,然后jump到另一个公共的类静态函数。
虽然是一个静态函数,不含有this指针,但是堆栈上的第一个参数已经被替换为对象的地址,用得到的对象地址就可以调用真正的消息处理函数,根据消息映射宏调用对应消息的处理过程。
自己搞thunk的时候要主要dep保护啊。fuck
请参考
http://www.cnblogs.com/georgepei/archive/2012/03/30/2425472.html
http://www.cnblogs.com/georgepei/archive/2012/03/30/2425472.html
#include "stdafx.h"
#include <Windows.h>
#include <assert.h>
#pragma pack(push, 1)
struct tagThunk
{
DWORD m_mov;
DWORD m_this;
BYTE m_jmp;
DWORD m_relproc;// relative jmp
BOOL Init(DWORD proc, void* pThis)
{
//mov [esp+4] pThis;
m_mov =0x042444C7;
m_this = (DWORD)pThis;
//jmp proc
m_jmp = 0xe9;
m_relproc = (DWORD)((INT_PTR)proc- ((INT_PTR)this + sizeof(tagThunk)));
return FlushInstructionCache(GetCurrentProcess(), this, sizeof(tagThunk));
}
void* GetProcAddress(){
return this;
}
};
#pragma pack(pop)
int s_this;
class ThunkTest
{
public:
int i;
tagThunk m_thunk;
//m_thunk位置不影响?, ATL是排在第一个
int j;
ThunkTest(){
s_this = (int)this;
i = 1;
j = 2;
}
static void static_fun(int noneParamter, int neededParameter)
{
assert(s_this == noneParamter);
ThunkTest* pThis = (ThunkTest*)noneParamter;
pThis->member_fun(neededParameter);
}
void member_fun(int neededParameter)
{
}
void test(int neededParameter)
{
m_thunk.Init((DWORD)static_fun, this);
void* proc = m_thunk.GetProcAddress();
_asm
{
push neededParameter
push 0 //noneParameter
//这样在调用thunk的时候会把 esp+4 替换为 this
mov edx, proc
call edx
add esp, 8
}
}
};
#include <new>
int WINAPI _tWinMain(HINSTANCE hInstance, HINSTANCE /*hPrevInstance*/, LPTSTR lpstrCmdLine, int nCmdShow)
{
int bytes = sizeof(ThunkTest) + sizeof(int);
//DEP问题
LPVOID ptr = VirtualAlloc(NULL, bytes, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
ThunkTest* pTest = new (ptr) ThunkTest;
pTest->test(123);
pTest->~ThunkTest();
VirtualFree(ptr, bytes, MEM_DECOMMIT);
return 0;
}
<span style="font-family: Arial, Helvetica, sans-serif;">
</span>
<span style="font-family: Arial, Helvetica, sans-serif;">
</span>
<span style="font-family: Arial, Helvetica, sans-serif;">参考:</span>
<span style="font-family: Arial, Helvetica, sans-serif;">http://www.codeproject.com/Articles/25198/Generic-Thunk-with-5-combinations-of-Calling-Conve</span>
2. destination of transfer instruction
Destinations of many transfer instructions are specified by OFFSET TO the source
for example :
when CPU executes the instruction at 0xFF000000 , the instruction like this :
0xFF000000 : 0xE9 0x33 0x55 0x77 0x99
0xFF000005 : ...;
0xE9 is a JMP instruction and the following 4 bytes will be interpreted as OFFSET
offset = 0x99775533 (on Intel x86 ,the lower byte stored on lower address) = -1720232653
source (src) = 0xFF000000 (the address of JMP instruction ) = 4278190080
destination (dst) = src+offset+5 ( 1 byte,JMP,4 bytes offset ) = 4278190080 – 1720232653 +5 = 2557957432 = 0x98775538
so after the instruction “ JMP -1720232653 “ the next instruction to be executed will be at :
0x98775538 : ...;