保护模式篇——总结与提升

写在前面

  此系列是本人一个字一个字码出来的,包括示例和实验截图。由于系统内核的复杂性,故可能有错误或者不全面的地方,如有错误,欢迎批评指正,本教程将会长期更新。 如有好的建议,欢迎反馈。码字不易,如果本篇文章有帮助你的,如有闲钱,可以打赏支持我的创作。如想转载,请把我的转载信息附在文章后面,并声明我的个人信息和本人博客地址即可,但必须事先通知我

你如果是从中间插过来看的,请仔细阅读 羽夏看Win系统内核——简述 ,方便学习本教程。

  看此教程之前,问几个问题,基础知识储备好了吗?上一节教程学会了吗?上一节课的练习做了吗?没有的话就不要继续了。


🔒 华丽的分割线 🔒


练习及参考

本次答案均为参考,可以与我的答案不一致,但必须成功通过。

  在看参考答案之前先看一个东西,我们需要知道fs在0环时存的是什么,首地址是什么。首先看一下fs的首地址是什么:

  从上图看出在0环的首地址是0xFFDFF000,那么这个地址是存什么的呢?我们用!pcr指令看一下:

KPCR for Processor 0 at ffdff000:
    Major 1 Minor 1
    NtTib.ExceptionList: 8054a4b0
        NtTib.StackBase: 8054acf0
       NtTib.StackLimit: 80547f00
     NtTib.SubSystemTib: 00000000
          NtTib.Version: 00000000
      NtTib.UserPointer: 00000000
          NtTib.SelfTib: 00000000

                SelfPcr: ffdff000
                   Prcb: ffdff120
                   Irql: 0000001c
                    IRR: 00000000
                    IDR: ffff20f8
          InterruptMode: 00000000
                    IDT: 8003f400
                    GDT: 8003f000
                    TSS: 80042000

          CurrentThread: 80553740
             NextThread: 00000000
             IdleThread: 80553740

              DpcQueue:

  可以看出这个地址存储的是KPCR结构体,那么KPCR是什么呢?它是一个结构体,由于Windows需要支持多个CPU,因此Windows内核中为此定义了一套以处理器控制区,即KPCR为枢纽的数据结构, 使每个CPU都有个KPCR。其中KPCR这个结构中有一个域PRCB结构, 这个结构扩展了KPCR。这两个结构用来保存与线程切换相关的全局信息。具体的细节将会在本系列教程的进程线程篇进行讲解。

  为了更加方便的查看结构体详情,我们dt一下:

dt _KPCR ffdff000
nt!_KPCR
   +0x000 NtTib            : _NT_TIB
   +0x01c SelfPcr          : 0xffdff000 _KPCR
   +0x020 Prcb             : 0xffdff120 _KPRCB
   +0x024 Irql             : 0x1c ''
   +0x028 IRR              : 0
   +0x02c IrrActive        : 0
   +0x030 IDR              : 0xffff20f8
   +0x034 KdVersionBlock   : 0x80546ab8 Void
   +0x038 IDT              : 0x8003f400 _KIDTENTRY
   +0x03c GDT              : 0x8003f000 _KGDTENTRY
   +0x040 TSS              : 0x80042000 _KTSS
   +0x044 MajorVersion     : 1
   +0x046 MinorVersion     : 1
   +0x048 SetMember        : 1
   +0x04c StallScaleFactor : 0x64
   +0x050 DebugActive      : 0 ''
   +0x051 Number           : 0 ''
   +0x052 Spare0           : 0 ''
   +0x053 SecondLevelCacheAssociativity : 0 ''
   +0x054 VdmAlert         : 0
   +0x058 KernelReserved   : [14] 0
   +0x090 SecondLevelCacheSize : 0
   +0x094 HalReserved      : [16] 0
   +0x0d4 InterruptMode    : 0
   +0x0d8 Spare1           : 0 ''
   +0x0dc KernelReserved2  : [17] 0
   +0x120 PrcbData         : _KPRCB

  做了这些铺垫,你可以继续看答案了。注意,要求是分析一下执行流程,并不是把每一个细节逆向明白,大体知道怎么处理即可:

