顯示廣告
隱藏 ✕
※ 本文為 uefangsmith 轉寄自 ptt.cc 更新時間: 2012-07-22 20:09:25
看板 Programming
作者 purpose (purpose)
標題 [心得] 淺談 KiFastSystemCall、SSDT、IDT
時間 Sun Mar 27 00:03:14 2011



這篇文章整理幾個名詞的簡介,以後看到時可以比較清楚狀況。

KiFastSystemCall      在 ntdll.dll 內的一個函數,與 system call 有關

                      WinDbg 可能叫它 SharedUserData!SystemCallStub,是別名


sysenter              約 Pentium II 以後,x86 指令集新增的指令,與前項有關


IDT                   每台 PC 開機後就會有的中斷描述表,由 CPU 管理

                      有被人叫「中斷向量表」(Interupt Vector Table)


SSDT                  Windows Kernel 掌管的系統服務表,調用「內核函數」時使用


一切都是圍繞在 user mode 切換到 kernel mode 的情景,且以 Windows 為主。

#########################

我們替程式做偵錯 (Debug、調試),偶爾會想要進入某個 Windows API 函數內,
於是一直做單步追蹤,往往追到 ntdll!KiFastSystemCall 附近,就跟不下去了。

這是因為一般的 Debugger 只能調試 user mode 的程式,

    (除非用 Live KD 這類 Kernel Debugger,才能進去看)
而 ntdll.dll 可以稱作「user mode 與 kernel mode 之牆」。

假設寫 C 程式碼,什麼事都不做,只呼叫了一個 kernel32!OpenProcess 函數。
編譯並用 Dependency Walker 檢查執行檔的「匯入表」,可以發現這個 .exe 必須
相依於 kernel32.dll。

當再用 OllyDbg 或 WinDbg 去調試此執行檔,並中斷在 main() 開頭,
此時檢查「載入模組」,會發現 ntdll.dll 跟 kernel32.dll 都被載入了。

而且 RunTime 下,這些 dll 載入的記憶體位址,都是小於 0x80000000。
換言之,都只能待在 user address space 裡面。

這是因為「開啟行程」這個動作,必須操作 Windows Kernel 掌管的資料,
位於 user mode 者,無法存取這些記憶體,
於是 Windows 開放了「行程操作相關」的介面,並放到 kernel32.dll 裡。

kernel32.dll 同樣還是 user mode 的程式,當它做好一些前置工作後,
    (比如把 ANSI 字串轉成 Unicode 字串,這類準備工作)
最終才會經由 ntdll.dll 這道牆切換到 kernel mode 裡去。


就好像用 gdi32.dll 裡的繪圖函數,最終還是要相依於 ntdll.dll,才能切換到

kernel mode,才能讓裡面的顯示卡 Driver 去控制硬體輸出內容。

而 ntdll.dll 裡面,負責切換至 kernel mode 工作的,就是 KiFastSystemCall 函數。
所以偵錯往往只能追到這裡,就跟不下去。

Ki 開頭,可以理解此函數會接觸到 Kernel,
System Call 是指任何待在 Kernel Mode 裡,某程式的某函數,
我們去調用該函數,就是調用 Sysyem Call。



以下來解釋,這個 Fast 的意思為何。

在沒有安裝作業系統前,由主機板內的 BIOS 程式碼負責開機,並且操控硬碟、顯示卡
..等硬體。

且正常的 BIOS 都會開放操縱介面,
使得只要利用 int 這個 cpu 指令,再搭配一個索引數字就能呼叫之。

比如 int 13h:
        命令 BIOS 去讀取某個硬碟 Sector 的內容,並且放到主記憶體的某處。

這段 BIOS 讀取硬碟的函數,在開機時會載入到主記憶體,
先假設是放在位址 0x100,
然後 BIOS 再對 CPU 註冊,說把第 19 號服務放在 0x100。

這個告知的動作,就是利用 CPU 的 IDTR 暫存器,來找出 IDT 表,並在第 19 項裡,
填入 0x100 的內容,當下次 CPU 執行到 int 13h 時,就會跳去 0x100 來執行。

