Windows分页


Windows分页

Windows32主要有两种分页方式,10-10-12 和 2-9-9-12,加起来就是32位,其中2,9,10,12代表2的x次方

10-10-12分页

分页方式

根据因特尔手册给出的图可以看出,当前程序的页目录索引存在CR3中,根据PDI的地址查找页表索引,再根据页表入口地址查找物理地址

具体的计算方法如下:

以线性地址0x8003f400为例,首先拆分此线性地址为:

PDI:10 0000 0000 PTI:00 0011 1111 OFFSET:0100 0000 0000

其中PDI、PTI分别为页目录、页表的首地址,OFFSET为最后物理地址的偏移

Windbg中用r cr3展示CR3的值

PDE = CR3 & 0xffffffe0 + PDI << 2
PTE = PDE & 0xfffff000 + PTI << 2
PA  = PTE & 0xfffff000 + OFFSET

CR3低5位、PDE低12位、PTE低12位均为属性

在WinDBG中调试,在调试前先将C:\boot.ini中的配置项改为

multi(0)disk(0)rdisk(0)partition(1)\WINDOWS="Microsoft Windows XP Professional_debugf(10-10-12)" /execute=optin /fastdetect /debug /debugport=com1 /baudrate=115200

配置中的 execute表示10-10-12分页方式,noexecute表示 2-9-9-12 分页方式, 和64位进行过渡

IDTR中的值和物理IDTR对比

2-9-9-12分页

由于技术的发展,4G的内存已经不够系统使用了,所以搞了个2-9-9-12分页模式扩展内存,当然物理内存还是那些,但将基址由32位扩展到了36位,同时为了内存对齐,PTE整体由原来的4字节扩大到了8字节

由于页的大小仍保持4KB,0x1000对齐,所以后12位依旧保留

但由于PTE的长度变成了8字节,所以4KB / 8 = 512B = 2 ^ 9,这便是9位PTI的由来,因为2的9次方个PDE就能找到所有的页,因此PDI=9,还剩下两位则被分配到了PDPI

PTE结构

PDPE结构

2-9-9-12分页方式相比于10-10-12的分页方式多了一个PDPI,即pdpi:2 pdi:9 pti:9 offset:12

计算方式为:

PDPE = CR3 & 0xffffffe0 + PDPI << 3
PDE = PDPE & 0xfffff000 + PDI << 3
PTE = PDE & 0xfffff000 + PTI << 3
PA  = PTE & 0xfffff000 + OFFSET

