目录
4.MmIsAddressValid(Win x86 10-10-12)
0.Win7设置10-10-12分页
CMD:
关闭指令(开启10-10-12分页)
bcdedit /set pae ForceDisable
bcdedit /set nx AlwaysOff
开启指令(开启2-9-9-12分页)
bcdedit /set pae forceEnable
bcdedit /set nx OptIn
1.10-10-12分页规则
x86下每个进程有4GB线性地址空间,但是有些地址无法访问,这是因为线性地址对应物理地址为空或者对应物理页属性限制.
代码示例:
MOV EAX, DWORD PTR DS:[0x12345678]
有效地址: 0x12345678
线性地址: DS.BASE + 0x12345678
物理地址: 10-10-12分页 通过高31-22位定位PDE,通过21-12位定位PTE,通过PTE指向基址 + 11-00位定位页内偏移
每个进程都有一个CR3(CR3是一个寄存器,每个核心一个) CR3指向一个物理页(4KB),10-10-12分页模式下CR3指向PDT
分页规则
物理页: 4KB(4096BYTE)
10: PDE(4BYTE),物理页(4KB),一张物理页可以存储 4096 / 4 (1024),要遍历每一个PDE 2^10
10: PTE(4BYTE),物理页(4KB),一张物理页可以存储 4096 / 4 (1024),要遍历每一个PTE 2^10
12: 物理页(4KB),要遍历每一个字节 2^12
10-10-12线性地址上限: 1024 * 1024 * 4096 4GB
10-10-12物理地址上限: 2^32(物理地址宽度) 4GB
相同物理页
MOV EAX, DWORD PTR DS:[0x12345FFF]
MOV EAX, DWORD PTR DS:[0x12345EEE]
线性地址前20位(PDE PTE)决定在哪张物理页,后12位(OFFSET)决定页内偏移
PTE规则
PTE可以没有物理页,且只能对应一个物理页.
多个PTE也可以指向同一个物理页.
2.10-10-12分页PDE PTE属性探测
测试代码使用内核函数MmIsAddressValid实现原理后文将分析
如果代码执行后不生效TLB(后需章节介绍)没有刷新,切换进程或打开新进程后在运行
<1>.P位
P = 1 有效的物理页
P = 0 无效的物理页
#include <stdio.h>
#include <windows.h>
DWORD g_Data = 0x12345678;
DWORD g_offs = (DWORD)&g_Data & 0xFFF;
DWORD g_PDE = 0;
DWORD g_PTE = 0;
_declspec(naked) VOID ChangePTEAttribute()
{
__asm
{
//获取g_Data线性地址
lea ecx, g_Data
//获取PDE
mov eax, ecx
shr eax, 0x14
and eax, 0xFFC
sub eax, 0x3FD00000
mov eax, [eax]
mov g_PDE, eax
//PDE->P
test al, 1
jz END
//获取PTE
mov eax, ecx
shr eax, 0xA
and eax, 0x3FFFFC
sub eax, 0x40000000
mov eax, [eax]
mov g_PTE, eax
//PTE->P
test al, 1
jz END
//获取0线性地址
mov ecx, 0
//0线性地址挂PTE
mov eax, ecx
shr eax, 0xA
and eax, 0x3FFFFC
sub eax, 0x40000000
mov edx, g_PTE
mov [eax], edx
END:
iretd
}
}
int main()
{
printf("INTERRUPT ADDR [0x%08x] \n",ChangePTEAttribute);
printf("ADDR [0x%08x] DATA [0x%08x] OFFS [0x%03x] \n",&g_Data, g_Data, g_offs);
system("Pause");
__asm
{
//通过中断门提权给0线性地址挂物理页
//eq 80b99500 0040ee00`00081005
INT 0x20
}
//中断门执行返回后 0线性地址与g_Data变量地址对应的为同一张物理页
*(LPDWORD)(0 + g_offs) = 0xFFFFFFFF;
printf("ADDR [0x%08x] DATA [0x%08x] OFFS [0x%03x] \n",&g_Data, g_Data, g_offs);
system("Pause");
return 0;
}
变量地址为: 0x00412030 转换为二进制 0000 0000 0100 0001 0010 0000 0011 0000
按照10-10-12分页格式转换:
10:00 0000 0001 001h PDE
10:00 0001 0010 012h PTE
12:0000 0011 0000 030h OFFSET
通过Windbg手动查看:
1).通过!process 0 0查找进程CR3
DriBase = Cr3 = PDT.Base = 0x0a207000h
2).查找PDE(大小4字节)(PDT(页目录表基址) + PDI(页目录项索引) * 4)
!dd(4Byte方式)查看物理地址
3).查找PTE(大小4字节)(PTT(页表基址) + PTI(页表项索引) * 4)
查找需要将PDE后12位抹除(为PDE属性)
4).查找线性地址对应物理地址(Physical Page + offset)
查找需要将PTE后12位抹除(为PTE属性)
变量线性地址0x00412030对应物理地址为:#44c94030
0线性地址默认不可访问是因为没有挂有效物理地址
上述代码执行完毕后将0线性地址对应物理地址改为变量所对应物理地址,即0线性地址此时存在有效物理页
<2>.R/W位
R/W = 0 只读
R/W = 1 可读可写
#include <stdio.h>
#include <windows.h>
CHAR* Data = "HHHHHH";
DWORD g_PDE = 0;
DWORD g_PTE = 0;
_declspec(naked) VOID ChangePTEAttribute()
{
__asm
{
//获取线性地址
mov ecx, Data
//获取PDE
mov eax, ecx
shr eax, 0x14
and eax, 0xFFC
sub eax, 0x3FD00000
mov eax, [eax]
mov g_PDE, eax
//PDE->P
test al, 1
jz END
//获取PTE
mov eax, ecx
shr eax, 0xA
and eax, 0x3FFFFC
sub eax, 0x40000000
mov edx, [eax]
mov g_PTE, edx
//PTE->P
test dl, 1
jz END
//页属性修改可读可写
or dl, 2
mov [eax], edx
END:
iretd
}
}
int main()
{
printf("INTERRUPT ADDR [0x%08x] \n",ChangePTEAttribute);
printf("ADDR [0x%08X] DATA [%s] \n",Data, Data);
system("Pause");
__asm
{
//通过中断门提权修改页属性
//eq 80b99500 0040ee00`00081005
INT 0x20
}
//默认情况Data[0]对应物理页属性为只读 通过修改可以改写
Data[0] = 'A';
printf("DATA [%s] PDE [0x%08x] PTE [0x%08x] \n",Data, g_PDE,g_PTE);
system("Pause");
return 0;
}
变量地址为: 0x0041010C 转换为二进制 0000 0000 0100 0001 0000 0001 0000 1100
按照10-10-12分页格式转换:
10:00 0000 0001 001h PDE
10:00 0001 0000 010h PTE
12:0001 0000 1100 10Ch OFFSET
通过Windbg手动查看:
1).通过!process 0 0查找进程CR3
DriBase = Cr3 = PDT.Base = 0x492e8000h
2).查找PDE(大小4字节)(PDT(页目录表基址) + PDI(页目录项索引) * 4)
!dd(4Byte方式)查看物理地址
3).查找PTE(大小4字节)(PTT(页表基址) + PTI(页表项索引) * 4)
查找需要将PDE后12位抹除(为PDE属性)
4).查找线性地址对应物理地址(Physical Page + offset)
查找需要将PTE后12位抹除(为PTE属性)
变量线性地址0x0041010C对应物理地址为:#3fe1d10c
常量字符串不可以修改是因为物理页属性限制修改R/W即可改写
<3>.U/S位
U/S = 0 特权用户
U/S = 1 普通用户
2G以上是内核才能访问的原因是U/S位的设置问题,如果将内核的某个页设置为1就可以在R3访问了
0 1 2是系统环可以访问系统页和用户页0环是特权级环1、 2环虽然不是特权级环 但是是系统环 3环是用户环 可以访问用户页
#include <stdio.h>
#include <windows.h>
DWORD g_PDE = 0;
DWORD g_PTE = 0;
_declspec(naked) VOID ChangePTEAttribute()
{
__asm
{
//获取线性地址
mov ecx, 0x80b99008
//获取PDE
mov eax, ecx
shr eax, 0x14
and eax, 0xFFC
sub eax, 0x3FD00000
mov edx, [eax]
test dl, 1
jz END
or dl, 4
mov [eax], edx
mov g_PDE, edx
//获取PTE
mov eax, ecx
shr eax, 0xA
and eax, 0x3FFFFC
sub eax, 0x40000000
mov edx, [eax]
test dl, 1
jz END
//全局页修改G位
and edx, 0xFFFFFEFF
//修改U/S位
or dl, 4
mov [eax], edx
mov g_PTE, edx
END:
iretd
}
}
int main()
{
printf("INTERRUPT ADDR [0x%08x] \n",ChangePTEAttribute);
system("Pause");
__asm
{
//通过中断门提权修改页属性
//eq 80b99500 0040ee00`00081005
INT 0x20
}
printf("PDE [0x%08x] PTE [0x%08x] \n",g_PDE,g_PTE);
//高2G空间默认为特权权限访问修改页属性后用户权限也可以访问
printf("DATA [0x%08x] \n",*(LPDWORD)0x80b99008);
system("Pause");
return 0;
}
地址为: 0x80B99008 转换为二进制 1000 0000 1011 1001 1001 0000 0000 1000
按照10-10-12分页格式转换:
10:10 0000 0010 202h PDE
10:11 1001 1001 399h PTE
12:0000 0000 1000 008h OFFSET
通过Windbg手动查看:
1).通过!process 0 0查找进程CR3
DriBase = Cr3 = PDT.Base = 0x491ef000h
2).查找PDE(大小4字节)(PDT(页目录表基址) + PDI(页目录项索引) * 4)
!dd(4Byte方式)查看物理地址
3).查找PTE(大小4字节)(PTT(页表基址) + PTI(页表项索引) * 4)
查找需要将PDE后12位抹除(为PDE属性)
4).查找线性地址对应物理地址(Physical Page + offset)
查找需要将PTE后12位抹除(为PTE属性)
线性地址0x80B99008对应物理地址为:#b99008
应用层不可以访问高2G内存是因为物理页属性限制修改U/S即可改写
<4>.PS位
只对PDE有意义
PS = 0 指向PTE
PS = 1 没有PTE 直接指向物理页 低22位是页内偏移 大页(2 ^ 22)4MB
<5>.A位
A = 0 未访问(读或者写)
A = 1 已访问(读或者写)
<6>.D位
D = 0没有被写过
D = 1被写过
3.PDT PTT 基址
10-10-12分页模式
1.页表被映射到了从0xC0000000到0xC03FFFFF的4M地址空间(1024PTT * 4096)
2.在这1024个表中有一张特殊的表:页目录表
3.页目录被映射到了0xC0300000开始处的4K地址空间
0xc0300000 指向页目录表(PDT)
0xc0000000 指向第一张页表(PTT)
页目录表其实是一张特殊的页表,它是第0x300张页表。
页目录表中每项PTE都指向一张页表,其中第0x300项指向了页目录表自己
PDI(PDE INDEX) PTI(PTE INDEX)
访问页目录表的公式:
0xC0300000 + PDI * 4
访问页表的公式:
0xC0000000 + PDI * 4096 + PTI * 4
4.MmIsAddressValid(Win x86 10-10-12)
内核ntoskrnl.exe 10-10-12 分页
5.代码实例
<1>.通过代码给0线性地址挂物理页并读写
#include <stdio.h>
#include <windows.h>
//读写物理页地址
LPDWORD g_lp = NULL;
//记录PDE PTE
DWORD dwPDE = 0;
DWORD dwPTE = 0;
//0环权限修改PDE PTE
VOID _declspec(naked)Fun()
{
__asm
{
pushad
pushfd
//获取物理页
mov ecx, g_lp
//PDE
mov eax, ecx
shr eax, 0x14
and eax, 0x0FFC
sub eax, 0x3FD00000
mov eax, [eax]
mov dwPDE, eax
//PTE
mov eax, ecx
shr eax, 0xA
and eax, 0x3FFFFC
sub eax, 0x40000000
mov eax, [eax]
mov dwPTE, eax
//获取0物理页
xor ecx, ecx
//PDE
mov eax, ecx
shr eax, 0x14
and eax, 0x0FFC
sub eax, 0x3FD00000
//备份对应线性地址
mov edx, eax
mov eax, [eax]
//判断是否存在PDE
and eax, eax
jnz Flag
mov ebx, dwPTE
mov [edx], ebx
Flag:
//PTE
mov eax, ecx
shr eax, 0xA
and eax, 0x3FFFFC
sub eax, 0x40000000
mov edx, dwPTE
mov [eax], edx
popfd
popad
retf
}
}
int main()
{
//Get Page
g_lp = (LPDWORD)VirtualAlloc(NULL,0x100,MEM_COMMIT|MEM_RESERVE,PAGE_READWRITE);
if (!g_lp)
{
MessageBox(NULL,"VirtualAlloc Fail !","ERROR",MB_OK|MB_ICONERROR);
exit(0);
}
g_lp[0] = 0x12345678;
printf("Data = %08x \n",g_lp[0]);
system("Pause");
//CALL GATE
printf("Function Addr = %08x \n",Fun);
system("Pause");
CHAR calldata[6] = {0,0,0,0,0x48,0};
__asm
{
call fword ptr [calldata]
}
printf("PDE = %08X PTE = %08X \n",dwPDE,dwPTE);
*(LPDWORD)0 = 0xFFFFFFFF;
printf("Data = %08x \n",g_lp[0]);
system("Pause");
return 0;
}
<2>.0线性地址实现SHELLCODE
#include <stdio.h>
#include <windows.h>
//PTE PDE
DWORD dwCodePDE = 0;
DWORD dwCodePTE = 0;
DWORD dwZeroPDE = 0;
DWORD dwZeroPTE = 0;
//SHELLCODE
CHAR Data[] =
{
0x6a,0x00,0x6a,0x00,0x6a,0x00,0x6a,0x00,//PUSH
0xE8,0x00,0x00,0x00,0x00, //CALL
0xc3 //ret
};
VOID _declspec(naked)Fun()
{
__asm
{
//保留寄存器数据
pushad
pushfd
//获取CODE PAGE
lea ecx, Data
//PDI
shr eax, 0x14
and eax, 0x00000FFC
sub eax, 0x3FD00000
mov eax, [eax]
mov dwCodePDE, eax
//获取LP PTI
shr ecx, 0xA
and ecx, 0x003FFFFC
sub ecx, 0x40000000
mov eax, [ecx]
mov dwCodePTE, eax
//0线性地址挂物理页
xor ecx, ecx
mov eax, ecx
//PDI
shr eax, 0x14
and eax, 0x00000FFC
sub eax, 0x3FD00000
mov edx, eax
mov eax, [eax]
mov dwZeroPDE, eax
//判断PDE是否为0
and eax, eax
jnz FLAG
mov eax, edx
mov ebx, dwCodePDE
mov [eax], ebx
mov dwZeroPDE, ebx
FLAG:
//修改0 PTE
shr ecx, 0xA
and ecx, 0x003FFFFC
sub ecx, 0x40000000
mov edx, dwCodePTE
mov [ecx], edx
mov ecx, [ecx]
mov dwZeroPTE, ecx
//恢复寄存器数据
popfd
popad
retf
}
}
int main()
{
//获取函数地址
DWORD dwAddr = (DWORD)MessageBox;
//修正SHELLCODE
DWORD dwOffset = (DWORD)Data & 0xFFF;
*(LPDWORD)&Data[9] = dwAddr - (dwOffset + 13);
printf("Function Addr = %08x \n", Fun);
system("Pause");
//CALL GATE
CHAR calldata[6] = {0,0,0,0,0x48,0};
__asm
{
call fword ptr [calldata]
call [dwOffset]
}
printf("Code PDE = %08x PTE = %08x \n",dwCodePDE,dwCodePTE);
printf("Zero PDE = %08x PTE = %08x \n",dwZeroPDE,dwZeroPTE);
system("Pause");
return 0;
}