天天看點

[Windows] 系統調用(R3 進入 R0)

背景

一個線程由使用者态進入核心态的途徑有3種典型的方式:

  • 通過 int 0x2e(軟中斷自陷) 或 KiFastSystemCall (快速系統調用),主動進入核心。
  • 引發異常或硬體中斷,被迫進入核心。
一般 R3 的 API 最終都會去調用 NT 函數,在 NT 函數中根據該 API 對應的索引号去 SSDT / SSDT Shadow 中查找對應的方法,最後通過 SSDT / SSDT Shadow 進入核心。

通過 int 0x2e 進入核心(xp以下)

在 xp 以下,SSDT / SSDT Shadow 進入核心的指令是 int 0x2e,該指令是一條自陷指令(也叫中斷門),之後會将使用者線程棧切換成核心線程棧,儲存 CONTEXT,到 IDT 中尋找 0x2e 對應的異常處理函數(KiSystemService),至此進入核心代碼空間。

[Windows] 系統調用(R3 進入 R0)
// 這個函數用來儲存寄存器現場和其他狀态資訊
SaveTrap()  
{
    Push 0                                    // LastError
    Push ebp
    Push ebx
    Push esi
    Push edi
    Push fs                                    // 此時的 fs 若是從使用者空間自陷進來的就指着 TEB,反之指着 kpcr
    Push kpcr.ExceptionList
    Push kthread.PreviousMode
    Sub esp,0x48                                // 騰給調式寄存器儲存用
    -----------至此,上面的這些語句連同int 2e中的語句在棧上構造了一個trap幀-----------------
    Mov CurTrapFrame,esp                        // 目前Trap幀的位址
    Mov CurTrapFrame.edx, kthread.TrapFrame    // 将上次的trap幀位址記錄到edx成員中
    Mov kthread.TrapFrame, CurTrapFrame,        // 修改本線程目前trap幀的位址
    
    Mov kthread.PreviousMode,GetMode(進入核心前的CS)  // 根據CS自動确定上次模式
    Mov kpcr.ExceptionList,-1                         // 表示剛進入核心時,尚未安裝seh
    
    Mov fs,kpcr                                     // 一進入核心就讓fs改指向目前cpu的描述符kpcr,不再指向TEB
    
    
    If(目前線程處于調試狀态)
       儲存DR0-DR7到trap幀中
}

// 這個函數用來查表,拷貝參數,調用系統服務
FindTableCall() 
{
    Mov edi,eax                            // 系統函數号,低12位為索引,第13為表示是哪張系統服務表中的索引
    Mov eax, edi.低12位                    // eax=真正的服務号
    If(edi.第13位=1)                        // if這是shadow SSDT中的系統函數号
    {
       If(目前線程.服務描述符表!=shadow)
          目前線程.服務描述符表=shadow        // 換用另外一張描述符表
    }
    服務表描述符=目前線程.服務描述符表[edi.第13位]
    Mod edi=服務表描述符.base                // 這個系統服務表的位址
    Mov ebx,[edi+eax*4]                    // 查表獲得這個函數的位址
    Mov ecx=服務表描述符.Number[eax]        // 查表獲得的這個系統函數的參數大小
    
    Mov esi,edx                            // esi = 使用者空間中的參數位址
    Mov edi,esp                            // esp已經為核心棧的棧頂位址
    Rep movsb                                // 将所有參數從使用者空間複制到核心空間,相當于N個連續push壓參
    Call  ebx                                // 調用對應的系統服務函數

}

