一、问题提出
在Windows操作系统中,dll属性查看的版本信息是在R3层获取的,如下图所示,在项目中经常要保证监控获取的透明性,能否在R0层直接获取这些信息?
二、libvmi简介
libvmi是Google的一个开源项目,利用Memory introspection技术在Dom0中监视DomU的情况。由于项目的某些需求,需要透明地监控Windows进程的列表,进程调用的dll库和版本。对于特定的操作系统版本,libvmi给的默认配置文件libvmi-example.conf中详细的配置参数,例如Windows 7的32位系统默认是
libvmi中个重要的函数:给定进程的虚拟地址空间,直接读取对应位数的数据。
vmi_read_32_va(vmi_instance_t vmi, addr_t vaddr,vmi_pid_t pid, uint64_t *value)
[in]vmi:libvmi实例
[in]vaddr: 读取的虚拟地址基地址
[in]pid:进程的pid
[out]value:读取的值
三、获取进程调用的dll基地址
首先使用libvmi自带的例子process-list.c得到进程的列表,以及每个进程的基地址,该例子基本思路是使用vmi_read_addr_ksym这个函数获取PsActiveProcessHead,然后再得到系统中的进程双向链表,获得每个进程的EPROCESS结构体基地址和进程PID。后绪的就是利用上面libvmi函数直接去读内存,遍历进程地址空间(代码没整理好,有点乱,整理好再补充上去,这里使用windbg遍历一次,知道思路写代码就容易了)。
首先找到要监控的具体的进程基地址,然后开始下列步骤找到模块链表:
- 通过进程的EPROCESS获取对应的_PEB结构
- 通过_PEB找到_PEB_LDR_DATA
- 在_PEB_LDR_DATA内部有3个LIST_ENTRY,这也就是进程加载的模块信息的链表头,_PEB_LDR_DATA内部的3个链表就是进程调用的所有dll库,而这三个链表的区别就是dll的排列顺序不同,里面的内容是一致的。
- 知道了链表头,只需要知道链表的结点就行了,需要遍历的结点是_LDR_DATA_TABLE_ENTRY结构,在这个结构中有一个FullDllName,也就是模块的路径,就是我们要找的。
下面以notepad为例,使用windbg查看遍历,步骤如下:
- 命令!process 0 0 notepad.exe
- Peb的地址是小于0x80000000,属于用户地址空间,是不能直接访问的,直接访问的话看到的都是问号,所以切换到这个应用程序后才能访问地址空间,使用下面的命令切换到notepad.exe这个进程,.process /p /r 0x93c85470
- 已经切换到notepad.exe,命令dt _EPROCESS 93c85470查看EPROCESS,可以看到Peb的地址7ffdf000,使用命令dt _PEB 0x7ffdf000查看PEB的信息,如图所示,在0x00c处找到了_PEB_LDR_DATA
- 继续跟进_PEB_LDR_DATA,