WIN32汇编语言教程:第13章 进程控制 · 13.3 进程调试(3)
.data?
stProcess PROCESSENTRY32 <?>
hSnapShot dd ?
.code
invoke CreateToolhelp32Snapshot,TH32CS_SNAPPROCESS,0
mov hSnapShot,eax
mov stProcess.dwSize,sizeof stProcess
invoke Process32First,hSnapShot,addr stProcess
.while eax
;在这里处理返回到PROCESSENTRY32中的进程信息
invoke Process32Next,hSnapShot,addr stProcess
.endw
invoke CloseHandle,hSnapShot
Process32First和Process32Next函数的第一个参数是前面得到的快照句柄,第二个参数指向一个PROCESSENTRY32结构,进程信息将被返回到这个结构中。结构的定义如下:
PROCESSENTRY32 STRUCT
dwSize DWORD ? ;结构的长度,必须预先设置
cntUsage DWORD ? ;进程的引用计数
th32ProcessID DWORD ? ;进程ID
th32DefaultHeapID DWORD ? ;进程默认堆的ID
th32ModuleID DWORD ? ;进程模块的ID
cntThreads DWORD ? ;被进程创建的线程数
th32ParentProcessID DWORD ? ;进程的父进程ID
pcPriClassBase DWORD ? ;被进程创建的线程的基本优先级
dwFlags DWORD ? ;内部使用
szExeFile db MAX_PATH dup(?) ;进程对应的可执行文件名
PROCESSENTRY32 ENDS
注意:在使用前需要先将dwSize填写为结构的长度,否则函数的执行会失败,在返回所有的进程信息后,需要使用CloseHandle函数将快照句柄关闭。
结构中返回的进程ID字段(th32ProcessID)和可执行文件名字段(szExeFile)是我们最关心的。通过比较文件名,就可以找出需要寻找的可执行文件产生的进程,然后通过进程ID就可以用OpenProcess函数获得进程句柄,有了进程句柄以后就可以对进程进行各种操作了。
在例子中,每当在PROCESSENTRY32结构中返回了一个进程的信息后,程序向列表框发送LB_ADDSTRING消息将可执行文件名添加到列表框中。由于列表框能够为每个项目定义一个32位的自定义数据,利用这个特征可将文件对应的进程ID保存到这里,方法就是向列表框发送LB_SETITEMDATA消息,这样在按下“终止”按钮以后,程序就可以通过LB_GETITEMDATA消息取回进程ID,使用OpenProcess函数获得该进程的句柄以后,再使用TerminateProcess函数将进程终止。
当例子程序在Windows 2000中运行时,如果试图打开系统底层的进程(如System或者[System Process]等进程)是不会成功的,因为用户程序并没有这么高的权限。
13.3.2 读写进程的地址空间
1. 进程地址空间的读写函数
当一个进程能够被我们以足够的权限打开以后,就可以通过ReadProcessMemory和WriteProcessMemory函数读写它的地址空间。只要能够对其他进程的地址空间进行读写,那么我们能够做的事情就多了,只要发挥想像力,就能够编出一些超乎想像的程序来。在广为流传的应用程序中,最为著名的就是FPE之类的游戏修改器。
FPE是Fix People Expert的缩写,不过这个软件可不是用来修理人(People)而是用来对付游戏的。FPE程序列出当前系统中运行的进程,让用户选择要对付的游戏程序名(现在读者可以骄傲地说,这一招我也会,13.3.1节中的进程列表例子不就是这样吗),然后让用户输入一个数值,比如现在游戏主角还剩下3条命就输入3,FPE将扫描游戏进程的所有地址空间,将当前数据为3的地址列入黑名单,接下来继续游戏,当又少了一条命的时候,再次输入2并扫描,如果上次黑名单中某个地址中的数据现在变成了2,代表生命的数据十有八九就存放在这个地址中,将它改成100的话,主角就长命百岁了!同样道理想让主角变成千千岁,万万岁也不在话下!另外,如果找到代表生命的地址,让FPE锁定(就是每隔很短的时间将要锁定的数值重新写到这个地址中)数值,游戏主角就是金刚不坏之躯了。
FPE是通用的修改软件,另一类专用的游戏修改器也使用同样的技术,比如打《暗黑破坏神》游戏的时候,很多人用过增加经验点数的修改器,因为这个游戏对数据经过了某种处理,用FPE一类的软件无法直接将要修改的数据搜索出来,有人就通过跟踪找到了变换后的数据地址和变换算法,并专门写了针对这个游戏的进程内存读写程序。
当然,经过本节介绍以后,读者就会觉得这些软件使用的技术并没有那么神秘,我们自己也可以写出同样的程序来。
首先来介绍一下这两个函数的用法:
invoke ReadProcessMemory,hProcess,lpBaseAddress,lpBuffer,\
dwSize,lpNumberOfBytesRead (读进程内存)
invoke WriteProcessMemory,hProcess,lpBaseAddress,lpBuffer,\
dwSize,lpNumberOfBytesWritten (写进程内存)
这两个函数的参数定义是一样的,各参数的定义为:
● hProcess——指定将要被读写的目标进程句柄。
● lpBaseAddress——目标进程中被读写的起始线性地址。
● lpBuffer——用来接收读取数据的缓冲区(对于ReadProcessMemory函数)或者要写到目标进程的数据缓冲区(对于WriteProcessMemory函数)。
● dwSize——要读写的字节数。
● lpNumberOfBytesRead或lpNumberOfBytesWritten——指向一个双字变量,用来供函数返回实际读写的字节数,如果不关心这个结果,可以在这里使用NULL。
如果函数执行成功,那么返回值是非0值,执行失败的话返回0。
使用这两个函数需要注意的地方有:
● 注意lpBaseAddress和lpBuffer参数指向的地址位于不同的进程空间内,lpBuffer指向的缓冲区位于本进程的地址空间内,而lpBaseAddress指向的地址位于目标进程的地址空间内。
● 要对目标进程进行读写的话,打开(或者创建)目标进程的时候必须包含对应的权限,如要读取目标进程必须包括PROCESS_VM_READ权限;要对目标进程进行写操作的时候,必须包括PROCESS_VM_OPERATION或者PROCESS_VM_WRITE权限。
● lpBaseAddress位置开始的dwSize大小的内存必须是可存取的,函数在执行前会对整个区域进行测试,如果中间有某处是不可存取的(如没有被递交到物理内存),那么函数直接返回失败,所以一般不会出现只读写了一部分内存的情况。
● 虽然在自己的进程中,代码段是不可写的,但是使用WriteProcessMemory函数去写目标进程的代码段却是允许的。
FPE等软件就是使用这两个函数来读写目标进程的数据的,但由于这些函数也可以用来读写目标进程的代码部分,所以也有一些程序使用它们来做内存补丁,下面介绍的一个例子演示了如何利用该函数来修改代码。