// int 2e的isr,核心服務函數總入口,注意這個函數可以嵌套、遞歸!!!
KiSystemService()
{
    SaveTrap();
    Sti                            // 開中斷
    ---------------上面儲存完寄存器等現場後,開始查SSDT表調用系統服務------------------
    FindTableCall();
    ---------------------------------調用完系統服務函數後------------------------------
    Move  esp,kthread.TrapFrame;    // 将棧頂回到 trap 幀結構體處
    Cli                            // 關中斷
    If(上次模式==UserMode)
    {
    Call  KiDeliverApc                // 周遊執行本線程的核心APC和使用者APC隊列中的所有APC函數
    清理Trap幀,恢複寄存器現場
    Iret                            // 傳回使用者空間
    }
    Else
    {
       傳回到原call處後面的那條指令處
    }

}      

總結一下,KiSystemService 大概做了什麼:

  • 儲存寄存器現場和其他狀态資訊
  • 查SSDT表調用對應的系統服務
  • 恢複調用棧
  • 判斷上次模式,如果是使用者模式則執行 APC,之後恢複現場傳回 r3 。如果是核心模式,則傳回到調用 call KiSystemService 的下一條指令。

通過 KiFastSystemCall 進入核心(xp以上)

快速調用指令(Intel的是sysenter,AMD的是syscall)調用系統服務。

老式的cpu不支援、不提供sysenter指令,隻能由int 2e模拟中斷方式進入核心,調用系統服務,但是,那種方式有一個明顯的缺點,就是速度慢!(如int 2e内部本身要儲存5個寄存器的現場,然後還要去IDT中查找isr,這個過程消耗的時間太多),是以x86系列從奔騰2代開始為系統調用專門增設了一條sysenter指令以及相應的寄存器msr。

[Windows] 系統調用(R3 進入 R0)
Sysenter()
{
   Mov ss,msr_ss
   Mov esp,msr_esp            //關鍵
   Mov cs,msr_cs
   Mov eip,msr_eip            //關鍵
}

Sysexit
{
   Mov cs,msr_cs
   Mov ss,msr_ss
   Mov esp,ecx            // 換用使用者空間中的棧
   Mov eip,edx            // 這樣,就傳回使用者空間中了,所有系統調用總是先傳回到NTDLL.dll中的某個固定位置,最後一路傳回到NTDLL中發起系統調用的那個存根函數體内
}

// 快速系統調用總入口
KiFastCallEntry()  
{
    Mov fs,kpcr            // 一進入核心,就将fs改指向處理器描述符kpcr
    Mov esp,TSS.ESP         // 一進入核心,就換用核心棧(每個線程的核心棧位址儲存在TSS中)
    Push ds
    Push edx               // edx為使用者空間棧的棧頂位址,儲存在這兒,友善以後回到使用者空間時恢複
    Push eflags
    Push cs
    Push sysenter指令的後面那條指令的位址      // 将使用者空間中的傳回位址儲存在這兒
    --------上面的5條push指令模拟中斷、異常發生時cpu自動儲存的那5個寄存器的現場------------
   Cli                              // 關中斷,構造 Trap 現場幀的過程中需要暫時關中斷
   Mov eflags,0x2
   SaveTrap();
   Sti                              // 開中斷
   ---------------上面儲存完寄存器等現場後,查SSDT表調用對應系統服務----------------------
   FindTableCall();
   ------------------------------------調用完系統服務函數後--------------------------------
   Move  esp,kthread.TrapFrame;            // 将棧頂回到trap幀結構體處
   Cli                              // 關中斷
   …
   Call  KiDeliverApc                     // 周遊執行本線程的核心APC和使用者APC隊列中的所有APC函數
   …
   清理Trap幀,恢複寄存器現場
   Sti                              // 開中斷
   -----------------------------------下面傳回使用者空間-------------------------------------
   Mov ecx,儲存的使用者空間棧頂位址
   Mov edx,儲存的傳回位址,也即sysenter指令的後面那條指令的位址
   sysexit                           // 可以把這條指令了解為一個fastcall調用約定函數
}      
  • 儲存現場,并将環境切入核心
  • 查SSDT表調用對應系統服務
  • 恢複調用棧
  • 執行 APC
  • 傳回 r3

繼續閱讀