1️⃣ 分析IDT表中0x2号中断的执行流程。

🔒 点击查看答案 🔒
.text:004085B6 _KiTrap02       proc near               ; DATA XREF: KiSystemStartup(x)+143↓o
.text:004085B6                                         ; INIT:005DD510↓o
.text:004085B6
.text:004085B6 var_8           = dword ptr -8
.text:004085B6 var_4           = dword ptr -4
.text:004085B6
.text:004085B6                 cli                     ; 屏蔽可屏蔽中断,别随便打扰我
.text:004085B7                 push    dword ptr ds:0FFDFF040h ; fs:[40h] TSS
.text:004085BD                 mov     eax, ds:0FFDFF03Ch ; fs:[3ch] GDT
.text:004085C2                 mov     ch, [eax+5Fh]   ; eax = GDT
.text:004085C5                 mov     cl, [eax+5Ch]
.text:004085C8                 shl     ecx, 10h
.text:004085CB                 mov     cx, [eax+5Ah]   ; 利用 ecx 获取 8003f058 段描述符的首地址,值为 8054AF68 ,它是个 TSS 段描述符
.text:004085CF                 mov     ds:0FFDFF040h, ecx ; fs:[40h],切换 TSS
.text:004085D5                 pushf
.text:004085D6                 and     [esp+8+var_8], 11111111111111111011111111111111b
.text:004085DD                 popf                    ; 将 NT位 置0
.text:004085DE                 mov     ecx, ds:0FFDFF03Ch ; fs:[3ch],GDT
.text:004085E4                 lea     eax, [ecx+58h]  ; 获取现在使用的 TSS
.text:004085E7                 mov     byte ptr [eax+5], 89h ; 修改目前 TSS 的属性
.text:004085EB                 mov     eax, [esp+4+var_4]
.text:004085EE                 push    0
.text:004085F0                 push    0
.text:004085F2                 push    0
.text:004085F4                 push    0
.text:004085F6                 push    dword ptr [eax+50h]
.text:004085F9                 push    dword ptr [eax+38h]
.text:004085FC                 push    dword ptr [eax+24h]
.text:004085FF                 push    dword ptr [eax+4Ch]
.text:00408602                 push    dword ptr [eax+20h]
.text:00408605                 push    0
.text:00408607                 push    dword ptr [eax+3Ch]
.text:0040860A                 push    dword ptr [eax+34h]
.text:0040860D                 push    dword ptr [eax+40h]
.text:00408610                 push    dword ptr [eax+44h]
.text:00408613                 push    dword ptr [eax+58h]
.text:00408616                 push    dword ptr ds:0FFDFF000h
.text:0040861C                 push    0FFFFFFFFh
.text:0040861E                 push    dword ptr [eax+28h]
.text:00408621                 push    dword ptr [eax+2Ch]
.text:00408624                 push    dword ptr [eax+30h]
.text:00408627                 push    dword ptr [eax+54h]
.text:0040862A                 push    dword ptr [eax+48h]
.text:0040862D                 push    dword ptr [eax+5Ch]
.text:00408630                 push    0
.text:00408632                 push    0
.text:00408634                 push    0
.text:00408636                 push    0
.text:00408638                 push    0
.text:0040863A                 push    0
.text:0040863C                 push    0
.text:0040863E                 push    0
.text:00408640                 push    0
.text:00408642                 push    0
.text:00408644                 push    dword ptr [eax+20h]
.text:00408647                 push    dword ptr [eax+3Ch]
.text:0040864A                 mov     ebp, esp
.text:0040864C                 cmp     ds:dword_47A2DC, 0
.text:00408653                 jz      short loc_40867D
.text:00408655                 jmp     short loc_408659
.text:00408657 ; ---------------------------------------------------------------------------
.text:00408657                 jmp     short loc_40867D
.text:00408659 ; ---------------------------------------------------------------------------
.text:00408659
.text:00408659 loc_408659:                             ; CODE XREF: _KiTrap02+9F↑j
.text:00408659                 cmp     ds:dword_47A2DC, 8
.text:00408660                 jb      short loc_40867D
.text:00408662                 jnz     short loc_40867B
.text:00408664                 cmp     ds:_KdDebuggerNotPresent, 0
.text:0040866B                 jnz     short loc_40867B
.text:0040866D                 cmp     ds:_KdDebuggerEnabled, 0
.text:00408674                 jz      short loc_40867B
.text:00408676                 call    _KeEnterKernelDebugger@0 ; KeEnterKernelDebugger()
.text:0040867B
.text:0040867B loc_40867B:                             ; CODE XREF: _KiTrap02+AC↑j
.text:0040867B                                         ; _KiTrap02+B5↑j ...
.text:0040867B                 jmp     short loc_40867B
.text:0040867D ; ---------------------------------------------------------------------------
.text:0040867D
.text:0040867D loc_40867D:                             ; CODE XREF: _KiTrap02+9D↑j
.text:0040867D                                         ; _KiTrap02+A1↑j ...
.text:0040867D                 inc     ds:dword_47A2DC
.text:00408683                 push    0
.text:00408685                 call    ds:__imp__HalHandleNMI@4 ; HalHandleNMI(x)
.text:0040868B                 dec     ds:dword_47A2DC
.text:00408691                 jnz     short loc_4086C6
.text:00408693                 mov     eax, ds:0FFDFF040h ; fs:[40h]
.text:00408698                 cmp     word ptr [eax], 58h ; 'X'
.text:0040869C                 jz      short loc_4086C6
.text:0040869E                 add     esp, 8Ch
.text:004086A4                 pop     dword ptr ds:0FFDFF040h ; fs:[40h]
.text:004086AA                 mov     ecx, ds:0FFDFF03Ch ; fs:[3ch]
.text:004086B0                 lea     eax, [ecx+28h]
.text:004086B3                 mov     byte ptr [eax+5], 8Bh ; 修改 TSS 属性为 Busy
.text:004086B7                 pushf
.text:004086B8                 or      [esp+4+var_4], 4000h
.text:004086BF                 popf                    ; 将 NT位 置1
.text:004086C0                 iret                    ; TSS 返回

