寫在前面
此係列是本人一個字一個字碼出來的,包括示例和實驗截圖。由於系統核心的複雜性,故可能有錯誤或者不全面的地方,如有錯誤,歡迎批評指正,本教程將會長期更新。 如有好的建議,歡迎反饋。碼字不易,如果本篇文章有幫助你的,如有閒錢,可以打賞支援我的創作。如想轉載,請把我的轉載資訊附在文章後面,並宣告我的個人資訊和本人部落格地址即可,但必須事先通知我。
你如果是從中間插過來看的,請仔細閱讀 羽夏看Win系統核心——簡述 ,方便學習本教程。
看此教程之前,問幾個問題,基礎知識儲備好了嗎?保護模式篇學會了嗎?系統呼叫篇學會了嗎?練習做完了嗎?沒有的話就不要繼續了。
? 華麗的分割線 ?
控制程式碼
控制程式碼是一種核心物件,當一個程式建立或者開啟一個核心物件時,將獲得一個控制程式碼,通過這個控制程式碼可以訪問核心物件。
HANDLE g_hMutex = ::CreateMutex( NULL , FALSE, "XYZ");
HANDLE g_hMutex = ::OpenMutex( MUTEX_ALL_ACCESSFALSE, "XYZ");
HANDLE g_hEvent = ::CreateEvent( NULL, TRUE, FALSE, NULL);
HANDLE g_hThread = ::CreateThread( NULL, 0, Proc,NULL, 0, NULL);
那麼,控制程式碼到底是何方神聖?為什麼作業系統要使用這個控制程式碼?控制程式碼存在的目的是為了避免在應用層直接修改核心物件,而控制程式碼就是一個索引,通過這個索引,我就可以在核心輕鬆找到對應的核心物件結構體的地址。
注意,我在這裡強調一下 控制程式碼是給3環用的,而不是給核心用的 。所以在寫驅動的時候,不要搞控制程式碼花裡胡哨的東西。Windows
所有涉及控制程式碼的API
,一旦到了真正函式實現的部分,就立刻使用ObReferenceObjectByHandle
把它轉化為真正的指向核心物件的指標,舉個例子,由於篇幅,只列出開頭部分:
; int __stdcall PspCreateProcess(int, ACCESS_MASK DesiredAccess, int, HANDLE Handle, int, HANDLE, HANDLE, HANDLE, int)
_PspCreateProcess@36 proc near ; CODE XREF: NtCreateProcessEx(x,x,x,x,x,x,x,x,x)+72↓p
; PsCreateSystemProcess(x,x,x)+1B↓p ...
; __unwind { // __SEH_prolog
push 11Ch
push offset stru_402EB0
call __SEH_prolog
mov eax, large fs:124h
mov [ebp+var_84], eax
mov cl, [eax+140h]
mov [ebp+AccessMode], cl
mov eax, [eax+44h]
mov [ebp+RunRef], eax
xor esi, esi
mov [ebp+var_1D], 0
mov [ebp+var_48], esi
mov [ebp+var_44], esi
test [ebp+arg_10], 0FFFFFFF0h
jnz short loc_4EFB0B
cmp [ebp+Handle], esi
jz short loc_4EFB1A
push esi ; HandleInformation
lea eax, [ebp+Object]
push eax ; Object
push dword ptr [ebp+AccessMode] ; AccessMode
push _PsProcessType ; ObjectType
push 80h ; '€' ; DesiredAccess
push [ebp+Handle] ; Handle
call _ObReferenceObjectByHandle@24 ; ObReferenceObjectByHandle(x,x,x,x,x,x)
mov ecx, [ebp+Object] ; Object
mov [ebp+var_1C], ecx
cmp eax, esi
jl loc_4F01FB
cmp [ebp+arg_20], esi
jz short loc_4EFB15
cmp [ecx+134h], esi
jnz short loc_4EFB15
call @ObfDereferenceObject@4 ; ObfDereferenceObject(x)
loc_4EFB0B: ; CODE XREF: PspCreateProcess(x,x,x,x,x,x,x,x,x)+3D↑j
mov eax, 0C000000Dh
jmp loc_4F01FB
; ---------------------------------------------------------------------------
下面是微軟官方對ObReferenceObjectByHandle
的解釋:
The ObReferenceObjectByHandle routine provides access validation on the object handle, and, if access can be granted, returns the corresponding pointer to the object's body.
控制程式碼表
上面介紹了控制程式碼是什麼,我們來介紹控制程式碼表這個東西。不加特殊說明,我們所說的控制程式碼表是程式控制程式碼表。那麼控制程式碼表在哪裡呢?看下面的結構體:
kd> dt _EPROCESS
ntdll!_EPROCESS
+0x000 Pcb : _KPROCESS
+0x06c ProcessLock : _EX_PUSH_LOCK
+0x070 CreateTime : _LARGE_INTEGER
+0x078 ExitTime : _LARGE_INTEGER
+0x080 RundownProtect : _EX_RUNDOWN_REF
+0x084 UniqueProcessId : Ptr32 Void
+0x088 ActiveProcessLinks : _LIST_ENTRY
+0x090 QuotaUsage : [3] Uint4B
+0x09c QuotaPeak : [3] Uint4B
+0x0a8 CommitCharge : Uint4B
+0x0ac PeakVirtualSize : Uint4B
+0x0b0 VirtualSize : Uint4B
+0x0b4 SessionProcessLinks : _LIST_ENTRY
+0x0bc DebugPort : Ptr32 Void
+0x0c0 ExceptionPort : Ptr32 Void
+0x0c4 ObjectTable : Ptr32 _HANDLE_TABLE
這個結構體我們之前接觸過,由於這個結構體十分龐大,只顯示了一部分。在0xc4
這個偏移就是存放控制程式碼表的。我們來看看它的結構是什麼:
kd> dt _HANDLE_TABLE
ntdll!_HANDLE_TABLE
+0x000 TableCode : Uint4B
+0x004 QuotaProcess : Ptr32 _EPROCESS
+0x008 UniqueProcessId : Ptr32 Void
+0x00c HandleTableLock : [4] _EX_PUSH_LOCK
+0x01c HandleTableList : _LIST_ENTRY
+0x024 HandleContentionEvent : _EX_PUSH_LOCK
+0x028 DebugInfo : Ptr32 _HANDLE_TRACE_DEBUG_INFO
+0x02c ExtraInfoPages : Int4B
+0x030 FirstFree : Uint4B
+0x034 LastFree : Uint4B
+0x038 NextHandleNeedingPool : Uint4B
+0x03c HandleCount : Int4B
+0x040 Flags : Uint4B
+0x040 StrictFIFO : Pos 0, 1 Bit
TableCode
就是所謂的控制程式碼表,但控制程式碼表是有結構的,下面我們來看看它的結構:
①:這一塊共計兩個位元組,高位位元組是給SetHandleInformation
這個函式用的,比如寫成如下形式,那麼這個位置將被寫入0x02
:
`SetHandleInformation(Handle,HANDLE_FLAG_PROTECT_FROM_CLOSE,HANDLE_FLAG_PROTECT_FROM_CLOSE);`
HANDLE_FLAG_PROTECT_FROM_CLOSE
巨集的值為0x00000002
,取最低位元組,最終 ① 這塊是0x0200
。
②:這塊是訪問掩碼,是給OpenProcess
這個函式用的,具體的存的值就是這個函式的第一個引數的值。
③ 和 ④ 這兩個塊共計四個位元組,其中bit0-bit2
存的是這個控制程式碼的屬性,其中bit2
和bit0
預設為0
和1
; bit1
表示的函式是該控制程式碼是否可繼承,OpenProcess
的第二個引數與bit1
有關,
bit31-bit3則是存放的該核心物件在核心中的具體的地址。
上述控制程式碼的結構沒有官方公開化文件,是經過逆向分析得到,如果以後如有本文沒有的,可以根據自己的需要進行逆向分析。
控制程式碼表結構
控制程式碼表的結構還是比較複雜的,具體結構如下:
TableCode
從上圖可知,指明控制程式碼表的級數,如果低兩位是0,那麼指向的控制程式碼表裡面存放的就是真正的控制程式碼;如果低兩位是1,那麼指向的第一張控制程式碼表的每一個成員都是一個地址,每一個地址指向每一個真正的控制程式碼表,以此類推低兩位是2的情況。
控制程式碼表索引查詢
既然我們瞭解了結構,我們來看看如何通過控制程式碼查詢對應的結構體地址,首先我們的測試程式碼如下:
#include "stdafx.h"
#include <stdlib.h>
#include <windows.h>
int main(int argc, char* argv[])
{
int pid;
printf("請輸入程式的pid:");
scanf("%d",&pid);
HANDLE hprocess=OpenProcess(PROCESS_ALL_ACCESS,FALSE,pid);
if (hprocess==INVALID_HANDLE_VALUE)
{
puts("程式控制程式碼無效!!!");
}
else
{
printf("開啟程式獲得控制程式碼成功!控制程式碼為:%d\n",hprocess);
}
system("pause");
CloseHandle(hprocess);
return 0;
}
然後我開啟了一個notepad
程式,從工作管理員找到它的PID
,輸入進去,得到輸出十進位制的2024
。
然後我們輸入指令獲取我們寫程式碼執行的程式的EPROCESS
結構體:
kd> !process 0 0
**** NT ACTIVE PROCESS DUMP ****
Failed to get VadRoot
PROCESS 89b6a7e8 SessionId: 0 Cid: 0110 Peb: 7ffd5000 ParentCid: 05f4
DirBase: 13a40260 ObjectTable: e1cef910 HandleCount: 46.
Image: notepad.exe
Failed to get VadRoot
PROCESS 89b84500 SessionId: 0 Cid: 0640 Peb: 7ffdf000 ParentCid: 06fc
DirBase: 13a40320 ObjectTable: e1d84558 HandleCount: 22.
Image: HandleTest.exe
然後我們讀取HandleTest.exe
,也就是我們呼叫OpenProcess
函式的程式,找到它的控制程式碼表( …… 表示省略):
kd> dt _EPROCESS 89b84500
ntdll!_EPROCESS
+0x000 Pcb : _KPROCESS
+0x06c ProcessLock : _EX_PUSH_LOCK
+0x070 CreateTime : _LARGE_INTEGER 0x01d80d3a`c4dffca0
+0x078 ExitTime : _LARGE_INTEGER 0x0
+0x080 RundownProtect : _EX_RUNDOWN_REF
+0x084 UniqueProcessId : 0x00000640 Void
+0x088 ActiveProcessLinks : _LIST_ENTRY [ 0x899782d8 - 0x89b24470 ]
+0x090 QuotaUsage : [3] 0x460
+0x09c QuotaPeak : [3] 0x650
+0x0a8 CommitCharge : 0x41
+0x0ac PeakVirtualSize : 0x824000
+0x0b0 VirtualSize : 0x7b2000
+0x0b4 SessionProcessLinks : _LIST_ENTRY [ 0x89978304 - 0x89b2449c ]
+0x0bc DebugPort : 0x89b16020 Void
+0x0c0 ExceptionPort : 0xe13a3f08 Void
+0x0c4 ObjectTable : 0xe1d84558 _HANDLE_TABLE
……
kd> dx -id 0,0,805539a0 -r1 ((ntdll!_HANDLE_TABLE *)0xe1d84558)
((ntdll!_HANDLE_TABLE *)0xe1d84558) : 0xe1d84558 [Type: _HANDLE_TABLE *]
[+0x000] TableCode : 0xe1f97000 [Type: unsigned long]
[+0x004] QuotaProcess : 0x89b84500 [Type: _EPROCESS *]
[+0x008] UniqueProcessId : 0x640 [Type: void *]
[+0x00c] HandleTableLock [Type: _EX_PUSH_LOCK [4]]
[+0x01c] HandleTableList [Type: _LIST_ENTRY]
[+0x024] HandleContentionEvent [Type: _EX_PUSH_LOCK]
[+0x028] DebugInfo : 0x0 [Type: _HANDLE_TRACE_DEBUG_INFO *]
[+0x02c] ExtraInfoPages : 0 [Type: long]
[+0x030] FirstFree : 0x7dc [Type: unsigned long]
[+0x034] LastFree : 0x0 [Type: unsigned long]
[+0x038] NextHandleNeedingPool : 0x800 [Type: unsigned long]
[+0x03c] HandleCount : 22 [Type: long]
[+0x040] Flags : 0x0 [Type: unsigned long]
[+0x040 ( 0: 0)] StrictFIFO : 0x0 [Type: unsigned char]
我們看看這裡控制程式碼表存的是什麼:
kd> dq 0xe1f97000
ReadVirtual: e1f97000 not properly sign extended
e1f97000 fffffffe`00000000 00000000`00000000
e1f97010 00000004`00000000 00000008`00000000
e1f97020 0000000c`00000000 00000010`00000000
e1f97030 00000014`00000000 00000018`00000000
e1f97040 0000001c`00000000 00000020`00000000
e1f97050 00000024`00000000 00000028`00000000
e1f97060 0000002c`00000000 00000030`00000000
e1f97070 00000034`00000000 00000038`00000000
如何根據控制程式碼查詢對應的結構體地址呢?首先我們對得到的控制程式碼進行除4就是索引。即我們偏移就是得到的控制程式碼乘2,我們來看看示例:
kd> dq 0xe1f97000+FD0
ReadVirtual: e1f97fd0 not properly sign extended
e1f97fd0 001f0fff`89b6a7d1 021f0001`e1d4a1f9
e1f97fe0 000f000f`e14134b1 000007b8`00000000
e1f97ff0 00000003`e14114e9 000f0003`e1009681
注意,最後兩位是控制程式碼表的屬性,我們需要清零才能得到結構體的地址,也就是0x89b6a7d0
就是結構體的地址,但是,這個結構體指向的不是EPROCESS
,而是_OBJECT_HEADER
結構體,這個是每一個核心物件都有的,被稱為物件頭:
kd> dt _OBJECT_HEADER
nt!_OBJECT_HEADER
+0x000 PointerCount : Int4B
+0x004 HandleCount : Int4B
+0x004 NextToFree : Ptr32 Void
+0x008 Type : Ptr32 _OBJECT_TYPE
+0x00c NameInfoOffset : UChar
+0x00d HandleInfoOffset : UChar
+0x00e QuotaInfoOffset : UChar
+0x00f Flags : UChar
+0x010 ObjectCreateInfo : Ptr32 _OBJECT_CREATE_INFORMATION
+0x010 QuotaBlockCharged : Ptr32 Void
+0x014 SecurityDescriptor : Ptr32 Void
+0x018 Body : _QUAD
也就是說,我們還需要對這個地址+0x18
的偏移,我們來看看:
kd> dt _EPROCESS 89b6a7d0+0x018
ntdll!_EPROCESS
……
+0x170 Session : 0xbadca000 Void
+0x174 ImageFileName : [16] "notepad.exe"
+0x184 JobLinks : _LIST_ENTRY [ 0x0 - 0x0 ]
正好就是我們的notepad
程式。
控制程式碼表不是僅僅只有程式結構體控制程式碼,還有執行緒、檔案等控制程式碼,我們如何判斷呢?就是通過_OBJECT_HEADER
這個結構體判斷的,如下指令所示:
kd> dt _OBJECT_HEADER 89b6a7d0
nt!_OBJECT_HEADER
+0x000 PointerCount : 0n16
+0x004 HandleCount : 0n3
+0x004 NextToFree : 0x00000003 Void
+0x008 Type : 0x89db0040 _OBJECT_TYPE
+0x00c NameInfoOffset : 0 ''
+0x00d HandleInfoOffset : 0 ''
+0x00e QuotaInfoOffset : 0 ''
+0x00f Flags : 0x20 ' '
+0x010 ObjectCreateInfo : 0x89cbdb98 _OBJECT_CREATE_INFORMATION
+0x010 QuotaBlockCharged : 0x89cbdb98 Void
+0x014 SecurityDescriptor : 0xe17b15f6 Void
+0x018 Body : _QUAD
kd> dx -id 0,0,805539a0 -r1 ((ntkrnlpa!_OBJECT_TYPE *)0x89db0040)
((ntkrnlpa!_OBJECT_TYPE *)0x89db0040) : 0x89db0040 [Type: _OBJECT_TYPE *]
[+0x000] Mutex : Unowned Resource [Type: _ERESOURCE]
[+0x038] TypeList [Type: _LIST_ENTRY]
[+0x040] Name : "Process" [Type: _UNICODE_STRING]
[+0x048] DefaultObject : 0x0 [Type: void *]
[+0x04c] Index : 0x5 [Type: unsigned long]
[+0x050] TotalNumberOfObjects : 0x1a [Type: unsigned long]
[+0x054] TotalNumberOfHandles : 0x68 [Type: unsigned long]
[+0x058] HighWaterNumberOfObjects : 0x1b [Type: unsigned long]
[+0x05c] HighWaterNumberOfHandles : 0x69 [Type: unsigned long]
[+0x060] TypeInfo [Type: _OBJECT_TYPE_INITIALIZER]
[+0x0ac] Key : 0x636f7250 [Type: unsigned long]
[+0x0b0] ObjectLocks [Type: _ERESOURCE [4]]
我們通過Type
裡面的Name
屬性,很容易判斷出它是Process
型別,也就是程式結構體。
本節練習
本節的答案將會在下一節進行講解,務必把本節練習做完後看下一個講解內容。不要偷懶,實驗是學習本教程的捷徑。
俗話說得好,光說不練假把式,如下是本節相關的練習。如果練習沒做好,就不要看下一節教程了,越到後面,不做練習的話容易夾生了,開始還明白,後來就真的一點都不明白了。本節練習不多,請保質保量的完成。
1️⃣ 思考當我用函式開啟一個核心物件時,如果用CloseHandle
,這個核心物件一定會被銷燬嗎?假設我用OpenProcess
開啟了一個現有的程式,但開啟後,這個程式被關閉了,這個核心物件還存在嗎?
2️⃣ 使用迴圈開啟100次某個核心物件,分析控制程式碼表的結構;然後開啟1000次某個核心物件,繼續分析上述操作。
下一篇
控制程式碼表篇——全域性控制程式碼表