我想弄清楚 GetWindowText 背后的系统调用是什么。我编写了一个简单的程序来调用 GetWindowText,并使用不同进程中的窗口句柄。
int CALLBACK WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
MessageBox(0,"Attach debugger and set bp","on GetWindowTextA",0);
HWND winmine = FindWindow(NULL,"Minesweeper");
if(winmine != NULL)
{
char buf[255] = "";
GetWindowTextA(winmine, buf, 254);
MessageBox(0,buf,"Found",0);
}
else
{
MessageBox(0,"?","Found nothing",0);
}
return 0;
}
我附加了一个调试器并单步执行 GetWindowTextA 调用,手动单步执行除这些 API 调用之外的所有内容(按顺序):
这些 API 调用似乎都无法读取内存中不属于调用进程的字符串。我使用了用户模式调试器,所以我当然不会在没有意识到的情况下单步执行到内核模式。这意味着 GetWindowText 无需执行上下文切换即可获取窗口名称。这似乎意味着存在的每个窗口的文本无需上下文切换即可访问..这是不对的,因为 Windows 无法为系统上的每个窗口/控件保留文本的副本,在每个单一进程。
我读过这篇 Raymond Chen 博客文章 GetWindowText 的秘密生活存档。它提到窗口名称存储在引号“特殊位置”中,但没有解释如何在不进行系统调用/上下文切换的情况下从不同进程访问此“特殊位置”。
所以我正在寻找有关如何完成此操作的任何解释。非常感谢您提供的任何信息。
无需执行上下文切换即可获取窗口名称。这似乎意味着每个存在的窗口的文本都可以在没有上下文切换的情况下访问。GetWindowText
此信息存储在所有使用
user32.dll
的进程之间共享的内存中。您可以尝试在您进程的虚拟空间中搜索其他进程窗口的 unicode 名称。
它在加载期间被映射到进程地址空间。涉及一些内核结构/部分:
user32.dll
,win32k!gSharedInfo
,win32k!ghSectionShared
和其他(我不知道)。实际上,
win32k!gpsi
的低16位表示基地址为
HWND
的窗口信息数组的索引。该窗口信息的第一个字段是另一个包含所有共享窗口信息的结构的内核地址。减去该部分的内核地址与其用户空间映射(存储在*(&user32!gSharedInfo + 1)
中)之间的差异,您可以得到相关信息。TEB!Win32ClientInfo
是将窗口句柄转换为该地址的函数,可供内部
user32!ValidateHwnd
函数使用,如user32
。user32!DefWindowProcWorker
的伪代码看起来像(不包括错误处理):
GetWindowTextW
在您的情况下使用 GetWindowTextW(HWND hwnd, wchar_t* buf, int size)
{
inner_hwnd = ValidateHwnd(hwnd);
if (TestWindowProcess(inner_hwnd))
SendMessageWorker(inner_hwnd, WM_GETTEXT, size, buf, FALSE);
else
DefWindowProcWorker(inner_hwnd, WM_GETTEXT, size, buf, FALSE);
}
调用的 DefWindowProcWorker
只会解析 WM_GETTEXT
引用的结构并将窗口的名称复制到 inner_hwnd
中。我从来不知道存储在其中的所有信息,尽管不使用各种
buf
/
user
参数污染进程的虚拟空间似乎是一个不错的选择。此外,完整性较低的进程不应该能够获取完整性较高的进程的敏感信息。making文本肯定存在,只是不是副本。窗口的文本存储在拥有该窗口的进程的“虚拟内存”中。可能在 RAM 中,如果进程已经休眠了一段时间,则不太可能,肯定在磁盘的页面文件中。这并不能阻止 GetWindowText()
复制。当您调用它时,即可即时运行。 GetWindowText() 是有限的,它被记录为只能复制窗口的标题文本,因此它可能使用会话的桌面堆来检索文本。与 SendMessage() 等 winapi 函数不同,您可以使用 WM_GETTEXT 从编辑控件获取 GB。这肯定跨越了流程边界。 作为操作系统功能,SendMessage 当然可以打破所有适用于正常进程的规则。操作系统可以轻松寻址任意进程的虚拟机。经常被打破的规则,你的调试器也会这样做。您可以使用 ReadProcessMemory() 和 WriteProcessMemory() 等函数来打破规则。