TL 的思路是,每次在系统调用 WndProc 的时候,让它鬼使神差地先走到我们的另一处代码,让我们有机会修改堆栈中的 hWnd。这处代码可能是类似这样的:
__asm
{
mov dword ptr [esp+4], pThis ;调用 WndProc 时,堆栈结构为:RetAddr, hWnd, message, wParam, lParam, ... 故 [esp+4]
jmp WndProc
}
由于 pThis 和 WndProc 需要被事先修改(但又无法在编译前定好),所以我们需要运行的时候去修改这部分代码。先弄一个小程序探测下这两行语句的机器码:
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
return 0;
}
int APIENTRY _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow)
{
MessageBox(NULL, NULL, NULL, MB_OK);
__asm
{
mov dword ptr [esp+4], 1
jmp WndProc
}
return 0;
}
最前面的 MessageBox 是为了等下调试的时候容易找到进入点。
然后使用 OllyDbg,在 MessageBoxW 上设置断点,执行到该函数返回:
这里我们看到,mov dword ptr [esp+4] 的机器码为 C7 44 24 04,后面紧接着的一个 DWORD 是 mov 的第二个操作数。jmp 的机器码是 e9,后面紧接着的一个 DWORD 是跳转的相对地址。其中 00061000h - 0006102Bh = FFFFFFD5h。
于是定义这样一个结构:
#pragma pack(push,1)
typedef struct _StdCallThunk
{
DWORD m_mov; // = 0x042444C7
DWORD m_this; // = this
BYTE m_jmp; // = 0xe9
DWORD m_relproc; // = relative distance
} StdCallThunk;
#pragma pack(pop)
class CMyWindow { public: CMyWindow():_hwnd(NULL){} ~CMyWindow(){VirtualFree(_pStdthunk, sizeof(StdCallThunk), MEM_RELEASE);} bool Create(); protected: LRESULT CALLBACK WndProc(UINT message, WPARAM wParam, LPARAM lParam); protected: MSG _msg; HWND _hwnd; StdCallThunk *_pStdthunk; protected: static LRESULT CALLBACK TempWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam); static LRESULT CALLBACK StaticWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam); static std::map<DWORD,CMyWindow*> _sWindow; }; std::map<DWORD,CMyWindow*> CMyWindow::_sWindow; bool CMyWindow::Create() { WNDCLASSEX wcex; LPCTSTR lpszClassName = _T("ClassName"); wcex.cbSize = sizeof(WNDCLASSEX); HINSTANCE hInstance = GetModuleHandle(NULL); wcex.style = CS_HREDRAW | CS_VREDRAW; wcex.lpfnWndProc = TempWndProc; wcex.cbClsExtra = 0; wcex.cbWndExtra = 0; wcex.hInstance = hInstance; wcex.hCursor = LoadCursor(NULL, IDC_ARROW); wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1); wcex.lpszClassName = lpszClassName; wcex.hIconSm = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL)); RegisterClassEx(&wcex); DWORD dw = GetCurrentThreadId(); _sWindow.insert(std::make_pair(dw,this)); _pStdthunk = (StdCallThunk *)VirtualAlloc(NULL, sizeof(StdCallThunk), MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE); _pStdthunk->m_mov = 0x042444c7; _pStdthunk->m_jmp = 0xe9; _hwnd = CreateWindow(lpszClassName, NULL, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hInstance, NULL); if (_hwnd == NULL) { MessageBox(NULL,TEXT("Error"),NULL,NULL); return FALSE; } ShowWindow(_hwnd, SW_SHOW); UpdateWindow(_hwnd); return TRUE; } LRESULT CALLBACK CMyWindow::TempWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { DWORD dw = GetCurrentThreadId(); std::map<DWORD,CMyWindow*>::iterator it; it = _sWindow.find(dw); if(it == _sWindow.end() || it->second == NULL) return false; CMyWindow *pThis = it->second; _sWindow.erase(it); WNDPROC pWndProc = (WNDPROC)pThis->_pStdthunk; pThis->_pStdthunk->m_this = (DWORD)pThis; pThis->_pStdthunk->m_relproc = (DWORD)&CMyWindow::StaticWndProc - ((DWORD)pThis->_pStdthunk + sizeof(StdCallThunk)); pThis->_hwnd = hWnd; SetWindowLong(hWnd, GWL_WNDPROC, (LONG)pWndProc); return pWndProc( hWnd, message, wParam, lParam); } LRESULT CALLBACK CMyWindow::StaticWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { return ((CMyWindow *)hWnd)->WndProc(message, wParam, lParam); } LRESULT CALLBACK CMyWindow::WndProc(UINT message, WPARAM wParam, LPARAM lParam) { int wmId, wmEvent; PAINTSTRUCT ps; HDC hdc; switch (message) { case WM_COMMAND: wmId = LOWORD(wParam); wmEvent = HIWORD(wParam); // 分析菜单选择: switch (wmId) { case IDM_ABOUT: //DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, About); MessageBox(NULL,TEXT("AboutDlg"),TEXT("关于"),NULL); break; case IDM_EXIT: DestroyWindow(_hwnd); break; } break; case WM_PAINT: hdc = BeginPaint(_hwnd, &ps); // TODO: 在此添加任意绘图代码... EndPaint(_hwnd, &ps); break; case WM_DESTROY: PostQuitMessage(0); break; default: DefWindowProc(_hwnd, message, wParam, lParam); break; } return TRUE; }