条目1、使用注册表来注入DLL。(P575)
原理:当User32.dll被映射到一个新的进程时,它的DLL_PROCESS_ATTACH通知会取得注册表HKEY_LOCAL_MACHINE/Software/Microsoft/Windows NT/CurrentVersion/Windows/AppInit_DLLs中的DLL列表,逐个加载。这个DLL列表的第一个DLL文件可以包含路径,其他DLL包含的路径则将被忽略,根据“随笔记录19中条目8”中的搜索算法对DLL的文件映像进行定位。为了使系统可以使用这个注册表项,我们还必须在与注册表项AppInit_DLLs同一级别下创建一个名为LoadAppInit_DLLs,类型为DWORD的注册表项,并将其值设置1。由于被注入的DLL在进程生命期的早期被载入,因此我们要慎重调用一些函数。另外,User32.dll不会检查每个DLL的载入或初始化是否成功。
示例代码:
(1) 创建用于注入的测试DLL,将其放在windows/system32目录下。
(2) 修改注册表信息,向AppInit_Dlls添加DLL名称:InjLib.dll (逗号隔开)
(3) 创建会使用User32.dll的客户端程序。
条目2、使用WINDOWS挂钩来注入DLL。(P576)
原理:我们可以通过SetWindowsHookEx函数将DLL注入到进程的地址空间中,最后一个参数dwThreadId指向的是被注入进程内的某个线程ID。这里以WH_GETMESSAGE挂钩来描述其执行步骤。(请先通过MSDN了解SetWindowsHookEx函数)
(1) 进程A调用SetWindowsHookEx(WH_GETMESSAGE,lpfn,hMod,dwThreadId)对线程dwThread执行WH_GETMESSAGE挂钩
(2) 线程dwThreadId准备提取消息进行处理,这时它会被系统监控到
(3) 系统检查SetWindowsHookEx中hMod指向的DLL是否已被载入到线程dwThreadId所在的进程(假设进程B)地址空间中,若否,则载入。这时,假设DLL被载入到进程B的hMod2位置。
(4) 系统计算lpfn在进程B中的位置,计算方式:lpfn + hMod2 - hMod1。
(5) 系统在进程B中递增该DLL(hMod2)的锁计数
(6) 系统在进程B的地址空间中调用lpfn函数(其位置在:lpfn + hMod2 - hMod1)
(7) lpfn函数返回时候,系统递减该DLL(hMod2)在进程B中的锁计数
示例代码:
(1) 创建用于注入的测试DLL。
(2) 创建客户端用于将DLL注入到目标进程
条目3、使用远程线程注入DLL。(P587)
原理:在目标进程中开启一个线程以执行LoadLibrary(Ex)操作。
步骤:
(1) 用VirtualAllocEx函数在远程进程的地址空间中分配一块内存
(2) 用WriteProcessMemory函数把DLL的路径名复制到(1)步骤分配的内存中
(3) 用GetProcAddress获取LoadLibrary(Ex)函数的实际地址
(4) 用CreateRemoteThread函数在远程进程中创建一个线程,让新线程调用正确的LoadLibrary(Ex)函数并在参数中传入(1)步骤分配的内存地址
(5) 用VirtualFreeEx来释放(1)步骤分配的内存
(6) 用GetProcAddress获取FreeLibrary函数的实际地址
(7) 用CreateRemoteThread函数在远程进程中创建一个线程,让新线程调用正确的FreeLibrary函数并在参数中传入远程DLL的HMODULE
示例代码:
(1) 创建用于注入的DLL
(2) 创建客户端用于将DLL注入到目标进程
条目4、使用木马DLL来注入DLL
原理:将进程必然会载入的DLL替换掉。我们可以采取以下两种方式进行替换
(1) 创建自己的DLL,把它取名为进程要载入的DLL文件名,把原DLL改成别的名称。
(2) 搜索进程文件的导入段,更改其导入段中的DLL名称,使之指向我们创建的DLL。
示例代码(1),我们采用“《WINDOWS核心编程第5版》随笔记录20”中的条目20,对目标程序强制执行DLL重定向。为了不影响目标程序原先的代码执行,凡涉及到调用原DLL的函数,我们采用“《WINDOWS核心编程第5版》随笔记录20”中的条目18,函数转发器修改新DLL。步骤如下:
(1) 创建目标程序使用的DLL,名为:OrgLib
(2) 创建目标程序,让其包含OrgLib!fun数。
(3) 创建木马DLL,让其和OrgLib同名,把原来的OrgLib.dll改名为OrgLibR.dll
(4) 创建Trojan文件夹,将目标程序TrojanClient.exe和木马DLL(OrgLib.dll)放在里面,并在该文件夹下创建TrojanClient.exe.local文件(夹)。把OrgLibR.dll的路径放入PATH环境变量中(或用set path命令行设置)。
(5) 测试运行,我们会发现TrojanClient.exe加载的是木马DLL,并运行无误。
示例代码(2),我们通过修改目标程序的PE段,修改其加载的DLL名称,这里要注意的是,我们的木马DLL名称的长度必须和被修改DLL名称的长度相同。
(1) 创建目标程序,让其包含User32!MessageBoxA函数。
通过dumpbin -imports Client.exe 查看User32.dll已在导入段中。
Dump of file client.exe
File Type: EXECUTABLE IMAGE
Section contains the following imports:
USER32.dll <--- 这里!
4080F0 Import Address Table
409620 Import Name Table
... ...
(2) 创建注入DLL,它将用来替换目标程序中的User32.DLL
(3) 创建补丁程序,修改目标程序中的DLL导入名称。
运行RepairImportDll.exe Client.exe User32.dll InjLib.dll 后我们查看Client.exe的导入段:
Dump of file Client.exe
File Type: EXECUTABLE IMAGE
Section contains the following imports:
INJLIB.DLL <-------这里!已经被改变。
4080F0 Import Address Table
409620 Import Name Table
0 time date stamp
0 Index of first forwarder reference
1DF MessageBoxA
KERNEL32.dll
... ...
(4) 测试运行,Client.exe运行无误。
条目5、把DLL作为调试器来注入(P598)
原理:调试器在载入被调试程序的时候,被调试程序会在地址空间准备完毕但主线程会尚未开始执行之前通知调试器。我们可以在这个时候给被调试程序注入一段加载DLL的代码。然后把被调试程序的EIP设置为这段加载代码的起始位置使之执行。调试API的知识请Google:Win32调试API,有三部分。
步骤:
(1) 通过DebugActiveProcess将目标进程附加到调试器上。
(2) 调用DebugSetProcessKillOnExit避免调试器退出时,目标进程也跟着退出。
(3) 通过WaitForDebugEvent建立调试循环体,一旦接收到通知,调试信息便会被填充到DEBUG_EVENT结构中。
(4) 初次附加时会产生CREATE_PROCESS_DEBUG_EVENT通知,在这里我们把一段加载DLL的代码写入到目标进程的地址空间A中。接着,我们通过GetThreadContext获取目标进程的当前CONTEXT信息,保存一份CONTEXT副本,然后更改其中EIP值为A。最后,通过SetThreadContext保存回去,让目标程序按新的EIP继续执行。
(5) 目标程序执行我们注入的代码,执行到INT3时,会产生EXCEPTION_BREAKPOINT(在EXCEPTION_DEBUG_EVENT中)通知(记住,在执行被调试程序的第一条指令前windows将发送一个EXCEPTION_BREAKPOINT通知)。在这里我们把先前保存下来的CONTEXT通过SetThreadContext还原回去。让目标程序按原先的EIP继续执行。
示例代码:
(1) 创建用于注入的DLL
(2) 创建调试器,用于将DLL注入到目标进程
(3) 测试运行,执行InjDebuggee 7572 f:/injdebuggee/injlib.dll,目标进程成功被注入DLL。
条目6、使用CreateProcess创建子进程的方式注入代码
原理:父进程在创建子进程的时候将子进程挂起,这时候父进程可以通过子进程的主线程句柄对线程执行的代码进行修改。
步骤:
(1) 让进程生成一个被挂起的子进程。
(2) 从.exe模块(子进程)的头文件中取得主线程的起始内存地址。
(3) 将位于该内存地址处的机器指令保存起来。
(4) 强制将一些手工编写的机器指令写入到该内存地址处。这些指令应该调用LoadLibrary来载入一个DLL。
(5) 让子进程的主线程恢复运行,从而让这些指令得到执行。
(6) 把保存起来的原始指令恢复到起始地址处。
(7) 让进程从起始地址继续执行,就好像什么也没发生过一样。
示例代码:
(1) 创建用于注入的DLL
将其放在任意目录下,比如:F:/InjLib.dll。
(2) 创建用于注入DLL的代理DLL,此代理DLL用于执行真正DLL的注入工作。
(3) 创建客户程序,用于将DLL注入到目标程序中。
(4) 测试运行,将InjSubProcess.exe和InjLibProx.dll放在同一目录下,运行:InjSubProcess F:/bak/ipmsg.exe F:/InjLib.dll,目标进程被成功注入InjLib.dll。
条目7、通过覆盖代码来拦截API
原理:在内存中对要拦截的函数进行定位,覆盖其头几个字节使之跳转到我们自身的函数内。
不足:首先,对CPU有依赖性:x86、x64、IA-64以及其他CPU的jmp指令各不相同。其次,在抢占式、多线程环境下易出错!
示例代码:
条目8、通过修改模块的导入段来拦截API
原理:通过修改目标进程的导入段中函数来达到拦截API的目的。
示例代码:
测试运行:当程序退出时,系统弹跳出对话话提示我们!
以上代码基于XP SP2系统所做的测试,不保证在其他版本的WINDOWS系统上运行正确。