基本概念
4GB内存空间
我们都了解过每个进程都有独立的4GB空间
4GB的虚拟内存结构:
虚拟内存地址范围 描述
0x00000000~0x0000FFFF 64kb大小的空指针区域,当然就不可以访问了
0x00001000~0x7FFFFFFF 加上上述的空指针区域,低2GB的用户态空间
0x80000000~0xFFFFFFFF 高2GB的内核态空间
但这个4GB空间并不是完全都使用或者真实存在的。
实际上,进程被分配到的“4GB内存空间”只是虚拟的的内存空间,并不是指真正意义上的物理内存,虚拟内存与物理内存之间有一层转换关系。
逻辑地址-线性地址-物理地址
辑地址(Logical Address) 是指由程序产生的与段相关的偏移地址部分。例如,你在进行C语言指针编程中,可以读取指针变量本身值(&操作),实际上这个值就是逻辑地址,它是相对于你当前进程数据段的地址,不和绝对物理地址相干。只有在Intel实模式下,逻辑地址才和物理地址相等(因为实模式没有分段或分页机制,cpu不进行自动地址转换);逻辑也就是在Intel 保护模式下程序执行代码段限长内的偏移地址(假定代码段、数据段如果完全一样)。应用程序员仅需与逻辑地址打交道,而分段和分页机制对您来说是完全透明的,仅由系统编程人员涉及。应用程序员虽然自己可以直接操作内存,那也只能在操作系统给你分配的内存段操作。
线性地址(Linear Address) 是逻辑地址到物理地址变换之间的中间层。程序代码会产生逻辑地址,或者说是段中的偏移地址,加上相应段的基地址就生成了一个线性地址。如果启用了分页机制,那么线性地址可以再经变换以产生一个物理地址。若没有启用分页机制,那么线性地址直接就是物理地址。Intel 80386的线性地址空间容量为4G(2的32次方即32根地址总线寻址)。
物理地址(Physical Address) 是指出现在CPU外部地址总线上的寻址物理内存的地址信号,是地址变换的最终结果地址。如果启用了分页机制,那么线性地址会使用页目录和页表中的项变换成物理地址。如果没有启用分页机制,那么线性地址就直接成为物理地址了
逻辑地址与线性地址
先看如下指令:
MOV eax,dword ptr ds:[0x12345678]
其中,0x12345678 是有效地址
ds.Base + 0x12345678 是线性地址
注意:当段寄存器的Base为0时,有效地址=线性地址,大多数时候都是如此;但也有特殊情况,比如fs段寄存器的Base不为0
物理地址
描述:
我们平时所用到的系统DLL(动态链接库)存在于物理地址中,当程序想要调用某个DLL时,DLL便会映射一份线性地址给程序,这样程序就能够通过线性地址找到DLL的物理地址
控制寄存器:Cr3
描述:
每个进程都有一个Cr3(准确的说是都有一个Cr3的值,Cr3本身是个寄存器,一个核,只有一套寄存器)
Cr3指向一个物理页,一共4096字节
有关Cr3结构部分将在下一篇详细说明,这里只引入基本概念
其实参与分页机制的并不只是CR3,但是展开说又太复杂,所以只说下面实验能用到的
具体看手册第3卷2.5 CONTROL REGISTERS
10-10-12分页
10-10-12分页是非PAE模式
非物理地址扩展模式 物理地址扩展模式
非PAE模式 PAE模式
PAE模式:Physical address extension,物理扩展模式。能够在32位操作系统访问超过4GB寻址大小的模式,允许将最多64GB 的物理内存用作常规的4 KB 页面,并扩展内核能使用的位数以将物理内存地址从32扩展到36。
非PAE模式:在非物理扩展模式下,32位最大只能4GB所以,即使你有8G的内存条,也是白费。
那么在非PAE模式下,操作系统分页机制如何实现的?每个分页4kb,一共4GB的内存,4194304KB大小也就是一共1048576个分页,那么如何高效的管理这些分页呢?
1024(PDT) × 1024(PTT) × 4096 = 4GB
1024(PTT) × 1024(PTE) × 4 = 4MB
1024(PDT) ×1024(PTE) = 1MB
地址解析4K
解析4M地址
PS
XD/NX标志位
PAE分页模式下,PDE与PTE的最高位为XD/NX位。即PDE/PTE的最高位, 在Intel中称为XD,AMD中称为NX,即No Execution。
我们平常所说,一个数据段具有 读、写、执行三种权限,而对于一个物理页,只有 读和写 两种权限。那如果攻击者通过一些恶意的构造以执行的方式运行了某段数据,那就会造成一些不可预期的情况。比如,RET的时候使EIP跳到了某段数据上,程序就会将这段数据当作代码来执行了,如果这段数据由攻击者恶意构造,这就是任意代码执行的漏洞了。
为了解决这样的问题,从而出现了一种硬件保护技术,我们通常称其为NX保护(堆栈不可执行保护),这个保护机制就是通过在PDE/PTE上设置了一个不可执行位 – XD/NX位。若是XD/NX位被置为1时,这个物理页上的数据就不可以被当作代码来执行,就算通过溢出使EIP跳至这页内存,执行的时候也会自动将这个程序crash掉。
下面做个小实验
通过线性地址找到物理地址
在win7_x32下打开cmd,利用bcdedit命令,先做了解如下所示:
修改当前pae模式以及nx模式,pae我们知道是物理扩展模式,nx是缓解机制,使某些内存区域不可执行,并使可执行区域不可写DEP,我们也要改为Always Off模式,如下所示:
修改指令如下:
名称 关闭指令 开启指令
PAE bcdedit /set pae ForceDisable bcdedir /set forceEnable
NX bcdedit /set nx AlwaysOff bcdedit /set nx OptIn
修改后属性如下:
修改后重启系统
打开Notepad,输入一串字符串,打开CE附加它,找到字符串的线性地址
将线性地址拆分为10-10-12三组
0028DEA8
0000 0000 0010 1000 1101 1110 1010 1000
0000 0000 00 //0
10 1000 1101 //0x28D
1110 1010 1000 //0xEA8
那么解析虚拟地址之后,如何通过也目录找到具体页表呢?我们需要了解一下页目录中的每一项PDE数据,也就是指针如何分解的,如下所示:
通过上图所示,我们知道低位12位是属性,高位才是地址,意味着我们只需要BaseAddress + 第几项PTT × 4(指针大小)就可以找到相对的页表指针,
找到了具体的页表地址,加上具体的偏移(虚拟地址分解出的低12位)就可以找到映射的物理内存数据保存,当然地址还是要把地位属性去掉,用地址+偏移即可。
如下所示:
获取CR3:!process 0 0