Windows2000下用户模式的内存扫描( 二 )


(Committed) 。一个未用的页面是指该页面未被保留或是提交,对一个进
程来讲一个未用的页面是不可访问的,访问这样的页面将导致访问违例 。
进程可以要求系统保留一些页面以备后用,系统返回一段保留的地址给进
程,但是这些地址同样是不可访问的,进程若想使用这段地址空间,使用
必须先提交 。只有一个提交的页面才是一个真正可以访问的页面 。不过你
提交了一个页面,系统并不会马上分配物理页面,只有在该页面第一次被
访问到时,系统才会分配页面并初始化 。另外,这三个状态的两两之间都
是可以相互转化的 。相关的API函数有 VirtualAlloc 、 VirtualAllocEx 、
VirtualFree 、 VirtualFreeEx 等 .
这样我们的工作已大大减少了,只需要扫描那些提交的页面就好了 。接下来要做的就
是得到一个进程的已提交的页面范围 。这就要用到另外两个 API函数VirtualQuery和
VirtualQueryEx 。两个函数的功能相似,不同就是VirtualQuery只是查询本进程而
VirtualQueryEx可以查询指定进程的内存空间信息,后者正是我们所需要的,函数原
型如下:
DWord VirtualQueryEx(
HANDLE hProcess , // handle to process LPCVOID lpAddress , // address of region PMEMORY_BASIC_INFORMATION lpBuffer , // information buffer SIZE_T dwLength // size of buffer ); 第一个参数是进程的句柄;第二个参数是内存地址指针;第三个参数是指向 MEMORY_BASIC_INFORMATION 结构的指针,用于返回内存空间的信息;第四个参数是 lpBuffer 的长度 。再来看一下结构 MEMORY_BASIC_INFORMATION 的声明: typedef struct _MEMORY_BASIC_INFORMATION { PVOID BaseAddress ; PVOID AllocationBase ; DWORD AllocationProtect ; SIZE_T RegionSize ; DWORD State ; DWORD Protect ; DWORD Type ; } MEMORY_BASIC_INFORMATION, *PMEMORY_BASIC_INFORMATION; 第一个参数是查询内存块的基地址;第二个参数指的是用VirtualAlloc分配该内存时实际分配的基地址,可以小于 BaseAddress,也就是说 BaseAddress 一定包含在 AllocationBase 分配的范围内;第三个参数指的是分 配该页面时,页面的一些属性,如 PAGE_READWRITE、PAGE_EXECUTE 等(其它属性 可参考 Platform SDK );第四 个参数指的是从 BaseAddress 开始,具有相同属性的页面的大小 。第五参数指的是页面的状态,有三种可能值: MEM_COMMIT、MEM_FREE 和 MEM_RESERVE,这个参数对我们来说是最重要的了,从中我们便可知指定内存页面的状态了; 第六个参数指的是页面的属性,其可能的取值与 AllocationProtect 相同;最后一个参数指明了该内存块的类型,有三种可能值: MEM_IMAGE 、 MEM_MAPPED 和 MEM_PRIVATE。这样我们就可得到进程中需要扫描的地址范围了 。到这里剩下的问题就是要读取指定的进程的指定的地地址空间的内容了 。这里要用到的是用于调试程序和错误处理( Debugging and Error Handling )的 API函数 。在“ Platform SDK: Debugging and Error Handling” 章节中,介绍了一部分与程序调试和错误处理相关的 API函数,有许多是很有用,例如我们下面用到的 ReadProcessMemory 和 WriteProcessMemory,它们原型如下: BOOL ReadProcessMemory( HANDLE hProcess , // handle to the process LPCVOID lpBaseAddress , // base of memory area LPVOID lpBuffer , // data buffer SIZE_T nSize , // number of bytes to read SIZE_T * lpNumberOfBytesRead // number of bytes read ); BOOL WriteProcessMemory( HANDLE hProcess , // handle to process LPVOID lpBaseAddress , // base of memory area LPCVOID lpBuffer , // data buffer SIZE_T nSize , // count of bytes to write SIZE_T * lpNumberOfBytesWritten// count of bytes written ); 参数很简单从它们的名字都可以猜出其意义了,这里就不多做说明了 。要说明的是 要对一个进程进行 ReadProcessMemory操作,当前进程对要读的进程必须有PROCESS_VM_READ访问权 。要对一个进程进行WriteProcessMemory操作,当前进程对要写的进程必须有PROCESS_VM_WRITE 和PROCESS_VM_OPERATION访问权 。要获得一个进程的句柄和对这个进程的一些控制权可以使用API函数OpenProcess得到,其使用不做详细说明了,只给出其原型: HANDLE OpenProcess( DWORD dwDesiredAccess , // access flag BOOL bInheritHandle , // handle inheritance option DWORD dwProcessId // process identifIEr ); 这样对一个进程的用户地址空间内存扫描的流程基本就阐述清楚了 。

推荐阅读