Windows XP 页保护(三)

ʕ •ᴥ•ʔ ɔ:

1 TLB

1.1 TLB结构

当程序访问一个线性地址,需要先查PDPT,然后查PDT,然后查页表PTT,最后才是访问物理页。这期间多次访问内存,效率非常低。于是TLB就被设计出来了。(64位CPU分页及TLB请点这儿

TLB(Translation Lookaside Buffer),也叫快表。TLB 表在CPU内部,一个CPU有一张TLB表,用来缓存线性地址和物理地址的映射关系,以及属性和访问次数。(实际上缓存的线性地址、物理地址为页基地址

37.png

38.png

  • LA:存储的是线性地址所在页基地址
  • PA:线性地址所在对应的物理页基址
  • ATTR:属性,除了G、XD位属性为OR外,其余的位的属性为AND
  • LRU:统计这个线性地址的读写情况的,这是因为TLB这个表在CPU内部,那么他就不会很大,当线性地址存储满了的时候,他就会看LRU的统计情况,把读写的次数比较少的项删除,然后再把新的线性地址项添上。

关于TLB表:

  1. TLB是受PDE和PTE中的G位影响的,PDE中的G位又受PS位影响,且G位属性:PDE_G || PTE_G
  2. 不同的CPU这个TLB表的大小不一样,能存几十项或者几百项。
  3. 只要CR3寄存器被重写,哪怕重新写的值和原来的相同TLB立马刷新。一核一套TLB。
  4. 操作系统的高2G映射基本不变,如果CR3被重写了,TLB刷新,重建高2G以上很浪费。故高2G的线性地址对应的物理页的G位常为1(全局页)。
    所以PDE和PTE中有个G标志位:
    • G = 1,刷新TLB时将不会刷新PDE/PTE的G位为1的页。
    • 当TLB满了,根据统计信息将不常用的地址废弃,最近最常用的保留.

TLB在X86体系的CPU里的实际应用最早是从Intel的486CPU开始的,在X86体系的CPU里边,一般都设有如下4组TLB:
第一组:缓存一般页表(4K字节页面)的指令页表缓存(Instruction-TLB);
第二组:缓存一般页表(4K字节页面)的数据页表缓存(Data-TLB);
第三组:缓存大尺寸页表(2M/4M字节页面)的指令页表缓存(Instruction-TLB);
第四组:缓存大尺寸页表(2M/4M字节页面)的数据页表缓存(Data-TLB)

1.2 练习:体验TLB的存在

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
#include "stdafx.h"
#include <tchar.h>
#include <windows.h>

DWORD TempVal;
void __declspec(naked) R0Function()
{
__asm
{
pushad
pushfd
// 1.给NULL挂物理页(修改PTE,这里概率蓝屏)0x01234867(G=0) 0x01234967(G=1)
mov dword ptr ds:[0xc0000000],0x01234867
// 2.写NULL指针,生成TLB记录
mov dword ptr ds:[0],0x12345678
// 3.再次修改物理页
mov dword ptr ds:[0xc0000000],0x02345867

// 4.模拟进程切换
//mov eax,cr3
//mov cr3,eax

//清空NULL的TLB缓存
//INVLPG dword ptr ds:[0]
// 4.读NULL,未能读取到 0x01234867,证明TLB已被刷新
mov eax,dword ptr ds:[0]
mov TempVal,eax

popfd
popad
retf
}
}

int _tmain(int argc, _TCHAR* argv[])
{

BYTE CallGate[] = {0,0,0,0,0x48,0};
printf("请在windbg执行: eq 8003f048 %04xec00`0008%04x\n", ((DWORD)R0Function>>16) & 0x0000FFFF,\
(DWORD)R0Function & 0xFFFF);

getchar();
__asm
{
call fword ptr ds:[CallGate];
}

PDWORD p = (PDWORD)NULL;
printf("TempVal的值为:%x\n",TempVal);//读
printf("线性地址NULL中的值为:%x\n",*p);//读
getchar();
return 0;
}

40.png

结论:修改一次NULL的PTE,PTE_G = 0后,再继续修改一次NULL的PTE,结果第一次从NULL地址读出来的值是第一次的赋值。说明TLB是存在的。

结论:PTE的G位为0的情况下,凡是涉及修改PTE,要想使修改后的值立即生效,必须使用INVLPG指令,否则由于存在TLB的缘故,修改PTE后第一次再读原地址的值会从TLB中取

1.3 练习:PTE_G、切换CR3对TLB的影响

从上一个练习可知,因为TLB的存在,修改PTE后读内存存在“误差”。

但是TLB会受PTE_G值和切换CR3所影响,具体表现为:修改CR3的值后,PTE_G = 1的TLB缓存不会被清理,PTE_G = 0的TLB缓存会被清理

结论:PTE的G位为0的情况下,凡是涉及修改PTE,要想使修改后的值立即生效,必须使用INVLPG指令,否则由于存在TLB的缘故,修改PTE后第一次再读原地址的值会从TLB中取

示例1:NULL的PTE_G = 0,修改CR3的值,PTE_G = 0指向的物理页记录的TLB会被刷新,读内存存在“误差”。

mov dword ptr ds:[0xc0000000],0x01234867

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
#include "stdafx.h"
#include <tchar.h>
#include <windows.h>

DWORD TempVal;
void __declspec(naked) R0Function()
{
__asm
{
pushad
pushfd
// 1.给NULL挂物理页(修改PTE,这里概率蓝屏)0x01234867(G=0) 0x01234967(G=1)
mov dword ptr ds:[0xc0000000],0x01234867
// 2.写NULL指针,生成TLB记录
mov dword ptr ds:[0],0x12345678
// 3.再次修改物理页
mov dword ptr ds:[0xc0000000],0x02345867

// 4.模拟进程切换
mov eax,cr3
mov cr3,eax

//清空NULL的TLB缓存
//INVLPG dword ptr ds:[0]
// 4.读NULL,未能读取到 0x01234867,证明TLB已被刷新
mov eax,dword ptr ds:[0]
mov TempVal,eax

popfd
popad
retf
}
}

int _tmain(int argc, _TCHAR* argv[])
{

BYTE CallGate[] = {0,0,0,0,0x48,0};
printf("请在windbg执行: eq 8003f048 %04xec00`0008%04x\n", ((DWORD)R0Function>>16) & 0x0000FFFF,\
(DWORD)R0Function & 0xFFFF);

getchar();
__asm
{
call fword ptr ds:[CallGate];
}

PDWORD p = (PDWORD)NULL;
printf("TempVal的值为:%x\n",TempVal);//读
printf("线性地址NULL中的值为:%x\n",*p);//读
getchar();
return 0;
}

41.png

示例2:NULL的PTE_G = 1,修改CR3的值,PTE_G = 1指向的物理页记录的TLB会被刷新,读内存存在“误差”。

mov dword ptr ds:[0xc0000000],0x01234967

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
#include "stdafx.h"
#include <tchar.h>
#include <windows.h>

DWORD TempVal;
void __declspec(naked) R0Function()
{
__asm
{
pushad
pushfd
// 1.给NULL挂物理页(修改PTE,这里概率蓝屏)0x01234867(G=0) 0x01234967(G=1)
mov dword ptr ds:[0xc0000000],0x01234967
// 2.写NULL指针,生成TLB记录
mov dword ptr ds:[0],0x12345678
// 3.再次修改物理页
mov dword ptr ds:[0xc0000000],0x02345867

// 4.模拟进程切换
mov eax,cr3
mov cr3,eax

//清空NULL的TLB缓存
//INVLPG dword ptr ds:[0]
// 4.读NULL,未能读取到 0x01234867,证明TLB已被刷新
mov eax,dword ptr ds:[0]
mov TempVal,eax

popfd
popad
retf
}
}

int _tmain(int argc, _TCHAR* argv[])
{

BYTE CallGate[] = {0,0,0,0,0x48,0};
printf("请在windbg执行: eq 8003f048 %04xec00`0008%04x\n", ((DWORD)R0Function>>16) & 0x0000FFFF,\
(DWORD)R0Function & 0xFFFF);

getchar();
__asm
{
call fword ptr ds:[CallGate];
}

PDWORD p = (PDWORD)NULL;
printf("TempVal的值为:%x\n",TempVal);//读
printf("线性地址NULL中的值为:%x\n",*p);//读
getchar();
return 0;
}

42.png

1.4 练习:INVLPG清空某线性地址的TLB

NULL的PTE_G = 1,使用指令INVLPG dword ptr ds:[0]会清空NULL在TLB的纪录,下一次读内存时将会重新解析线性地址并缓存至TLB,不会存在“误差”。指令INVLPG强行删除某项全局页缓存或非全局页的缓存。

结论:PTE的G位为0的情况下,凡是涉及修改PTE,要想使修改后的值立即生效,必须使用INVLPG指令,否则由于存在TLB的缘故,修改PTE后第一次再读原地址的值会从TLB中取。

INVLPG dword ptr ds:[0]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
#include "stdafx.h"
#include <tchar.h>
#include <windows.h>

DWORD TempVal;
void __declspec(naked) R0Function()
{
__asm
{
pushad
pushfd
// 1.给NULL挂物理页(修改PTE,这里概率蓝屏)0x01234867(G=0) 0x01234967(G=1)
mov dword ptr ds:[0xc0000000],0x01234967
// 2.写NULL指针,生成TLB记录
mov dword ptr ds:[0],0x12345678
// 3.再次修改物理页
mov dword ptr ds:[0xc0000000],0x02345867

// 4.模拟进程切换
mov eax,cr3
mov cr3,eax

//清空NULL的TLB缓存
INVLPG dword ptr ds:[0]
// 4.读NULL,未能读取到 0x01234867,证明TLB已被刷新
mov eax,dword ptr ds:[0]
mov TempVal,eax

popfd
popad
retf
}
}

int _tmain(int argc, _TCHAR* argv[])
{

BYTE CallGate[] = {0,0,0,0,0x48,0};
printf("请在windbg执行: eq 8003f048 %04xec00`0008%04x\n", ((DWORD)R0Function>>16) & 0x0000FFFF,\
(DWORD)R0Function & 0xFFFF);

getchar();
__asm
{
call fword ptr ds:[CallGate];
}

PDWORD p = (PDWORD)NULL;
printf("TempVal的值为:%x\n",TempVal);//读
printf("线性地址NULL中的值为:%x\n",*p);//读
getchar();
return 0;
}

43.png

2 中断和异常

2.1 中断

中断通常是由CPU外部的输入输出设备(硬件)所触发的,供外部设备通知,CPU“有事情需要处理”,因此又叫中断请求(Interrupt Request)。

中断请求的目的是希望CPU暂时停止执行当前正在执行的程序,转去执行中断请求所对应的中断处理例程(中断处理程序在哪有IDT表决定)

80×86有两条中断请求线:

  • 非屏蔽中断线,称为NMI(NonMaskable Interrupt)
  • 可屏蔽中断线,称为INTR(Interrupt Require)

⚠️注意:

  1. 中断的本质:改变CPU执行的路线。
  2. 操作系统并没有使用时钟进行线程切换,时钟只是线程切换的一个条件。

一、非可屏蔽中断

特别说明:

  • 当非可屏蔽中断产生时,CPU在执行完当前指令后会立即进入中断处理程序。
  • 非可屏蔽中断不受EFLAG寄存器中IF位的影响,一旦发生,CPU必须处理。
  • 非可屏蔽中断处理程序位于IDT表中的2号(0x2)位置。

二、可屏蔽中断

在硬件级,可屏蔽中断是由一块专门的芯片来管理的,通常称为中断控制器。它负责分配中断资源和管理各个中断源发出的中断请求。为了便于标识各个中断请求,中断管理器通常用IRQ(Interrupt Request)后面加上数字来表示不同的中断。

比如:在Windows中 时钟中断的IRQ编号为0 也就是:IRQ0。

44.png

45.png

特别说明:

  1. 如果自己的程序执行时不希望CPU去处理这些中断,可以用CLI指令清空EFLAG寄存器中的IF位,IF = 0。(STI指令可使IF = 1)

  2. 硬件中断与IDT表中的对应关系并非固定不变的,参见:APIC(高级可编程中断控制器)。

  3. 大多数操作系统时钟中断在10-100ms之间,Windows系列为10-20ms。

2.2 异常

异常通常是CPU在执行指令时检测到的某些错误,比如除0、访问无效页面等。

中断与异常的区别:
1、中断来自于外部设备,是中断源(比如键盘)发起的,CPU是被动的
2、异常来自于CPU本身,是CPU主动产生的。
3、INT N虽然被称为“软件中断”,但其本质是异常EFLAG的IF位对INT N无效

⚠️注意:无论是由硬件设备触发的中断请求还是由CPU产生的异常,处理程序都在IDT表。

常见的异常处理程序:

46.png

比如缺页异常,CPU会执行IDT表中的0xE号中断处理程序,由操作系统来接管。

缺页异常的产生:

1、当PDE/PTE的P=0时。

2、当PDE/PTE的属性为只读但程序试图写入时。

3、当物理内存空间不足时,线性地址对应的物理页将被存储到文件中。

如下图左,产生缺页异常时,如果PTE下标0、10、11位为0,其余位为1,则会从文件中重新写回一个物理页。

47.png

有效位(下标9、10、11),在发生缺页时(PTE的P=0)使用。分以下四种情况(具体可见内存管理-无处不在的缺页异常)。关于这部分知识,等以后学习了内存管理的缺页异常就知道了,这里简单介绍一下。

14.png

22.png

int N中断号对应信息如下:

59.png

1.3 分析IDT表中0x2号中断的执行流程

调用门、任务门、中断门、陷进门段描述符如下(GDT中无调用门描述符,IDT中无中断门描述符,但是我们可以构造):

29调用门描述符.png

55.png

可以看到低4字节中的高16位,除任务门中为任务段外都为段选择子,用于查GDT表。

  1. 查IDT表。0x2号中断,查idt表下标2,即第三个。

    • P = 1,S = 0。

    • Type = 0x5 = 0101,对应任务门(Type域查表)

    • 段选择子:0x0058。

    • 偏移Offset:任务门描述符不存在偏移,而非0x0000113e。

    48.png

  2. 查IDT表。段选择子0x0058–0101 1000– 1011 0 00 = 11,即第12个。(段选择子结构查表)

    49.png

  3. 从TSS段描述符中获取Base,然后获取EIP的值TSS[8]即第9个。

    • TSS.Base = 0x80552768

    50.png

  4. 得到EIP = 0x8054340c,则0x2号中断应该执行的代码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    kd> uf 8054340c
    nt!Kei386EoiHelper+0x560:
    8054340c fa cli //if = 0,屏蔽中断。sti--if = 1
    8054340d 64ff3540000000 push dword ptr fs:[40h]//fs.base + 0x40
    80543414 64a13c000000 mov eax,dword ptr fs:[0000003Ch]
    8054341a 8a685f mov ch,byte ptr [eax+5Fh]
    8054341d 8a485c mov cl,byte ptr [eax+5Ch]
    80543420 c1e110 shl ecx,10h
    80543423 668b485a mov cx,word ptr [eax+5Ah]
    80543427 64890d40000000 mov dword ptr fs:[40h],ecx
    8054342e 9c pushfd
    8054342f 812424ffbfffff and dword ptr [esp],0FFFFBFFFh
    80543436 9d popfd
    80543437 648b0d3c000000 mov ecx,dword ptr fs:[3Ch]
    8054343e 8d4158 lea eax,[ecx+58h]
    80543441 c6400589 mov byte ptr [eax+5],89h
    80543445 8b0424 mov eax,dword ptr [esp]
    80543448 6a00 push 0
    8054344a 6a00 push 0
    8054344c 6a00 push 0
    8054344e 6a00 push 0
    80543450 ff7050 push dword ptr [eax+50h]
    80543453 ff7038 push dword ptr [eax+38h]
    80543456 ff7024 push dword ptr [eax+24h]
    80543459 ff704c push dword ptr [eax+4Ch]
    8054345c ff7020 push dword ptr [eax+20h]
    8054345f 6a00 push 0
    80543461 ff703c push dword ptr [eax+3Ch]
    80543464 ff7034 push dword ptr [eax+34h]
    80543467 ff7040 push dword ptr [eax+40h]
    8054346a ff7044 push dword ptr [eax+44h]
    8054346d ff7058 push dword ptr [eax+58h]
    80543470 64ff3500000000 push dword ptr fs:[0]
    80543477 6aff push 0FFFFFFFFh
    80543479 ff7028 push dword ptr [eax+28h]
    8054347c ff702c push dword ptr [eax+2Ch]
    8054347f ff7030 push dword ptr [eax+30h]
    80543482 ff7054 push dword ptr [eax+54h]
    80543485 ff7048 push dword ptr [eax+48h]
    80543488 ff705c push dword ptr [eax+5Ch]
    8054348b 6a00 push 0
    8054348d 6a00 push 0
    8054348f 6a00 push 0
    80543491 6a00 push 0
    80543493 6a00 push 0
    80543495 6a00 push 0
    80543497 6a00 push 0
    80543499 6a00 push 0
    8054349b 6a00 push 0
    8054349d 6a00 push 0
    8054349f ff7020 push dword ptr [eax+20h]
    805434a2 ff703c push dword ptr [eax+3Ch]
    805434a5 8bec mov ebp,esp
    805434a7 33db xor ebx,ebx
    805434a9 648a1d51000000 mov bl,byte ptr fs:[51h]
    805434b0 391ddc275580 cmp dword ptr [nt!NtBuildNumber+0x46f4 (805527dc)],ebx
    805434b6 7414 je nt!Kei386EoiHelper+0x620 (805434cc)

    nt!Kei386EoiHelper+0x60c:
    805434b8 8d05d8275580 lea eax,[nt!NtBuildNumber+0x46f0 (805527d8)]
    805434be 50 push eax
    805434bf 6a00 push 0
    805434c1 8bcc mov ecx,esp
    805434c3 8bd5 mov edx,ebp
    805434c5 e83acbfbff call nt!KeRestoreFloatingPointState+0x21a (80500004)
    805434ca eb24 jmp nt!Kei386EoiHelper+0x644 (805434f0)

    nt!Kei386EoiHelper+0x620:
    805434cc 833de027558008 cmp dword ptr [nt!NtBuildNumber+0x46f8 (805527e0)],8
    805434d3 721b jb nt!Kei386EoiHelper+0x644 (805434f0)

    nt!Kei386EoiHelper+0x629:
    805434d5 7517 jne nt!Kei386EoiHelper+0x642 (805434ee)

    nt!Kei386EoiHelper+0x62b:
    805434d7 803d406a558000 cmp byte ptr [nt!KdDebuggerNotPresent (80556a40)],0
    805434de 750e jne nt!Kei386EoiHelper+0x642 (805434ee)

    nt!Kei386EoiHelper+0x634:
    805434e0 803d416a558000 cmp byte ptr [nt!KdDebuggerEnabled (80556a41)],0
    805434e7 7405 je nt!Kei386EoiHelper+0x642 (805434ee)

    nt!Kei386EoiHelper+0x63d:
    805434e9 e8566bfbff call nt!KeEnterKernelDebugger (804fa044)

    nt!Kei386EoiHelper+0x642:
    805434ee ebfe jmp nt!Kei386EoiHelper+0x642 (805434ee)

    nt!Kei386EoiHelper+0x644:
    805434f0 891ddc275580 mov dword ptr [nt!NtBuildNumber+0x46f4 (805527dc)],ebx
    805434f6 ff05e0275580 inc dword ptr [nt!NtBuildNumber+0x46f8 (805527e0)]
    805434fc 6a00 push 0
    805434fe ff158c904d80 call dword ptr [nt+0x108c (804d908c)]
    80543504 ff0de0275580 dec dword ptr [nt!NtBuildNumber+0x46f8 (805527e0)]
    8054350a 754a jne nt!Kei386EoiHelper+0x6aa (80543556)

    nt!Kei386EoiHelper+0x660:
    8054350c c705dc275580ffffffff mov dword ptr [nt!NtBuildNumber+0x46f4 (805527dc)],0FFFFFFFFh
    80543516 8bcc mov ecx,esp
    80543518 e88fe5ffff call nt!KeReleaseInStackQueuedSpinLockFromDpcLevel+0x4 (80541aac)
    8054351d 83c408 add esp,8
    80543520 64a140000000 mov eax,dword ptr fs:[00000040h]
    80543526 66833858 cmp word ptr [eax],58h
    8054352a 742a je nt!Kei386EoiHelper+0x6aa (80543556)

    nt!Kei386EoiHelper+0x680:
    8054352c 81c48c000000 add esp,8Ch
    80543532 648f0540000000 pop dword ptr fs:[40h]
    80543539 648b0d3c000000 mov ecx,dword ptr fs:[3Ch]
    80543540 8d4128 lea eax,[ecx+28h]
    80543543 c640058b mov byte ptr [eax+5],8Bh
    80543547 9c pushfd
    80543548 810c2400400000 or dword ptr [esp],4000h
    8054354f 9d popfd
    80543550 cf iretd

    nt!Kei386EoiHelper+0x6aa:
    80543556 b802000000 mov eax,2
    8054355b e9fc280000 jmp nt!KiCoprocessorError+0xe4 (80545e5c)

    nt!KiCoprocessorError+0xe4:
    80545e5c 55 push ebp
    80545e5d 6a00 push 0
    80545e5f 6a00 push 0
    80545e61 6a00 push 0
    80545e63 50 push eax
    80545e64 6a7f push 7Fh
    80545e66 e80546fbff call nt!KeRegisterBugCheckReasonCallback+0x208 (804fa470)
    80545e6b c3 ret

Type域查表:

13.png

15.png

段选择子结构:

5.png

32位TSS段描述符:

79.png

3 控制寄存器

控制寄存器用于控制和确定CPU的操作模式

CR0、CR1、CR2、CR3、CR4:

  • CR1:保留。
  • CR2:缺页线性地址。
  • CR3:页目录表基址。

3.1 CR0

51.png

说明:

  1. PE位:为CR0的位0,是启用保护(Protection Enable)标志。这个标志仅开启段级保护,而并没有启用分页机制
    • PE = 1,保护模式。
    • PE = 0。实地址模式。

若要启用分页机制,那么PE和PG标志都要置位。

  1. PG位:当设置该位时即开启了分页机制。在开启这个标志之前必须已经或者同时开启PE标志。
    • PG = 0 且PE = 0 ,处理器工作在实地址模式下。
    • PG = 0且PE = 1,处理器工作在没有开启分页机制的保护模式下(目前没有这样的操作系统)。
    • PG = 1且PE = 0 ,在PE没有开启的情况下,无法开启PG。(没有这样的状态)
    • PG = 1且PE = 1,处理器工作在开启了分页机制的保护模式下
  2. WP位:对于Intel 80486或以上的CPU,CR0的位16是写保护(Write Proctect)标志。当设置该标志时,处理器会禁止超级用户程序(例如特权级0的程序)向用户级只读页面执行写操作
    当CPL<3的时候:
    • WP = 0,可以读写任意用户级物理页,只要线性地址有效。
    • WP = 1,可以读取任意用户级物理页,但对于只读的物理页,则不能写

3.2 CR2

当CPU访问某个无效页面时,会产生缺页异常,此时,CPU会将引起异常
的线性地址存放在CR2中。

52.png

3.3 CR3

CR3一包含了页目录的基地址和二个标志(PCD和PWT)。该寄存器也被称为页目录基地址寄存器(PDBR)。页目录基地址只有高20位确定,低12位是0,所以页目录地址必须是页边界对齐的(4K字节)。

对齐的(4K字节)。PCD和PWT标志控制着页目录在处理器内部数据缓冲区的缓存(它们不控制TLB页目录信息的缓存)。

55.png

3.4 CR4

CR4包含了一组标志,这些标志启用了架构方面的几个扩展,并指明了系统对某些处理器支持的能力。。在保护模式下,MOV指令允许读取或者装载控制寄存器(在0级特权下)。这个限制意味着应用程序或者操作系统过程(运行在1、2、3级特权下)不能读取或者装载控制寄存器。装载控制寄存器时,保留位应该保持以前读取的值。

53.png

PAE/PSE说明:

  • PAE = 1,是2-9-9-12分页。
  • PAE = 0,是10-10-12分页。

应特别注意,PDE的PS位受CR4的PSE位影响,具体如下:

54.png

4 PCD和PWT标志位

PCD和PWT标志存在于PDBR(CR3)、PDPE、PDE、PTE中,分别如下:

55.png

10-10-12分页PDE、PTE:

14.png

2-9-9-12分页PDPE:

26.png

PDE:

27.png

PTE:

29.png

1、CPU缓存

1)CPU缓存是位于CPU与物理内存之间的临时存储器,它的容量比内存小的多但是交换速度却比内存要快得多。

2)CPU缓存可以做的很大,有几K、几十K、几百K 甚至上M的也有。