而 IDTR 暫存器的大小為 48 位元,前 32 位元記載「IDT」在主記憶體的存放處,
後 16 位元記載表格 Size。


IDT 全名為 Interrupt Descriptor Table (中斷描述表),裡面最多可以
記載 256 個中斷服務的位址 (int 00h ~ int ffh)。而中斷服務的英文
為 ISR (Interrupt Service Routine)。


IDT 詳細解釋,可參考〈IDT系列:(一)初探IDT,Interrupt Descriptor Table,
中斷描述符表〉一文:

        http://blog.csdn.net/fwqcuc/archive/2010/09/01/5855460.aspx
IDT系列:(一)初探IDT,Interrupt Descriptor Table,中断描述符表 - fwqcuc的专栏 - 博客频道 - CSDN.NET IDT,Interrupt Descriptor Table,中断描述符表是CPU用来处理中断和程序异常的。一、有关IDT的基本知识1、中断时一种机制,用来处理硬件需要向CPU输入信息的情况。 比如鼠标,键盘等。2、中断和异常的产生是随机的,在CPU正常运行过程中随时可能产生。CPU的中断处理机制3、中断可以由硬件产生(称为外部中断),也可以由软件产生(称为内部中断),在程序中写入int n指令可以产生n号中断和异常(n从0-ffh)。4、同时CPU的中断异常机制还是重要特性的支持原理,比如程序调试,程序运 ...
 

如果在 Windows 下,想查看 IDT 的內容,想知道是不是還能用 int 13h 命令 BIOS
讀取硬碟磁區,可以使用軟體 SysReveal,並進入 IDT 頁查看。進去後,預設
只會顯示被 IDT hook 的項目,按右鍵把「Show hooks Only」取消即可。

事實上進入 Windows 後,這類系統操作都被 Windows Kernel 或各硬體 Driver 掌管,
原本的 IDT 都被改過了,指向 C:\WINDOWS\system32\ntoskrnl.exe 裡的函數。

ntoskrnl.exe 全部都運行在 Kernel Address Space 裡,比如在 C 程式碼內寫
inline assembly:

  __asm int 03h

並且用 Visual C++ 進行偵錯,不設中斷點。當 CPU 執行到該指令時,
就會由 IDT 查到目標為 ntoskrnl.exe 裡的某函數,位址在 0x804E089D,
然後不透過 ntdll.dll 就跳到 Kernel Mode 裡的 system call,呼叫
該目前負責該行程的 Debugger 中斷在該行,讓操作調試器者可以進行觀察。
如果找不到 Debugger 就由 Windows 決定要如何處理。

這個 int 03h 是少數不用透過 ntdll.dll 呼叫 system call 的狀況,因為你甚至
也沒有調用 Windows API 函數,來達成目的。

而在 Windows 2000 (含) 以前的時代,透過某 Windows API 要進入 Kernel Mode 時,
也是使用 ntdll.dll,差別是,那時沒有 KiFastSystemCall 這東西。

因為 KiFastSystemCall 的底層,是使用一個新的 IA-32 指令:sysenter
這個指令大約是在 Pentium II 時期,Intel 加入的 CPU 指令 (同時期的 AMD 是 K6)。

而直到 Windows XP,微軟才把 OS 最低需求,設為 Pentium 家族 233 Mhz 以上。

(至少要有 Pentium II 以上等級,時脈才有 233 Mhz),因為 KiFastSystemCall 就是
在 Windows XP 以後出現。


在 Windows 2000 調用 System Call 是使用原始的 IDT 與 int 2Eh 這個方法。
假設今天調用 kernel32!ReadFile 要做動作,最後會來到 ntdll!NtReadFile 裡:

        ntdll!NtReadFile:

        77f8c552        mov eax, 0xA1
        77f8c557        lea edx, [esp+0x4]
        77f8c55b        int 2E
        77f8c55d        ret 0x24

