Windows系統呼叫中的現場儲存
Windows核心分析索引目錄:https://www.cnblogs.com/onetrainee/p/11675224.html
Windows系統呼叫中的現場儲存
我們之前介紹過三環進零環的步驟,通過中斷或者快速呼叫來實現。
但是我們是否考慮過CPU從三環進入零環時,其三環的暫存器該如何儲存。
這一篇檔案就來介紹其系統呼叫中的(三環)現場儲存的問題。
一、幾個重要的結構體介紹
1. _Ktrap_frame
該結構體簡單來說用於三環的暫存器儲存,儲存於零環,由作業系統維護,每個執行緒都有自己的 _Ktrap_frame 結構體(ethread+0x108處)。
詳細資訊可以檢視這篇文章:解析windows核心每日一講 陷阱排程
我們之前講過進入0環時獲取新的四個暫存器非常重要, SS\CS\EIP\ESP
如下圖,當進入零環時,作業系統會獲取ESP值,該值指向_Ktrap_frame結構體,將舊的SS\CS\EIP\ESP\EFLAG依次壓入(0x78-0x68處),之後ESP +0x070 ErrCode處。
之後便進入獲取的EIP來執行核心函式。
2. _ETHREAD 結構體
該結構體儲存了和執行緒相關的資訊,位於0環(其並非三環的 TEB,執行緒環境塊)
在_ETHREAD結構體第一個成員是 _KTHRAD,可以看出其大小0x200,裡面儲存了執行緒中的一些資訊
kd > dt _ethread
ntdll!_ETHREAD
+ 0x000 Tcb : _KTHREAD
+ 0x200 CreateTime : _LARGE_INTEGER
····
kd > dt _kthread
ntdll!_KTHREAD
+ 0x000 Header : _DISPATCHER_HEADER
···
+0x128 TrapFrame : Ptr32 _KTRAP_FRAME
3. KPCR結構體(kernel processor control region 核心執行緒控制區)
有一篇文章,裡面大體介紹了該結構體 --> [Windows核心分析]KPCR結構體介紹 (CPU控制區 Processor Control Region)
簡單來說,KPCR結構體中儲存著 關於CPU 的資訊,一個核有一個自己私有的KPCR結構體,八核則每個核有自己的單獨的KPCR結構體
我們需要用到最後一個成員中的中的CurrentThread來獲取當前執行緒的_KTHREAD結構體。
kd > dt _kpcr
ntdll!_KPCR
+ 0x000 NtTib : _NT_TIB
+ 0x000 Used_ExceptionList : Ptr32 _EXCEPTION_REGISTRATION_RECORD
····
+0x120 PrcbData : _KPRCB
kd > dt _KPRCB
ntdll!_KPRCB
+ 0x000 MinorVersion : Uint2B
+ 0x002 MajorVersion : Uint2B
+ 0x004 CurrentThread : Ptr32 _KTHREAD
+ 0x008 NextThread : Ptr32 _KTHREAD
···
二、分析 nt!KiSystemService 核心函式
該函式位於ntkrnlpa.exe真正的核心中(並非ntdll.dll),一開始希望使用IDA反彙編檢視其程式碼,但是無法顯示。
我們只能採取"曲線救國"的方法,通過int 2eh中斷號查詢GDT表,從表中獲取中斷描述符,將中斷描述符拼接拆分,獲取 函式的EIP 地址。
之後,我們使用 Windbg 的U指令,來檢視該其該函式在記憶體中的反彙編程式碼
反彙編程式碼可能有點長,我們在正式分析之前需要明確幾個注意事項。
0)這些反彙編程式碼的目的就是將 _Ktrap_frame 結構體中的暫存器填滿,然後將[KTHREAD+0x128]處舊的TRAPFRAME切換為這個新的KTRAPFRAME然後呼叫核心函式。
1)函式開始時ESP位於_Ktrap_framec+0x070 ErrCode處。ErroCode這裡沒有,因此可以看待彙編程式碼中第一行使用 push 0。
2)FS暫存器在0環時是指向自己的KPCR,而在3環時儲存的是該執行緒的TEB結構體。(R3可以檢視:利用C++實現模組隱藏)
因此在反彙編程式碼 83e8cff6 、83e8cffb 是手動將FS暫存器儲存KPCR。
原理是以30h作為段選擇子查詢GDT這張表,找出段描述符,然後載入到fs段暫存器中。
3) 0x83e8d007處 KPCR:[124] 為當前執行緒的ETHREAD結構體(看一中的表,KTHREAD也即ETHREAD的頭部),將其儲存到暫存器esi中,之後要經常使用。
4)0x83e8d026處修改esp,將其指向 _Ktrap_frame 結構體第一個成員,0x83e8d036 處也將ebp修改為 _Ktrap_frame 結構體第一個成員。
5)因為 esi 指向ETHREAD, [esi+128h] 則表示當前執行緒的_Ktrap_frame,在進入核心時,其儲存了一箇舊的frame。
0x83e8d038將獲取舊的_Ktrap_frame
0x83e8d03e將舊的Frame放入新的Frame的edx暫存器中
0x83e8d049將新的Frame掛靠在ETHREAD+0x128處,這樣完成了新舊替換。
下圖三個箭頭依次對應上面三個階段,這樣通過FS暫存器就可以查詢到該FRAME了,回去只需要找到這裡完成替換即可。
6)Frame前部分成員和除錯有關,因此0x83e8d045這裡需要判斷是否在除錯狀態,如果處於除錯狀態,則jmp進一個地址,將暫存器入棧再回來。
硬體除錯,其實就是通過這個部分。對於反除錯,這裡可以做些文章...
7)還有部分涉及許可權切換,0x83e8d030 比如有些API可以讓3環也可以讓0環許可權呼叫,但有的不讓(看保護模式呼叫門等知識),此時就會進行位運算判斷"先前模式"的許可權級別。
8)當這些工作全部完成之後,會執行 83e8d06d e9dd000000 jmp nt!KiFastCallEntry+0x8f (83e8d14f)
對,你沒看錯,就是快速呼叫時的函式,相當於中斷多走了一步,處理了上面的一些資訊,而快速呼叫提前就可以處理好直接呼叫 nt!KiFastCallEntry+0x8f函式。
三、nt!KiSystemService函式的反彙編解讀
1 nt!KiSystemService: 2 // 壓棧 按照 _Ktrap_frame 結構中寄存的值 3 83e8cfee 6a00 push 0 // errorcode 填入零來進行對齊 4 83e8cff0 55 push ebp 5 83e8cff1 53 push ebx 6 83e8cff2 56 push esi 7 83e8cff3 57 push edi 8 83e8cff4 0fa0 push fs 9 10 // 根據30h這個值作為段選擇子,查詢gdt這張表,找出段描描述符,將其載入到fs段暫存器中 11 // 30h -> 0011 0000 -> index = 00110 | TI = 0 |00 -> TI=0查LDT表,查索引為6查出來的是 834093f7`ac003748 這個值。 12 // 834093f7`ac003748 段描述符,根據段描述符屬性拼接起來的 83f7ac00 13 // 83f7ac00 指向的是 KPCR,FS在零環的時候,指向KPCR,不再是三環時執行緒TEB這個結構體 14 83e8cff6 bb30000000 mov ebx,30h 15 83e8cffb 668ee3 mov fs,bx 16 83e8cffe bb23000000 mov ebx,23h 17 83e8d003 8edb mov ds,bx 18 83e8d005 8ec3 mov es,bx 19 20 21 // 有些API 0環和三環都可以呼叫,通過下面操作可以檢視原來呼叫的是0環還是3環,再進行呼叫API的許可權驗證 22 // 驗證完該執行緒,如果是0環,則bl為0;如果是3環,則bl=1 23 83e8d007 648b3524010000 mov esi,dword ptr fs:[124h] // 將當前CPU正在跑的執行緒放到esi中 24 83e8d00e 64ff3500000000 push dword ptr fs:[0] // 把老的EXCEPTION_LIST 存入 _Ktrap_frame 結構體中(fs上邊那部分) 25 83e8d015 64c70500000000ffffffff mov dword ptr fs:[0],0FFFFFFFFh // 將現在EXCPTION_LIST放入異常連結串列 26 83e8d020 ffb63a010000 push dword ptr [esi+13Ah] // 將老的"先前模式"也儲存到棧中 27 83e8d026 83ec48 sub esp,48h // 將當前ESP提升到 _Ktrap_frame 第一個成員 28 83e8d029 8b5c246c mov ebx,dword ptr [esp+6Ch] // 取出3環壓入的引數CS _KTRAP_FRAME + 0x6c,指向三環原來的CS值 29 83e8d02d 83e301 and ebx,1 // 將原來CS的值進行與運算, 0環最低為為0,3環最低為為1 30 83e8d030 889e3a010000 mov byte ptr [esi+13Ah],bl // 新的"先前模式",原來是三環模式為1;原來是零環模式為0 31 83e8d036 8bec mov ebp,esp // ebp 同樣指向_Ktrap_frame第一個成員 32 83e8d038 8b9e28010000 mov ebx,dword ptr [esi+128h] // esi通過0x83e8d007處[KPCR+124],指向Ethread // Ethread+128h 指向的是 _KTRAP_FRAME這個結構體指標 33 83e8d03e 895d3c mov dword ptr [ebp+3Ch],ebx // 將該 _KTRAP_FRAME地址暫時儲存在 edx 中 34 83e8d041 83652c00 and dword ptr [ebp+2Ch],0 35 83e8d045 f64603df test byte ptr [esi+3],0DFh // 判斷是否屬於除錯狀態 一個位 +0x003 DebugActive : UChar 36 83e8d049 89ae28010000 mov dword ptr [esi+128h],ebp // 因為新的 trap_frame地址已經發生變化,所以要將新的trapFrame放到執行緒放到這個位置 37 38 83e8d04f fc cld 39 83e8d050 0f859afeffff jne nt!Dr_kss_a (83e8cef0) // 在0x83e8d045處根據判斷結果,如果是除錯狀態,則進行跳轉。 40 // 若為除錯,則跳過去將除錯相關的暫存器也存入_KTRAP_FRAME這個結構體 41 // 否則不是偵錯程式,則將中是-1。(硬體斷點使用這個,反除錯很管用) 42 83e8d056 8b5d60 mov ebx,dword ptr [ebp+60h] // 3環的EBP 43 83e8d059 8b7d68 mov edi,dword ptr [ebp+68h] // 3環的EIP 44 83e8d05c 89550c mov dword ptr [ebp+0Ch],edx // edx存放著三環引數的指標 45 // {mov edx,esp;sysenter} 46 83e8d05f c74508000ddbba mov dword ptr [ebp+8],0BADB0D00h // 作業系統需要用的標誌 47 83e8d066 895d00 mov dword ptr [ebp],ebx // 3環的ebp儲存到DbgEbp的位置 48 83e8d069 897d04 mov dword ptr [ebp+4],edi // 3環的eip存放到 DbgEip 49 83e8d06c fb sti // 關閉中斷 50 83e8d06d e9dd000000 jmp nt!KiFastCallEntry+0x8f (83e8d14f) // 取出系統呼叫號,3環傳進來的。 51 // 注意,這個就是快速呼叫首先進入的函式,相當於中斷多走了這一步,系統呼叫直接呼叫。
四、nt!KiFastCallEntry的反彙編程式碼
該反彙編程式碼閱讀量同樣很大,找時間一定給讀出來,如果感興趣可以自行百度相關解讀。
1 nt!KiFastCallEntry: 2 83e8d0c0 b923000000 mov ecx,23h 3 83e8d0c5 6a30 push 30h 4 83e8d0c7 0fa1 pop fs 5 83e8d0c9 8ed9 mov ds,cx 6 83e8d0cb 8ec1 mov es,cx 7 83e8d0cd 648b0d40000000 mov ecx,dword ptr fs:[40h] 8 83e8d0d4 8b6104 mov esp,dword ptr [ecx+4] 9 83e8d0d7 6a23 push 23h 10 83e8d0d9 52 push edx 11 83e8d0da 9c pushfd 12 83e8d0db 6a02 push 2 13 83e8d0dd 83c208 add edx,8 14 83e8d0e0 9d popfd 15 83e8d0e1 804c240102 or byte ptr [esp+1],2 16 83e8d0e6 6a1b push 1Bh 17 83e8d0e8 ff350403dfff push dword ptr ds:[0FFDF0304h] 18 83e8d0ee 6a00 push 0 19 83e8d0f0 55 push ebp 20 83e8d0f1 53 push ebx 21 83e8d0f2 56 push esi 22 83e8d0f3 57 push edi 23 83e8d0f4 648b1d1c000000 mov ebx,dword ptr fs:[1Ch] 24 83e8d0fb 6a3b push 3Bh 25 83e8d0fd 8bb324010000 mov esi,dword ptr [ebx+124h] 26 83e8d103 ff33 push dword ptr [ebx] 27 83e8d105 c703ffffffff mov dword ptr [ebx],0FFFFFFFFh 28 83e8d10b 8b6e28 mov ebp,dword ptr [esi+28h] 29 83e8d10e 6a01 push 1 30 83e8d110 83ec48 sub esp,48h 31 83e8d113 81ed9c020000 sub ebp,29Ch 32 83e8d119 c6863a01000001 mov byte ptr [esi+13Ah],1 33 83e8d120 3bec cmp ebp,esp 34 83e8d122 7597 jne nt!KiFastCallEntry2+0x49 (83e8d0bb) 35 83e8d124 83652c00 and dword ptr [ebp+2Ch],0 36 83e8d128 f64603df test byte ptr [esi+3],0DFh 37 83e8d12c 89ae28010000 mov dword ptr [esi+128h],ebp 38 83e8d132 0f8538feffff jne nt!Dr_FastCallDrSave (83e8cf70) 39 83e8d138 8b5d60 mov ebx,dword ptr [ebp+60h] 40 83e8d13b 8b7d68 mov edi,dword ptr [ebp+68h] 41 83e8d13e 89550c mov dword ptr [ebp+0Ch],edx 42 83e8d141 c74508000ddbba mov dword ptr [ebp+8],0BADB0D00h 43 83e8d148 895d00 mov dword ptr [ebp],ebx 44 83e8d14b 897d04 mov dword ptr [ebp+4],edi 45 83e8d14e fb sti 46 83e8d14f 8bf8 mov edi,eax 47 83e8d151 c1ef08 shr edi,8 48 83e8d154 83e710 and edi,10h 49 83e8d157 8bcf mov ecx,edi 50 83e8d159 03bebc000000 add edi,dword ptr [esi+0BCh] 51 83e8d15f 8bd8 mov ebx,eax 52 83e8d161 25ff0f0000 and eax,0FFFh 53 83e8d166 3b4708 cmp eax,dword ptr [edi+8] 54 83e8d169 0f8333fdffff jae nt!KiBBTUnexpectedRange (83e8cea2) 55 83e8d16f 83f910 cmp ecx,10h 56 83e8d172 751a jne nt!KiFastCallEntry+0xce (83e8d18e) 57 83e8d174 8b8e88000000 mov ecx,dword ptr [esi+88h] 58 83e8d17a 33f6 xor esi,esi 59 83e8d17c 0bb1700f0000 or esi,dword ptr [ecx+0F70h] 60 83e8d182 740a je nt!KiFastCallEntry+0xce (83e8d18e) 61 83e8d184 52 push edx 62 83e8d185 50 push eax 63 83e8d186 ff154c99fb83 call dword ptr [nt!KeGdiFlushUserBatch (83fb994c)] 64 83e8d18c 58 pop eax 65 83e8d18d 5a pop edx 66 83e8d18e 64ff05b0060000 inc dword ptr fs:[6B0h] 67 83e8d195 8bf2 mov esi,edx 68 83e8d197 33c9 xor ecx,ecx 69 83e8d199 8b570c mov edx,dword ptr [edi+0Ch] 70 83e8d19c 8b3f mov edi,dword ptr [edi] 71 83e8d19e 8a0c10 mov cl,byte ptr [eax+edx] 72 83e8d1a1 8b1487 mov edx,dword ptr [edi+eax*4] 73 83e8d1a4 2be1 sub esp,ecx 74 83e8d1a6 c1e902 shr ecx,2 75 83e8d1a9 8bfc mov edi,esp 76 83e8d1ab 3b351c97fb83 cmp esi,dword ptr [nt!MmUserProbeAddress (83fb971c)] 77 83e8d1b1 0f832e020000 jae nt!KiSystemCallExit2+0xa5 (83e8d3e5) 78 83e8d1b7 f3a5 rep movs dword ptr es:[edi],dword ptr [esi] 79 83e8d1b9 f6456c01 test byte ptr [ebp+6Ch],1 80 83e8d1bd 7416 je nt!KiFastCallEntry+0x115 (83e8d1d5) 81 83e8d1bf 648b0d24010000 mov ecx,dword ptr fs:[124h] 82 83e8d1c6 8b3c24 mov edi,dword ptr [esp] 83 83e8d1c9 89993c010000 mov dword ptr [ecx+13Ch],ebx 84 83e8d1cf 89b92c010000 mov dword ptr [ecx+12Ch],edi 85 83e8d1d5 8bda mov ebx,edx 86 83e8d1d7 f6050869f88340 test byte ptr [nt!PerfGlobalGroupMask+0x8 (83f86908)],40h 87 83e8d1de 0f954512 setne byte ptr [ebp+12h] 88 83e8d1e2 0f858c030000 jne nt!KiServiceExit2+0x17b (83e8d574) 89 83e8d1e8 ffd3 call ebx 90 83e8d1ea f6456c01 test byte ptr [ebp+6Ch],1 91 83e8d1ee 7434 je nt!KiFastCallEntry+0x164 (83e8d224) 92 83e8d1f0 8bf0 mov esi,eax 93 83e8d1f2 ff156801e583 call dword ptr [nt!_imp__KeGetCurrentIrql (83e50168)] 94 83e8d1f8 0ac0 or al,al 95 83e8d1fa 0f853b030000 jne nt!KiServiceExit2+0x142 (83e8d53b) 96 83e8d200 8bc6 mov eax,esi 97 83e8d202 648b0d24010000 mov ecx,dword ptr fs:[124h] 98 83e8d209 f68134010000ff test byte ptr [ecx+134h],0FFh 99 83e8d210 0f8543030000 jne nt!KiServiceExit2+0x160 (83e8d559) 100 83e8d216 8b9184000000 mov edx,dword ptr [ecx+84h] 101 83e8d21c 0bd2 or edx,edx 102 83e8d21e 0f8535030000 jne nt!KiServiceExit2+0x160 (83e8d559) 103 83e8d224 8be5 mov esp,ebp 104 83e8d226 807d1200 cmp byte ptr [ebp+12h],0 105 83e8d22a 0f8550030000 jne nt!KiServiceExit2+0x187 (83e8d580) 106 83e8d230 648b0d24010000 mov ecx,dword ptr fs:[124h] 107 83e8d237 8b553c mov edx,dword ptr [ebp+3Ch] 108 83e8d23a 899128010000 mov dword ptr [ecx+128h],edx