2️⃣ 分析IDT表中0x8号中断的执行流程。

🔒 点击查看答案 🔒
.text:0040969D _KiTrap08       proc near               ; DATA XREF: KiSystemStartup(x)+CB↓o
.text:0040969D                                         ; INIT:005DD540↓o
.text:0040969D
.text:0040969D var_4           = dword ptr -4
.text:0040969D
.text:0040969D                 cli                     ; 屏蔽可屏蔽中断
.text:0040969E                 mov     ecx, ds:0FFDFF03Ch ; 取 GDT 首地址
.text:004096A4                 lea     eax, [ecx+50h]  ; 取 TSS 段描述符地址,eax = 8003f050
.text:004096A7                 mov     byte ptr [eax+5], 89h ; 设置 TSS 属性
.text:004096AB                 pushf
.text:004096AC                 and     [esp+4+var_4], 0FFFFBFFFh
.text:004096B3                 popf                    ; 清空 NT位
.text:004096B4                 mov     eax, ds:0FFDFF03Ch ; 取 GDT 首地址
.text:004096B9                 mov     ch, [eax+57h]
.text:004096BC                 mov     cl, [eax+54h]
.text:004096BF                 shl     ecx, 10h
.text:004096C2                 mov     cx, [eax+52h]   ; 取 TSS 段描述符指向的首地址
.text:004096C6                 mov     eax, ds:0FFDFF040h ; 将 TSS 备份给 eax
.text:004096CB                 mov     ds:0FFDFF040h, ecx ; 切换 TSS
.text:004096D1
.text:004096D1 loc_4096D1:                             ; CODE XREF: .text:004096E1↓j
.text:004096D1                 push    0
.text:004096D3                 push    0
.text:004096D5                 push    0
.text:004096D7                 push    eax             ; 出错的 TSS
.text:004096D8                 push    8
.text:004096DA                 push    7Fh
.text:004096DC                 call    _KeBugCheck2@24 ; KeBugCheck2(x,x,x,x,x,x)
.text:004096DC _KiTrap08       endp

