滴水自带的编程达人虚拟机好像被修改过有点坑,符号下载下来会有问题,建议自己重新搞一个

0x00 保护模式

0 段的机制

0.1 段寄存器

image-20210316182250099

段寄存器一共96位(16 + 80),读取段寄存器时只能读取16位 eg: mov ax, es

image-20210316183104275

struct SegMent
{
    WORD Selector;
    WORD Attribute;
    DWORD Limit;
    DWORD Base;
}

Q&A:

1)段寄存器只能看见16位,如何证明有96位?

2)写段寄存器时,只给了16位,剩下的80位填什么?

0.2 段描述符

image-20210319092841178

image-20210319204052301

GS寄存器 Windows操作系统没有使用,所以一进入R0就会被清空,所以单步调试的时候就无法被赋值。

0.3 段描述符的属性 (8 - 23 字节)

P位:高4字节15位 1 段描述符有效 0 段描述符无效

G位:高4字节23位 0 Limit单位是字节即Limit最大值为000F FFFF 1 Limit单位是4字节即最大值为FFFF FFFF

以ES段寄存器(23)为例:

image-20210319211451112

S位:高4字节12位 1 代码或数据段描述符 0 系统段描述符

TYPE域:高4字节8 - 11位 (不同段TYPE域意义不同)
S位为 1 时 第5位为9或f时为代码段或数据段,第6位大于等于8则为代码段小于8为数据段
数据段E位就是内存取反,代码段C位就是一致代码段
image-20210322194851856

image-20210320183727822S位为 0 时 系统段描述符image-20210320190605101

D/B位:高4字节第22位 image-20210320203304227
image-20210320203922942

数据段权限检查,CS段选择子的后两位叫当前特权级别(CPL)(判断程序在0环还是3环)image-20210321162725524

数据段检查 CPL <= DPL & RPL <= DPL才可访问 值越小等级越高 DPL一般要么全0要么全1image-20210321164412967image-20210321164701401

0.4 代码的跨段执行(长跳转)

image-20210322192548743

JMP 0x20:0x004183D7 !!无法改变CPL权限!!

1.拆分段选择子
2.查表得到段描述符(代码段,调用门,TSS任务段,任务门)
3.权限检查
image-20210322193604685
4.加载段描述符
5.代码执行 CS.Base + Offset -> Eip

0.5 长调用与短调用的堆栈图

  1. CALL : [esp - 4] = 返回地址, jmp xxx
  2. RET : jmp [esp], esp+4image-20210323220112675
  3. CALL CS:EIP : PUSH CS ,PUSH 返回地址 (跨段不提权)
  4. RETF : POP EIP, POP CSimage-20210323220410068
  5. 长调用(跨段并提权),堆栈会发生变化所以ESP和SS都要压栈,SS和ESP从TSS段来image-20210323221230001

    长调用实验(调用门),除了在0环堆栈中保存的四个寄存器,其余在内核中被修改的如果没有进行保存则都会被保留。

    //eq ffffffff8003f048 0040EC00`00081020
    #include <windows.h>
    #include <stdio.h>
    
    BYTE GDT[6] = {0};
    DWORD dwH2GValue;
    DWORD GDT_ADDR; 
    WORD GDT_LIMIT; 
    
    __declspec(naked) void getData() {
        __asm {
                int 3
                pushad
                pushfd
                mov eax,0x8003f048
                mov ebx,[eax]
                mov dwH2GValue,ebx
                sgdt GDT
                popfd
                popad
                retf
        }
    }
     
    int main(int argc, char* argv[]) {
        char cs_eip[6] = {0, 0, 0, 0, 0x48, 0}; // 这里的eip被废弃
        __asm {
            call fword ptr [cs_eip]
        }
        GDT_LIMIT = *(PWORD)(&GDT[0]);
        GDT_ADDR = *(PDWORD)(&GDT[2]);
        printf("%x %x %x\n",dwH2GValue,GDT_ADDR,GDT_LIMIT);//可能会报错,FS段寄存器被清空
    
        getchar();
        return 0;
    }
    
    

    image-20210324150428550

