我想弄清楚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; }
我附加了一个debugging器,并通过GetWindowTextA调用,手动逐步除了这些API调用(按顺序):
这些API调用似乎都不能读取调用进程不拥有的内存中的string。 我使用了一个用户模式的debugging器,所以我当然没有意识到它在步进时没有以kernelmode结束。 这意味着GetWindowText获取窗口名称,而不执行上下文切换。 这似乎意味着存在的每个窗口的文本都可以在没有上下文切换的情况下访问……并且这是不对的,因为Windows没有办法为系统上的每个窗口/控件保留一个文本的副本,每个窗口单一过程。
我读过这篇文章 。 它提到窗口名称存储在引用“一个特殊的地方”,但没有解释如何从一个不同的进程访问这个“特殊的地方”没有系统调用/上下文切换。
所以我正在寻找任何解释如何做到这一点。 任何信息,你可以提供非常感谢。
GetWindowText
获取窗口名称,而不执行上下文切换。 这似乎意味着存在的每个窗口的文本都可以在没有上下文切换的情况下访问。
此信息存储在所有使用user32.dll
的进程之间共享的内存中。 您可以尝试在您的进程的虚拟空间中搜索其他进程窗口的unicode名称。
它在user32.dll
加载期间被映射到进程地址空间。 有一些内核结构/部分涉及: win32k!gSharedInfo
, win32k!ghSectionShared
, win32k!gpsi
和其他(我不知道的)。
实际上, HWND
的低16位表示索引到基地址为*(&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); }
在WM_GETTEXT
调用的DefWindowProcWorker
将解析inner_hwnd
引用的结构,并将窗口名称复制到buf
。
看来EDITTEXT控件的文本不是以这种方式存储的
我从来不知道存储在那里的所有信息,尽管用各种user
/ gdi
参数不污染进程的虚拟空间似乎是一个不错的选择。 此外,较低的完整性流程不应该能够获得更高的完整性流程敏感信息。
因为Windows无法保存每个窗口的文本副本
文本肯定存在,只是不作为副本。 窗口的文本存储在拥有窗口的进程的虚拟内存中。 可能在RAM中,如果这个进程已经休眠了一段时间,绝对不是在分页文件的磁盘上。 这不会停止GetWindowText() 进行复制。 即时通话,当你打电话。
GetWindowText()是有限的,它被记录为只能够复制窗口的标题文本,所以它可能会使用桌面堆会话来检索文本。 除此之外,不要限制像SendMessage()这样的winapi函数,你可以使用WM_GETTEXT从Edit控件获取一个GB。 这肯定是跨越了过程的边界。
作为操作系统函数,SendMessage当然可以打破适用于正常进程的所有规则。 操作系统在处理任意进程的虚拟机时没有问题。 通常被破坏的规则,你的调试器也会这样做。 使用可以用来打破规则的函数ReadProcessMemory()和WriteProcessMemory()。