保護模式篇——總結與提升


寫在前面

  此系列是本人一個字一個字碼出來的,包括示例和實驗截圖。由於系統內核的復雜性,故可能有錯誤或者不全面的地方,如有錯誤,歡迎批評指正,本教程將會長期更新。 如有好的建議,歡迎反饋。碼字不易,如果本篇文章有幫助你的,如有閑錢,可以打賞支持我的創作。如想轉載,請把我的轉載信息附在文章后面,並聲明我的個人信息和本人博客地址即可,但必須事先通知我

你如果是從中間插過來看的,請仔細閱讀 羽夏看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系統內核——驅動篇


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM