-
-
[原创]通过C代码实现挂载NULL指针的PTE
-
发表于:
2023-8-9 21:40
2871
-
实验:通过C代码实现挂载NULL指针的PTE(XP系统 2-9-9-12分页)
1、NULL未挂载时的PDPTE、PDE、PTE
PROCESS 8980bda0 SessionId: 0 Cid: 04a4 Peb: 7ffdc000 ParentCid: 0258
DirBase: 0aa803c0 ObjectTable: e179a008 HandleCount: 18.
Image: test_R0.exe
kd> !dq 0aa803c0 + 0 // PDPTE0
# aa803c0 00000000`00315801 00000000`00196801
# aa803d0 00000000`00197801 00000000`00094801
# aa803e0 00000000`bae71400 00000000`00000000
# aa803f0 00000000`00000000 00000000`00000000
# aa80400 00000000`bae71420 00000000`00000000
# aa80410 00000000`00000000 00000000`00000000
# aa80420 00000000`bae71440 00000000`00000000
# aa80430 00000000`00000000 00000000`00000000
kd> !dq 00315000 + 0 // PDE0
# 315000 00000000`13ea4867 00000000`00335867
# 315010 00000000`0bde3867 00000000`00000000
# 315020 00000000`00000000 00000000`00000000
# 315030 00000000`00000000 00000000`00000000
# 315040 00000000`00000000 00000000`00000000
# 315050 00000000`00000000 00000000`00000000
# 315060 00000000`00000000 00000000`00000000
# 315070 00000000`00000000 00000000`00000000
kd> !dq 13ea4000 + 0 // PTE0,此时空指针的PTE还未被挂载
#13ea4000 00000000`00000000 00000000`00000000
#13ea4010 00000000`00000000 00000000`00000000
#13ea4020 00000000`00000000 00000000`00000000
#13ea4030 00000000`00000000 00000000`00000000
#13ea4040 00000000`00000000 00000000`00000000
#13ea4050 00000000`00000000 00000000`00000000
#13ea4060 00000000`00000000 00000000`00000000
#13ea4070 00000000`00000000 00000000`00000000
2、通过特殊的线性地址,页表基址:0xC0000000,在代码中操作各个变量的PTE
#include "stdafx.h"
#include <windows.h>
#include <stdio.h>
int pPET_x;
int PTE_x_low;
int PTE_x_high;
// 读高2G内容:变量x的PTE
// 0x00401120
void __declspec(naked) ReadXPTE() {
__asm {
// int 3;
push eax;
mov eax, dword ptr ds:[pPET_x]; // 取出全局变量的值
mov eax, dword ptr ds:[eax]; // 取出PTE的高4字节
mov dword ptr ds:[PTE_x_high], eax; // 将PTE的高4字节复制给全局变量PTE_x_high
mov eax, dword ptr ds:[pPET_x]; // 取出全局变量的值
mov eax, dword ptr ds:[eax+4]; // 取出PTE的低4字节
mov dword ptr ds:[PTE_x_low], eax; // 将PTE的低4字节复制给全局变量PTE_x_low
pop eax;
iretd;
}
}
// 给空指针挂载PTE
// 0x00401050
void __declspec(naked) WriteNULLPTE() {
__asm {
// int 3;
push eax;
mov eax, dword ptr ds:[PTE_x_high]; // 将PTE的高4字节挂载到NULL的高4字节位
mov dword ptr ds:[0xC0000000], eax;
mov eax, dword ptr ds:[PTE_x_low]; // 将PTE的低4字节挂载到NULL的低4字节位
mov dword ptr ds:[0xC0000000+4], eax;
pop eax;
iretd;
}
}
int main(int argc, char* argv[])
{
int* ptr = NULL;
// 申请指定线性地址的变量x,0x0012f000的后3位为0是为了使得空指针和变量x的物理页偏移相等,指向同一个物理地址
int* ptr_x = (int*)(VirtualAlloc((LPVOID)0x0012f000, 4, MEM_COMMIT, PAGE_EXECUTE_READWRITE));
*ptr_x = 0x12345678;
// 拆分变量x的线性地址 2-9-9-12分页
int lineAddress = (int)ptr_x;
int PDPTI_x = lineAddress >> 30;
int PDI_x = (lineAddress & 0x3FE00000) >> 21;
int PTI_x = (lineAddress & 0x1FF000) >> 12;
int offset_x = lineAddress & 0xFFF;
// 通过页表基址获取变量x的PTE的线性地址
pPET_x = 0xC0000000 + PDPTI_x * (2 << 21) + PDI_x * (2 << 12) + PTI_x * 8;
// 通过中断门提权,访问高2G地址0xC0000000
// gdt表中0x1偏移的段描述符段中DPL=0
// idt表中0x20偏移的中断描述符:0040EE00 00081120
__asm {
int 0x20
}
// 将变量x的PTE挂载到NULL的PTE上
// gdt表中0x1偏移的段描述符段中DPL=0
// idt表中0x21偏移的中断描述符:0040EE00 00081050
__asm {
int 0x21
}
// 读取NULL的值
printf("挂载PTE后空指针的值:%x\n", *ptr); // 运行结果:挂载PTE后空指针的值:12345678
getchar();
return 0;
}
3、NULL挂载后的PDPTE、PDE、PTE
// 程序重启过,CR3不同,但是实验效果是一致的。
PROCESS 89dfe020 SessionId: 0 Cid: 0564 Peb: 7ffd6000 ParentCid: 05b4
DirBase: 0aa80360 ObjectTable: e10e9d48 HandleCount: 16.
Image: test_R0.exe
kd> !dq 0aa80360 // PDPTE0
# aa80360 00000000`238db801 00000000`2409c801
# aa80370 00000000`2805d801 00000000`268da801
# aa80380 00000000`bae71340 00000000`2bb89801
# aa80390 00000000`271ca801 00000000`29b47801
# aa803a0 00000000`bae713c0 00000000`00000000
# aa803b0 00000000`00000000 00000000`00000000
# aa803c0 00000000`bae713e0 00000000`00000000
# aa803d0 00000000`00000000 00000000`00000000
kd> !dq 238db000 // PDE0
#238db000 00000000`2001f867 00000000`1bdbb867
#238db010 00000000`1b69e867 00000000`00000000
#238db020 00000000`00000000 00000000`00000000
#238db030 00000000`00000000 00000000`00000000
#238db040 00000000`00000000 00000000`00000000
#238db050 00000000`00000000 00000000`00000000
#238db060 00000000`00000000 00000000`00000000
#238db070 00000000`00000000 00000000`00000000
kd> !dq 2001f000 // PTE0,此时空指针的PTE已经被挂载上了x的PTE
#2001f000 00000000`1ff74867 00000000`00000000
#2001f010 00000000`00000000 00000000`00000000
#2001f020 00000000`00000000 00000000`00000000
#2001f030 00000000`00000000 00000000`00000000
#2001f040 00000000`00000000 00000000`00000000
#2001f050 00000000`00000000 00000000`00000000
#2001f060 00000000`00000000 00000000`00000000
#2001f070 00000000`00000000 00000000`00000000
4、总结
本次实验通过C代码访问线性地址0xC0000000直接操作各个变量的PTE,通过位运算的方式直接操作地址运算,后续逆向分析内核文件ntkrnlpa.exe看看它是怎么判断一个线性地址是有效的。 本次实验遇到的问题: (1) 在访问0xC0000000时发现是高2G的地址,需要CPU提权后才能操作。 (2) 读取变量x的PTE发现是8个字节,需要两个4字节变量来分开保存,要注意高4字节和低4字节。 (3) 当直接定义变量x后挂载PTE,发现NULL取值不是x的值,这是因为二者的物理页偏移不同,因此通过virtualAlloc函数指定变量的地址,通过设置变量x地址的后3位为0,即可使得NULL和x指向同一个物理地址。 (4) 本实验在处理中断门时,是手动处理的函数地址和中断门描述符。鉴于这部分是低2G的地址,不需要提权就可以直接操作,因此本实验没有写相关代码。
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课
最后于 2023-8-9 21:44
被ATrueMan编辑
,原因: 格式