我们知道将动态连接库注入到其他进程中有很多种方法。最常见的方法是使用钩子函数(Hook),但是这种方法主要有两个缺点:第一如果某个进程没有加载User32.dll,那么Hook DLL将永远也不会被加载。第二Hook DLL加载的时机问题,只有在进程发出User32调用的时候, Hook DLL才有可能被加载。也就是说假设进程正在进行复杂的数值计算而没有时间进行消息调用的时候,Hook DLL是不会被加载。理论上我们没有精确的办法来确定我们的Hook DLL是否已经注入到我们想要的进程中。另外一种最常见的方法是使用函数CreateRemoteThread,在其他进程中开启一个线程来装载DLL。应该说这是一种比较完美的解决放案,这种方法避免了上述使用钩子函数的所有缺点,但是遗憾的是这个函数只能使用在WinNT/2000下。
本文将讨论一种将动态连接库注入到其他进程中的一种新方法。它的思路与使用函数CreateRemoteThread的方法相类似,只不过可以使用在Win9x,Win2k,WinXP等操作系统下。在这里我们将向读者演示我们是如何将DLL(InjectDll.dll)注入到Explorer.exe进程中!
程序的思路如下
1:得到Explorer.exe进程中任意一个线程的ID.
2:根据这个线程的ID,得到这个线程的句柄Handle
3:挂起这个线程,并保存线程当前的“上下文”
4:改变这个线程的EIP指针,使它指向我们装载DLL的函数(InjectCodeFun),然后恢复这个线程。
5:我们的装载DLL的函数运行完成后,再次挂起这个线程,使用我们以前保存的“上下文”,恢复这个线程到它被改变前的状态,并继续运行。
经过上述几个步骤,Explorer.exe进程中就会替我们装载我们的DLL(InjectDll.dll)了,有趣的是Explorer.exe对此丝毫没有察觉任何异常 !
下面我们将详细解释一下如何编程实现上述过程。
步骤1的实现是很容易的,我们只需要调用ToolHelp的函数就可以得到我们所要得,这里我们就不详细说明了,请参考源代码中GetProcessID, GetThreadID 两个函数。
步骤2就比较麻烦了,在Win9x中没有提供一个函数可以由Thread ID得到Thread Handle(幸运的是Win2K提供这种功能)。好在我们在国外一些BBS上可以找到这个函数,它使用了一些未公开的结构,本文的目的不是讨论这个问题,读者如果有兴趣的话,可以参考我们的源代码OpenThread2函数。这个函数的作用就是传入一个Thread ID参数返回相应的 Thread Handle。
步骤3 的实现也是很容易和规范的,我们可以用SuspendThread,GetThreadContext等SDK函数轻松完成。
步骤4 这个步骤是最重要的步骤了。为了说明方便,我们将引用我们源代码中的语句,请读者参考源代码中InjectCodeIntoThread 函数。
首先改变线程的EIP指针,我们可以用下列代码完成
ThreadContext.Eip = (DWORD)m_lpCodeBase;
SetThreadContext(m_hInjectThread,&ThreadContext);
变量m_lpCodeBase指向我们的装载DLL的函数(InjectCodeFun)的首地址。
这里最关键的部分是我们如何产生我们的装载DLL的函数(InjectCodeFun)。注意我们不能简单地在我们的程序里写一个函数,然后将它的首地址赋值给EIP。这是因为装载DLL的函数是要运行在Explorer.exe地址空间中的,如果我们使用自己地址空间中的函数的话,那么必然会导致系统崩溃。解决的办法是将我们写的装载DLL函数(InjectCodeFun)放在所有程序共享的地址空间中去,在Win9x中0x80000000 ~ 0xFFFFFFFF这段地址就是我们想要的共享地址空间,那么如何将我们写的装载DLL函数放在这段地址空间呢 ?方法有很多,我们使用一种规范的方法“内存映像文件”来解决这个问题。我们通过函数CreateFileMapping来分配一段共享地址空间,然后将我们写的装载DLL函数拷贝到这段地址空间中去。具体代码请参源代码中InitInject函数。
在我们写的装载DLL函数(InjectCodeFun)中还有两个问题我们需要解释一下,第一 在这个函数中我们不能使用任何我们自己程序中定义的变量,道理跟上面讲的一样,因为地址空间不同。还有我们不能直接调用函数,例如在InjectCodeFun中直接使用LoadLibray。这是因为如果直接使用LoadLibray那么就需要经过程序的Import表,跳转一下才能到达真正的Windows的LoadLibray函数。但是不同的进程有不同的Import,所以我们不能直接调用函数。我们可以使用一种叫做“动态构造函数”的技术来创建我们的函数。首先用GetProcAddress得到函数LoadLibray的直接地址,然后在调用LoadLibray的地方,使用一个特殊的数字来代替它如 0x11111111,最后在将我们的函数拷贝到共享地址空间之后,搜索共享内存找到这个特殊数字,用我们先前得到的正确地址替换它既可。第二个有趣的现象是我们所写的装载DLL函数(InjectCodeFun)是不应该返回的。这是因为这个函数是在Explorer的线程中运行的,我们不知道堆栈的正确内容,不知道ESP所指向的地址是什么,如果函数返回的话,我们将失去对程序的控制。我们的办法是,当调用完LoadLibray之后,向我们的主程序发送一个自定义消息,通告我们的程序已经完成装载任务,然后让线程进入死循环状态。
步骤5当我们的程序接受到了自定义消息后,就会再次挂起这个线程,把我们以前保存的线程的“上下文”用函数SetThreadContext恢复,然后恢复运行这个线程。结果是Explorer.exe丝毫没有感觉到自己被中断过。
以上就是我们所介绍的方法,读者可以参考我们的源代码来具体了解上述方法。源代码的功能是将我们的DLL(InjectDll.dll)注入到Explorer.exe 中,在InjectDll.dll中我们创建了一个新的线程,然后在屏幕的左上角显示当前的时间。源代码分为Win9x版本和Win2k版本,这两个版本的主要差别是分配共享内存的方法不同而已。源代码已经在PWn98,PwinMe,Win2k,WinXP等操作系统下,使用VC6编译通过。
读者可以在下列网址:http://webaide.myetang.com/ 或http://netaide.top263.net/ 的“下载”页面中找到源代码。
作者: RobinHao (webaide2k@sina.com)
转载请征得作者同意.