段/页/门

  当你一步步做好练习学习,看到这一篇文章的时候,恭喜你,你的基础已经差不多。有关其他的保护模式的细节,自己就能独立研究了。学保护模式,真的不易。
  在本篇章,我们讲解了什么是段,什么是页。经历过重重蓝屏的折磨,学到这你应该有比较深入的了解。段是第一道防线,主要是对权限的检查。页是最后一道防线,是对内存的进一步保护。如果想要在保护模式下读取内存中某地址存的内容,必须经历过段和页的双重考验才能成功。比如fs的0地址为什么能访问,大于0xFFF的地址没法访问,是由于段的限制。为什么代码段只能读不能写,这也是段的限制。还有之前我们讲过0地址,你即使有了0环的权限,也访问不了,这是因为没有挂正确的物理页。高2G的内存低权限访问不了,是由于页的限制。
  我们开始学页的时候,首先是10-10-12分页,再到后来的2-9-9-12分页。它们的结构基本相似,不过多了一层嵌套,只是后者的支持的物理页更大更多了。可以说,分页的发展,依靠需求来推动。
  门在保护模式的地位也是不低的。门也有很多种:调用门、中断门、陷阱门、任务门等等。门的主要作用是提权。在操作系统中,所有的代码实现都是在内核实现的,包括所谓的API等等。
  看来操作系统并不是全知全能的,它需要和CPU搞好关系,成就出如此复杂的系统,当然别的硬件也是不可或缺。

深入PAE分页

  在讲10-10-12分页的时候,我们讲解了目录表基址和页表基址,也知道它们的用途。但我并没有在2-9-9-12分页进行介绍,但是可以通过逆向分析的手段来进行。下面我们来对操作系统如何在2-9-9-12分页模式下来挂物理页。
  我们先从WinDbg看看,我打开一个Notepad,通过往常的方式查看它的PDPTT,如下图的!dq 129001a0,先看好四个成员,因为它仅有四个。

  然后我们看看它的最后一项,查看它的成员,如下图的!dq 3b303000。你就会惊奇地发现,前四项是一模一样的,只是属性不太一样。我们根据我们的发现画一个图:

  我们可以说PDPTT的第四个成员的PDT的前四个成员就是PDPTT的所有成员,然后我们通过逆向,用IDA看看它的作用:

