系统调用003 系统服务表
文章目錄
- 前言
- SystemServiceTable 系統服務表
- 系統服務表在哪
- 判斷調用的函數在哪個表
- API函數的調用過程
前言
我們現在已經知道API怎么從三環進入零環,從三環進零環需要帶兩個寄存器,分別是eax和edx。eax保存的是系統的服務號,edx保存的是三環的esp,通過esp可以找到三環的參數。
這次要解決的問題是如何通過eax找到零環的函數,零環的函數是怎么被調用的,并且零環的函數執行的時候是怎么使用三環的參數的。
SystemServiceTable 系統服務表
在操作系統里,有一張非常重要的表,SystemServiceTable 系統服務表 。系統服務表是操作系統內核的一張表,結構如圖:
總共有四個成員:
系統服務表在哪
系統服務表位于_KTHREAD結構體0xE0的位置。
判斷調用的函數在哪個表
問題在于系統服務表有兩張,那么我們怎么判斷需要調用的函數在哪個表呢
- 要找兩張表取決于eax系統服務號。這個系統服務號總共有32位,但是真正只使用了13位。
- 系統服務號在使用的時候分為兩部分,低12位表示的是函數地址表的索引,下標為12的位置的值,決定了使用哪張表
- 如果第12位為0,則找第一張表,如果第12位為1,則找第二張表。
API函數的調用過程
接下來就來分析零環代碼是怎么通過服務號找到零環函數,并且零環的函數是如何使用在三環的參數的。
用IDA打開ntkrnlpa.exe,找到KiFastCallEntry函數。KiFastCallEntry和KiSystemService前面的代碼都是用于保存現場,大致相同。
我們從保存現場之后開始分析。
.text:0046579D mov edi, eax ; 取出三環傳進來的系統調用號首先將三環傳遞過來的系統調用號保存到edi里
.text:0046579F shr edi, 8 ; 系統調用號右移8位 .text:004657A2 and edi, 30h ; 判斷調用號的第12是0還是1 .text:004657A5 mov ecx, edi ; ecx存儲的值是00或者0x10然后將系統調用號右移8位,然后再和0x30做與運算。此時edi的結果只能有兩種,要么是0x0,要么是0x10。如果是0的話,就說明調用號下標為12的位置是0,如果edi的結果是0x10,那久說明調用號下標為12的位置是1。
.text:004657A7 add edi, [esi+0E0h] ; edi指向KTHREAD--->ServiceTableesi指向的是ETHREAD線程結構體,這個結構體+0xE的位置是ServiceTable系統服務表。用系統服務表直接加上edi的值。如果edi的值是0,加上ServiceTable的基址還是等于原來的值,這個時候就會找第一張系統服務表。
如果edi的值是0x10,再加上ServiceTable的基址指向的剛好是第二張表。因為系統服務表的大小正好是0x10,加上0x10就能找到第二張表。這個地方的算法非常巧妙。
.text:004657AD mov ebx, eax ; 把三環的系統服務號存到ebx備份 .text:004657AF and eax, 0FFFh ; 系統調用號 只保留后12位接著把系統調用號備份到ebx,然后和0FFFh做與運算,保留后12位
.text:004657B4 cmp eax, [edi+8] .text:004657B7 jnb _KiBBTUnexpectedRange這里用eax和edi+0x8的位置做比較,此時的edx指向的是系統服務表,[edi+8]的位置是第三個成員ServiceLimit,是服務函數的個數。如果傳入的調用號的低12位大于服務函數的個數,就跳轉到_KiBBTUnexpectedRange。
這里是判斷要找的函數有沒有超過函數地址表的范圍。如果沒有越界,代碼會繼續往下走。
.text:004657BD cmp ecx, 10h ; 判斷是否是查第二張系統服務表 .text:004657C0 jnz short loc_4657DC ; 如果是查第一張系統服務表則跳轉接著拿ecx和0x10進行比較,ecx的值來自于edi,存儲的值是00或者0x10,如果ecx是10的話就說明要查第二張系統服務表。
如果是查第一張系統服務表就會跳轉,查第二張則繼續往下執行
.text:004657C2 mov ecx, ds:0FFDFF018h .text:004657C8 xor ebx, ebx .text:004657CA .text:004657CA loc_4657CA: ; DATA XREF: _KiTrap0E+113↓o .text:004657CA or ebx, [ecx+0F70h] .text:004657D0 jz short loc_4657DC .text:004657D2 push edx .text:004657D3 push eax .text:004657D4 call ds:_KeGdiFlushUserBatch .text:004657DA pop eax .text:004657DB pop edx最后會調用_KeGdiFlushUserBatch函數。繼續往下分析,假設最后查的是第一張系統服務表
.text:004657DC loc_4657DC: ; CODE XREF: _KiFastCallEntry+B0↑j .text:004657DC inc dword ptr ds:0FFDFF638h ; _KPCRB->0x518 KeSystemCall增加1 .text:004657E2 mov esi, edx ; edx存儲的三環的參數指針這里將edx保存到esi,edx存儲的是三環的參數指針
.text:004657E4 mov ebx, [edi+0Ch] ;ebx指向參數表起始位置此時的edi指向系統服務表的起始位置,[edi+0Ch]存儲的是ParamTableBase 參數表的基址,此時ebx指向參數表起始位置。
.text:004657E9 mov cl, [eax+ebx] ; eax->函數地址表索引 ebx->參數表起始位置 cl->參數的個數參數表的基址加上函數地址表的索引,再取內容得到的值就是要調用的函數的參數個數。
.text:004657EC mov edi, [edi] ; edi指向系統服務表 第一個成員是函數地址表接下來取出函數地址表放到edi
.text:004657EE mov ebx, [edi+eax*4] ; ebx->零環的函數地址接下來用函數地址表edi加上索引eax乘以4,這行代碼執行完成之后ebx存儲的是零環的函數地址
.text:004657F1 sub esp, ecx ; 提升堆棧 提升高度為CL接著提升堆棧,提升的大小為CL,也就是參數的大小,為了容納三環的參數
.text:004657F3 shr ecx, 2 ; 參數總長度/4=參數的個數 .text:00465804 rep movsd ; 開始拷貝參數然后將ecx右移兩位,右移兩位相當于除以4,得到的結果是參數的個數。為什么要右移呢?原因很簡單,因為后面的rep串操作指令的循環次數取決于ecx的值,而movsd一次復制4個字節。
.text:004657F8 cmp esi, ds:_MmUserProbeAddress ; 判斷三環的參數的地址范圍是否越界 .text:004657FE jnb loc_4659AC ; 越界跳轉到錯誤處理模塊此時esi指向的是三環的函數指針,這里跟一個全局變量進行比較,這個全局變量是用戶程序能訪問地址的最大范圍。
這里是為了判斷三環的參數的地址范圍是否越界,如果越界則跳轉到錯誤處理的模塊
.text:00465804 rep movsd ; 開始拷貝參數 .text:00465806 call ebx ; 調用函數最后將三環的參數賦值到零環,開始調用真正的內核函數
總結
以上是生活随笔為你收集整理的系统调用003 系统服务表的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 系统调用002 KiSystemServ
- 下一篇: 系统调用004 SSDT