4

我正在摆弄 wndprocs 和 WinSpy++,我偶然发现了 calc.exe 的一个奇怪的东西。它似乎缺少 WndProc。

这是我的屏幕截图:我制作的一个测试程序,WinSpy++ 窗口,显示 N/A,以及罪魁祸首。

我制作的一个测试程序,Winspy 窗口,显示 N/A,以及罪魁祸首。

也许该工具有点过时,但经验证据证明没有 WndProc 存在。

我不知道这是设计使然(这会很奇怪),还是我遗漏了什么......

以下是参考代码:

Function FindWindow(title As String) As IntPtr
    Return AutoIt.AutoItX.WinGetHandle(title)
End Function

Function GetWindowProc(handle As IntPtr) As IntPtr
    Return GetWindowLong(handle, WindowLongFlags.GWL_WNDPROC)
End Function
4

2 回答 2

4

简而言之(关于您的代码):GetWindowLong()失败,因为您试图读取目标进程地址空间中的地址。

解释

GetWindowLong()返回 0 表示有错误,来自 MSDN:

如果函数失败,则返回值为零。要获取扩展错误信息,请调用 GetLastError。

检查一下Marshal.GetLastWin32Error(),您可能会看到错误代码为ERROR_ACCESS_DENIED(数值为 0x5)。

为什么?因为GetWindowLong()试图获取窗口过程的地址(或句柄)(不是在您的代码中,而是在目标进程中,理论上它甚至可能是默认窗口过程,但我从未见过一个应用程序主窗口不能处理至少几条消息)。您可以使用此技巧(但我从未尝试过!)查看窗口是否使用默认程序(您是否有地址),我不知道...有人应该尝试。

现在想想是什么WNDPROC

LRESULT (CALLBACK* WNDPROC) (HWND, UINT, WPARAM, LPARAM);

一个地址(在进程 A 中有效)在进程 B 中是不可调用的(它根本没有意义)。Windows DLL 代码段是跨进程共享的(我假设,我没有检查,但在安全和性能之间的博弈中这是合理的)。

此外CallWindowProc(NULL, ...),将理解NULL为调用该窗口类(在HWND所有者上)的窗口过程的特殊值。来自 MSDN:

...如果此值是通过调用 GetWindowLong 函数获得的...窗口或对话框过程的地址,或仅对 CallWindowProc 有意义的特殊内部值。

Microsoft Spy++ 是如何做到的(也许 WinSpy++ 没有)?没有 WinSpy++ 源代码很难说。当然,这不是那么容易GetWindowLong()正确的方法应该涉及CreateRemoteThread()并从中做到LoadLibrary(),但 Microsoft Spy++ 和 WinSpy++ 源代码均不可用(AFAIK)以供进一步检查......

更新

WinSpy++ 检查/调试与问题无关(您应该向开发人员发布票证,您的源代码可能会因我上面解释的内容而失败,您应该 -始终- 检查错误代码)但我们可能会寻找乐趣。

InjectThread.c我们看到它使用WriteProcessMemory+ CreateRemoteThreadthenReadProcessMemory来读取数据(不相关的代码省略):

// Write a copy of our injection thread into the remote process
WriteProcessMemory(hProcess, pdwRemoteCode, lpCode, cbCodeSize, &dwWritten);

// Write a copy of the INJTHREAD to the remote process. This structure
// MUST start on a 32bit boundary
pRemoteData = (void *)((BYTE *)pdwRemoteCode + ((cbCodeSize + 4) & ~ 3));

// Put DATA in the remote thread's memory block
WriteProcessMemory(hProcess, pRemoteData, lpData, cbDataSize, &dwWritten);

hRemoteThread = CreateRemoteThread(hProcess, NULL, 0, 
    (LPTHREAD_START_ROUTINE)pdwRemoteCode, pRemoteData, 0, &dwRemoteThreadId);

// Wait for the thread to terminate
WaitForSingleObject(hRemoteThread, INFINITE);

// Read the user-structure back again
if(!ReadProcessMemory(hProcess, pRemoteData, lpData, cbDataSize, &dwRead))
{
    //an error occurred
}

“General”选项卡和“Class”选项卡中的窗口过程不同(在“Class”选项卡中它正确显示一个值)。来自DisplayClassInfo.c

//window procedure
if(spy_WndProc == 0)    
{
    wsprintf(ach, _T("N/A"));
}
else                    
{
    wsprintf(ach, szHexFmt, spy_WndProc);
    if(spy_WndProc != spy_WndClassEx.lpfnWndProc)
        lstrcat(ach, _T(" (Subclassed)"));
}

//class window procedure
if(spy_WndClassEx.lpfnWndProc == 0)
    wsprintf(ach, _T("N/A"));
else
    wsprintf(ach, szHexFmt, spy_WndClassEx.lpfnWndProc);

如您所见,它们是不同的值(以不同的方式获得)。要填写的代码spy_WndProcWinSpy.cGetRemoteWindowInfo.cGetRemoteInfo()从in 中提取代码WinSpy.c

GetClassInfoEx(0, spy_szClassName, &spy_WndClassEx);
GetRemoteWindowInfo(hwnd, &spy_WndClassEx, &spy_WndProc, spy_szPassword, 200);

现在GetRemoteWindowInfo()我们看到一个调用GetClassInfoExProc(在另一个进程中注入):

pInjData->wndproc = (WNDPROC)pInjData->fnGetWindowLong(pInjData->hwnd, GWL_WNDPROC);
pInjData->fnGetClassInfoEx(pInjData->hInst,
    (LPTSTR)pInjData->szClassName, &pInjData->wcOutput);

如您所见(请使用源代码跟随)wcOutput是“类”选项卡wndproc中显示的内容和“常规”选项卡中显示的内容。只是GetWindowLong()失败但GetClassInfoEx没有(但它们不一定会检索相同的值,因为(如果我没记错的话)你所拥有的WNDCLASSEX是你注册的东西,RegisterClassEx但你得到的GetWindowLong()是你所迷恋的东西SetWindowLong()

于 2014-12-03T12:36:42.823 回答
0

你说的对。它没有 WndProc(...) 函数。它只是简单地使用 DlgProc 来处理对话事件。我现在这样做了,因为我已经用 C/C++ 编写了“服务器/瘦客户端”代码来捕获对 Windows API 函数的直接调用,例如 WndProc(...)。任何 Windows GUI 函数 - 以 BeginPaint(...) 为例。我使用 CALC.EXE 作为测试,可执行文件在服务器上运行,同时 GUI 调用被中继/返回到瘦客户端。仅通过 Vista 测试了 calc.exe 版本。较新的版本有可能以不同的方式“编程”——这意味着不使用 Win32 SDK。但是,即使 MFC 也只是 Win32 SDK 的一个外壳,

于 2015-03-13T17:47:30.503 回答