Windows DLL在目标进程中的描述(2)

来源:互联网 发布:rf3148编程器 编辑:程序博客网 时间:2024/05/19 03:19

显示当前进程DLL加载信息的系统调用

         经过前面对一些研究背景,windows进程相关数据结构,windows DLL基本原理的介绍,本文的读者将会清楚的理解接下来将要做的:写一个显示当前进程DLL的加载信息的windows系统调用。这部分内容主要分3步进行,如下:

1.    编写调用该系统调用的应用程序。

为了方便验证我们即将写的系统调用显示了当前进程的所有DLL信息,所以在这里我

首先要写一个自己的DLL文件,再写一个加载该DLL的简单应用程序。

         1)在VC6.0里我们新建一个Win32 Dynamic-Link Library工程,就叫Mydll吧。新建一个文件C++ 源文件,也就叫Mydll.cpp吧。代码如下:

  1. #include   "windows.h"
  2. extern "C" _declspec(dllexport)int add(int a,int b)
  3.     return a+b;
  4. }

    这个DLL中只有一个导出函数add,实现两个整数相加。编译,连接后在该项目的Debug目录下会产生Mydll.dllMydll.libMydll.exp三个文件。

         2)在VC6.0中新建一个Win32 Console Application工程,就叫RecordDll吧。新建一个RecordDll.c文件。代码如下:

  1. #include<stdio.h>
  2. #include<stdlib.h>
  3. extern _declspec(dllexportint add(int a,int b);
  4. #pragma comment(lib,"Mydll.lib") //隐式加载DLL
  5. int main()
  6. {
  7.     int  v1=1,v2=2;
  8.     int v3=0;
  9.     v3=add(v1,v2);
  10.     _asm{
  11.         mov eax,297 //297为系统调用号,将系统调用号放入eax寄存器
  12.         int 0x2E    //陷入系统调用
  13.     }
  14.     printf("1+2=%d",v3);
  15.     system("pause");//暂停进程
  16. }

Mydll.lib文件拷贝到RecordDll工程目录下,进行编译,连接。第三,四行将为隐式加载Dll。其中嵌入了两行汇编代码:mov eax,297  297为系统调用号,将系统调用号放入eax寄存器中,int 0x2E  处理器执行int 0x2E指令来激活Windows系统服务调用。最后一行的system("pause");就是将当前这个简单的进程暂停,方便之后用某些观察DLL加载信息的小工具来验证我们的系统调用。

         到此,我们已经将应用程序编写好了,将RecordDll.exeMydll.dll保存好,后面将会用到。

2.编写记录DLL加载信息的系统调用

         在这个步骤里将详细介绍编写一个windows系统调用的步骤。

(1)       首先在源代码中找到systable.asm文件(我选的是i386文件夹下的那个,还有一个amd64的)。在第393行加入一行代码:TABLE_ENTRY  RecordDll ,0 ,0  两个0分别代表没有参数,参数个数为0。并将下面的TABLE_END 296加一改成297

(2)       其次在源代码中找到psimpers.c文件(当然在别的文件中也行,但是要注意相应的头文件是否包含)加入我们自己写的:

 

  1. NTSTATUS
  2. NtRecordDll()
  3. {
  4.      NTSTATUS Status = STATUS_SUCCESS;
  5.      PEPROCESS myProcess;
  6.      PPEB myPeb;
  7.      PPEB_LDR_DATA myLdr;
  8.      PLIST_ENTRY ListHead=NULL,Current=NULL;
  9.      PLDR_DATA_TABLE_ENTRY Entry=NULL;
  10.      
  11.      
  12.      
  13.      PAGED_CODE();
  14.      
  15.      DbgPrint("*********Doing*********/n/n");
  16.      myProcess=PsGetCurrentProcess();
  17.      myPeb=myProcess->Peb;
  18.      myLdr=myPeb->Ldr;
  19.      ListHead=&myLdr->InInitializationOrderModuleList;
  20.      Current=ListHead->Flink;
  21.      DbgPrint("Now is the InInitializationOrderModuleList/n/n");
  22.      while(Current!=ListHead)
  23.         {
  24.             Entry = CONTAINING_RECORD( Current, LDR_DATA_TABLE_ENTRY, InInitializationOrderLinks);
  25.             DbgPrint("FullDllName: %ws/n/n",Entry->FullDllName.Buffer);
  26.             DbgPrint("BaseDllName: %ws/n/n",Entry->BaseDllName.Buffer);
  27.             DbgPrint("DllBase: 0x%X/n/n",Entry->DllBase);
  28.             DbgPrint("SizeOfImage: %d/n/n",Entry->SizeOfImage);
  29.             DbgPrint("LoadCount: %d/n/n",Entry->LoadCount);
  30.             DbgPrint("EntryPoint: 0x%X/n/n/n/n/n",Entry->EntryPoint);
  31.             
  32.             Current=Entry->InInitializationOrderLinks.Flink;
  33.         }
  34. return Status ;
  35. }

190行使用PsGetCurrentProcess();函数返回当前进程。191行直接通过当前进程的指针获得当前进程的PEB数据结构,192PEB中有一个_PEB_LDR_DATA结构指针,该数据结构为本进程维护着3个模块链表即InLoadOrderModuleListInMemoryOrderModuleListInInitializationOrderModuleList所谓“模块”就是PE格式的可执行映像,包括EXE映像和DLL映像。前两个是模块队列,内容相同,只不过一个是按照在内存中的顺序排列,一个是按照加载顺序排列。第三个是初始化队列,每当一个进程装入一个模块,即EXE映像或DLL映像时,就要为其分配一个LDR_DATA_TABLE_ENTRY数据结构,并立刻将其挂入InLoadOrderModuleList队列。完成动态连接之后,再将其挂入InInitializationOrderModuleList队列,以便一次调用它们的初始化函数。相应的LDR_DATA_TABLE_ENTRY结构中有三个队列的队列头,因而可以方便挂在三个队列中。193行将InInitializationOrderModuleList队列的队列头得到,194行,将队列的第一个结点元素的指针得到。197行到208行,遍历这个队列的每一个LDR_DATA_TABLE_ENTRY数据结构,将其中加载的DLL相关信息显示出来,比如有:DLL的全名(FullDllName,DLL的文件名(BaseDllName,DLL映像在该进程虚拟空间中的地址(DllBase,DLL映像大小(SizeOfSection)等等。最后该系统调用函数返回STATUS_SUCCESS

3编译,替换新内核,在Windbg中观察实验结果。

(1)     C:/WRK-v1.2/base/ntos目录下输入命令:nmake x86= ,会自动编译刚刚写好的新内核。生成的新内核是C:/WRK-v1.2/base/ntos/BUILD/EXE下的 wrkx86.exe

(2)     启动虚拟机Windows 2003 server,将物理机上刚刚编译好的新内核wrkx86.exe覆盖到虚拟机上的C:/WINDOWS/system32目录下。顺便也将我们最开始写的RecordDll.exeMydll.dll一起从物理机上拷贝到虚拟机的桌面上,关闭虚拟机。

(3)     打开Windbg,进入Kernel Debug模式。再次启动虚拟机Windows 2003 server Debug版。

这时候程序暂停。我们再来看看Windbg中的显示结果:

 

 

 

这个结果显示我们这个小程序只加载了3DLL,他们分别为:ntdll.dllkernel32.dllMydll.dll。并且他们在进程虚拟空间的起始地址为分别为:0x7C8000000x77E400000x10000000。至此验证我们的系统调用功能实现成功!

 

实验总结

         通过这次实验,我熟悉了Windows内核描述进程的相关数据结构,了解了Windows Dll的基本使用原理,也明确的知道了Windows Dll在一个进程中是如何描述的。但是知道这些也是不够的,对于我们的最终目的:写一个系统调用,将进程的运行时状态保存在文件中,必要的时候可以还原回去并继续执行。我最好是能完全弄懂Windows Dll的加载过程,在将一个进程记录到文件里时,不需要将DLL的内容写出去而是记录几个Dll相关信息就行。就我目前所了解到的:Windows DLL(除了ntdll.dll之外)都是通过ntdll.dll中的LdrInitializeThunk这个函数实现的。但是目前微软还没有公开这部分代码,所以给我们的学习带来的不便。网上也有一些零零散散的LdrInitializeThunk相关资料,我会继续学习下去。