现在的位置: 首页 > 综合 > 正文

插入DLL和挂接API——Windows核心编程学习手札之二十二

2013年02月04日 ⁄ 综合 ⁄ 共 6173字 ⁄ 字号 评论关闭

插入DLL和挂接API

——Windows核心编程学习手札之二十二

如下情况,可能要打破进程的界限,访问另一个进程的地址空间:

1)为另一个进程创建的窗口建立子类时;

2)需要调试帮助时,如需要确定另一个进程正在使用那个DLL

3)需要挂接其他进程时;

基于之上情况,下面的方法可将DLL插入到另一个进程的地址空间中,一旦DLL进入另一个进程的地址空间,就可以对另一个进程为所欲为。

使用注册表来插入DLL

整个系统的配置都是在注册表中维护的,可调整其设置改变系统的行为特性,下面的关键字:

HKEY_LOCAL_MACHINE/Software/Microsoft/WindowsNT/

CurrentVersion/Windows/AppInit_DLLS

AppInit_DLLS关键字包含一个DLL文件或一组DLL文件名(用空格或逗号隔开,避免使用含空格的文件名),列出第一个DLL文件名可包含其路径,但包含路径的其他DLL将被忽略,因此最好将DLL放入Windows的系统目录中,这样不需要设定路径,如设置该关键字值为C:/MyLib.dll。当重启计算机及Windows进行初始化时,系统将保存这个关键字的值。然后当User32.dll库被映射到进程中时,将接收到一个DLL_PROCESS_ATTACH通知,这个通知被处理时,User32.dll便检索保存这个关键字的值,并且为字符串中指定的每个DLL调用LoadLibrary函数。当每个库被加载时,便调用和该库相关的DllMain函数,其fdwReason的值是DLL_PROCESS_ATTACH,如此,每个库就能够对自己进行初始化。该方法简单,但不足有:

1)系统是在初始化时读取该关键字的值,因此修改该值需重新启动计算机;

2 插入的DLL是映射到使用User32.dll进程中,所有基于GUI的应用程序都使用User32.dll,不过多数基于GUI的应用程序不使用插入的DLL,而且如将DLL插入编译器或链接程序,这种方法将不起作用;

3)插入的DLL被映射到每个基于GUI应用程序中,DLL映射的进程太多,“容器”进程崩溃的可能性越大;

4)插入的DLL被映射到每个基于GUI应用程序中,应仅必要时保持DLL的插入状态。

使用Windows挂钩来插入DLL

进程A(类似Microsoft Spy++的一个实用程序)安装了一个挂钩WN_GETMESSAGE,以便查看系统中的各个窗口处理的消息,挂钩是通过调用SetWindowsHookEx函数来安装的:

       HHOOK hHook=SetWindowsHookEx(WH_GETMESSAGE,GetMsgProc,hinstDll,0);

参数WH_GETMESSAGE用于指明要安装的挂钩类型,参数GetMsgProc是窗口准备处理一个消息时系统调用的函数的地址(在进程地址空间内);第三个参数hinstDll是包含GetMsgProc函数的DLL。在Windows中,DLLhinstDll的值用于标识DLL被映射到的进程的地址空间中的虚拟内存地址,最后一个参数0表示要挂接的线程,也可以传递系统中另一个线程的ID,传递0告诉系统想要挂接系统中的所有GUI线程。安装挂钩后的情况:

1)进程B中的一个线程准备将一条消息发送到一个窗口;

2)系统查看该线程上是否已安装了WH_GETMESSAGE挂钩;

3)系统查看包含GetMsgProc函数的DLL是否包含被映射到进程B的地址空间中;

4)如果该DLL尚未被映射,系统将强制该DLL映射到进程B的地址空间,并且将进程B中的DLL映像的自动跟踪计数递增1

5)当DLLhinstDll用于进程B时,系统查看该函数,并且检查该DLLhinstDll是否与其在进程A时所处的位置相同;如果两个hinstDll在系统位置上,那GetMsgProc函数的内存地址在两个进程的地址空间中的位置也是相同的,这种情况下,系统只要调用进程A的地址空间中的GetMsgProc函数即可;如位置不同,则系统要确定进程B的地址空间中GetMsgProc函数的虚拟内存地址,用下面公式确定:

       GetMsgProc B=hinstDll B+(GetMsgProc A-hinstDll A)

GetMsgProc A的地址减去hinstDll A的地址,得到GetMsgProc函数的地址位移(以字节为计量单位),将这个位移与hinstDll B的地址相加,得到GetMsgProc函数在用于进程B的地址空间中该DLL的映像时其位置;

6)系统将进程B中的DLL映像的自动跟踪计数递增1

7)系统调用进程B的地址空间中的GetMsgProc函数;

8)当GetMsgProc函数返回时,系统将进程B中的DLL映像的自动跟踪计数递减1

当系统插入或者映射包含挂钩过滤器函数的DLL时,整个DLL均被映射,而不只是挂钩过滤器函数被映射,这意味着DLL中包含的任何一个函数或所有函数都被映射,可被进程B的环境运行的线程所调用。

若要为进程B中的线程创建的窗口建立子类,首先可以在创建该窗口的挂钩上设置一个WH_GETMESSAGE挂钩,然后在GetMsgProc函数被调用时,调用SetWindowLongPtr函数来建立窗口的子类,子类的过程与GetMsgProc函数在同一DLL文件中。

进程B中不再需要DLL时删除DLL映像,方法是调用:

       BOOL UnhookWindowsHookEx(HHOOK hhook);

