危险漫步博客
新鲜的“黑客思维”就是从全新的角度看待黑客技术,从更高的层面去思考;专注于黑客精神及技术交流分享的独立博客。
文章2306 浏览20612826

神奇的Windows内存映射

虚拟内存技术的核心思想是将地址和数据相分离,虚拟内存地址有MMU寻址系统解决,数据的提交则有缓存技术解决,换句话说虚拟内存是缓存技术的延伸,虚拟内存技术优点的一个重要方面就是内存映射技术,那么内存映射技术到底“神奇”在何处呢?现在,危险漫步就给大家讲讲:

一、神奇的内存映射技术

编译生成程序后,如果只运行一次程序,它会显示中文的“危险漫步”,单击确定后,请不要退出已运行的程序,再运行一次程序,则会显示英文的“WXMB”,是不是很有意思,程序无形中增加了一个“程序运行次数的开关”,并且实现了进程间的通信。

上面的小程序只是关闭了PE加载器内存映射文件时的写时复制功能(write_on_copy),接下来一步一步通过实验的方法理解内存映射文件机制的一些细节内容。

二、磁盘文件内存映射后的完整性

内存映射文件机制实际上是将磁盘文件“原封不动”的映射到虚拟内存中,理解“原封不动”一词就能明白为什么通过内存映射文件操作PE文件时还要计算节偏移。看下面程序代码:

四、PE加载器II载PE文件l的内存映射文件特点

当运行一个程序时,Windows的PE加载器会有一个内存映射文件的过程,因为这个过程是有系统完成的,所以与应用程序的内存映射文件略有不同,具体分析如下:

1.内存保护特点

编译程序后,用OD载入目标程序,在OD的命令窗口中输入:dd.eax(此时eax为cmd.exe在虚拟内存中的基址),选中内存420000到492FFC之间的区域(整个cmd.exe内存映射文件后的虚拟内存中的数据),右键“备份”一一“保存数据到文件”,将保存文件_00420000.mem的后缀名改为exe,双击正常运行。

从内存中备份出来的文件内容与磁盘文件一样可以实现正常运行,说明内存中的数据与磁盘文件的数据应该是一致的。

三、内存映射文件的实时性

内存映射文件还有一个好处——能快速实现对磁盘文件的修改,即对内存的操作实际上相当于对磁盘文件的操作(实时性),这要比使用C/C++ I/0或者Windows提供的ReadFile()、WriteFile0效率的多,下面的代码中省略号部分与l.cpp一致。

四、PE加载器Il载PE文件时的内存映射文件特点

当运行一个程序时,Windows的PE加载器会有一个内存映射文件的过程,因为这个过程是有系统完成的,所以与应用程序的内存映射文件略有不同,具体分析如下:

1.内存保护特点

编译成功后,直接运行程序,运行后用C32ASM打开cmd.exe,cmd.exe的MZ头中的M变成了a (Ox61),对内存的修改实时转到对文件的修改。

编译程序后运行,程序先用GetModuleHandle()得到当前运行程序的句柄一一加

载基址(一般为Ox400000,此处即为PE内存映射文件的虚拟内存起始位置),然后查询该基址处的内存保护属性为2,即为PAGE READONLY,即内存为只读,为了保证程序能在墓址处写入数据Ox61,程序先调用VirtualProtect0将-该处内存设置为PAGE_READWRITE (Ox4),但是实际的设置结果为Ox8即PAGE_WRITECOPY(写时复制),写时复制机制会在后面讨论,这里说明一个问题,虽然可以通过系统提供的API函数修改内存保护属性,但是最终修改结果有系统决定,这保证了写时复制机制的使用。

2.内存修改特点

在三中,修改文件的虚拟内存数据后,磁盘文件的内容也发生了改变,4.1的程序虽然修改了基址处的虚拟内存内容,但是磁盘文件并没有被“实时”修改,原因是因为当系统的Shell程序(explorer.exe或cmd,exe)调用CreateProcess()创建进程时,会先用ZwOpenFile()打开CreateProcess()的第一个参数(PE文件名),在这个过程中会传递FILEGENERIC_READ标志,即只对磁盘文件进行读操作。结合下面的实例可以理解上面的过程。

编译程序后运行,用OD载入程序,bp ZwOpenFile,F9运行程序,注意栈区栈顶位置即为函数的返回地址,按CTRL+G跳转到7c818fcc的位置,ZwOpenFile函数的原型如下:

因为Windows的API函数调用是stdcall方式,参数是从右到左依次进栈的,ACCESS_MASK的值即想对磁盘文件进行的操作为第二个参数,所以在反汇编代码中从函数调用位置起倒数第二个push指令的内容即为ACCESS_MASK,——对应于push 1000AI,1000A1是多个属性的“位或”值,在winddk的wdm.h中有如下定义:

也就是说PE加载器ZwOpenFile打开PE文件时使用的ACCESS MASK是FILE_GENERIC_READ(读操作),因此当我们修改了文件在内存中的数据时,文件并没有更新。

3.再次验证

修改三中的程序源码,将Cr eateFile()函数的GENERIC_READ l GENEIRC_WRITE修改为GENERIC—READ,运行程序,磁盘文件并未被修改。

结论:要实现内存文件的修改实时转换为磁盘文件的修改必须对文件具有写权限,同样要想在内存映射文件的虚拟内存区域写入数据,必须保证对该内存区域具有写权限,同时Windows的PE加载器再加载PE文件时,会保证写时复制机制的使用,即保证PE文件的内存映射文件区域为PAGE—WRITECOPY或PAGE—EXECUTE—READWRJTE。

所谓写时复制机制指的是同一个可执行文件的多个进程(运行多次,比如运行2个记事本)时,当其中一个进程如果要改写其映射文件的数据时,Windows会及早发现该操作,并开辟一个新的页文件,把要修改页的内容复制到该新页文件中,并将新的页文件与当前进程相关联。写时复制引入的根本原因是文件共享,比如运行2个记事本程序.第一次运行时,PE加载器会调用ZwOpenFile()传递FILE_GENERIC_READ的ACCESS__MASK,同时映射文件的内存区域根据PE的节属性设置,然后MapViewFile()创建一个视图,当我们第二次运行记事本时(第一次运行的记事本还在运行),PE加载器只会在MapViewFile()创建一个记事本的视图,如果此时第一个程序的内存映射文件数据已发生变化,则笫二个程序也会变化,这也正是一中程序执行效果的原因。

五:写时复制机制的关闭

一中的“神奇”程序实际上是关闭了进程的写时复制机制,通过data_seg编译器关键字关闭写时复制机制同样适用于DLL文件。

上面的代码通过/merge开关将程序所有节都添加到.text节中,并将该节的属性设置为rwes,即读、写、执行、共享。但是当我们按照一中的执行方法运行程序时,并不会得到英文的“WXMB”提示。

总的来说,通过引入虚拟内存,Windows实现了不同任务多个实例间的任务隔离,通过写时复制实现了同一任务多个实例间的隔离,通过CPU的特权级别实现了同一任务单个实例间的隔离,内存映射的最大好处就是在需要时实现数据的提交或者说缓存!

相关推荐