CPU缓存与TLB的区别:

TLB:

​ 线性地址 <—–> 物理地址

CPU缓存:

​ 物理地址 <—–> 物理地址中的内容

2、关于PWT/PCD属性

PWT:Page Write Through

  • PWT = 1,写Cache的时候也要将数据写入内存中。
  • PWT = 0,写Cache时由CPU控制寄存器决定是否写入内存。

PCD:Page Cache Disable

  • PCD = 1,禁止某个页写入缓存,直接写内存。
  • PCD = 0,可以写入Cache。

比如,做页表用的页,已经存储在TLB中了,可以不需要再缓存了。

5 保护模式:测试1

以下代码对每一个线性地址都做了检查,起始这已经包括跨页检查了。

跨页检查的目的:如线性地址0x0040ffd,如果要取4字节内容,则最后一个字节在下一个页,这个时候还需要对下一个页的PDE、PTE做检查。而我的代码是对每一个线性地址做检查,粒度是每一个地址,而非页,所以自然包含了跨页内存检查。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
#include "stdafx.h"
#include <tchar.h>
#include <windows.h>
#include <stdlib.h>
/*
1.给定一个线性地址,根据地址读取指定长度内容。
int ReadMemory(OUT BYTE* buffer,IN DWORD dwAddr,IN DWORD dwLeght)
要求:
1) 可以自己指定分页方式。
2) 页不存在,要提示,不能报错。
3) 可以正确读取数据。
4)要考虑跨页数据的内存检查
*/
VOID R0GetAddrText();
DWORD dwRet = 0;
int g_inti = 0;
DWORD g_dwAddr,g_dwLen;
BYTE g_Buffer[0x1000] = {0};

void R0ParaPDEPTE()
{
__asm
{
pushad;
pushfd;

//获取参数中的线性地址和要获取的内容的长度
//int 3;
mov eax,g_dwAddr; //获取dwAddr
mov ecx,g_dwLen; //获取dwLeght
mov edx,eax; //edx = eax == 线性地址
//判断读取长度
cmp ecx,0x0;
mov dwRet,0x0; //dwRet = dwLeght == 0,返回0,读取失败
je __GoRet;
//jmp __GetPDE; //不用jmp,会自动执行下面地址

//先找PDE
__GetPDE:
shr eax,18; //eax = dwAddr>>18
and eax,0x3ff8; //eax = (dwAddr>>18) & 0x3ff8 = PDPTI * 4KB + PDI * 8
add eax,0xc0600000; //eax = C0600000 + PDPTI * 4KB + PDI * 8
//eax 指向PDE
mov eax,[eax]; //eax = PDE低4字节
mov esi,eax; //esi = eax == PDE低4字节

//判断PDE_P位
and eax,0x1; //eax == PDE_P
test eax,0x1;
mov dwRet,0x1; //dwRet == 1
je __GoRet; //PDE_P == 0,返回1,读取失败

//判断PDE_PS位
and esi,0x80;
cmp esi,0x0;
je __GetPTE; //PDE_PS == 0,小页
mov dwRet,0x1;
jmp __GoRet;

//再找PTE
__GetPTE:
mov eax,edx;
shr eax,9; //eax =dwAddr>>9
and eax,0x7ffff8; //eax = (dwAddr >> 9) & 0x7ffff8 = PDPTI * 2MB + PDI * 4KB + PTI * 8
add eax,0xc0000000; //eax = 0xc0000000 + PDPTI * 2MB + PDI * 4KB + PTI * 8
//eax指向PTE
mov eax,[eax]; //eax = PTE低4字节
mov edi,eax; //esi = eax == PTE
//判断PTE_P位
and eax,0x1; //eax = PTE_P
test eax,0x1;
mov dwRet,0x2;
je __GoRet; //PTE_P == 0,dwRet = 2,返回2,读取失败

//判断PTE_PAT
and edi,0x80;
test edi,0x1;
mov dwRet,0x3;
jne __GoRet; //PTE_PAT == 1,这种情况目前还不清楚,先不处理
mov dwRet,0x4;

__GoRet:
popfd
popad
}
return;
}

