在注册表路径HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersioin\Windows\下,AppInit_Dlls键的值可能会包含一个DLL的文件名活一组DLL的文件名(通过空格或逗号分隔)。将自己写的DLL文件的路径值写入AppInit_Dlls中,再创建一个名为LoadAppInit_Dlls,类型为DWORD的注册表项,并将其值设为1.当User32.dll被映射到一个新的进程时,会受到DLL_PROCESS_ATTACH通知。当User32.dll对它进行处理的时候,会获取上述注册表键的值,并调用LoadLibrary来载入这个字符串中指定的每个DLL。
调用函数SetWindowsHookEx来安装钩子,此函数的声明如下:
HHOOK WINAPI SetWindowsHookEx(
In int idHook,
In HOOKPROC lpfn,
In HINSTANCE hMod,
In DWORD dwThreadId
);
idHook表示要安装的挂钩的类型,lpfn是一个函数的地址,在窗口即将处理一条消息的时候,系统应该调用这个函数,hMod标识一个DLL,这个DLL包含了lpfn函数,dwThreadId表示要给哪个线程安装挂钩。如果这个参数传0,表示要给系统中所有GUI线程安装挂钩。
例如进程A使用SetWindowsHookEx(WH_GETMESSAGE,GetMsgProc,hInstDll,0)函数安装挂钩后:
进程B中的一个线程准备向一个窗口派送一条消息 系统检查该线程是否安装了WH_GETMESSAGE挂钩 系统检查GetMsgProc所在的DLL是否已经被映射到进程B的地址空间中,如果DLL尚未被映射,那么系统会强制将该DLL映射到进程B的地址空间中,并将进程B中该DLL的锁计数器递增 由于DLL的hInstDll是在进程B中映射的,因此系统会对他进行检查,看他在进程A中的位置是否相同,如果相同,那么在两个进程空间中,GetMsgProc函数位于相同的位置,系统就可以直接在进程A的地址空间中调用GetMsgProc。如果不相同,那么系统必须确定GetMsgProc函数在进程B的地址空间中的虚拟内存地址。使用公式GetMsgProc B=hInstDll B+(GetMsgProc A-hInstDll A)获得 系统在进程B中递增该DLL的锁计数器 系统在进程B的地址空间中调用GetMsgProc函数 当GetMsgProc返回的时候,系统递减该DLL在进程B中的锁计数器简单来说就是DLL文件替换。通俗说法如下:
A.exe想要调用B.dll,并且使用里面的FunC函数,这样的话我们把B.Dll改名BB.Dll(有的不用,直接根据路径劫持),然后我们自己写一个B.Dll(假的)里面有一个FunC这个函数,然后我们在这个函数里加载BB.Dll(原B.Dll),并且调用里面的FunC函数,之后我们在干一些自己的事,对于A.exe来说通常没什么异常感觉,这样我们的目的就达到了,记住此时的你,也就是B.dll(假的)的权限和内存归属都是A的,也即是你和A是一家的了,类似于代码注入之后直接修改内存一样。
WIndows上的Dll加载有一个默认的规则,就是先在主程序目录下查找B.dll,如果没有就在系统路径下找,如果还没有,就去环境变量路径里找,就因为这个我们可以轻松的在相应的位置给做劫持,然后问题就是如果实现劫持,就要知道B.Dll里面的所有函数名字以及函数参数,这个地方比较不好搞,此地不考虑。
APC注入的原理是利用当线程被唤醒时APC中的注册函数会被执行的机制,并以此去执行我们的DLL加载代码,进而完成DLL注入的目的,其具体流程如下:
1)当EXE里某个线程执行到SleepEx()或者WaitForSingleObjectEx()时,系统就会产生一个软中断。
2)当线程再次被唤醒时,此线程会首先执行APC队列中的被注册的函数。
3)利用QueueUserAPC()这个API可以在软中断时向线程的APC队列插入一个函数指针,如果我们插入的是Loadlibrary()执行函数的话,就能达到注入DLL的目的。