0.6 调用门(CALL FAR)

  1. 执行流程image-20210324122842706
  2. 门描述符 Type - 1100 调用门 DPL = CPLimage-20210324131300068
  3. 带参的调用门

       //eq ffffffff8003f048 0040EC03`00081020
       #include <windows.h>
       #include <stdio.h>
       int g_a, g_b, g_c;
       __declspec(naked) void getParam(int a, int b, int c) {
           __asm {
               int 3
               pushad
               pushfd
               //             .- 8 个通用寄存器和标志寄存器占用大小 36字节(pushad/pushfd)
               //             |     .- cs 和 返回地址 占用大小
               //             |     |
               mov eax, [esp+0x24+0x08+0x08] // 参数 a
               mov g_a, eax
               mov eax, [esp+0x24+0x08+0x04] // 参数 b
               mov g_b, eax
               mov eax, [esp+0x24+0x08+0x00] // 参数 c
               mov g_c, eax
               popfd
               popad
               retf 0x0c
           }
       }
       int main(int argc, char* argv[])
       {
           char cs_eip[6] = {0, 0, 0, 0, 0x48, 0};
           __asm {
               push 1
               push 2
               push 3
               call fword ptr [cs_eip];
           }
           return 0;
       }

image-20210325200129487

堆栈: 返回地址 - CS - 参数三 - 参数二 - 参数一 - ESP - SS

调用门Windows中不使用

0.7 中断门(INT X)

image-20210327110430776

IDT表中都是系统段描述符:1. 任务门描述符 2. 中断门描述符 3. 陷阱门描述符

INT 3// 3 - index 没有RPL了,段权限检查时只查CPL

老式CPU会使用中断门,新式CPU使用的是快速调用

中断门描述符(不允许传参数)
image-20210327110840925

没有提权时的堆栈:返回地址 - EFLAGS - CS

提权时的堆栈: 返回地址 - CS - EFLAGS - ESP - SS

使用IRET/IRETD指令返回 - IRET16位 IRETD32位

//eq ffffffff8003f500 0040EE00`00081030
#include <windows.h>
#include <stdio.h>
DWORD dwH2GValue;
void __declspec(naked) GetH2GValue()
{
    __asm
    {
        int 3
        pushad
        pushfd
        mov eax,[0x8003f00c]
        mov ebx,[eax]                // 获取高2G地址的值
        mov dwH2GValue,ebx
        popfd
        popad
        iretd                          //iret 会蓝屏
    }
}
void PrintH2GValue()
{
    printf("%x \n", dwH2GValue);
}
int main(int argc, char* argv[])
{
    __asm
    {
        int 0x20
    }
    
    PrintH2GValue();
    getchar();
    return 0;
}

image-20210327203124904

陷阱门描述符(不允许传参数)
image-20210327111135986

中断门在执行的时候会将IF位(中断允许标志)清零,但陷阱门不会

IF=0 时:程序不再接收可屏蔽中断
可屏蔽中断:比如程序正在运行时,我们通过键盘敲击了锁屏的快捷键,若IF位为1,CPU就能够接收到我们敲击键盘的指令并锁屏
不可屏蔽中断:断电时,电源会向CPU发出一个请求,这个请求叫作不可屏蔽中断,此时不管IF位是否为0,CPU都要去处理这个请求

0.8 任务段

image-20210327213729242

image-20210327214153552

TSS是一块内存,大小为104个字节

CPU如何找到TSS呢?TR段寄存器

image-20210327215740417

TSS段描述符image-20210327215937008

TYPE = 9,说明该段描述符没有加载到TR寄存器中/TYPE = B,说明该段描述符已经加载到TR寄存器中

image-20210327220702961

LTR 把TSS段描述符的内容加载到TR寄存器里,并修改TYPE位
STR 读取TR的选择子

修改TR寄存器image-20210328113240133

//eq ffffffff8003f0c0 0000E912`FDCC0068
#include <Windows.h>
#include <stdio.h>
DWORD dwOK;
DWORD dwESP;
DWORD dwCS;

void __declspec(naked) func()
{
    dwOK = 1;
    __asm
    {
        int 3
        
        mov eax,esp
        mov dwESP,eax
        mov ax,cs
        mov word ptr [dwCS],ax
        //回去的代码没写。。。会蓝屏
    }
}

