DLL注入和API拦截指南

Home / Article MrLee 2016-5-25 2704

在 Windows中,每个进程有自己的私有地址空间。当我们用指针来引用内存的时候,指针的值表示的是进程自己的地址空间中的一个内存地址。进程不能创建一个指针来引用属于其它进程的内存。因此,如果进程有一个缺陷会覆盖随机地址处的内存,那么这个缺陷不会影响到其它进程所使用的内存。 独立的地址空间对开发人员和用户都是非常有利的。对开发人员来说,系统更有可能捕获错误的内存读/写。对用户来说,操作系统变得更加健壮了,因为一个应用程序的错误不会导致其它应用程序或操作系统崩溃。当然,这样的健壮性也是要付出代价的,因为它使我们很难编写能够与其它进程通信的应用程序或对其它进程进行操控的应用程序。 应用程序需要跨越进程边界来访问另一个进程的地址空间的情况如下:
  • 我们想要从另一个进程创建的窗口派生子类窗口
  • 我们需要一些手段来辅助调试——例如,我们需要确定另一个进程正在使用哪些DLL
  • 我们想要给另一个进程安装挂钩
1.DLL注入的一个例子 我们可以调用SetWindowLongPtr来让窗口在内存块中的窗口过程指向新的(我们自己的)WndProc。 SetWindowLongPtr(hWnd, GWLP_WNDPROC, MySubclassProc) ; 但是,从另一个进程创建的窗口派生子类窗口的问题在于,子类窗口的窗口过程在另一个地址空间中。这样,当函数成功,并去访问新的窗口过程 MySubclassProc ,这就出现了问题,因为 MySubclassProc 的地址将是无法预测的。 2.使用注册表来注入DLL HKEY_LOCAL_MACHINE/Software/Microsoft /Windows NT/CurrentVersion/Windows/ 我们可以在注册表项中添加AppInit_Dlls键的值,可能会包含一个DLL的文件名或一组DLL的文件名(通过空格或逗号分隔),为了能让系统使用这个注册表项,我们还应该创建一个名为LoadAppInit_Dlls,类型为DWORD的注册表项,并将它的值宿设为1。 当User.dll被映射到一个新的进程时,会收到DLL_PROCESS_ATTACH通知。当User.dll对它进行处理的时候,会取得上述注册表键的值,并调用LoadLibrary来载入这个字符串中的指定的每个DLL。当系统载入每个DLL的时候,会调用它们的DllMain函数并将参数 fdwReason的值设为DLL_PROCESS_ATTACH,这样每个DLL就能够对自己进行初始化。 在用来注入DLL的所有方法中,这是最方便地一种。但这种方法也有一些缺点:
  • 我们的DLL只会被映射到那些使用了User.dll的进程中。所有基于GUI的应用程序都使用了User.dll,但大多数基于CUI的应用程序都不会使用它。
  • 我们的DLL回被映射到每个基于GUI的应用程序中,但我们可能只想把DLL注入到一个或少数几个应用程序中。
  • 我们的DLL回被映射到每个基于GUI的应用程序中,在应用程序终止之前,它将一直存在于进程的地址空间中。