因為需要的參數都已經 push 到堆疊裡去了,
所以透過 lea 指令,於 RunTime 算出參數區的起始位址,放到 edx 暫存器,
再指定目標 System Call 的編號到 eax 暫存器,此例為 A1h,
最後是利用 int 2Eh 觸發,而 ret 0x24 是平衡堆疊,並回到上個 Stack Frame 去。

這個 int 2Eh 在 IDT 裡記載的位址假設為 0x8053A2FB,
這是對應到 nt!KiSystemService 函數,進去後會查出 A1h 所指的 System Call 其實
是 nt!NtReadFile 函數,再透過其進行硬體的讀取操作。

    (nt! 裡的 nt 不是指 nt.dll 而是專指 ntoskrnl.exe)

在 Windows XP 時,調用 System Call 是使用 ntdll!KiFastSystemCall,在這個函數
裡面,會透過 CPU 新的 sysenter 指令,還有新的幾個 MSR 暫存器記錄相關資料,
來高速的從 user mode (ring 3) 切換到 kernel mode (ring 0),
並呼叫裡面的 System Call。

sysenter 比較快的其中一個原因是,int 指令會先把當下某些暫存器的內容,
先存到記憶體去,然後才切換到目標 ISR,而 sysenter 有 MSR 暫存器的輔助,
就可以省略記憶體讀取的動作。

以 kernel32!OpenProcess 舉例,最終會來到 ntdll!NtOpenProcess:

      (有可能調試器是寫 ntdll!ZwOpenProcess,總之位址應該一樣,純粹是別名;
      好像 ntdll 裡面,ZwXXX 跟 NtXXX 一定是指同個函數?)

   1. mov eax, 7Ah
   2. mov edx, 7FFE0300h
   3. call dword ptr [edx]
   4. retn 10h

   5. mov edx, esp
   6. sysenter

第四行的目的就不用講了。
第一行的 7Ah 是將來指定 System Call 的識別號碼用,稍後再談。

第二行跟第三行指令的目的,就是為了要進入 KiFastSystemCall 函數,
也就是上面的第五行與第六行指令。

第五行指令的動作,就跟 Windows 2000 的版本的那個 lea 一樣,是為了傳送 System
Call 所需的參數起始位址。因為 Kernel Mode 運行的程式,有自己的 Stack,這通常
是位於 Kernel Address Sapce 的某處。


所以要把現在 User Mode Stack 的頂端,從現在的 ESP 裡拿出來,記於 edx 裡。
而第六行的 sysenter 會利用 MSR 暫存器設置環境,而不再像 int 指令一樣用記憶體。
設置環境後,就切換到 ring 0 執行 ntoskrnl.exe 裡的 nt!NtOpenProcess。

在進入 ring 0 後,事實上會先到 ntoskrnl.exe (Windows Kernel) 裡找出所謂的
SSDT (System Services Descriptor Table,系統服務描述符表)。

並從 SSDT 裡查出識別號為 0x7A 時,也就是查詢該表格的第 122 項,對應的是
哪個內核函數。

簡單來說利用 KiFastSystemCall (sysenter) 幫助我們快速切換到 Ring 0,並利用
SSDT 來查詢出 kernel32.dll 裡的 OpenProcess 這個 user mode 函數,是對應
到 kernel mode 內的 (模組的) 哪個函數。

而所謂的 SSDT hook 是比如某個遊戲防作弊的程式,他把自己寫成 Driver 檔,
於是可以運行在 Kernel Mode 裡,而且他更改了 SSDT 裡的表格項,比如第 n 項,
原本是用來讓 kernel32!CreateProcess 轉成 nt!NtCreateProcess 的。

被其修改掉,導向至他自己的 Driver,然後每次有程式調用 CreateProcess 時,
這個 Driver 就檢查開啟目標是否為該遊戲,如果是該遊戲,就禁止開啟,返回
user mode,如果不是就放行。