int main(int argc, char* argv[])
{
    char bu[0x10];
    int iCr3;

    printf("input CR3:\n");
    scanf("%x", &iCr3);    //在Windbg中 !process pid 0 获得DirBase

    DWORD iTss[0x68] = {
        0x00000000,        //link
        0x00000000,        //esp0        //(DWORD)bu
        0x00000000,        //ss0
        0x00000000,        //esp1
        0x00000000,        //ss1
        0x00000000,        //esp2
        0x00000000,        //ss2
        (DWORD)iCr3,    //cr3
        0x00401020,        //eip 跳过去执行的位置
        0x00000000,        //eflags
        0x00000000,        //eax
        0x00000000,        //ecx
        0x00000000,        //edx
        0x00000000,        //ebx
        (DWORD)bu,        //esp
        0x00000000,        //ebp
        0x00000000,        //esi
        0x00000000,        //edi
        0x00000023,        //es
        0x00000008,        //cs        0x0000001B
        0x00000010,        //ss        0x00000023
        0x00000023,        //ds
        0x00000030,        //fs
        0x00000000,        //gs
        0x00000000,        //dit
        0x20ac0000};
        char buff[6];
        *(DWORD*)&buff[0] = 0x12345678;
        *(WORD*)&buff[4] = 0xC0;
        __asm
        {
            call fword ptr[buff]
        }
        return 0;
}

image-20210328172238999

0.9 任务门(INT X)

任务门描述符(IDT表内)

image-20210329163847423

image-20210329164153730

1 页的机制

1.1 10-10-12分页

物理内存按页管理,一页的大小一般是4KB,CR3中存放物理地址

image-20210416125119036

在虚拟机的Boot.ini 中将noexecute 改成 execute 就将分页模式改为10-10-12分页

image-20210416130136995

image-20210416131923808

复制PTE信息修改变量值 x--> 物理页 <--0xf7cimage-20210418194557108

1.2 PDE与PTE

image-20210416164143602

P = 0时四种情况image-20210420145607226

线性地址的前20位可以判断是否在同一物理页

1.3 PDE-PTE属性(P_R/W_G)

image-20210417201105708

G位(全局位):当PS位 = 0时 PDE的G位无意义

R/W位:0 - 只读 ; 1 - 可读可写

image-20210417210559454

1.4 U/S_PS_A_D位

U/S位(权限位):0 - 特权用户可以访问 ; 1 - 普通用户可以访问

image-20210418110015284

P/S位(PDE特有image-20210418103029084

A位(访问位):1 - 被访问过

D位(脏位):1 - 被写过

1.5 页目录表与页表基址

0xC0300000 为页目录表基址,物理内存不连续,线性地址是连续的

image-20210420202643615

image-20210420204113645

0xC0000000 为页表基址,PDT为PTT中一张特殊的表

image-20210420211602754

MmIsAddressValid逆向:

可以查看NT5的源码进行分析,函数所在文件路径为“XPSP1\NT\base\ntos\mm\pagfault.c”

image-20210422153516545


1.6 2-9-9-12分页(PAE分页)

image-20210422162908814image-20210422163137223

地址位为 12 - 35位

image-20210422211318760image-20210422211905443
image-20210422211401749

NX(XD)标志位

image-20210422213039193

2-9-9-12分页实验

image-20210423205647572image-20210423205729373

1.7 TLB

存储在CPU内部

image-20210427194336824

image-20210427195827187

image-20210427195940316

Shadow Walker技术实现内存隐藏 ,原理是数据页表缓存和指令页表缓存的差异,修改指令页表缓存更改指令,比较适用于R0代码(刷新不那么频繁)

TLB实验:

//eq ffffffff8003f048 0040EC00`00081020
#include <windows.h>
#include <stdio.h>

DWORD x;

void __declspec(naked) PageOnNull() {
    __asm
    {
        mov dword ptr ds:[0xc0000000],0x01234867

        mov dword ptr ds:[0],0x12345678

        //INVLPG dword ptr ds:[0]
        //mov eax,cr3
        //mov cr3,eax

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

        mov eax,dword ptr ds:[0]
        mov x,eax

        retf
    }
}
 
int main(int argc, char* argv[]) {

    
    char cs_eip[6] = {0, 0, 0, 0, 0x48, 0}; 
    
    
    __asm {
        call fword ptr [cs_eip]    // 0x48调用门提权
    }

    printf("%x\n",x);
    getchar();
    return 0;
}

1.8 中断与异常

中断通常是硬件发起的,异常通常是执行指令检测到错误

中断分为可屏蔽中断(INTR)和不可屏蔽中断(NMI),中断本质就是改变了CPU执行路线image-20210427210301007image-20210427210602621image-20210427210804930image-20210427211212259

1.9 控制寄存器

Cr1为保留位,Cr3为页目录表基址

image-20210427212620824image-20210427212832261

image-20210427212927585

image-20210427213301338

1.10 PWT/PCD位

image-20210427213822373

image-20210427214039493