关于列举进程Module的那点小事儿

列举进程对应Module其实是个比较常见的问题,最开始想到的是TlHelp32.h头文件里的Module32First和Module32Next函数,代码很简单:

但是运行起来会发现,通过这套函数在编译成64位的时候,读32位的程序的Module会出错

在编译成32位的时候读32位程序虽然不会出错

然而在读64位的程序的时候会直接报错

这套函数的兼容性有问题,令人尴尬的是用EnumProcessModules加GetModuleFileNameEx函数依然解决不了这个问题

其实原因很简单,Module32那套函数用了NtMapViewOfSection(为啥看到这函数我第一反应就是注入………),对虚拟内存中的节表(Section)进行读取

但是32位系统和64位系统的地址空间是不一样的,这一点在Windows核心编程中很详细的写了

根据ProcessExplorer读出来的信息可以看出,64位的exe和dll的ImageBase和32位的都不同,微软提供的这套函数无法根据实际判断文件是32位还是64位,也就无法判断NtMapViewOfSection需要读取的空间范围

所以,最基本的要做成像ProcessExplorer那样,64位程序要能完美的读出64位和32位的程序Modules信息,32位的程序要能完美的读出32位程序的Module信息。

简单的方法是选择用VirtualQueryEx加GetMappedFileName的方式遍历对应内存的节表(Section),读出用户态每个节的信息,即可完整的读出整个进程对应的Module信息

用图形化工具看Windows的节表大体就是这样的(这是32位下的)

VirtualQueryEx加GetMappedFileName这两个函数在MSDN上都有很明确的解释

对于VirtualQueryEx来说,在正常情况下可以查询用户内存区域所有节,进入内核区域会失败,其HANDLE传入查找的进程的句柄,LPCVOID参数是在该进程中查询的起始地址(内存中的虚拟地址),lpBuffer参数传递PMEMORY_BASIC_INFORMATION结构体,用来存放查询到的相关信息,dwLength为该结构体的大小。

PMEMORY_BASIC_INFORMATION结构体定义如下

然后使用GetMappedFileName函数将文件的路径读出来,函数原型如下

但是要注意的是,该函数读出来的路径是物理路径像这种\\Device\\HarddiskVolume3\\,需要转换一下,这个我就不多说了,直接贴一下代码

 

代码很简单,并没有什么太多需要解释的,用VirtualQueryEx从0x0的内存位置开始查找,查找到的是从该地址开始的第一个节,如果该内存空间被使用(查找的内存位置和节的基址相同),就读出节对应的目录,每次增加RegionSize(该节的大小,如果该段内存空间未被使用,该值为这段空间的大小),直到查找失败(如果不是特殊原因),这里的结构体大小是固定的,基本不会出现空间不足的情况,所以就没加什么错误判断(其实我懒)。

中间分了两种情况写的,因为64位系统的指针和32位系统的指针长度不同,ULONG64是一个8字节的指针,ULONG是一个4字节指针(在64位系统下长度不够),除此之外没什么别的区别。

从图中可以看出,用这组函数,在编译成64位程序时可以完美读出32位和64位程序的Module信息

然而到了这里,有没有第三种方法呢?答案是肯定的,我们先来分析一下VirtualQueryEx和GetMappedFileName这俩函数

可以看到在VirtualQueryEx中调用了NtQueryVirtualMemory(ZwQueryVirtualMemory 在ring3中这俩函数是一样的)这个函数

在网上可以查到该函数的定义如下,该函数是Native函数,MSDN上并未公开这个函数

可以看出该函数有6个参数

所以这时候先记下这几个参数RCX RDX R8 R9 RSP+0x20 RSP+0x28(64位程序函数传参的基本知识 不多解释)

再继续跟到GetMappedFileName里面看看

巧的是在里面依然用了NtQueryVirtualMemory这个函数

明显不同的是第三个参数

用windbg看看这个第三个参数的MEMORY_INFORMATION_CLASS 结构到底是什么东西

可以很显然看出来为该值为0和2时候的作用

好了到了这个时候,前期的准备工作已经做完了,直接贴一下代码,代码是从我总的代码里抽出来的,有些头文件包含之类的就不贴了

 

 

代码其实很简单,先是调用Native函数的基本步骤,至于在循环的时候,因为32位系统和64位系统的内存空间大小不同,所以写了两套,在定义MEMORY_INFORMATION_CLASS结构体的时候我也并没有把所有的都枚举到,因为只用了第一个和第三个,用到的话再加。至于还有一点需要注意到的是,在内核中的字符串对象是PUNICODE_STRING类型,在第二次调用ZwQueryVirtualMemory的时候获取信息的Buffer需要做一次类型转换。

运行一波发现效果是一样的

这是我在做进程管理器时候想到的一些小东西,记录下来,当然在Ring3下列举进程Module可以用Hook的方式使其获取不到,很多恶意代码也是这么做的,我写的代码也主要是针对Ring3,所以并没有做内核的东西。

至于最后还有一些想法,其实在64位系统下做调试,VS真的是个好东西,相对windbg操作简单、界面友好,对符号表的支持更好,所以在Ring3下调试我很多时候都直接用了VS。

发表评论

电子邮件地址不会被公开。 必填项已用*标注