TLB机制

 Windows内核分析索引目录:https://www.cnblogs.com/onetrainee/p/11675224.html

TLB

1. CPU寻址模式

2. TLB

3. 缓存

4. shadowwalk技术

5.TLB感知实验

6.全局页

 

1. CPU寻址模式

  之前在分页时介绍过这张图,里面分析过其对应的寻址模式:

  1)CPU先尝试从TLB找到物理地址;

  2)如果TLB中不存在对应的物理地址,则手动计算出物理地址;

  3)之后再从缓存中尝试获取物理地址对应的数据;

  4)如果缓存中没有,则去物理内存中找到相应地址来读取对应的数据。

  

 

 

2. TLB

  TLB与缓存要区分清楚,TLB是通过线性地址找物理地址的,缓存是通过物理地址找数据的。

  TLB中以页为单位来存储的,缓存直接按照字节来进行存储的,要区分他俩之间的关系,不要混为一谈。

 

3. 缓存

  以三级缓存为例,当一级缓存满了之后会往二级缓存中存储,二级满了会往三级缓存中存储,三级缓存定期备份到物理内存中。

  

 

4. shadowwalk技术

  有很多游戏等会存在CRC校验,其读取的是数据TLB,但执行的是指令TLB,因此我们保留数据TLB,修改指令TLB,这样就实现了过CRC校验。

  shadowwalk等工具就是利用TLB的两张表分离实现过CRC校验的。

 

5.TLB感知实验

  1)实验大体步骤

    ① 准备两个地址,在不同的页上;

    ② 向零地址挂上TLB,

    ③ 读取数据,加载到TLB中;

    ④ 再向0地址挂另一个物理页的PTE;

    ⑤ 我再读零地址,如果读出来的还是原来的数据,则证明存在TLB。

  2)实验代码:

    如下,我们利用windbg创建调用门:eq 8003f048 0040ec00`00081005

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

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

char* p = NULL;
char* p1 = NULL;
int temp = 0;
int temp1 = 0;

// eq 8003f048 0040ec00`00081005
void __declspec(naked)test(){
    _asm{

        pushfd;
        pushad;
        push fs;
        mov ax,0x30;
        mov fs,ax;
        // 挂p位的
        mov eax,dword ptr ds:[p]; // 取出p的地址
        shr eax,9; // 由移9位
        and eax,0x7FFFF8; // 
        add eax,0xc0000000;
        // 取出pte    
        mov ebx,dword ptr [eax]; 
        mov ecx,[eax+4];
        // 往0地址挂
        mov dword ptr ds:[0xc0000000],ebx;
        mov dword ptr ds:[0xc0000004],ecx;
        // 加载到tlb中
        mov eax, dword ptr ds:[0];
        //int 3;
        //取出值
        mov temp,eax;

        /*---------------*/

        // 挂p1的
        mov eax,dword ptr[p1]; // 取出p1的地址
        shr eax,9; // 由移9位
        and eax,0x7FFFF8; // 
        add eax,0xc0000000;
        // 取出pte
        mov ebx,dword ptr [eax]; 
        mov ecx,[eax+4];
        // 往0地址挂
        mov dword ptr ds:[0xc0000000],ebx;
        mov dword ptr ds:[0xc0000004],ecx;
        // 加载到tlb中
        mov eax, dword ptr ds:[0];
        // 取出值
        mov temp1,eax;
        //int 3;

        pop fs;
        popad;
        popfd;
        retf;
    }
}

int main(int argc, char* argv[])
{
    // 分配内存
    p = (char*)VirtualAlloc(NULL,0x1000,MEM_COMMIT,PAGE_EXECUTE_READWRITE);
    p1 = (char*)VirtualAlloc(NULL,0x1000,MEM_COMMIT,PAGE_EXECUTE_READWRITE);
    char buf[]={0,0,0,0,0x48,0};
    printf("%x,p1=%x,p=%x\r\n",test,p1,p);
    system("pause");
    temp = 100;
    temp1 = 200;
    *(int*)p = 0x1000;
    *(int*)p1 = 0x2000;

    _asm{
        call fword ptr buf;
    }
    printf("%x,%x\t\n",temp,temp1);
    printf("hello world!\r\n");
    system("pause");
    return 0;
}

  3)实验结果:

    实验结果如下,可以发现第二次虽然我们在0地址处手工挂靠了物理页,但其因为第一次保存在TLB缓存当中,当第二次再开始读取的时候,通过TLB获取第一个PTE的线性地址,因此读到的是第一个。

    因此通过这个实验你就可以感知到TLB缓存的存在,下面我们学习如何刷新TLB缓存。

    

   4)TLB刷新

    如何刷新TLB呢?其如果刷新cr3,则就刷新TLB,因此我们如果想刷新,写上 mov eax,cr3; mov cr3,eax即可。

    

 

