系統調用的學習
上文回顧
上篇文章分析得到:
1.3環進0環的兩種方式,分别是中斷門和快速調用,CPU支援快速調用,那麼_KUSER_SHARED_DATA 結構體的 SystemCall 屬性指向的函數是 KiFastSystemCall,執行 KiFastSystemCall,使用快速調用的方式進0環;如果不支援,那麼SystemCall 指向的函數是KiIntSystemCall,執行 KiIntSystemCall,使用中斷門的方式進0環。
2.快速調用不需要通路記憶體,而中斷門需要讀TSS和IDT表
3.int 0x2e 和 sysenter 指令進0環後,分别調用了兩個函數 KiSystemService 和 KiFastCallEntry。
4.原來的寄存器存儲到了_KTRAP_FRAME 結構體裡,3環API參數指針通過EDX傳給0環。
上篇文章分析結果,兩個函數最後執行同一段代碼,如圖
圖中标記的是407781函數,之後是執行的相同代碼部分,7781函數在_KiFastCallEntry函數内部。圖檔太大沒法截圖全部。
KiSystemService / KiFastCallEntry 填充_KTRAP_FRAME 後續部分
這兩個函數雖然入口不同,但是填充完 _KTRAP_FRAME 後,就會執行相同的代碼。
預備知識:
eax中存儲的系統服務号 0BAh
edx存儲的三環的參數指針
系統服務表
_KTHREAD+0xE0= +0x0e0 ServiceTable : Ptr32 Void
有兩張系統服務表,第一張是用來找核心函數的,第二張是找Win32k.sys驅動函數的
結構:0x10大小
typedef struct _SERVICE_DESCRIPTOR_TABLE
{
PULONG ServiceTableBase; // 指針,指向函數位址,每個成員占4位元組
PULONG ServiceCounterTableBase; // 目前系統服務表被調用的次數
ULONG NumberOfService; // 服務函數的總數
PUCHAR ParamTableBase; // 服務函數的參數總長度,以位元組為機關,每個成員占一個位元組
// 如:服務函數有兩個參數,每個參數占四位元組,那麼對應參數總長度為8
// 函數位址成員 與 參數總長度成員 一一對應
} SSDTEntry, *PSSDTEntry;
- 系統服務表裡的函數都是來自核心檔案導出的函數
- 它并不包含核心檔案導出的所有函數,而是3環最常用的核心函數
SSDT表
全稱:System Services Descriptor Table(系統服務描述符表)
SSDT的每個成員叫做系統服務表
檢視SSDT表
kd> dd nt!KeServiceDescriptorTable
第二張表為0,使用KeServiceDescriptorTable這個公開的導出函數,我們無法看到win32k.sys這張表結構
win32k.sys系統服務表已經可見
系統服務号:低12位就是函數參數表和函數位址表的下标,而第13位(下标12)如果是0,表示找第一張系統服務表Ntoskrl.exe,如果是1,那麼找第二張表win32k.sys.後12位是函數位址表和函數參數表的索引。
分析
.text:00407781
.text:00407781 loc_407781: ; CODE XREF: _KiBBTUnexpectedRange+18↑j
.text:00407781 ; _KiSystemService+6E↑j
.text:00407781 mov edi, eax ;eax中是系統服務号
.text:00407783 shr edi, 8 ;edi右移8位
.text:00407786 and edi, 30h ;判斷系統服務号12位,0:edi == 0x00 ;1:edi == 0x10
.text:00407789 mov ecx, edi
.text:0040778B add edi, [esi+0E0h]; [esi+0E0h]:_KTHREAD+0xE0=ServiceTable,edi指向系統服務表,這裡将系統服務表所在位址直接加上edi的運算結果,巧妙地得到要查哪張表(兩張表是連續的),每張表占16位元組
.text:00407791 mov ebx, eax
.text:00407793 and eax, 0FFFh ;與運算後,隻保留系統服務号低12位
.text:00407798 cmp eax, [edi+8] ;edi指向系統服務表,[edi+8]:NumberOfService,判斷要找的函數是否超出範圍
.text:0040779B jnb _KiBBTUnexpectedRange ;若大于系統調用号的個數則跳轉,即系統調用号越界
.text:004077A1 cmp ecx, 10h ;ecx 儲存的是 edi 與0x30與運算後的結果,隻能是0x00或0x10
.text:004077A4 jnz short loc_4077C0 ;若系統調用号小于0x1000,則跳轉
.text:004077A6 mov ecx, ds:0FFDFF018h ;隻有當ecx == 0x10才會向下執行,作用是動态加載GUI等圖形相關函數
.text:004077AC xor ebx, ebx
.text:004077AE
.text:004077AE loc_4077AE: ; DATA XREF: _KiTrap0E+110↓o
.text:004077AE or ebx, [ecx+0F70h]
.text:004077B4 jz short loc_4077C0
.text:004077B6 push edx
.text:004077B7 push eax
.text:004077B8 call ds:_KeGdiFlushUserBatch
.text:004077BE pop eax
.text:004077BF pop edx
.text:004077C0
.text:004077C0 loc_4077C0: ; CODE XREF: _KiFastCallEntry+B4↑j
.text:004077C0 ; _KiFastCallEntry+C4↑j
.text:004077C0 inc dword ptr ds:0FFDFF638h;
.text:004077C6 mov esi, edx ;edx:三環參數指針
.text:004077C8 mov ebx, [edi+0Ch] ; [edi+0Ch]:ParamTableBase(參數表指針)
.text:004077CB xor ecx, ecx ;eax儲存的是3環傳入的系統調用号
.text:004077CD mov cl, [eax+ebx] ;eax儲存的是3環傳入的系統調用号,ebx儲存的是是參數表指針,這條指令的目的是得到核心函數的參數總長度,存入cl
.text:004077D0 mov edi, [edi] ;取出系統調用表的第一個成員(函數位址指針)
.text:004077D2 mov ebx, [edi+eax*4] ;函數位址指針 + 系統調用号*4(乘4是因為每個成員占4位元組),ebx中存入函數位址
.text:004077D5 sub esp, ecx ;提升堆棧,提升高度為cl,目的是要把三環堆棧中的參數存進來
.text:004077D7 shr ecx, 2 ;參數總長度/4=參數個數
.text:004077DA mov edi, esp ;設定參數的0環堆棧位址
.text:004077DC cmp esi, ds:_MmUserProbeAddress ;判斷esi與使用者程式能通路的最大位址範圍,是否越界
.text:004077E2 jnb loc_407990 ;如果越界了 進行跳轉,處理異常
.text:004077E8
.text:004077E8 loc_4077E8: ; CODE XREF: _KiFastCallEntry+2A4↓j
.text:004077E8 ; DATA XREF: _KiTrap0E+106↓o
.text:004077E8 rep movsd ;複制參數(執行幾次取決于參數個數<-004077D7結果 )到0環的堆棧
.text:004077EA call ebx ;調用核心函數NtReadVirtualMemory
實驗:
檢視函數位址:
[函數位址表 + 系統服務号*4] = 核心函數位址 805aa712
檢視參數位址
[參數表 + 系統服務号] = 核心函數參數個數(機關:位元組)14
檢視核心函數反彙編: