保护模式篇——TLB与CPU缓存
写在前面
此系列是本人一个字一个字码出来的,包括示例和实验截图。由于系统内核的复杂性,故可能有错误或者不全面的地方,如有错误,欢迎批评指正,本教程将会长期更新。 如有好的建议,欢迎反馈。码字不易,如果本篇文章有帮助你的,如有闲钱,可以打赏支持我的创作。如想转载,请把我的转载信息附在文章后面,并声明我的个人信息和本人博客地址即可,但必须事先通知我。
你如果是从中间插过来看的,请仔细阅读 羽夏看Win系统内核——简述 ,方便学习本教程。
看此教程之前,问几个问题,基础知识储备好了吗?上一节教程学会了吗?上一节课的练习做了吗?没有的话就不要继续了。
🔒 华丽的分割线 🔒
练习及参考
本次答案均为参考,可以与我的答案不一致,但必须成功通过。
2️⃣ 自己实验有和没有DEP
保护的程序,看看效果。
🔒 点击查看答案 🔒
这题目有一个坑,接下来听我娓娓道来。
首先说一句:你设置操作系统的DEP
保护所有程序有效了吗?(具体启用查看上一节教程,选中为除下列选定程序之外的所有程序和服务启用DEP
,直接点确定),不然不起效果,白皮书原话:
If IA32_EFER.NXE = 1, execute-disable (if 1, instruction fetches are not allowed from the 2-MByte region controlled by this entry; see Section 4.6); otherwise, reserved (must be 0).
也就是说IA32_EFER
的NXE
位为0,就算最高位是1也是无尽于是。我们更改这个选项就是使它为1,这个就是是坑。好了,根据我的代码先运行一下:
发现报错了,这个和10-10-12
分页的不同之处。然后把PAGE_READWRITE
改为PAGE_EXECUTE_READWRITE
,然后再运行:
这次运行成功,既然它们的页的首地址都是一样的,我们来看看它们的PDE
或者PTE
哪里不同,先是PAGE_READWRITE
的:
PROCESS 8947dda0 SessionId: 0 Cid: 0690 Peb: 7ffde000 ParentCid: 0204
DirBase: 13b00380 ObjectTable: e1702740 HandleCount: 18.
Image: mytest.exe
kd> !dq 13b00380
# 13b00380 00000000`4ac31001 00000000`4a972001
# 13b00390 00000000`4ac33001 00000000`4aa70001
# 13b003a0 00000000`baf903c0 00000000`4a8a3001
# 13b003b0 00000000`4a6a4001 00000000`4a5a1001
# 13b003c0 00000000`baf90360 00000000`4a1a6001
# 13b003d0 00000000`4a227001 00000000`4a264001
# 13b003e0 00000000`baf90400 00000000`00000000
# 13b003f0 00000000`00000000 00000000`00000000
kd> !dq 4ac31000
# 4ac31000 00000000`4a92a067 00000000`4aa13067
# 4ac31010 00000000`4aaa9067 00000000`4ab2f067
# 4ac31020 00000000`00000000 00000000`00000000
# 4ac31030 00000000`00000000 00000000`00000000
# 4ac31040 00000000`00000000 00000000`00000000
# 4ac31050 00000000`00000000 00000000`00000000
# 4ac31060 00000000`00000000 00000000`00000000
# 4ac31070 00000000`00000000 00000000`00000000
kd> !dq 4aa13000+1d0*8
# 4aa13e80 80000000`4adc7067 00000000`00000000
# 4aa13e90 00000000`00000000 00000000`00000000
# 4aa13ea0 00000000`00000000 00000000`00000000
# 4aa13eb0 00000000`00000000 00000000`00000000
# 4aa13ec0 00000000`00000000 00000000`00000000
# 4aa13ed0 00000000`00000000 00000000`00000000
# 4aa13ee0 00000000`00000000 00000000`00000000
# 4aa13ef0 00000000`00000000 00000000`00000000
然后是PAGE_EXECUTE_READWRITE
:
PROCESS 895c8da0 SessionId: 0 Cid: 068c Peb: 7ffdf000 ParentCid: 0204
DirBase: 13b00300 ObjectTable: e168ea78 HandleCount: 18.
Image: mytest.exe
kd> !dq 13b00300
# 13b00300 00000000`4711c001 00000000`472dd001
# 13b00310 00000000`4735e001 00000000`4741b001
# 13b00320 00000000`319e5001 00000000`31ae6001
# 13b00330 00000000`31b27001 00000000`31b64001
# 13b00340 00000000`37e87001 00000000`37c88001
# 13b00350 00000000`37d89001 00000000`37c46001
# 13b00360 00000000`baf903a0 00000000`46a2a001
# 13b00370 00000000`46a6b001 00000000`46ba8001
kd> !dq 4711c000
# 4711c000 00000000`475d6067 00000000`472bf067
# 4711c010 00000000`47715067 00000000`4751b067
# 4711c020 00000000`00000000 00000000`00000000
# 4711c030 00000000`00000000 00000000`00000000
# 4711c040 00000000`00000000 00000000`00000000
# 4711c050 00000000`00000000 00000000`00000000
# 4711c060 00000000`00000000 00000000`00000000
# 4711c070 00000000`00000000 00000000`00000000
kd> !dq 472bf000+1d0*8
# 472bfe80 00000000`476b3067 00000000`00000000
# 472bfe90 00000000`00000000 00000000`00000000
# 472bfea0 00000000`00000000 00000000`00000000
# 472bfeb0 00000000`00000000 00000000`00000000
# 472bfec0 00000000`00000000 00000000`00000000
# 472bfed0 00000000`00000000 00000000`00000000
# 472bfee0 00000000`00000000 00000000`00000000
# 472bfef0 00000000`00000000 00000000`00000000
可以看出PTE
的最高位置为1,导致这个页被认为是数据,不是代码,故导致内存访问错误,如果我们在访问之前改了它,那么就能访问它吗?结果是可以的,我就不赘述了。
🔒 点击查看代码 🔒
#include "stdafx.h"
#include <iostream>
#include <windows.h>
char shellcode[]={
0x6A,0x00, //PUSH 0
0x6A,0x00, //PUSH 0
0x6A, 0x00 , //PUSH 0
0x6A ,0x00 , //PUSH 0
0xB8, 0x00 ,0x00 ,0x00, 0x00 , //MOV EAX,0000
0xFF, 0xD0 , //CALL EAX
0xC3 //RET
};
int main(int argc, char* argv[])
{
int msgbox=(int)MessageBoxW;
*(int*)&shellcode[9]=msgbox;
LPVOID scaddr = VirtualAlloc(NULL,1024,MEM_COMMIT,PAGE_READWRITE);
memcpy(scaddr,shellcode,sizeof(shellcode));
printf("Shellcode物理页:%p\n",scaddr);
_asm
{
mov eax,scaddr;
call eax;
}
VirtualFree(scaddr,0,MEM_RELEASE);
system("pause");
return 0;
}
TLB
CPU
通过页的方式对物理内存进行了,需要通过某种运算方式才能访问真正的内存。但是,如果频繁访问某一个线性地址,每次都得通过推算到真正的物理地址然后进行读写操作,是不是挺浪费效率的?Intel
就考虑到性能的问题,提供了TLB
这一个机制,提供缓存提高读写效率。
TLB
的全称为Translation Lookaside Buffer
,它的结构如下:
LA(线性地址) | PA(物理地址) | ATTR(属性) | LRU(统计) |
---|---|---|---|
0x81010111 | …… | …… | 1 |
对于TLB
,给出如下说明:
1. ATTR(属性):如果是2-9-9-12
分页,属性是PDPE
、PDE
、PTE
三个属性共同决定的。如果是10-10-12
分页就是PDE
和PTE
共同决定。
2. 不同的CPU
这个表的大小不一样。
3. 只要Cr3
变了,TLB
立马刷新,一核一套TLB
。
学到现在我们知道,操作系统的高2G映射基本不变,如果Cr3
改了,TLB
刷新重建高2G以上很浪费。所以PDE
和PTE
中有个G
标志位,如果G
位为1刷新TLB
时将不会刷新PDE/PTE
的G
位为1的页,当TLB
满了,根据统计信息将不常用的地址废弃,最近最常用的保留。
TLB
有不同的种类,用于不同的缓存目的,它在X86
体系里的实际应用最早是从Intel
的486CPU
开始的,在X86
体系的CPU
里边,一般都设有如下4组TLB
:
第一组:缓存一般页表(4K字节页面)的指令页表缓存:Instruction-TLB
第二组:缓存一般页表(4K字节页面)的数据页表缓存:Data-TLB
第三组:缓存大尺寸页表(2M/4M字节页面)的指令页表缓存:Instruction-TLB
第四组:缓存大尺寸页表(2M/4M字节页面)的数据页表缓存:Data-TLB
CPU缓存
CPU缓存是位于CPU
与物理内存之间的临时存储器,它的容量比内存小的多但是交换速度却比内存要快得多。它可以做的很大,但不是TLB
,它们有很大的不同。TLB
存的是线性地址与物理地址的对应关系,CPU缓存存的是物理地址与内容对应关系。
更多的细节请参考白皮书的Chapter 11 Memory Cache Control
,本篇教程主要是针对内核安全层面,就不再赘述了。
PWT 与 PCD
PWT
全称为Page Write Through
,PWT = 1
时,写Cache
的时候也要将数据写入内存中。
PCD
全称为Page Cache Disable
,PCD = 1
时,禁止某个页写入缓存,直接写内存。比如,做页表用的页,已经存储在TLB
中了,可能不需要再缓存了。
本节练习
本节的答案将会在下一节进行讲解,务必把本节练习做完后看下一个讲解内容。不要偷懒,实验是学习本教程的捷径。
俗话说得好,光说不练假把式,如下是本节相关的练习。如果练习没做好,就不要看下一节教程了,越到后面,不做练习的话容易夹生了,开始还明白,后来就真的一点都不明白了。本节练习很少,请保质保量的完成。
1️⃣ 在10-10-12
分页模式下体会TLB
的存在。要求:通过代码挂物理页,不能通过Windbg
挂。原物理页挂完写入值读取,然后把地址换物理页继续读取,看看值是否发生变化。然后用INVLPG
指令之后再看看值是否变化。
下一篇
本文来自博客园,作者:寂静的羽夏 ,一个热爱计算机技术的菜鸟
转载请注明原文链接:https://www.cnblogs.com/wingsummer/p/15364649.html