要点回顾
前文主要讲解段的机制,其实段的机制还有很多细节,需要在实验的过程中详细总结。
从本文开始,开始进入页的机制的学习。
4GB内存空间
每个进程都有自己的4GB的内存空间,如果开启两个进程那么就是2x4=8GB。
那么当前计算机的内存条是多少呢?可能是4GB,或者更少。那么这样就会有一个问题,内存不够,明显是不正常的。
也就是说,每个进程都有4GB的内存空间的时候,进程的4GB空间是不是真实存在的?
每个进程的4GB进程空间是假的,并不存在的。操作系统为每个进程分配了4GB的虚假内存。
当使用某一块空间的时候,经过转换以后把数据储存到物理内存中。
那么物理内存是不是就是内存条呢?不是。
物理地址
有效地址、线性地址、物理地址。
如指令:MOV EAX, DWORD PTR DS:[0x12345678]
有效地址:0x12345678
线性地址:DS.BASE + 0x1234578
大多数情况下,DS.BASE是0。也就是说,在大多数情况下有效地址和线性地址是一样的。但也有特殊情况,比如FS段寄存器就不是一样的。
每个进程的所谓4GB进程空间是假的,这就意味着拿着0x12345678去寻找是没有办法直接找到数据的,因为这个地址本身就是假的,是不存在的。
CPU会将线性地址转换为物理地址,然后通过物理地址找到数据。
每个EXE中都会用到一些系统层面的DLL,这些DLL存放在物理内存中,但是给每份EXE都映射了一份线性地址。
这意味着如果想知道程序B做了什么事情,通常情况下要进入到程序B的进程空间中进行HOOK。
其实没有必要这么复杂,因为在我们自己的程序如程序A的进程空间中HOOK系统相关的DLL,就可以知道程序B做了什么事情。
但如果看到这里就去做实验,那么会是失败的,因为需要页的知识,不了解内部实现细节。如果HOOK了会发现对其他程序没有影响。
CPU会将线性地址转换为物理地址,转换的过程就是接下来要了解的。
它转换的方式在x8632位CPU中有两种转换形式,一种是10-10-12的形式,另一种是2-9-9-12的形式。
如果CPU是64位的情况,它还有一种形式,且相对复杂。
如果了解了32位转换,那么再去了解64位的转换就会简单很多。
10-10-12分页
10-10-12形式,加起来会发现是32。说的就是将线性地址拆分为3份,第一份10位,第二份10位,第三份12位。
设置分页方式
boot.ini
[boot loader]
timeout=30
default=multi(0)disk(0)rdisk(0)partition(1)\WINDOWS
[operating systems]
multi(0)disk(0)rdisk(0)partition(1)\WINDOWS="Microsoft Windows XP Professional [VirtualKD]" /DEBUG /DEBUGPORT=bazis /fastdetect /noexecute=optin
multi(0)disk(0)rdisk(0)partition(1)\WINDOWS="Microsoft Windows XP Professional" /noexecute=optin fastdetect
默认情况下是2-9-9-12分页形式,将 noexecute 修改为 execute,即可切换为10-10-12分页形式,保存后重启即可。
使用线性地址寻找物理地址
物理内存在哪里
每个进程都有一个CR3的值,CR3本身是个寄存器,一个核心,只有一套寄存器。CR3是唯一一个储存物理地址的寄存器。
CR3指向一个物理页,一共4096字节,如下图:
CPU拿到线性地址的时候,它要做的事情就是把线性地址拆分成3份,去找物理地址。所以CPU找的过程要拆,那么我们也要拆。
拆完了以后到哪里去找呢?它先找一个寄存器,CR3寄存器。它是唯一一个存储物理地址的寄存器。
CR3中储存了一个值,这个值指向了一个页,这个页有4KB(4096字节)。
4096字节中每个成员是4个字节,4个字节是一个地址。每个成员4个字节,意味着4096个字节可以储存1024个地址。每个地址又指向一个具体的页。
就像翻书先翻目录,只不过这本书厚一些,它有两层目录。
通过第一层目录找到第二层目录,通过第二层目录找到物理页。
10-10-12形式,加起来会发现是32。说的就是将线性地址拆分为3份,第一份10位,第二份10位,第三份12位。
第一份10,就是看在第一级的页哪个位置,并找到地址。
然后通过找到的地址指向一个页,第二份10就是找到在第二个页里面哪个位置。
第三份12,就是要找的地址在物理页的什么位置。
寻找物理地址
打开记事本,并随便写点东西。
记事本本身就是一个进程,它有自己的4GB进程空间,输入的内容一定是存在它的进程空间里的。
下面先来找到 Hello world 的线性地址,然后通过10-10-12的形式找到它的物理地址。
搜索出5个结果,通过修改记事本中的内容可以发现第一个地址的值改变了。
0x000AA8A0,该线性地址就是我们需要用的。
接下来通过这个线性地址找到物理地址。
先来计算一下,10-10-12,加起来刚好是32位。它说的就是线性地址,把线性地址分成3份,第一份10位,第二份10位,第三份12位。
线性地址:0x000AA8A0。
转二进制:0000 0000 0000 1010 1010 8A0,前面刚好是20位,后面的先不管它。
第一份:0000 0000 00,第二份:00 1010 1010,第三份:8A0。
寻找CR3
!process 0 0
寻找物理地址之前需要先找到CR3,找哪个进程的物理地址,就找哪个进程的CR3。
CR3:0x146A0000
查看物理地址
CR3:0x146A0000
第一份:0000 0000 00 (0*4)、第二份:00 1010 1010 (AA*4)、第三份:8A0
以前使用dd 0x12345678,查看某个地址的内存,但是现在不能这样了,因为要查的是物理内存。
现在需要在前面加上一个感叹号,如:!dd 0x12345678 + 第一份*4,也就是:!dd 0x146A0000 + 0*4
为什么要乘以4?因为每个成员是4字节。
- 寻找第一级
!dd 146a0000 + 第一份(0*4)
需要注意的是,后面选中的三位(067)是属性,在使用的时候将其改为0即可。
- 寻找第二级
!dd 14dc0000 + 第二份(AA*4),也就是!dd 14dc0000 + 2A8
同样的,后三位在使用的时候修改为0。
- 寻找第三级
!dd 13fdd067 + 第三份,这个时候不需要再乘以4了。!dd 13fdd067 + 8A0
寻找第三级,并以字节的形式查看。
现在,通过一个线性地址,找到了物理地址。
也就是通过hello world的线性地址,找到了它的物理地址。