Windows 2000之后,微软的GetWindowText提高了安全性,对于其它进程的窗口,如果是个有ES_PASSWORD的Edit,就返回ERROR_ACCESS_DENIED。XP SP2和2003中,微软干脆不让取任何进程外Edit窗口(包括RichEdit各个版本)的文字,无论是否为密码框。
传统的方法是写一个鼠标或者键盘的全局钩子DLL,Windows会将它加载到所有装载了USER32.DLL的进程中。这里讨论另外一种解决方法,即通过远程线程。
Jeffrey Richter的名著Programming Application for Microsoft Windows (4/e)介绍了这个概念,如果不了解远程线程,可以先参考一下。那本书的方法是利用LoadLibraryA/W的原型和ThreadProc一致,在CreateRemoteThread时,传入LoadLibraryA/W的地址和dll名字,就可以将DLL装入。
不借助DLL,就只能创建一个真正的远程线程。这里,我们会碰到下面几个问题:
(1) 参数如何传入;
(2) 如何将本进程的代码拷贝到另外一个进程中;
(3) 如何在另外一个进程中调用Windows API;
(4) 结果如何传回本进程。
(1)和(2)的救星是WriteProcessMemory,在此之前,本进程必须得到Debug特权。这是通过OpenProcessToken/LookupPrivilegeValue和AdjustTokenPrivileges来完成的。
(3)的解决方法比较特殊,由于PE的重定位机制,一个进程内的MessageBoxA地址有可能是0x7B001234,在另外一个进程中,就是0x7C001234。然而有一个例外,就是KERNEL32.DLL。这个DLL的加载地址*几乎*不会因进程而变化(有一些例外)。利用这个特性,我们就可以把本进程的两个函数:LoadLibraryA和GetProcAddress的地址,在远程线程中调用。调用的参数所需的内存可以由(2)在本进程分配,在远程线程中使用。下面的代码演示了如何在远程线程中调用USER32.DLL的GetWindowTextA函数,其中P是为远程线程分配的内存块:
push P.pTagUser32
call P.pLoadLibrary
mov HM, eax
push P.pTagGetWindowTextA
push eax
call P.pGetProcAddress
push dword ptr [P.cbText]
push dword ptr [P.Text]
push dword ptr [P.Wnd]
call eax
对于问题(4),无非就是一个IPC机制。我们所需要的,是尽可能简单、代码短小的一个。WM_COPYDATA是这种要求下最理想的选择,参考下面代码:
push P.pTagSendMessageA
push HM
call P.pGetProcAddress
push PCS
push dword ptr [P.Wnd]
push WM_COPYDATA
push dword ptr [P.WndDest]
call eax
这样,在本进程接受WM_COPYDATA,就可以收到远程线程发来的、另一个进程中Edit框中的文字。