替换掉就行了。这时thunk技术就有用武之地了,我们先定义一个结构:
#pragma pack(push,1) //该结构必须以字节对齐
struct Thunk {
BYTE Call;
int Offset;
WNDPROC Proc;
BYTE Code;
CMyWnd* Window;
BYTE Jmp;
BYTE ECX;
};
#pragma pack(pop)
类定义:
class CMyWnd
{
public:
BOOL Create();
LRESULT WINAPI WindowProc(UINT,WPARAM,LPARAM);
static LRESULT WINAPI InitProc(HWND,UINT,WPARAM,LPARAM);
static LRESULT WINAPI stdProc(HWND,UINT,WPARAM,LPARAM);
WNDPROC CreateThunk();
WNDPROC GetThunk(){return m_thunk}
private:
WNDPROC m_thunk
}
在创建窗口的时候把窗口过程设定为this->m_thunk,m_thunk的类型是WNDPROC,因此是完全合法的,当然这个m_thunk还没有初始化,在创建窗口前必须初始化:
WNDPROC CMyWnd::CreateThunk()
{
Thunk* thunk = new Thunk;
///////////////////////////////////////////////
//
//系统调用m_thunk时的堆栈:
//ret HWND MSG WPARAM LPARAM
//-------------------------------------------
//栈顶 栈底
///////////////////////////////////////////////
//call Offset
//调用code[0],call执行时会把下一条指令压栈,即把Proc压栈
thunk->Call = 0xE8; // call [rel]32
thunk->Offset = (size_t)&(((Thunk*)0)->Code)-(size_t)&(((Thunk*)0)->Proc); // 偏移量,跳过Proc到Code[0]
thunk->Proc = CMyWnd::stdProc; //静态窗口过程
//pop ecx,Proc已压栈,弹出Proc到ecx
thunk->Code[0] = 0x59; //pop ecx
//mov dword ptr [esp+0x4],this
//Proc已弹出,栈顶是返回地址,紧接着就是HWND了。
//[esp+0x4]就是HWND
thunk->Code = 0xC7; // mov
thunk->Code = 0x44; // dword ptr
thunk->Code = 0x24; // disp8[esp]
thunk->Code = 0x04; // +4
thunk->Window = this;
//偷梁换柱成功!跳转到Proc
//jmp [ecx]
thunk->Jmp = 0xFF; // jmp [r/m]32
thunk->ECX = 0x21; // [ecx]
m_thunk = (WNDPROC)thunk;
return m_thunk;
}
这样m_thunk虽然是一个结构,但其数据是一段可执行的代码,而其类型又是WNDPROC,系统就会忠实地按窗口过程规则调用这段代码,m_thunk就把Window字段里记录的this指针替换掉堆栈中的hWnd参数,然后跳转到静态的stdProc:
//本回调函数的HWND调用之前已由m_thunk替换为对象指针
LRESULT WINAPI CMyWnd::stdProc(HWND hWnd,UINT uMsg,UINT wParam,LONG lParam)
{
CMyWnd* w = (CMyWnd*)hWnd;
return w->WindowProc(uMsg,wParam,lParam);
}
这样就把窗口过程转向到了类成员函数WindowProc,当然这样还有一个
问题,就是窗口句柄hWnd还没来得及记录,因此一开始的窗口过程应该先定位