VOID __declspec(naked) R0GetAddrText()
{
while(g_dwLen)
{
R0ParaPDEPTE();
switch(dwRet)
{
case 0:
case 1:
case 2:
case 3: __asm retf;
case 4: break;
}
memcpy((PBYTE)((DWORD)g_Buffer+g_inti),(PBYTE)g_dwAddr,0x1);
//g_Buffer[g_inti] = ((PBYTE)g_dwAddr)[g_inti];
g_inti++;
g_dwAddr++;
g_dwLen--;
}
__asm retf;

}

int ReadMemory(OUT BYTE* buffer,IN DWORD dwAddr,IN DWORD dwLeght)
{

//1.若线性地址在低2G,则0~4KB与0x7FFFFFFF-4KB~0x7FFFFFFF为保留地址,不可读
//实际判断PDE、PTE就可以,无须根据线性地址判断(线性地址的读写实际都是根据此判断的)
//实际流程和函数MmIsAddressValid差不多
//g_Buffer = buffer;
g_dwAddr = dwAddr;
g_dwLen = dwLeght;

BYTE CallGate[] = {0,0,0,0,0x48,0};
printf("请在windbg执行: eq 8003f048 %04xec00`0008%04x\n", ((DWORD)R0GetAddrText>>16) & 0x0000FFFF,\
(DWORD)R0GetAddrText & 0xFFFF);

system("Pause");
__asm
{
call fword ptr ds:[CallGate]
}

switch(dwRet)
{
case 0:
{
printf("要读取内容的长度为0,请检查!\n");
return 0;
}
case 1:
{
printf("要读取内容的PDE无效,请检查!\n");
return 0;
}
case 2:
{
printf("访问空地址或当前内存读取错误(PTE),请检查!\n");
return 0;
}
case 3:
{
printf("要读取内容的PTE_PAT = 1,目前该程序还不支持!\n");
return 0;
}
case 4:
{
printf("读取成功!\n");
return 0;
}
case 5:
{
printf("有毒!\n");
return 0;
}
}
return 0;
}

