分享
 
 
 

Windows2000下用户模式的内存扫描

王朝system·作者佚名  2006-01-08
窄屏简体版  字體: |||超大  

Windows2000下用户模式的内存扫描

Sprite

简述:

本文简要介绍了在Windows2000下实现内存扫描的基本理论和实现的办

法。内存扫描是一项重要的技术,有相当广泛的应用范围:如病毒扫描、

游戏修改等。Windows2000是一个完全保护的系统,且具有两种工作模式,

即用户态和核心态(User Model and Kernel Model)。内存扫描也可分为

用户态的内存扫描与核心态的内存扫描。本文主要讲述的是工作于用户态

的内存扫描。

一.相关理论

早期在DOS坏境下进行内存扫描是一件相对简单的事情。因为DOS工作在

CPU的实模式下,没有采用虚存技术也没有提供内存的保护机制,只要实实

在在的扫描完所有的物理内存,一切工作也就完成了,早期有一些防毒软

件就是用了这样的办法。当然为了提高效率,我们并不用扫描所有的内存

区域,因为有些空间是没有被用到的,扫描这些地方也是只浪费时间。这

可以通过遍历DOS系统的MCB(Memory Control Block)链,来得到实际内

存的使用区域,从而使扫描的效率大大提高。相似的思路在Windows2000下

的内存扫描也是适用的。

Windows2000则是一个完全保护的系统,工作于CPU的保护模式下,引入

了虚存技术。每个进程拥有独立的4GB的地址空间,其中低的2GB为进程的私有空间,高的2GB为系统空间的映射(如果在Boot.ini文件中使用

“/3GB”的开关可以使进程的私有空间增大到3GB,系统空间1GB)。对于

每个进程来讲其虚拟的地址空间是连续的,实际上它们是以页面为单位

离散的存在于物理内存中,一些可能被交换到硬盘上的页面文件中,而

且还有大部分的空间是未提交(Uncommitted)的。因此在Windows2000

中对进程的用户空间进行扫描必须依次对每个进程的空间进行扫描。一

个进程的低2GB有空间的分布如下表:

范围

大小

作用

0x0~~ 0xFFFF

64 KB

不可访问区域,只是用来防止非法的指针访问,访问该范围的地址会导致访问违例。

0x10000~~

0x7FFEFFFF

2 GB 减去至少192 KB

进程的私有地址空间

0x7FFDE000~~

0x7FFDEFFF

4 KB

进程中第一个线程的线程环境块,即TEB(Thread environment block)

0x7FFDF000~~ 0x7FFDFFFF

4 KB

进程的进程环境块,即PEB(Process environment block)

0x7FFE0000~~

0x7FFE0FFF

4 KB

一个共享的只读用户数据块,该块映射到到系

统空间的一个数据块,其中存放的是一些系统

信息如系统时间、时钟的滴答数、系统版本号

等。这样访问这些信息的时候系统就不用切换

到核心模式。

0x7FFE1000~~

0x7FFEFFFF

60 KB

不可访问

0x7FFF0000~~ 0x7FFFFFFF

64 KB

不可访问,用于防止线程的缓冲跨越两种模式

空间的边界

表1

二.实现

从上表可以看出,我们要扫描范围的起点和终点不是从0~~2GB,而只是其中的一

部分。要得到这个起点和终点可以使用API函数GetSystemInfo,函数的原型如下:

VOID GetSystemInfo(

LPSYSTEM_INFO lpSystemInfo // system information

);

而在结构SYSTEM_INFO中有两个域:lpMinimumApplicationAddresslpMaximumApplicationAddress(类型都是LPVOID) 中,我们就可以得到一个应用程序可用的最小和最大的地址空间。这样我们就得到了要扫描的地址的起点和终点。那么是不是这起点和终点间所有的地址都要扫描呢?并不是这样的,因为一般情况下一个进程是用不着这么大(接近2GB)的地址空间的。因此一个进程的大部分地址空间都是未用(Free)或是保留(Reserved)的,真正用到的只是那些已提交(Committed)的内存而已。

内存页面可以有三种状态:未用(Free)、保留(Reserved)和提交

(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

);

这样对一个进程的用户地址空间内存扫描的流程基本就阐述清楚了。

三 相关的问题:

在实际操作中会遇到一些问题。如果我们指定了写相关的访问权(如

PROCESS_VM_WRITE、PROCESS_SET_INFORMATION、PROCESS_ALL_ACCESS等),用

OpenProcess打开一些普通进程是没什么问题,但要是打开的是系统安全进程

(如System、Winlogon、smss、csrss、services、lsass等)或是一些注册为

服务的进程时,就会遇到“访问拒绝”的错误,这是为了系统的安全而采取的保

护手段。说明了当前的进程没有足够的权限来进行此操作。在进程控制结构中

有一个“访问令牌”(Access tokens),里面包含有本进程的权限信息。一些常

用的权限如表1所示(摘自Inside Windows2000,Third Edition)。

权限名

权限含义

SeBackup

在备份的时候绕过安全检查

SeDebug

可对一个进程进行调试

SeShutdown

可关闭本地系统

SeTakeOwnerShip

在没有得到自由访问权的情况下得到一个对象的所有权

表2