6.全局页

  1)将其修改为全局页

    如果将页置为全局页,则需要修改PTE的第8位,G位,表示全局页。

    

    因此我们在写入PTE前,使用 or ebx,0x100将该位置1,哪怕再刷新CR3也没有效果。

    

  2)通过cr4来禁止修改全局页

    cr4存在一个PGE位,当该位为1,则允许使用全局页,现在我们将该位关掉。

    

    因为在vc6.0环境下不支持cr4的修改,因此我们借助IDA来直接硬编码:

// cr4 PGE位关闭全局页

__emit 0x0f; // mov eax,cr4;
__emit 0x20;
__emit 0xE0;

mov ecx,0x80;
not ecx;
and eax,ecx;

__emit 0x0f; // mov cr4,eax;
__emit 0x22;
__emit 0xe0; 

  之后我们发现及时设置全局页,其也是无效的,会被置换出来

  

  其实CPU中存在一个强制刷新地址的指令,哪怕是全局页表也会被刷新

  invlpg dword ptr ds:[0],使用该条指令则会强制刷新缓存

 

全部试验代码如下:

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

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

char* p = NULL;
char* p1 = NULL;
int temp = 0;
int temp1 = 0;

// eq 8003f048 0040ec00`00081005
void __declspec(naked)test(){
    _asm{

        pushfd;
        pushad;
        push fs;
        mov ax,0x30;
        mov fs,ax;
        // 挂p位的
        mov eax,dword ptr ds:[p]; // 取出p的地址
        shr eax,9; // 由移9位
        and eax,0x7FFFF8; // 
        add eax,0xc0000000;
        // 取出pte    
        mov ebx,dword ptr [eax]; 
        mov ecx,[eax+4];
        // 往0地址挂
        xor ebx,0x100;
        mov dword ptr ds:[0xc0000000],ebx;
        mov dword ptr ds:[0xc0000004],ecx;
        // 加载到tlb中
        mov eax, dword ptr ds:[0];
        //int 3;
        //取出值
        mov temp,eax;

        mov eax,cr3;
        mov cr3,eax;
        /*---------------*/

        // cr4 PGE位关闭全局页
        /*
        __emit 0x0f; // mov eax,cr4;
        __emit 0x20;
        __emit 0xE0;

        mov ecx,0x80;
        not ecx;
        and eax,ecx;

        __emit 0x0f; // mov cr4,eax;
        __emit 0x22;
        __emit 0xe0;
        */


        // 挂p1的
        mov eax,dword ptr[p1]; // 取出p1的地址
        shr eax,9; // 由移9位
        and eax,0x7FFFF8; // 
        add eax,0xc0000000;
        // 取出pte
        mov ebx,dword ptr [eax]; 
        mov ecx,[eax+4];
        // 往0地址挂
        mov dword ptr ds:[0xc0000000],ebx;
        mov dword ptr ds:[0xc0000004],ecx;
        // 加载到tlb中
        invlpg dword ptr ds:[0];
        mov eax, dword ptr ds:[0];
        // 取出值
        mov temp1,eax;
        //int 3;
        pop fs;
        popad;
        popfd;
        retf;
    }
}

int main(int argc, char* argv[])
{
    // 分配内存
    p = (char*)VirtualAlloc(NULL,0x1000,MEM_COMMIT,PAGE_EXECUTE_READWRITE);
    p1 = (char*)VirtualAlloc(NULL,0x1000,MEM_COMMIT,PAGE_EXECUTE_READWRITE);
    char buf[]={0,0,0,0,0x48,0};
    printf("%x,p1=%x,p=%x\r\n",test,p1,p);
    system("pause");
    temp = 100;
    temp1 = 200;
    *(int*)p = 0x1000;
    *(int*)p1 = 0x2000;

    _asm{
        call fword ptr buf;
    }
    printf("%x,%x\t\n",temp,temp1);
    printf("hello world!\r\n");
    system("pause");
    return 0;
}
posted @ 2020-04-20 18:25  OneTrainee  阅读(1449)  评论(0编辑  收藏  举报