|
楼主 |
发表于 2008-7-22 16:22:47
|
显示全部楼层
|阅读模式
来自 山西省长治市
前段时间做了个查看星号密码框的工具。本以为挺简单的向星号密码文本框控件发送一个WM_GETTEXT消息就行了。这也是网上关于星号查看最流行的一种说法。然而根据我的测试这种方法是错误的!根本得不到任何内容,我猜测如果获得WM_GETTEXT消息的是一个星号密码框,Windows便会检测该消息的来源,若来自其它进程只则返回一个空字符串。
在Win98的时代这个方法也许会有效吧……
另外网上还有一种比较流行的说法是先向目标文本框发送EM_SETPASSWORDCHAR消息,以0作为wParam,去掉其密码遮盖符,再发送WM_GETTEXT消息取得文本框的内容,最后再次发送EM_SETPASSWORDCHAR消息恢复目标文本框的密码遮盖符。我测试该方法时不知为什么成功率非常非常低,大家可以去试试。
虽然向别的进程发送WM_GETTEXT消息不成功,但是通过WM_GETTEXT消息是肯定可以获得自己进程的文本框的内容的,于是我产生了一个想法:我们可以先注入文本框所属的进程,再通过该进程本身向文本框发送WM_GETTEXT消息,取得文本框内容后再将此内容发回给我们的程序!
以下是代码实现:
首先需要一个定时器Timer用于检测鼠标的位置。(当然也可以通过安装鼠标钩子实现)
在Timer的回调函数中,应该实现以下功能:
复制内容到剪贴板
代码:
GetCursorPos(&point);
//取得星号密码框的句柄
hwndFromPoint=WindowFromPoint(point)
//注入hwndFromPoint所在的进程以星号获取密码
StarInj(hwndFromPoint);
现在看看这个StarInj函数,该函数应该实现以下功能:
复制内容到剪贴板
代码:
//获得远程进程的PID
GetWindowThreadProcessId(hwndFromPoint,&dwProcessId);
//接着获得内核对象
hRemoteProcess=OpenProcess(……,dwProcessId);
//最后在远程进程中创建一个线程来执行ThreadProc函数,pRaram为传递给ThreadProc函数的参数
CreateRemoteThread(hRemoteProcess,……,ThreadProc, pParam,……);
于是,问题转化为了ThreadProc函数应该如何编写?我们先看看ThreadProc函数需实现的大致功能:
复制内容到剪贴板
代码:
DWORD _stdcall ThreadProc(void *pParam){
char str[256];
//向星号文本框发送WM_GETTEXT消息,取得密码后放入str变量中
SendMessage(hwndFromPoint,WM_GETTEXT,(WPARAM)255,(LPARAM)str);
//将str的内容发回给我们的本地进程的文本框,即将密码显示出来
//hwndLocal是本地进程用来显示密码的文本框的句柄
SendMessage(hwndLocal,WM_SETTEXT,NULL,(LPARAM)str);
}
至此,任务完全明确了。但是不要忘了Windows的内存管理方式:每个进程独享4G空间,互不干涉。因此以上代码还会产生很多问题!
1、ThreadProc并不在远程进程的地址空间中。也就是说传递给CreateRemoteThread的地址值在远程进程的地址空间中并不是ThreadProc函数的内容。
2、用于保存密码的局部变量str也不在远程进程的地址空间中,因为变量会放在本地进程的栈里。
3、ThreadProc应该如何去取得hwndFromPoint和hwndLocal呢?
4、SendMessage只不过是我们模块的输入节中形式替换程序的地址,并非SendMessage函数在远程进程中的实际地址!
现在我们来解决这些问题。
首先需要定义一个数据结构RemotePara,里面包含ThreadProc函数需要用到的一切数据,即:SendMessage函数的真实地址、hwndFromPoint和hwndLocal的值,以及需要用到的字符串str:
复制内容到剪贴板
代码:
typedef struct _RemotePara{
DWORD dwSendMessage;
DWORD hwndRemote;
DWORD hwndLocal;
char str[256];
}RemotePara;
RemotePara myRemotePara;
现在只需要正确的初始化myRemotePara,然后将它的地址作为ThreadProc的参数传递给CreateRemoteThread函数就行了。
当然,由于myRemotePara和ThreadProc都不在远程进程的地址空间中,所以我们必须在调用CreateRemoteThread之前把myRemotePara数据和ThreadProc函数都写入到远程进程的地址空间中。
现在来看看正确的代码吧
1、正确的ThreadProc函数实现:
复制内容到剪贴板
代码:
//by Fypher
DWORD _stdcall ThreadProc(RemotePara *lpPara){
//定义函数指针pSendMessage,目的是用来指向真实的SendMessage函数地址
typedef BOOL (WINAPI *pSendMessage)(HWND,UINT,WPARAM,LPARAM);
pSendMessage mySendMessage;
//从myRemotePara参数中得到SendMessage函数的地址
mySendMessage=(pSendMessage)lpPara->dwSendMessage;
//取得星号密码框的内容,放入lpPara->str中
mySendMessage((HWND)(lpPara->hwndRemote),WM_GETTEXT,
(WPARAM)255,(LPARAM)lpPara->str);
//将lpPara->str的内容发回给我们的本地进程的文本框,即将密码显示出来
mySendMessage((HWND)(lpPara->hwndLocal),WM_SETTEXT,
NULL,(LPARAM)lpPara->str);
return 0;
}
2、正确的Timer回调函数实现:
复制内容到剪贴板
代码:
//by Fypher
void Dlg_OnTimer(HWND hwnd, UINT id){
POINT point;
GetCursorPos(&point);
//取得星号文本框的句柄
HWND hwndFromPoint=WindowFromPoint(point);
if(!hwndFromPoint)
return;
//取得本地程序用来显示密码的文本框的句柄
HWND hwndLocal=GetDlgItem(hwndAPP,IDC_STAR_CONTENT);
//开始工作,呵呵
StarInj(&hwndFromPoint,&hwndLocal);
}
3、正确的StarInj函数实现(本程序最关键的函数):
复制内容到剪贴板
代码:
//by Fypher
void StarInj(HWND hwndRemote,HWND hwndLocal){
const DWORD THREADSIZE=2048;
DWORD ThreadId;
DWORD dwProcessId=NULL;
//得到星号文本框所属进程的ID
GetWindowThreadProcessId(hwndRemote,&dwProcessId);
if(!dwProcessId)
return;
//取得该进程内核对象
HANDLE hRemoteProcess = OpenProcess(PROCESS_CREATE_THREAD|//申请创建线程的权限
PROCESS_VM_OPERATION|//申请分配空间的权限
PROCESS_VM_WRITE,//申请写入空间的权限
FALSE,dwProcessId);
if(!hRemoteProcess)
return;
//在远程进程里分配空间,把我们的ThreadProc函数写进去!
void *pRemoteThread=VirtualAllocEx(hRemoteProcess,0,THREADSIZE,
MEM_COMMIT|MEM_RESERVE,
PAGE_EXECUTE_READWRITE);
if(!pRemoteThread){
CloseHandle(hRemoteProcess);
return;
}
if(!WriteProcessMemory(hRemoteProcess,pRemoteThread,
&ThreadProc,THREADSIZE,0))
VirtualFreeEx(hRemoteProcess,pRemoteThread,0,MEM_RELEASE);
CloseHandle(hRemoteProcess);
return;
}
//初始化我们的myRemotePara
RemotePara myRemotePara;
ZeroMemory(&myRemotePara,sizeof(RemotePara));
myRemotePara.hwndLocal=(DWORD)hwndLocal;//本地文本框的句柄
myRemotePara.hwndRemote=(DWORD)hwndRemote;//星号文本框的句柄
//取得SendMessage函数的真实地址,并赋值给myRemotePara.dwSendMessage
HINSTANCE hUser32=LoadLibrary(TEXT(\"user32.dll\"));
myRemotePara.dwSendMessage = (DWORD)GetProcAddress(hUser32,\"SendMessageA\");
if(!myRemotePara.dwSendMessage){
VirtualFreeEx(hRemoteProcess,pRemoteThread,0,MEM_RELEASE);
CloseHandle(hRemoteProcess);
return;
}
//在远程进程里分配空间,把我们的myRemotePara写进去!
RemotePara *pRemotePara=(RemotePara *) VirtualAllocEx(hRemoteProcess,0,
sizeof(RemotePara),MEM_COMMIT,
PAGE_READWRITE);
if(!pRemotePara){
VirtualFreeEx(hRemoteProcess,pRemoteThread,0,MEM_RELEASE);
CloseHandle(hRemoteProcess);
return;
}
if(!WriteProcessMemory(hRemoteProcess,pRemotePara,&myRemotePara,
sizeof(myRemotePara),0)){
VirtualFreeEx(hRemoteProcess,pRemoteThread,0,MEM_RELEASE);
VirtualFreeEx(hRemoteProcess,pRemotePara,0,MEM_RELEASE);
CloseHandle(hRemoteProcess);
return;
}
//一切准备就绪,开始执行!^_^
HANDLE hThread=CreateRemoteThread(hRemoteProcess,0,0,
(LPTHREAD_START_ROUTINE)pRemoteThread,
pRemotePara,0,&ThreadId);
//等待远程线程结束后,释放掉我们分配到的空间。
WaitForSingleObject(hThread,INFINITE);
VirtualFreeEx(hRemoteProcess,pRemotePara,0,MEM_RELEASE);
VirtualFreeEx(hRemoteProcess,pRemoteThread,0,MEM_RELEASE);
CloseHandle(hRemoteProcess);
}
说明一点:最后的释放空间是非常必要的一个步骤,因为我们在不停地向远程进程申请空间(取决于我们Timer的频率),如果不及时释放会很容易耗尽远程进程的内存空间,这样不仅占用内存,而且会导致我们的程序因分配不到空间而失去作用。
好了,至此程序已经完成90%了,但是还是不能工作,因为还有一个非常严重问题。大家可以试一下,目前这个程序一运行就会被挂起!问题在于我们的进程为了释放分配到的空间,因此必须等待远程线程的结束。而同时远程线程向我们本地进程的文本框发送消息时会等待本地进程的答复。于是两个线程形成死锁,导致我们的程序失去响应。
因此StarInj函数不能在Timer的回调函数中由主线程直接调用,应该改成在Timer的回调函数中生成一个新的线程来负责异步执行StarInj,这样问题就解决了。实现很简单,我就不举例了 |
|