使任何想要用 CreateProcess 開啟遊戲程式,並動手腳的軟體都失效,這就是所謂的
SSDT hook。同樣可以用 SysReveal 軟體,查詢 SSDT 的完整內容,以及有哪個表項
被人偷偷 SSDT hook 了。


#########################

以下為參考來源與延伸資料,可找到更完整的技術細節:

    城裡城外看SSDT - 李馬:
        http://blog.titilima.com/ssdt.html

    HOOK SYSENTER:
        http://kost0911.pixnet.net/blog/post/36476081
Hook SYSENTER @ Rootkit --逆向工程技術-- :: 痞客邦 PIXNET ::
其實所有的HOOK,都基本是一樣道理。就是勾住你的目標函數,實現你自己的功能。只要你掌握了,HOOK的原理。剩下的就是尋找目標函數了。 今天回憶一下 HOOK SYSENTER,目的當然 ...
 

      修正:
            關於 〈HOOK SYSENTER〉一文,最早原創似乎來自 gotiger 的網站:
            http://hi.baidu.com/gotiger/blog/item/c6d8bb90fe834e13d31b705d.html

            上面 kost0911 站的殘月影,疑似轉載,特於此補充說明。

    sysenter/sysexit - 紅塵萬丈 - Yahoo!奇摩部落格:
        http://tw.myblog.yahoo.com/jw!tzeXibOaEQUcL.ja6fRpXFg-/article?mid=179
sysenter/sysexit - 紅塵萬丈 - Yahoo!奇摩部落格
sysenter/sysexit 比傳統 int 0x80 的系統呼叫有更快的執行速度。原因在傳統的 int 中,user space application 的 CS/EIP/SP…等返回時需要用到的資料都被存放在堆疊(記憶體)中,而自 Intel P ... ...
 

    Windows XP/2003 系统调用(一):
        http://blog.csdn.net/better0332/archive/2009/06/15/4269554.aspx
Windows XP/2003 系统调用(一) - 井底之蛙 - 博客频道 - CSDN.NET     今天在这里主要想了解一下Windows如何从ring3下的Win32 API转到ring0下的Kernel Routine。以NtReadFile为例:kd> u ntdll!NtReadFile (Win 2003 SP1)ntdll!ZwReadFile:7c821b78 b8bf000000      mov     eax,0BFh;(系统调用号)7c821b7d ba00 ...
 

有錯麻煩指正,有建議或補充還請提出,謝謝。
保留文章出處,轉載可。

--
--
※ 發信站: 批踢踢實業坊(ptt.cc)
◆ From: 124.8.146.225
akasan:好文推 雖然我不在MS的世界XD1F 61.60.218.62 台灣 03/27 01:15

謝謝
※ 編輯: purpose         來自: 124.8.129.73         (03/27 07:32)
horngsh:好文一篇! 讚!2F 112.104.148.1 台灣 03/27 11:12
zha0:CVC ?3F 220.135.121.210 台灣 03/29 10:40
purpose:cvc?4F 124.8.139.17 台灣 03/29 12:11
loteslogin:氣質5F 140.111.148.157 台灣 03/29 13:53
VictorTom:推:)6F 115.248.178.161 印度 03/30 23:17

从CreateFile(APP)到NtCreateFile(Kernel Mode)
http://blog.sina.com.cn/s/blog_61613a320100eg4d.html
从CreateFile(APP)到NtCreateFile(Kernel Mode)_成为达尔文_新浪博客 从CreateFile(APP)到NtCreateFile(Kernel Mode)_成为达尔文_新浪博客,成为达尔文, ...
 

Using Nt and Zw Versions of the Native System Services Routines
http://msdn.microsoft.com/en-us/library/ff565438.aspx
※ 編輯: purpose         來自: 124.8.143.166        (05/16 23:22)

--
※ 看板: uefacool 文章推薦值: 0 目前人氣: 0 累積人氣: 30 
分享網址: 複製 已複製
guest
x)推文 r)回覆 e)編輯 d)刪除 M)收藏 ^x)轉錄 同主題: =)首篇 [)上篇 ])下篇