页管理与段管理的关系
分页是用于从虚拟地址到物理地址转换的结构,与段式内存管理的关系如下图:
10-10-12分页结构
需要注意以下几点:
- CR3 寄存器存储了一个页目录表的物理地址,处理器每个核有一个 CR3 寄存器,而每个进程对应一个页目录表,维护一个 4GB 的线性空间,改变 CR3 寄存器的值也就切换了进程空间。
- 页目录表(PDE),页表(PTT)和物理页的大小均为 4KB 。其中 PDT 中的 PDE 和 PTT 中的 PTE 大小均为 4B,因此 PDT 和 PTT 中的元素个数均为 1024 个。
- PDE 中的第 0x301 项 PDE 指向所在的 PDT,又因为 PDE 和 PTE 结构一致,因此操作系统就可以通过线性地址访问 PDT 和 PTT 中的任意位置。
- 为避免套娃,所有线性地址向物理地址转换时所查询 PDE 和 PTE 所用的地址均为物理地址。
- PDE 和 PTE 不一定都存放地址,因为对应线性地址可能没有映射到物理页,多个 PTE可能指向同一个物理页(共享内存),多个 PDE 也可能指向同一个 PTT(所有进程高 2G 内存对应的 PTT 相同)。
线性地址到物理地址转换过程
转换过程如下图所示:
由于 PDT 和 PTT 中元素个数均为
2
10
2^{10}
210 ,而物理页大小为
2
12
2^{12}
212 字节,因此可以将线性地址划分为 10bit ,10bit ,12bit 三部分,前两部分按照 4 字节寻址,后一部分按照 1 字节寻址,这样便可以通过 10-10-12 分页将线性地址转换为物理地址。
首先 CPU 通过 CR3 寄存器 + 线性地址 Directory * 4 得到一个 PDE。判断 PDE 中的 PS 位是否为 1 ,如果 PS 为 1 则 PDE 直接指向一个 4MB 大小的物理页的基址,直接将线性地址的低 22 位加上该物理页的基址即可得到物理地址。如果 PS 为 0 则根据 PDE 中存储的 PTT 基址 +线性地址 Table * 4 得到一个物理页基址,将该物理页基址加上线性地址 Offset 即可得到线性地址对应的物理地址。
根据这一寻址方式,我们可以推断出两类特殊的线性地址:
- 当前 CR3 指向的 PTE 的线性地址:0xC0300000
- 因此第 N 个 PDE 的线性地址为:0xc030000 + N * 4
- 其中第 0x301 个 PDE 存放着 CR3,对应线性地址为:0xC0300c00
- 第一个 PDE 指向的 PTT(如果有)基址对应的线性地址:0xC0000000
- 因此第 N 个 PDE 指向的 PTT 的第 M 个 PTE的线性地址为:0xc0000000 + N * 4096 + M * 4
物理页属性
物理页的属性 = PDE 属性 & PTE 属性
- P位:有效位
注意:当PDE或PTE中有一个的属性P=0时,物理页就是无效的 - R/W位:读写位
- R/W=0:只读
- R/W=1:可读可写
- U/S位:权限位
- U/S=0:只有特权用户才能访问(0到2环)
- U/S=1:普通用户也可以访问(3环)
- PS位:PDE特有,PS即PageSize
- PS=1:PDE直接指向物理页,低22位=页内偏移,偏移最大值为4MB,俗称"大页"
- PS=0:PDE指向PTE
- A位:访问位
- A=1:该PDE/PTE被访问过
- A=0:该PDE/PTE未被访问过
- D位:脏位
- D=1:该PDE/PTE被写过
- D=0:该PDE/PTE未被写过
实验
根据线性地址找物理地址
通过 CE 确定记事本内容所在逻辑地址 0xAEF80。由于 Windows 的数据段寄存器基址为 0,因此该地址可以看做是线性地址。
WinDbg 获取 CR3
kd> !process 0 0
.
.
.
Failed to get VadRoot
PROCESS 8a01dda0 SessionId: 0 Cid: 0498 Peb: 7ffde000 ParentCid: 05f8
DirBase: a9024000 ObjectTable: e1bf5dc8 HandleCount: 52.
Image: notepad.exe
将 0xAEF80 地址按 10-10-12 分页进行拆分:0xAEF80 = 0x0 << 22 | 0xAE << 12 | 0x80
在 WinDbg 查找到对应物理地址:
kd> !dd a9024000 + 0
#a9024000 a8471067 a959f067 a997d067 00000000
#a9024010 a90b0067 00000000 00000000 00000000
#a9024020 00000000 00000000 00000000 00000000
#a9024030 00000000 00000000 00000000 00000000
#a9024040 00000000 00000000 00000000 00000000
#a9024050 00000000 00000000 00000000 00000000
#a9024060 00000000 00000000 00000000 00000000
#a9024070 00000000 00000000 00000000 00000000
kd> !dd a8471000 + ae * 4
#a84712b8 a9236067 a8cf9067 a89ba067 a8f3b067
#a84712c8 a8e7c067 a99bd067 a9afe067 ae80d067
#a84712d8 00000000 00000000 00000000 00000000
#a84712e8 00000000 00000000 00000000 00000000
#a84712f8 00000000 00000000 00000000 00000000
#a8471308 00000000 00000000 00000000 00000000
#a8471318 00000000 00000000 00000000 00000000
#a8471328 00000000 00000000 00000000 00000000
kd> !dd a9236000 + f80
#a9236f80 006b0073 00310079 00330032 00000000
#a9236f90 00000000 00000000 00000000 00000000
#a9236fa0 00000000 00000000 00000000 00000000
#a9236fb0 00000000 00a4000c 00080002 000a0100
#a9236fc0 98755206 000a0000 00020002 0008010e
#a9236fd0 00000000 00310079 00020094 000c010c
#a9236fe0 7468759c 00000001 bebacc94 46625cd3
#a9236ff0 31f3e0a1 69364999 58c99d96 42ce2f9b
kd> !db a9236f80
#a9236f80 73 00 6b 00 79 00 31 00-32 00 33 00 00 00 00 00 s.k.y.1.2.3.....
#a9236f90 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
#a9236fa0 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
#a9236fb0 00 00 00 00 0c 00 a4 00-02 00 08 00 00 01 0a 00 ................
#a9236fc0 06 52 75 98 00 00 0a 00-02 00 02 00 0e 01 08 00 .Ru.............
#a9236fd0 00 00 00 00 79 00 31 00-94 00 02 00 0c 01 0c 00 ....y.1.........
#a9236fe0 9c 75 68 74 01 00 00 00-94 cc ba be d3 5c 62 46 .uht.........\bF
#a9236ff0 a1 e0 f3 31 99 49 36 69-96 9d c9 58 9b 2f ce 42 ...1.I6i...X./.B
获取 CR3
构造调用门提权后读取 0xC0300C00 处的地址。
#include<stdio.h>
#include<stdlib.h>
unsigned int _cr3;
__declspec(naked) void test(){
__asm{
int 3
mov eax,dword ptr ds:[0xc0300c00]
mov _cr3,eax
retf
}
}
int main(){
unsigned char buf[6]={0,0,0,0,0x48,0};
printf("test: %X\n",test);
system("pause");
__asm{
push fs
pushad
pushfd
call fword ptr ds:[buf]
popfd
popad
pop fs
}
_cr3 = (_cr3 >> 12) << 12;
printf("CR3: %X\n",_cr3);
return 0;
}
通过修改物理页属性使常量可修改
首先测试一下 0 环权限是否可以修改常量
#include <stdio.h>
#include <stdlib.h>
const int val = 0;
__declspec(naked) void callGate() {
__asm {
mov val, 0x12345678
retf
}
}
int main() {
unsigned char buf[] = {0, 0, 0, 0, 0x48, 0};
printf("callGate: %X\nval: %X", callGate, &val);
system("pause");
__asm {
call fword ptr ds:[buf]
}
return 0;
}
触发 c0000005 错误(好在没有蓝屏)。
判断读写属性不能只看权限。调试器修改内存数据并不是简单的提权,实际上也修改了 CR3 寄存器的写保护标志。
利用调用门提权后将页表的 R/W 位置 1 。可以修改常量。
#include <stdio.h>
#include <stdlib.h>
const int val = 0;
__declspec(naked) void callGate() {
__asm {
push 0x30
pop fs
pushad
pushfd
lea eax, val
shr eax, 20
and eax, 0xFFC
or eax, 0xC0300000
mov ebx,[eax]
or ebx, 2
mov [eax],ebx
lea eax, val
shr eax, 10
and eax, 0x3FFFFC
or eax, 0xC0000000
mov ebx,[eax]
or ebx, 2
mov [eax], ebx
popfd
popad
retf
}
}
int main() {
unsigned char gate[] = {0, 0, 0, 0, 0x48, 0};
printf("Callgate: %X\nval addr: %X\n", callGate, &val);
system("pause");
__asm {
push fs
pushad
pushfd
call fword ptr ds:[gate]
popfd
popad
pop fs
mov val,0x12345678
}
printf("val: %X\n", val);
return 0;
}
通过修改物理页属性读高2G内存
复用上一实验代码
#include <stdio.h>
#include <stdlib.h>
unsigned int *val = (unsigned int *) 0x8003F00C;
__declspec(naked) void callGate() {
__asm {
push 0x30
pop fs
pushad
pushfd
mov eax, val
shr eax, 20
and eax, 0xFFC
or eax, 0xC0300000
mov ebx,[eax]
or ebx, 4
mov [eax],ebx
mov eax, val
shr eax, 10
and eax, 0x3FFFFC
or eax, 0xC0000000
mov ebx,[eax]
or ebx, 4
mov [eax], ebx
popfd
popad
retf
}
}
int main() {
unsigned char gate[] = {0, 0, 0, 0, 0x48, 0};
unsigned tval;
printf("Callgate: %X\n", callGate);
system("pause");
__asm {
push fs
pushad
pushfd
call fword ptr ds:[gate]
popfd
popad
pop fs
}
tval = *val;
printf("val: %X\n", tval);
return 0;
}
不稳定,下过 int3 断点后成功率会高一些,原因未知。
挂物理页执行shellcode
代码如下,调用门提权后判断 0 地址对应 PDE 和 PTE 是否有效,若无效则将 buf 对应结构的内容写入。
#include <Windows.h>
#include <stdio.h>
#include <stdlib.h>
// push 0; push 0; push 0; push 0; call MessageBox; retn;
unsigned char buf[] = {0x6a, 0x00, 0x6a, 0x00, 0x6a, 0x00, 0x6a, 0x00, 0xe8, 0x00, 0x00, 0x00, 0x00, 0xc3};
__declspec(naked) void callGate() {
__asm {
push 0x30
pop fs
pushad
pushfd
lea eax, buf
mov ebx, dword ptr ds:[0xC0300000]
test ebx, ebx
je __getPDE
shr eax, 10
and eax, 0x3FFFFc
or eax, 0xC0000000
mov eax, [eax]
mov dword ptr ds:[0xC0000000], eax
jmp __return
__getPDE:
shr eax, 20
and eax, 0xFFC
or eax, 0xC0300000
mov eax, [eax]
mov dword ptr ds:[0xC0300000], eax
__return:
popfd
popad
retf
}
}
int main() {
unsigned int funcAddress = (unsigned int) MessageBox;
unsigned int _offset = ((unsigned int) buf) & 0xFFF;
unsigned char gate[] = {0, 0, 0, 0, 0x48, 0};
*(unsigned int *) (&buf[9]) = funcAddress - (13 + _offset);
printf("callGate: %X\n", callGate);
system("pause");
__asm {
push fs
pushad
pushfd
call fword ptr ds:[gate]
popfd
popad
pop fs
mov eax, _offset
call eax
}
return 0;
}
运行结果: