谈谈 DLL 注入的几种方式
云游戏的键鼠捕捉用到了 Hook 这个技术,就顺便翻了一下核心编程,写下来备忘。
使用注册表注入
- x32 :
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Windows
- x64 :
HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Microsoft\Windows NT\CurrentVersion\Windows
这个位置下 AppInit_DLLs
和 LoadAppInit_DLLs
。 将后者置为 1,然后在前者的值输入待注入 DLL 名称(多个 DLL 用 Space
分隔,第一个可以带路径,后边的不行)。
- 优点:方法简单
- 缺点:只有引用了 User32.dll 的才会被注入,而且被注入进程一启动就会注入,结束才会反注入,注入周期不可控
使用 Windows Hook 注入
核心方法:
SetWindowsHookEx
UnhookWindowsHookEx
设置挂钩的时候,可以指定挂钩类型、线程 ID 、回调方法、DLL 句柄。这些已经可以准确的注入到一个进程中了。通过 Set、Unhook 两个方法可以准确的控制注入周期。
- 优点:准确的控制注入周期
- 缺点:依赖消息循环,没消息循环的线程没法注入
远程线程注入(大杀器)
核心方法:
CreateRemoteThread
VirtualAllocEx
VirtualFreeEx
ReadProcessMemory
WriteProcessMemory
可以用 CreateRemoteThread
在指定进程中创建一个线程,让它执行我们自己的代码,这样可以让远程线程 Load 一个我们自己的 DLL,这就可以为所欲为了……需要注意的是,CreateRemoteThread
的参数 PTHREAD_START_ROUTINE
这个函数地址,需要调用 GetProcAddress
。如果直接传函数地址,是本进程的……显然是不对的。
PTHREAD_START_ROUTINE pfnThreadRtn = (PTHREAD_START_ROUTINE)GetProcAddress(GetModuleHandle(TEXT("Kernel32")), "LoadLibraryW");
而函数参数也要使用 VirtualAllocEx
来分配内存,然后 WriteProcessMemory
写入。这才是远程进程可以获取的。
- 优点:可以为所欲为
- 缺点:写起来有点复杂
同名 DLL 替换
如果已知一个程序必然载入一个 DLL ,则可以做一个同名 DLL 然后将方法跟原 DLL 保持一致,在方法调用中间做点其他手脚。这种如果程序校验 DLL ,就没戏。或者直接修改程序的 EXE 的导入段,这就要求对 PE 结构非常熟悉。
作为调试器注入
核心编程没说的太细,看起来要写 CPU 代码,没太细看。感觉也挺复杂,不太实用。
修改子进程的主线程开始位置代码
如果要注入的进程是子进程,可以创建它的时候挂起它,然后从 exe 模块中拿到子进程的主线程起始地址,把这里记下来,之后改成执行自己的代码,这个时候恢复子进程主线程,就可以执行自己的代码了,然后再把之前保存的执行一波。看起来也挺复杂,核心编程也没给具体例子。
API 拦截
第一种:把要拦截的 API 起始位置的几个字节保存起来,然后将此位置改写为 CPU 的 JUMP 指令,跳转到自己的方法。不过这种方法非常危险,不建议用。
第二种:修改模块导入段拦截 API
核心方法:
ImageDirectoryEntryToData
WriteProcessMemory
VirtualProtect
获取到模块的导入段信息,之后查找到指定方法,用自己的方法替换原来的方法。如果 WriteProcessMemory
失败,就用 VirtualProtect
修改一下页面保护属性。