程序编写的过程中,往往有些功能需要由其它的进程权限才能够完成这些工作,如得到其它进程中某个窗口的标题;建立隐藏的守护进程来监测本进程的运行情况;在Win7下,利用其它有高UAC权限的进程来完成一些功能等等。都需要使用到其它进程来完成这些工作。本文就来讲解如何获取其它进程中密码框中的密码。
以上代码实现及理论均为Win32平台为主。
A. 为什么选择远程线程 ?
我们知道,远程线程就是想让其它线程执行我们自己的代码,如果要想使自己的代码被其它进程执行,首先能想到的就是DLL,因为DLL可以被其它进程附加,并且可以执行所有的功能。但强大的功能也需要一个导火索来给它这个机会爆发,因为在进程运行进来后,LoadLibrary仍然可以使一个动态库附加映射进某个进程,但当我们执行LoadLibrary时,是在自己进程中执行的,无法使其它进程有机会执行这个代码,从而也就没机会使我们的代码附加到其它进程中,结果就是,强大的代码永远没有得到一个可以执行的机会。
而远程线程就是系统提供给我们的一个伯乐,它可以在你创建线程时,指定一个起始运行的入口,这样LoadLibrary就有了执行的机会,从而,DLL里面的代码就是会一气呵成的执行。
所以远程线程是必须的。
B. 一些必须了解的概念
1. 地址空间
在Win32平台下(Linux及主流平台),每个进程拥有4GB独立的地址空间,一般叫做线程地址空间,相对于每个进程来说,在自己的空间内访问一些地址内容,即访问变 量的值及执行一些代码都是非常方便的有效的,每个进程完成自己的工作,相互不会产生任何干扰。
2. 远程线程
一般而言,线程是线程可调试,实际完成一些工作(即执行一些代码)的最小实体,一个进程可以包含一个及以上的线程,这些线程并行工作,完成实际的功能。而远 程线程,故名思议,即非本进程的线程。假设有A,B两个进程,远程线程就是由A进程创建,但运行于B进程的地址空间中,拥有B进程的进程权限。
3. 代码注入
代码注入就是,将精心准备好的代码(即数据)放进某个进程的地址空间中。
C. 为什么不选择DLL的方式 ?
前面已经提到过,DLL里面就是可以执行的代码,我们只需要将DLL附加进进程就得以机会去执行这些代码。但DLL是以文件形式存放在磁盘中,有些功能可能会很 小,或者有些功能需要隐藏一些实现细节,如果附加一个DLL的话,效果并不是那么的友好,所以如果有机会不用DLL,依然能够使远程线程实现强大的功能,那自然 是最佳选择,但这也使得代码编写起来更难,也就需要更强的编程的基本功。
D. 需要使用到的主要API(以Win32平台为例)
OpenProcess:打开一个进程,得到该进程的句柄。
VirtualAllocEx:简言之为在指定的进程中申请一些空间。
WriteProcessMemory:简言之为在指定进程中写入一些数据。
CreateRemoteThread:在指定的进程中创建一个线程。
E. 具体实现 -- Talk is cheap, show me the code! (Linus)
以得到其它进程中某一窗体的标题为例,来简单的介绍远程线程的用法。
得到一个窗口的标题,一般会调用 GetWindowText,它的使用方法如下:
The GetWindowText function copies the text of the specified window's titlebar (if it has one) into a buffer. If the specified window is a control, thetext of the control is copied. However, GetWindowText cannot retrievethe text of a control in another application.
上面的使用方法是摘自MSDN上GetWindowText的描述,从描述中可以看到,GetWindowText不能够得到其它应用程序的标题,即一个进程的程序不能获得其它进程窗口的句柄。但如果那段代码是由其它进程来执行,执行后我们再得到结果,是不是就可以做到了呢。
假设我们的进程为A,我们想得到B进程中某一个控制的标题,要想完成以上功能,首先我们要能让我们的代码放到B进程的地址空间中,其次让B执行这些代码,最后从B进程中得到执行的结果。
代码实现如下(为突出有效代码,合法性检测都没有添加):
#include <iostream>
#include <windows.h>
using namespace std;
void Test();
void main()
{
Test();
}
__declspec(naked) void __stdcall GetWindowTextSpy(DWORD dwParam)
{
_asm
{
mov eax, [esp + 4] // 得到传入的参数 dwParam, 此时为窗口句柄
push [eax + 0] // 缓冲区长度
mov ebx, eax
add ebx, 8
push ebx // 缓冲区地址
push [eax + 4] // 目标窗口句柄
call GetWindowText
ret 4
}
}
__declspec(naked) void __stdcall EndLabel(DWORD dwParam)
{
}
void Test()
{