.text:00439980 ; BOOLEAN __stdcall MmIsAddressValid(PVOID VirtualAddress)
.text:00439980                 public _MmIsAddressValid@4
.text:00439980 _MmIsAddressValid@4 proc near           ; CODE XREF: IopIsAddressRangeValid(x,x)+2F↑p
.text:00439980                                         ; IopGetMaxValidMemorySize(x,x)+29↑p ...
.text:00439980
.text:00439980 PS              = dword ptr -8
.text:00439980 HPDE            = dword ptr -4
.text:00439980 VirtualAddress  = dword ptr  8
.text:00439980
.text:00439980                 mov     edi, edi
.text:00439982                 push    ebp
.text:00439983                 mov     ebp, esp
.text:00439985                 push    ecx
.text:00439986                 push    ecx
.text:00439987                 mov     ecx, [ebp+VirtualAddress] ; ecx = VirtualAddress
.text:0043998A                 push    esi
.text:0043998B                 mov     eax, ecx
.text:0043998D                 shr     eax, 18
.text:00439990                 mov     esi, 11111111111000b
.text:00439995                 and     eax, esi        ; esi = 11111111111000b
.text:00439997                 sub     eax, -0C0600000h ; 目录表基址
.text:0043999C                 mov     edx, [eax]      ; 取PDE的低四个字节
.text:0043999E                 mov     eax, [eax+4]    ; 取PDE的高四个字节
.text:004399A1                 mov     [ebp+HPDE], eax ; HPDE:PDE的高四个字节
.text:004399A4                 mov     eax, edx        ; eax = LPDE(PDE的低四个字节)
.text:004399A6                 push    edi
.text:004399A7                 and     eax, 1          ; 得到PDE的P位
.text:004399AA                 xor     edi, edi
.text:004399AC                 or      eax, edi        ; 判断P是否为1
.text:004399AE                 jz      short loc_439A11 ; 如果无效则跳
.text:004399B0                 mov     edi, 10000000b  ; edi = 10000000b
.text:004399B5                 and     edx, edi        ; 判断PS位是否为1,即是否是大页
.text:004399B7                 push    0
.text:004399B9                 mov     [ebp+PS], edx
.text:004399BC                 pop     eax             ; eax = 0
.text:004399BD                 jz      short loc_4399C3
.text:004399BF                 test    eax, eax
.text:004399C1                 jz      short loc_439A15 ; jmp
.text:004399C3
.text:004399C3 loc_4399C3:                             ; CODE XREF: MmIsAddressValid(x)+3D↑j
.text:004399C3                 shr     ecx, 9
.text:004399C6                 and     ecx, 7FFFF8h
.text:004399CC                 mov     eax, [ecx+0C0000004h] ; 取PTE的高四个字节,0xC0000000+0x4
.text:004399D2                 sub     ecx, -0C0000000h ; ecx += 0xC0000000
.text:004399D8                 mov     edx, [ecx]      ; 取PTE的低四个字节
.text:004399DA                 mov     [ebp+HPDE], eax
.text:004399DD                 push    ebx
.text:004399DE                 mov     eax, edx        ; eax = LPTE
.text:004399E0                 xor     ebx, ebx
.text:004399E2                 and     eax, 1
.text:004399E5                 or      eax, ebx
.text:004399E7                 pop     ebx
.text:004399E8                 jz      short loc_439A11 ; 如果PTE的P位无效跳走
.text:004399EA                 and     edx, edi        ; edi = 10000000b
.text:004399EC                 push    0
.text:004399EE                 mov     [ebp+PS], edx
.text:004399F1                 pop     eax             ; eax = 0
.text:004399F2                 jz      short loc_439A15
.text:004399F4                 test    eax, eax
.text:004399F6                 jnz     short loc_439A15 ; jmp
.text:004399F8                 and     ecx, esi
.text:004399FA                 mov     ecx, [ecx+0C0600000h] ; PDE的低四个字节
.text:00439A00                 mov     eax, 10000001b
.text:00439A05                 and     ecx, eax
.text:00439A07                 xor     edx, edx        ; 清理标志位
.text:00439A09                 cmp     ecx, eax
.text:00439A0B                 jnz     short loc_439A15
.text:00439A0D                 test    edx, edx        ; jmp
.text:00439A0F                 jnz     short loc_439A15
.text:00439A11
.text:00439A11 loc_439A11:                             ; CODE XREF: MmIsAddressValid(x)+2E↑j
.text:00439A11                                         ; MmIsAddressValid(x)+68↑j
.text:00439A11                 xor     al, al
.text:00439A13                 jmp     short loc_439A17
.text:00439A15 ; ---------------------------------------------------------------------------
.text:00439A15
.text:00439A15 loc_439A15:                             ; CODE XREF: MmIsAddressValid(x)+41↑j
.text:00439A15                                         ; MmIsAddressValid(x)+72↑j ...
.text:00439A15                 mov     al, 1
.text:00439A17
.text:00439A17 loc_439A17:                             ; CODE XREF: MmIsAddressValid(x)+93↑j
.text:00439A17                 pop     edi
.text:00439A18                 pop     esi
.text:00439A19                 leave
.text:00439A1A                 retn    4
.text:00439A1A _MmIsAddressValid@4 endp

  既然有10-10-12分页的逆向经验,我们不难逆出它们。通过逆向结果:VirtualAddress >> 18 + C0600000就是指向的PDE也就是说,得出的索引是2^14,最大值4000H0C0600000就是第一个PDT表的首地址,C0601000是第二个PDT表的首地址,C0602000是第三个PDT表的首地址,C0603000是第四个PDT表的首地址。
  可能你不清楚PDPTE的作用,我给你举个例子你就明白为什么我会有哪个线性地址就是第几个PDT表了:我们在找PDE的时候,我们只是去了后面的21位乘8个字节找的。

  可以从上图看出PDPTI × 1000H + PDI × 8,而一个页就是1000H,说明它们是接壤的。这就是PDPTE在寻找物理地址的作用。
  既然知道它的结构了,那么代码就自己写吧,在练习与思考就有对应的题目。