.process /i [ID]切换一个进程,选择当前的EIP(实验中为004011c8

分割字节为2-9-9-12方式,得出pdpi:0 pdi:2 pti:1 offset:1c8

使用!dq读取CR3+0八个字节

读取CR3

接下来的操作基本与10-10-12分页相似

打印物理地址中的内存值

可以看出与线性地址中的值相同

物理地址与线性地址对比

内存跨页

当程序运行到页的边界时往往会出现跨页的情况,如401ffe 401fff 402000 402001这4个地址组成了一个完整的数据,由于页大小为0x1000,在40200处出现了跨页,此时需要拆分401ffe402000

有代码:

mov eax, dword ptr ds:[0x401ffe];
int 0x3;

Ctrl+F5运行后,成功在WinDBG中断下来

首先拆分401ffe0000000001 0000000001 ffe

获取CR3(此处CR3值为23ea9000)计算各个偏移后获取到物理地址中相对应的数据

物理地址中的数据

可以看出和线性地址中的数据并不一样,在402000后的数据发生了变化,此时就产生了分页

线性地址中的数据

拆分4020000000000001 0000000010 000,计算一系列地址后获取物理地址,找到了上面402000后消失的数据

402000物理地址

eax的值也为上述内容

PDE

属性

属性

位数 名称 全名 值为1时 值为0时
0 P 存在位 存在 不存在
1 R/W 读写位
2 U/S 用户/超级用户 用户 超级用户(系统)
3 PWT write-through / /
4 PCD 不允许缓存 / /
5 A 可访问位 访问过 没访问
6 0 保留位 / /
7 PS 页尺寸 注1 4KB
8 G 全局页 / /
9 AVL 软件可利用位 / /

注1:一个页为4MB且没有PTE,只有PDE和OFFSET

PTE

属性

和PDE的0相对应的位位D位,脏页位,即被使用过即为脏页

修改PTE使程序访问0x0线性地址

执行mov eax, dword ptr ds:[0]的情况下无法访问内存,是因为该地址在PTE中为缺页状态,访问不到物理地址。

写中断,断到Windbg中

unsigned int i;
_asm {
    int 0x3;
    mov eax, dword ptr ds:[0];
    mov i, eax;
}
printf("%x\n", i);
!dd cr3 #查看CR3地址中的值,实验时cr3为3c505000,其中的值为 3c2d6867 3c3d5867
!dd 3c2d6000 #找到线性地址为0时的PTE,发现并没有数据

PTE为0

选择PDI中第二个值,查看PTE,这与PE结构的开头相对应

PTE加偏移后的值

可执行文件头

修改第一个PTE的值为第二个PTE的值

修改PTE

输入g继续执行,输出i

输出结果

10-10-12查找线性地址中的PDE

Windbg执行!dd cr3 l400查看CR3(此处为225d6000)地址中的值,去除低12位搜索相似值的地址,在225d6c00处找到相似值

与CR3值相似的值

取该数据对应的地址 - CR3,即PDE = 225d6c00 - 225d6000 = c00

由于CR3+PDI * 4 = pde,则PDI = (PDE - CR3) / 4 = 300

0x300地址的页比较特殊,既指向PDE又指向PTE也指向OFFSET,称为上帝页,所以可以得出如下结果

PDI:300 PTI:300 OFFSET:000 => 1100 0000 0011 0000 0000 000 => c0300000

CR3物理地址和线性地址中的值对比

10-10-12查找线性地址中的PTE

由于上述的0x300被称为上帝页,则可根据分离的PDI、PTI以及OFFSET推测出PTI为0时的PTE,将PTI和OFFSET置0则可得出PTE 0的页地址C0000000

PDI PTI OFFSET

300 000 000 ===> C0000000

PTI线性地址和PTI物理地址

那么也可推测出PTI为1时,PTE为C0001000,以此类推,公式为PTE = c0000000 + PDI*0x1000 + PTI * 0x4

PTI线性地址和PTI物理地址

打印线性地址中的内容

// page.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"
#include <windows.h>

WORD w_cs;
WORD w_ss;

DWORD PDTable[0x400];

DWORD GetFunAddress(void* Fun){
    BYTE* p;
    p = (BYTE*)Fun;
    if ( *p == 0xe9){
        p = p+5+ *(DWORD*)&p[1];
    }
    return (DWORD)p;
}


_declspec(naked)void fun()
{
    _asm{
        push ebp
        mov ebp,esp
        sub esp,0x44
        push ebx
        push esi
        push edi
    }

    _asm{
        mov ax,cs
        mov cx,ss
        mov w_cs,ax
        mov w_ss,cx
    }

    DWORD* pPDIAddress;
    DWORD dwPDE;
    int i;
    pPDIAddress = (DWORD*)0xc0300000;
    //进入0环后修改每个PDE属性
    for ( i = 0; i < 0x400; i++){
        dwPDE = *(pPDIAddress+i);
        PDTable[i] = dwPDE;
        if( (dwPDE&0x1) != 0) //判断是否为有效PDE
        {
            *(pPDIAddress+i) = dwPDE | 4; //将U/S位修改为1
        }
    }

    _asm{
        pop edi
        pop esi
        pop ebx
        mov esp,ebp
        pop ebp
        retf
    }
}


int main(int argc, char* argv[])
{
    LPVOID pAddress;
    DWORD dwFunAddress;
    DWORD* pReadAddress;
    int i;

    memset(&w_cs,0,2);
    memset(PDTable,0x0,0x400);

    pAddress = VirtualAlloc((void*)0x60000000,0x4000,MEM_COMMIT|MEM_RESERVE,PAGE_EXECUTE_READWRITE);

    if ( pAddress != NULL ){
        dwFunAddress = GetFunAddress(fun);
        memcpy(pAddress,(void*)dwFunAddress,0x200);
    }

    printf("拷贝函数成功\n");
    //进入0环之前要先修改GDTR
    _asm{
        _emit(0x9a)

        _emit(0x10)
        _emit(0x10)
        _emit(0x10)
        _emit(0x10)

        _emit(0xc3)
        _emit(0x00)
    }

    printf("调用门函数调用成功\n");
    printf("w_cs = %x, w_ss = %x\n", w_cs,w_ss);
    printf("--------------PDE-----------\n");
    pReadAddress = (DWORD*)0xc0300000;


    for ( i = 0; i < 0x3; i++){
        printf("[0x%08x] = 0x%08x\n", pReadAddress+i, *(pReadAddress+i));
    }

    for ( i = 0; i < 0x100; i += 2){
        printf(" 0x%08x-------0x%08x\n", *(DWORD*)&PDTable[i+1],*(DWORD*)&PDTable[i]);
    }
    return 0;
}

2-9-9-12打印线性地址

和上面的10-10-12一样,寻找上帝页

!dq cr3列出PDPE,在一番查找下找到CR3+24的位置下,找到相似的内存值,并可以确定上帝页地址,此时PTE为C0000000

PDPI    PDI     PTI   OFFSET
 3       3       3     000    =====> C0603000

也由此可以得出计算公式

PTE = C0000000 + PDPI * 0x200 * 0x1000 + PDI * 0x1000 + PTI * 8


文章作者: Kevin。
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Kevin。 !
评论
 上一篇
MIT 6.828环境搭建及Lab1运行 MIT 6.828环境搭建及Lab1运行
MIT 6.828 环境搭建及Lab1运行最近学了点Windows保护模式对操作系统底层有些感兴趣了,边学Windows边开个新坑吧,6.828的资料在网上不算难找,慢慢填坑 运行环境搭建搭建环境的系统:Ubuntu18.04.6 安装编译
2022-04-23
下一篇 
TSS 任务状态段描述符 TSS 任务状态段描述符
TSS 任务状态段描述符​ TSS(Task State Segment)任务状态段描述符用于描述保存任务重要信息的系统段,权限发生变化要借用TSS。任务状态段寄存器TR的可见部分为当前任务状态段描述符的选择子,不可见部分是当前任务状
2022-04-15
  目录