当洋线程调用UnhookWindowsHookEx函数时,系统将遍历将DLL插入到的各个进程的内部列表,并对DLL的自动跟踪计数进行递减,当递减到0时,DLL就从进程的地址空间中被删除。

使用远程线程来插入DLL

Windows的多数函数允许进程只对自己进行操作,可防止一个进程破坏另一个进程的运行,但对调试程序和一些工具而言,则需要操作其他进程。使用远程线程来插入DLL的方法要求目标进程中的线程调用LoadLibrary函数来加载必要的DLL,需要在目标进程中创建一个新线程,Windows提供了这样一个函数:

       HANDLE CreateRomoteThread(

                                          HANDLE hProcess,

                                          PSECURITY_ATTRIBUTES psa,

                                          DWORD dwStackSize,

                                          PTHREAD_START_ROUTINE pfnStartAddr,

                                          PVOID pvParam,

                                          DWORD fdwCreate,

                                          PDWORD pdwThreadId);

参数hProcess指明拥有新创建线程的进程,参数pfnStartAddr指明线程函数的内存地址,该内存地址和远程进程是相关的,线程函数代码不能在自己进程的地址空间中。

如执行下面代码:

       HANDLE hThread=CreateRemoteThread(hProcessRemote,NULL,0,

                      LoadLibraryA,”C://MyLib.dll”,0,NULL);

或选用Unicode

    HANDLE hThread=CreateRemoteThread(hProcessRemote,NULL,0,

                      LoadLibraryW,L”C://MyLib.dll”,0,NULL);

当在远程进程中创建新线程时,该线程立即调用LoadLibrary函数,并将DLL的路径名的地址传递给它。直接将LoadLibrary作为线程执行函数(所传递的参数就是远程线程的起始地址),会出先问题:

1)当编译或链接一个程序时,产生的二进制代码包含一个输入节,这一节是有一系列输入函数的形式替换程序(thunk)组成。当代码调用一个如LoadLibrary函数时,链接程序将生成一个模块输入节中的形实替换程序并调用,然后,该形实替换程序便转移到实际的函数。在CreateRemoteThread的调用中使用一个对LoadLibrary的直接调用,则将在模块的输入节中转换成LoadLibrary的形实替换程序的地址,将形实替换程序的地址作为远程线程的起始地址来传递,会导致线程开始执行莫名其妙的代码。

解决方法是:若要强制直接调用LoadLibrary函数,避开形实替换程序,必须调用GetProcAddress函数,获取LoadLibrary的准确内存位置。对CreateRemoteThread调用的前提是,Kernel32.dll已经被同时映射到本地和远程进程的地址空间中,每个应用程序都需要Kernel32.dll,将Kernel32.dll映射到每个进程的同一个地址,如调用如下函数:

       PTHREAD_START_ROUTINE pfnThreadRtn=(PTHREAD_START_ROUTINE)

                 GetProcAddress(GetModuleHandle(TEXT(“Kernel32”)),”LoadLibraryA”);

       HANDLE hThread=CreateRemoteThread(hProcessRemote,NULL,0,

                       pfnThreadRtn,” C://MyLib.dll”,0,NULL);

或选用Unicode

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);

2)字符串” C://MyLib.dll”是在调用进程的地址空间中,该字符串的地址已经被赋予新创建的远程线程,该线程将其传递给LoadLibrary,但是,当LoadLibrary取消对内存地址的引用时,DLL路径名字符串将不再存在,远程进程的线程就可能引发访问违规,向用户显示一个未处理的异常条件消息框,并终止远程进程。

解决方法是:将DLL的路径名字符串放入远程进程的地址空间中,然后当CreateRemoteThread函数被调用时,必须将放置该字符串的地址(相对于远程进程的地址)传递给它,Windows提供了一个函数使得一个进程能够分配另一个进程的地址空间中的内存:

       PVOID VirtualAllcoEx(

                                   HANDLE hProcess,

                                   PVOID pvAddress,

                                   SIZE_T dwSize,

                                   DWORD flAllocationType,

                                   DWORD flProtect);

另一个函数则能够释放该内存:

       BOOL VirtualFreeEx(

                                   HANDLE hProcess,

                                   PVOID pvAddress,

                                   SIZE_T dwSize,

                                   DWORD dwFreeType);

一旦为该字符串分配内存,还需要将该字符串从进程的地址空间拷贝到远程进程的地址空间中,Windows提供了一些函数,使得一个进程能够从另一个进程的地址空间中读取数据,并将数据写入另一个进程的地址空间。

       BOOL ReadProcessMemory(

                                          HANDLE hProcess,

                                          PVOID pvAddressRemote,

                                          PVOID pvBufferlocal,

                                         DWORD dwSize,

                                          PDWORD pdwNumbytesRead);

       BOOL WriteProcessMemory(

                                          HANDLE hProcess,

                                          PVOID pvAddressRemote,

                                          PVOID pvBufferlocal,

                                         DWORD dwSize,

                                          PDWORD pdwNumbytesWritten);

远程进程由hProcess参数来标识,参数pvAddressRemote用于指明远程进程的地址,参数pvBufferlocal是本地进程中的内存地址,参数dwSize需要传送的字节数,pdwNumbytesWrittenpdwNumbytesRead用于指明实际传送的字节数,当函数返回时,可查看这两个参数的值。

对此,执行步骤归纳如下:

1)  使用VirtualAllocEx函数,分配远程进程的地址空间中的内存;

2)  使用WriteProcessMemory

抱歉!评论已关闭.