练习与思考

1️⃣ 在2-9-9-12分页模式下用代码实现给0地址挂物理页,不能用Windbg挂,并验证TLB的存在。

🔒 点击查看答案 🔒


  此题目和之前的10-10-12分页的题目差不多,答案差不多,效果也是一样的。

  首先构造一个调用门:eq 8003f098 0040EC0000081250,注意调用门和裸函数的地址一致。

  有关实验是否成功的评判标准,在之前的类似的题目是一样的,我就不再赘述了。


🔒 点击查看代码 🔒
#include "stdafx.h"
#include <iostream>
#include <windows.h>

int isinv=0;
int num1=0;
int num2=0;

void __declspec(naked) callgate()
{
    _asm
    {
        push 0x30;
        pop fs;

        pushad;
        pushfd;

        mov edi,0xC0000000;

        mov eax,0x10000;
        shr eax,9;
        add eax,edi;
        mov edx,dword ptr ds:[eax];
        mov dword ptr ds:[edi],edx;
        add eax,4;
        mov edx,dword ptr ds:[eax];
        mov dword ptr ds:[edi+4],edx;


        mov edx,dword ptr ds:[0];
        mov [num1],edx;

        mov eax,isinv ;
        test eax,eax;
        jz end;

        invlpg dword ptr ds:[0];

end:

        mov eax,0x20000;
        shr eax,9;
        add eax,edi;
        mov edx,dword ptr ds:[eax];
        mov dword ptr ds:[edi],edx;
        add eax,4;
        mov edx,dword ptr ds:[eax];
        mov dword ptr ds:[edi+4],edx;

        mov edx,dword ptr ds:[0];
        mov [num2],edx;

        popfd;
        popad;
        retf;
    }
}



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

    LPVOID page1 = VirtualAlloc((LPVOID)0x10000,0x1000,MEM_COMMIT,PAGE_EXECUTE_READWRITE);
    LPVOID page2 = VirtualAlloc((LPVOID)0x20000,0x1000,MEM_COMMIT,PAGE_EXECUTE_READWRITE);

    if (!page1||!page2)
    {
        puts("分配内存失败!!!");
        VirtualFree(page1,0,MEM_FREE);
        VirtualFree(page2,0,MEM_FREE);
        system("pause");
        return 0;
    }

    *(int*)page1 = 0x12345;
    *(int*)page2 = 0x67890;

    puts("是否清理缓存?");
    scanf("%d",&isinv);


    const char buffer[6]={0,0,0,0,0x9B,0};

    _asm
    {
        push fs;
        call fword ptr [buffer];
        pop fs;
    }

    printf("第一次挂页的值:%x\n换页后的值:%x\n",num1,num2);

    VirtualFree(page1,0,MEM_FREE);
    VirtualFree(page2,0,MEM_FREE);

    system("pause");
    return 0;
}

2️⃣ 在VirtualBox的XP虚拟机中我以2-9-9-12分页进入操作系统,但是,结果通过PCHunterWinDbg发现,还是10-10-12分页的分页模式,这是为什么呢?

🔒 点击查看答案 🔒
配置虚拟机的时候,是否启用了 PAE/NX 这个选项了吗?

结语

  到此,保护模式篇就结束了。仔细复习一下之前学过的东西。下一步我们将要踏入下一个篇章。学保护模式的时候我们蹒跚学步,到后来就可以小跑进行了。后面的教程我会根据我的空余时间,加快更文的速度。

下一篇

  羽夏看Win系统内核——驱动篇

posted @ 2021-10-31 09:59  寂静的羽夏  阅读(790)  评论(3编辑  收藏  举报