vc++向其他进程注入代码的三种方法导言:我们在Codeproject()上可以找到许多密码间谍程序(译者注:那些可以看到别的程序中密码框内容的软件),他们都依赖于Windows钩子技术。要实现这个还有其他的方法吗?有!但是,首先,让我们简单回顾一下我们要实现的目标,以便你能弄清楚我在说什么。要读取一个控件的内容,不管它是否属于你自己的程序,一般来说需要发送WM_GETTEXT消息到那个控件。这对edit控件也有效,但是有一种情况例外。如果这个edit控件属于其他进程并且具有ES_PASSWORD风格的话,这种方法就不会成功。只有“拥有(OWNS)”这个密码控件的进程才可以用WM_GETTEXT取得它的内容。所以,我们的问题就是:如何让下面这句代码在其他进程的地址空间中运行起来:::SendMessage(hPwdEdit,WM_GETTEXT,nMaxChars,psBuffer);一般来说,这个问题有三种可能的解决方案:1.把你的代码放到一个DLL中;然后用windows钩子把它映射到远程进程。2.把你的代码放到一个DLL中;然后用CreateRemoteThread和LoadLibrary把它映射到远程进程。3.不用DLL,直接复制你的代码到远程进程(使用WriteProcessMemory)并且用CreateRemoteThread执行之。在这里有详细的说明:Ⅰ.Windows钩子示例程序:HookSpy和HookInjExWindows钩子的主要作用就是监视某个线程的消息流动。一般可分为:1.局部钩子,只监视你自己进程中某个线程的消息流动。2.远程钩子,又可以分为:a.特定线程的,监视别的进程中某个线程的消息;b.系统级的,监视整个系统中正在运行的所有线程的消息。如果被挂钩(监视)的线程属于别的进程(情况2a和2b),你的钩子过程(hookprocedure)必须放在一个动态连接库(DLL)中。系统把这包含了钩子过程的DLL映射到被挂钩的线程的地址空间。Windows会映射整个DLL而不仅仅是你的钩子过程。这就是为什么windows钩子可以用来向其他线程的地址空间注入代码的原因了。在这里我不想深入讨论钩子的问题(请看MSDN中对SetWindowsHookEx的说明),让我再告诉你两个文档中找不到的诀窍,可能会有用:1.当SetWindowHookEx调用成功后,系统会自动映射这个DLL到被挂钩的线程,但并不是立即映射。因为所有的Windows钩子都是基于消息的,直到一个适当的事件发生后这个DLL才被映射。比如:如果你安装了一个监视所有未排队的(nonqueued)的消息的钩子(WH_CALLWNDPROC),只有一个消息发送到被挂钩线程(的某个窗口)后这个DLL才被映射。也就是说,如果在消息发送到被挂钩线程之前调用了UnhookWindowsHookEx那么这个DLL就永远不会被映射到该线程(虽然SetWindowsHookEx调用成功了)。为了强制映射,可以在调用SetWindowsHookEx后立即发送一个适当的消息到那个线程。同理,调用UnhookWindowsHookEx之后,只有特定的事件发生后DLL才真正地从被挂钩线程卸载。2.当你安装了钩子后,系统的性能会受到影响(特别是系统级的钩子)。然而如果你只是使用的特定线程的钩子来映射DLL而且不截获如何消息的话,这个缺陷也可以轻易地避免。看一下下面的代码片段:BOOLAPIENTRYDllMain(HANDLEhModule,DWORDul_reason_for_call,LPVOIDlpReserved){if(ul_reason_for_call==DLL_PROCESS_ATTACH){//用LoadLibrary增加引用次数charlib_name[MAX_PATH];::GetModuleFileName(hModule,lib_name,MAX_PATH);::LoadLibrary(lib_name);//安全卸载钩子::UnhookWindowsHookEx(g_hHook);}returnTRUE;}我们来看一下。首先,我们用钩子映射这个DLL到远程线程,然后,在DLL被真正映射进去后,我们立即卸载挂钩(unhook)。一般来说当第一个消息到达被挂钩线程后,这DLL会被卸载,然而我们通过LoadLibrary来增加这个DLL的引用次数,避免了DLL被卸载。剩下的问题是:使用完毕后如何卸载这个DLL?UnhookWindowsHookEx不行了,因为我们已经对那个线程取消挂钩(unhook)了。你可以这么做:○在你想要卸载这个DLL之前再安装一个钩子;○发送一个“特殊”的消息到远程线程;○在你的新钩子的钩子过程(hookprocedure)中截获该消息,调用FreeLibrary和(译者注:对新钩子调用)UnhookwindowsHookEx。现在,钩子只在映射DLL到远程进程和从远程进程卸载DLL时使用,对被挂钩线程的性能没有影响。也就是说,我们找到了一种(相比第二部分讨论的LoadLibrary技术)WinNT和Win9x下都可以使用的,不影响目的进程性能的DLL映射机制。但是,我们应该在何种情况下使用该技巧呢?通常是在DLL需要在远程进程中驻留较长时间(比如你要子类[subclass]另一个进程中的控件)并且你不想过于干涉目的进程时比较适合使用这种技巧。我在HookSpy中并没有使用它,因为那个DLL只是短暂地注入一段时间――只要能取得密码就足够了。我在另一个例子HookInjEx中演示了这种方法。HookInjEx把一个DLL映射进“explorer.exe”(当然,最后又从其中卸载),子类了其中的开始按钮,更确切地说我是把开始按钮的鼠标左右键点击事件颠倒了一下。你可以在本文章的开头部分找到HookSpy和HookInjEx及其源代码的下载包链接。Ⅱ.CreateRemoteThread和LoadLibrary技术示例程序:LibSpy通常,任何进程都可以通过LoadLibrary动态地加载DLL,但是我们如何强制一个外部进程调用该函数呢?答案是CreateRemoteThread。让我们先来看看LoadLibrary和FreeLibrary的函数声明:HINSTANCELoadLibrary(LPCTSTRlpLibFileName//addressoffilenameoflibrarymodule);BOOLFreeLibrary(HMODULEhLibModule//handletoloadedlibrarymodule);再和CreateRemoteThread的线程过程(threadprocedure)ThreadProc比较一下:DWORDWINAPIThreadProc(LPVOIDlpParameter//threaddata);你会发现所有的函数都有同样的调用约定(callingconvention)、都接受一个32位的参数并且返回值类型的大小也一样。也就是说,我们可以把LoadLibrary/FreeLibrary的指针作为参数传递给CrateRemoteThread。然而,还有两个问题(参考下面对CreateRemoteThread的说明)1.传递给ThreadProc的lpStartAddress参数必须为远程进程中的线程过程的起始地址。2.如果把ThreadProc的lpParameter参数当做一个普通的32位整数(FreeLibrary把它当做HMODULE)那么没有如何问题,但是如果把它当做一个指针(LoadLibrary把它当做一个char*),它就必须指向远程进程中的内存数据。第一个问题其实已经迎刃而解了,因为LoadLibrary和FreeLibrary都是存在于kernel32.dll中的函数,而kernel32可以保证任何“正常”进程中都存在,且其加载地址都是一样的。(参看附录A)于是LoadLibrary/FreeLibrary在任何进程中的地址都是一样的,这就保证了传递给远程进程的指针是个有效的指针。第二个问题也很简单:把DLL的文件名(LodLibrary的参数)用WriteProcessMemory复制到远程进程。所以,使用CreateRemoteThread和LoadLibrary技术的步骤如下:1.得到远程进程的HANDLE(使用OpenProcess)。2.在远程进程中为DLL文件名分配内存(VirtualAllocEx)。3.把DLL的文件名(全路径)写到分配的内存中(WriteProcessMemory)4.使用CreateRemoteThread和LoadLibrary把你的DLL映射近远程进程。5.等待远程线程结束(WaitForSingleObject),即等待LoadLibrary返回。也就是说当我们的DllMain(是以DLL_PROCESS_ATTACH为参数调用的)返回时远程线程也就立即结束了。6.取回远程线程的结束码(GetExitCodeThtread),即LoadLibrary的返回值――我们DLL加载后的基地址(HMODULE)。7.释放第2步分配的内存(VirtualFreeEx)。8.用CreateRemoteThread和FreeLibrary把DLL从远程进程中卸载。调用时传递第6步取得的HMODULE给FreeLibrary(通过CreateRemoteThread的lpParameter参数)。9.等待线程的结束(WaitSingleObject)。同时,别忘了在最后关闭所有的句柄:第4、8步得到的线程句柄,第1步得到的远程进程句柄。现在我们看看LibSpy的部分代码,分析一下以上的步骤是任何实现的。为了简单起见,没有包含错误处理和支持Unicode的代码。HANDLEhThread;charszLibPath[_MAX_PATH];//LibSpy.dll的文件名//(包含全路径!);void*pLibRemote;//szLibPath将要复制到地址DWORDhLibModule;//已加载的DLL的基地址(HMODULE);HMODULEhKernel32=::GetModuleHandle(Kernel32);//初始化szLibPath//...//1.在远程进程中为szLibPath分配内存//2.写szLibPath到分配的内存pLibRemote=::VirtualAllocEx(hProcess,NULL,sizeof(szLibPath),MEM_COMMIT,PAGE_READWRITE);::WriteProcessMemory(hProcess,pLibRemote,(void*)szLibPath,sizeof(szLibPath),NULL);//加载LibSpy.dll到远程进程//(通过CreateRemoteThread&LoadLibrary)hThread=::CreateRemoteThread(hProcess,NULL,0,(LPTHREAD_START_ROUTINE)::GetProcAddress(hKernel32,LoadLibraryA),pLibRemote,0,NULL);::WaitForSingleObject(hThread,INFINITE);//取得DLL的基地址::GetExitCodeThread(hThread,&hLibModule);//扫尾工作::CloseHandle(hThread);::VirtualFreeEx(hProcess,pLibRemote,sizeof(szLibPath),MEM_RELEASE);我们放在DllMain中的真正要注入的代码(比如为SendMessage)现在已经被执行了(由于DLL_PROCESS_ATTACH),所以现在可以把DLL从目的进程中卸载了。//从目标进