//2-9-9-12分页
int _tmain(int argc, _TCHAR* argv[])
{
DWORD bTestNum = 0x666;
printf("测试地址%x\n",&bTestNum);
DWORD dwAddr,dwLen;
BYTE bBuffer[0x1000];
printf("函数功能:读取指定地址处的指定长度的内容!(0-4096字节)\n请输入指定地址(十六进制):");
scanf("%x",&dwAddr);
printf("输入要读取字节长度:");
scanf("%d",&dwLen);

ReadMemory(bBuffer,dwAddr,dwLen);

printf("读取内容:");
if(dwRet == 0x4)
{
printf("读取内容:");
for(;dwLen > 0;)
{
dwLen--;
printf("%x",g_Buffer[dwLen]);
}
printf("\n");
}
system("Pause");
return 0;
}

56.png

6 保护模式:测试2

题目:

申请长度为100的DWORD的数组,且每项的值用该项的地址初始化;
把这个数组所在的物理页挂到0x1000的地址上;
定义一个指针,指向0x1000这个页里的数组所在的地址,用0x1000这个页的线性地址打印出这数组的值。

要求:
数组所在的物理页,是同一个页。

本题在:2-9-9-12分页模式下,页大小:4KB,PTE_PAT = 0条件下解题。

  1. 先获取数组pArr的PDE、PTE。
  2. 检查线性地址0x1000的PDE_P:(若PDE存在且PS = 1,仅需要替换PTE即可)
    • PDE_P = 1,判断PDE_PS:
      • PDE_PS = 0,则直接用pArr的PTE替换即可。
      • PDE_PS = 1,替换PDE和PTE。
  3. 修改PTE就要清空TLB。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