要对一个任意进程(包括系统安全进程和服务进程)进行指定了写相关的访问权的OpenProcess操作,只要当前进程具有SeDeDebug权限就可以了。要是一个用户是Administrator或是被给予了相应的权限,就可以具有该权限。可是,就算我们用Administrator帐号对一个系统安全进程执行OpenProcess(PROCESS_ALL_ACCESS,FALSE, dwProcessID)还是会遇到“访问拒绝”的错误。什么原因呢?原来在默认的情况下进程的一些访问权限是没有被使能(Enabled)的,所以我们要做的首先是使能这些权限。与此相关的一些API函数有OpenProcessToken、LookupPrivilegeValue、AdjustTokenPrivileges。我们要修改一个进程的访问令牌,首先要获得进程访问令牌的句柄,这可以通过OpenProcessToken得到,函数的原型如:

BOOL OpenProcessToken(

HANDLE ProcessHandle,

DWORD DesiredAccess,

PHANDLE TokenHandle

);

第一参数是要修改访问权限的进程句柄;第三个参数就是返回的访问令牌指针;第二个参数指定你要进行的操作类型,如要修改令牌我们要指定第二个参数为TOKEN_ADJUST_PRIVILEGES(其它一些参数可参考Platform SDK)。通过这个函数我们就可以得到当前进程的访问令牌的句柄(指定函数的第一个参数为GetCurrentProcess()就可以了)。接着我们可以调用AdjustTokenPrivileges对这个访问令牌进行修改。AdjustTokenPrivileges的原型如下:

BOOL AdjustTokenPrivileges(

HANDLE TokenHandle, // handle to token

BOOL DisableAllPrivileges, // disabling option

PTOKEN_PRIVILEGES NewState, // privilege information

DWORD BufferLength, // size of buffer

PTOKEN_PRIVILEGES PreviousState, // original state buffer

PDWORD ReturnLength // required buffer size

);

第一个参数是访问令牌的句柄;第二个参数决定是进行权限修改还是除能(Disable)所有权限;第三个参数指明要修改的权限,是一个指向TOKEN_PRIVILEGES结构的指针,该结构包含一个数组,数据组的每个项指明了权限的类型和要进行的操作; 第四个参数是结构PreviousState的长度,如果PreviousState为空,该参数应为NULL;第五个参数也是一个指向TOKEN_PRIVILEGES结构的指针,存放修改前的访问权限的信息,可空;最后一个参数为实际PreviousState结构返回的大小。在使用这个函数前再看一下TOKEN_PRIVILEGES这个结构,其声明如下:

typedef struct _TOKEN_PRIVILEGES {

DWORD PrivilegeCount;

LUID_AND_ATTRIBUTES Privileges[];

} TOKEN_PRIVILEGES, *PTOKEN_PRIVILEGES;

PrivilegeCount指的数组原素的个数,接着是一个LUID_AND_ATTRIBUTES类型的数组,再来看一下LUID_AND_ATTRIBUTES这个结构的内容,声明如下:

typedef struct _LUID_AND_ATTRIBUTES {

LUID Luid;

DWORD Attributes;

} LUID_AND_ATTRIBUTES, *PLUID_AND_ATTRIBUTES

第二个参数就指明了我们要进行的操作类型,有三个可选项:

SE_PRIVILEGE_ENABLED、SE_PRIVILEGE_ENABLED_BY_DEFAULT、

SE_PRIVILEGE_USED_FOR_ACCESS。要使能一个权限就指定Attributes

SE_PRIVILEGE_ENABLED。第一个参数就是指权限的类型,是一个LUID的

值,LUID就是指locally unique identifier,我想GUID大家是比较熟

悉的,和GUID的要求保证全局唯一不同,LUID只要保证局部唯一,就是

指在系统的每一次运行期间保证是唯一的就可以了。另外和GUID相同的

一点,LUID也是一个64位的值,相信大家都看过GUID那一大串的值,我

们要怎么样才能知道一个权限对应的LUID值是多少呢?这就要用到另外

一个API函数LookupPrivilegeValue,其原形如下:

BOOL LookupPrivilegeValue(

LPCTSTR lpSystemName, // system name

LPCTSTR lpName, // privilege name

PLUID lpLuid // locally unique identifier

);

第一个参数是系统的名称,如果是本地系统只要指明为NULL就可以了,

第三个参数就是返回LUID的指针,第二个参数就是指明了权限的名称,

如“SeDebugPrivilege”。在Winnt.h中还定义了一些权限名称的宏,

如:

#define SE_BACKUP_NAME TEXT("SeBackupPrivilege")

#define SE_RESTORE_NAME TEXT("SeRestorePrivilege")

#define SE_SHUTDOWN_NAME TEXT("SeShutdownPrivilege")

#define SE_DEBUG_NAME TEXT("SeDebugPrivilege")

这样通过这三个函数的调用,我们就可以用OpenProcess(PROCESS_ALL_ACCESS,FALSE, dwProcessID)来打获得任意进程的句柄,并

且指定了所有的访问权。

四 总结

用户模式的内存扫描还是具有想当的局限性,它不能完全扫描

Windows2000的全部内存空间。要对系统空间进行扫描,在Windows2000下,用户模式的应用程序是不能实现的。要实现对系统空间的扫描,必须

通过工作于核心模式的程序—驱动程序来实现。

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有