这是<一篇文章带你...>系列的第四篇,主要会阐明DLL注入的基本原理和几种主流方式,虽然这些方法已经有点滞后了。但是DLL注入的基本原理是不会改变的。自己做的笔记帮助自己理解。准备匆忙。大佬们轻拍。
DLL注入的主要原理就是强制进程自己将需要注入的dll文件注入到自身进程空间内,最好配合Hook技术。
Dll注入可以从三个方向入手:第一:在进程创建初期按照导入表加载dll的时候。第二:进程运行时期利用LoadLibrary函数加载,第三:利用某些系统机制:例如windows消息机制等。
此方法是最常见的dll注入的方法,原理是由于CreateRemoteThread的函数原型和CreateThread是一致的。所以模仿CreateThread创建线程的方式实现注入。
由于创建的是远程线程,是需要将注入的参数(也就是Dll文件的路径)写入目标进程空间。所以基本步骤如下:
打开目标进程句柄
向目标进程中开辟空间并写入Dll文件路径
获取LoadLibrary的地址
利用CreateRemoteThread函数调用LoadLibrary加载dll
RtlCreateUserThread是CreateRemoteThread的底层实现,所以使用RtlCreateUserThread的原理是和使用CreateRemoteThread的原理是一样的。唯一的区别是使用CreateRemoteThread写入目标进程的是Dll的路径,而RtlCreateUserThread写入的是一段shellcode。
和CreateRemoteThread一样都是需要获取目标进程句柄,获取LoadLibrary地址,dll路径。
接着我们需要获取RtlCreateUserThread地址,RtlCreateUserThread函数原型如下:
如何构造shellcode?
将shellcode写入进程内存中,然后调用RtlCreateUserThread执行shellcode。
APC中文名称为异步过程调用, APC是一个链状的数据结构,可以让一个线程在其本应该的执行步骤前执行其他代码,每个线程都维护这一个APC链。当线程从等待状态苏醒后,会自动检测自己得APC队列中是否存在APC过程。
所以只需要将目标进程的线程的APC队列里面添加APC过程,当然为了提高命中率可以向进程的所有线程中添加APC过程。然后促使线程从休眠中恢复就可以实现APC注入。
首先依旧是将DLL文件路径写入进程。
然后使用QueueUserAPC将APC例程添加到APC队列中,QueueUserAPC三个参数分别是APC例程,线程句柄,例程参数。所以还需要获取线程句柄
当然,为了提高命中率,可以使用遍历所有线程,然后利用te32.th32OwnerProcessID是否等于目标进程PID的策略进行进程全局注入。
使用傀儡进程:以挂起方式创建进程,然后向其中写入shellcode,利用shellcode执行LoadLibrary
首先以挂起方式创建进程,CreateProcess的第6个参数可以设定进程创建的方式
然后需要保存进程的上下文信息,主要是EIP的值。以便于Load完成后返回。
接着需要构建我们替换执行的代码。这段代码的目的是Load我们的恶意的dll文件,所以至少需要做两个方面的准备,第一:需要知道LoadLibrary的地址。第二需要知道Dll的路径。为了让程序更好的运行还需要保存现场。最后利用ret方式返回。
构造完这些之后将shellcode写入,由于内存本身就有执行属性,所以不需要修改内存属性。
最后利用SetThreadContext将我们的EIP设置为shellcode起始地址。并调用ResumeThread重启线程
为了提高命中率可以向目标进程的所有线程进行注入.利用CreateToolhelp32Snapshot
等三个函数遍历线程。
反射式dll注入不需要dll文件落地,减少被查杀的风险。首先将需要注入的dll写入进程内存,然后为该dll添加一个导出函数,利用这个导出函数让其自动的装载dll。注射器是将DLL文件写入目标进程内存。反射装载器实现的就是模拟dll装载器装载dll文件的操作。
首先说一下注射器,注射器的目的是将Dll文件写入到目标进程空间,然后获取里面导出函数ReflectiveLoader。
首先需要打开目标进程句柄
向目标进程写入DLL数据
利用导出表获取导出函数地址ReflectiveLoader。
然后利用CreateRemoteThread跨线程调用ReflectiveLoader。
接下来就是ReflectiveLoader的编程,ReflectiveLoader其实就是一个dll加载器。
首先需要知道当前dll的基地址,而ReflectiveLoader肯定位于这个DLL中所以在此函数地址出逐次递减,然后判断是否存在MZ标志,MZ的地址就是DLL的基地址
接着获取注射器所需要的API函数LoadLibrary,GetProcess,VirtualAlloc的地址。
有了前面这几个API函数做支撑,可以将DLL文件载入到内存,这个是模拟装载器的载入,并不是单纯的读取。
然后就是修正IAT和重定位表。修正OEP。和加壳很像!
总结一下
切换输入法时候,输入法管理器imm32.dll就会加载IME模块,这样就形成了输入法注入的充要条件。而由于这个Ime文件本质上只是个存放在C:\WINDOWS\system32目录下的特殊的DLL文件,因此我们可以利用这个特性,在Ime文件中使用LoadLibrary()函数待注入的DLL文件。
首先就是编写ime文件,至少需要两个导出函数BOOL ImeClass_Register(HINSTANCE hinstDLL)
,BOOL WINAPI ImeInquire(LPIMEINFO lpIMEInfo, LPTSTR lpszUIClass, LPCTSTR lpszOption)
这两个是必须要实现的。剩下的导出函数可以选择不去实现。
接着编写注射器。利用ImmInstallIME安装ime文件,当输入法切换的时候就可以注入dll了。
我的理解是:windows维护着消息队列,应用程序会从队列中取出消息,不同的消息有着不同的编号,我们根据编号idHook
,设置不同钩子。如何设置钩子?可以利用SetWindowsHookEx这个API函数,函数原型如下:第一个参数是消息编号,第二个参数为Hook函数
具体操作如下:
DLL劫持法(输入表DLL替换法),原理是利用搜索DLL路径存在先后顺序(exe程序目录>系统目录>当前目录>Path),当较高层存在一个同名的DLL文件的时候,就会直接加载较高层的DLL文件。常常用于病毒的白加黑。需要注意的是黑DLL路径优先级一定要高于原来的dll文件,第二,一定要具有源dll文件所有的导出函数。
毕竟新创建的进程在加载User32.dll时,都会自动调用LoadLibrary去加载注册表中某个表项键值里写入的Dll路径
在创建远程线程创建初期在DllMain中防御远程线程,此时尚未调用LoadLibrary。可以对线程的合法性判断
LoadLibrary之前首先可以挂钩LoadLibrary函数,然后检查dll路径合法性
LoadLibrary之后枚举可疑内存和模块
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)