作者:shenzi
链接:http://blog.youkuaiyun.com/shenzi
独立的地址空间对开发人员和用户都是非常有利的。对开发人员来说,系统更有可能捕获错误的内存读/写。对用户来说,操作系统变得更加健壮了,因为一个应用 程序的错误不会导致其它应用程序或操作系统崩溃。当然,这样的健壮性也是要付出代价的,因为它使我们很难编写能够与其它进程通信的应用程序或对其它进程进 行操控的应用程序。
应用程序需要跨越进程边界来访问另一个进程的地址空间的情况如下:
- 我们想要从另一个进程创建的窗口派生子类窗口
- 我们需要一些手段来辅助调试——例如,我们需要确定另一个进程正在使用哪些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