#include "stdafx.h"
#include <tchar.h>
#include <windows.h>
#include <stdlib.h>

PDWORD pArr = NULL;
DWORD dwArr = 0;

VOID __declspec(naked) CallGate_RW8003f048()
{
__asm
{
pushad;
pushfd;
//先找pArr的PDE
//int 3;
mov eax,dwArr;
push eax;
shr eax,18; //eax = dwAddr>>18
and eax,0x3ff8; //eax = (dwAddr>>18) & 0x3ff8 = PDPTI * 4KB + PDI * 8
add eax,0xc0600000; //eax = C0600000 + PDPTI * 4KB + PDI * 8
//eax 指向PDE
mov eax,[eax]; //eax = PDE低4字节
mov ebx,eax; //ebx = eax = PDE低4字节
//再找pArr的PTE
pop eax;
shr eax,9; //eax =dwAddr>>9
and eax,0x7ffff8; //eax = (dwAddr >> 9) & 0x7ffff8 = PDPTI * 2MB + PDI * 4KB + PTI * 8
add eax,0xc0000000; //eax = 0xc0000000 + PDPTI * 2MB + PDI * 4KB + PTI * 8
//eax指向PTE
mov eax,[eax]; //eax = PTE低4字节
mov ecx,eax; //ecx = eax = PTE低4字节

//找0x1000的PDE;
mov eax,0x1000;
mov esi,eax;
shr eax,18; //eax = dwAddr>>18
and eax,0x3ff8; //eax = (dwAddr>>18) & 0x3ff8 = PDPTI * 4KB + PDI * 8
add eax,0xc0600000; //eax = C0600000 + PDPTI * 4KB + PDI * 8

mov edx,eax; //edx = eax指向0x1000的PDE
mov eax,[eax]; //eax = PDE低4字节
mov edi,eax; //edi = eax = 0x1000的PDE低4字节

//判断0x1000_PDE_P位
and eax,0x1; //eax == PDE_P
test eax,0x1;
je __FixPDE; //PDE_P == 0,修改PDE

//判断0x1000_PDE_PS位
and edi,0x80;
test edi,0x1;
jne __FixPDE; //0x1000_PDE_PS == 1,大页

__FIXPTE:
mov eax,esi;
shr eax,9; //eax =dwAddr>>9
and eax,0x7ffff8; //eax = (dwAddr >> 9) & 0x7ffff8 = PDPTI * 2MB + PDI * 4KB + PTI * 8
add eax,0xc0000000; //eax = 0xc0000000 + PDPTI * 2MB + PDI * 4KB + PTI * 8
//eax指向PTE
mov [eax],ecx; //0x1000_PTE = pAddr_PTE
INVLPG dword ptr ds:[0x1000];//清空0x1000的TLB

__GoRet:
popfd;
popad;
retf;

__FixPDE:
mov [edx],ebx; //0x1000_PDE = pAddr_PDE
jmp __FIXPTE;
}
}

int _tmain(int argc, _TCHAR* argv[])
{
DWORD i = 0;
BYTE CallGate[] = {0,0,0,0,0x48,0};
pArr = (PDWORD)VirtualAlloc(0,0x1000,MEM_COMMIT,PAGE_READWRITE);
dwArr = (DWORD)pArr;
while(i < 100)
{
pArr[i] = (DWORD)&pArr[i];
i++;
}
printf("请在windbg执行: eq 8003f048 %04xec00`0008%04x\n", ((DWORD)CallGate_RW8003f048>>16) & 0x0000FFFF,\
(DWORD)CallGate_RW8003f048 & 0xFFFF);

system("Pause");
__asm
{
call fword ptr ds:[CallGate];
}

PDWORD p = (PDWORD)0x1000;
i = 0;
while(i < 100)
{
printf("元素 %d 的值为:%x\n",i+1,p[i]);
i++;
}
system("Pause");
return 0;
}

57.png