3.使用Windows挂钩来注入DLL 通过调用SetWindowsHookEx来安装挂钩: HHOOK hHook = SetWindowsHookEx(WH_GETMESSAGE, GetMsgProc,hInstDll, 0); 和用来注册表来注入DLL的方法相比,这种方法允许我们在不需要该DLL得时候从进程的地址空间中撤销对它的映射: BOOL UnhookWindowsHookEx(HHOOK hHook); 具体是怎么一回事还不是很清楚,估计得看看程序实现。 4.使用远程线程来注入DLL 注入DLL的第三种方法是使用远程线程(remote thread),它提供了最高的灵活性。 从根本上说,DLL注入计数要求目标进程中的一个线程调用LoadLibrary来载入我们想要的DLL。由于我们不能轻易地控制别人进程中的线程,因此这种方法要求我们在目标进程中创建一个新的线程。Windows提供了如下所示的CreateRemoteThread函数,它使得在另一个进程中创建线程变得非常容易: HANDLE CreateRemoteThread( HANDLE hProcess, PSECURITY_ATTRIBUTES psa, DWORD dwStackSize, PTHREAD_START_ROUTINE pfnStartAddr, PVOID pvParam, DWORD fdwCreate, PDWORD pdwThreadId); 除了CreateRemoteThread有一个额外的参数hProcess之外,它与CreateThread完全相同。这个参数用来标识新创建的线程归哪个进程所有。参数pfnStartAddr是线程函数的内存地址。当然,这个内存地址应该在远程进程的地址空间中,因为线程函数的代码不能在我们自己进程的地址空间中。 PTHREAD_START_ROUTINE pfnThreadRtn = (PTHREAD_START_ROUTINE) GetProcAddress(GetModuleHandle(TEXT("Kernel32")), "LoadLibraryW");
     HANDLE hThread = CreateRemoteThread(hProcessRemote, NULL, 0, pfnThreadRtn, L"C://MyLib.dll", 0, NULL); "C://MyLib.dll" 位于调用进程的地址空间中 ,我们把这个地址传给新创建的远程线程,远程线程去访问这个内存地址的时候,DLL的路径字符串并不在那里,远程进程的线程很可能会引发访问违规。为了解决这个问题,我们需要把DLL得路径字符串存放到远程进程的地址空间中去。我们可以调用VirtualAllocEx在远程进程的地址空间中为字符串分配一块内存,再把字符串从进程的地址空间复制到远程进程的地址空间中。 BOOL ReadProcessMemory( HANDLE hProcess, LPCVOID pvAddressRemote, PVOID pvBufferLocal, SIZE_T dwSize, SIZE_T* pdwNumBytesRead); BOOL WriteProcessMemory( HANDLE hProcess, PVOID pvAddressRemote, LPCVOID pvBufferLocal, SIZE_T dwSize, SIZE_T* pdwNumBytesWritten); 让我们总结一下必须采取的步骤: 1)用VirtualAllocEx 函数在远程进程的地址空间中分配一块内存。 2)用 WriteProcessMemory 函数把DLL的路径名复制到第1步分配的内存中。 3)用GetProcAddress 函数来得到LoadLibraryW LoadLibraryA 函数(在Kernel32.dll中)的实际地址。 4)用 CreateRemoteThread 函数在远程进程中创建一个线程,让新线程调用正确的LoadLibrary函数并在参数中传             入第1步分配的内存地址。这时,DLL已经被注入到远程进程的地址空间中,DLL的DllMain函数会收到                               DLL_PROCESS_ATTACH 通知并且可以执行我们想要执行的代码。当DllMain返回的时候,远程线程会                         从LoadLibraryW/A调用返回到BaseThreadStart函数。 BaseThreadStart函数然后调用ExitThread,使远程线程             终止。 现在远程进程中有一块内存,它是我们在第1步分配的,DLL也还在远程进程的地址空间中。为了对它们进程清理,我们需要在远程线程退出之后执行后续步骤。 5)用VirtualFreeEx来释放第1步分配的内存。 6)用GetProcAddress来得到FreeLibrary函数的实际地址。 7)用CreateRemoteThread函数在远程进程中创建一个线程,让该线程调用FreeLibrary函数并在参数中传入远程                 DLL的HMODULE。 5.使用木马DLL来注入DLL 注入DLL的另一种方式是,把我们知道的进程必然会载入的一个DLL替换掉。
  • 修改DLL的名称
  • 修改应用程序的.exe模块的导入段。
6.把DLL作为调试器来注入
7.使用CreateProcess来注入代码
8.API拦截的一个例子
  • 通过覆盖代码来拦截API
  • 通过修改模块的导入段来拦截API
 拦截函数(hook function)
通过覆盖代码来拦截API
1.定位要焊接的函数,得到它的内存地址;
2.把这个函数起始的几个字节保存到我们自己的内存中;
3.用CPU的一条JUMP指令来覆盖这个函数其实的几个字节,这条JUMP指令用来跳转到我们的替代函数的内存地址。我们的替代函数签名必须与要拦截的函数的函数签名完全相同:所有的参数必须相同,返回值必须相同,调用约定也必须相同。
4.当线程调用被拦截函数的时候,跳转指令实际上会跳转到我们的替代函数,这时,我们就可以执行自己想要执行的任何代码。
5.为了撤销对函数的拦截,我们必须把2中保存下来的字节放回被拦截函数起始的几个字节中。
6.我们调用被拦截函数,让该函数执行它的正常处理。
7.当原来的函数返回时,我们再次执行2、3,这样我们的替代函数将来还会被调用到。
这种方法已经基本过时淘汰,它对CPU有依赖性,而且这在抢占式、多线程环境中根本不能工作。
通过修改模块的导入段来拦截API
这种方法不仅容易实现,而且也相当健壮。我们从模块的导入段说起。。。
一个模块的导入段包含一组DLL,为了能让模块运行,这些DLL是必须的。此外,导入段还包括一个符号表,其中列出了该模块从各DLL中导入的符号。当该模块调用一个导入函数的时候,线程实际上会先从模块的导入表中得到相应的导入函数的地址,然后再跳转到那个地址。
因此,为了拦截一个特定的函数,我们所需要做的就是修改它在模块的导入段中的地址。
完全不存在对CPU的依赖性,而且由于我们并没有修改函数的代码,不必担心线程同步的问题。

本文链接:https://www.it72.com/9401.htm

推荐阅读
最新回复 (0)
返回