Write in front
this series is written word by word, including examples and experimental screenshots. Due to the complexity of the system kernel, there may be errors or incompleteness. If there are errors, criticism and correction are welcome. This tutorial will be updated for a long time. If you have any good suggestions, welcome feedback. Code words are not easy. If this article is helpful to you, if you have spare money, you can reward and support my creation. If you want to reprint, please attach my reprint information to the back of the article and state my personal information and my blog address, but you must notify me in advance.
If you look from the middle, please read it carefully Yu Xia's view of Win system kernel -- a brief introduction , easy to learn this tutorial.
before reading this tutorial, ask a few questions. Have you prepared the basic knowledge? Have you learned the protection mode? Have you learned the system call? Have you finished your exercise? If not, don't continue.
🔒 Gorgeous dividing line 🔒
handle
handle is a kernel object. When a process creates or opens a kernel object, it will get a handle through which the kernel object can be accessed.
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);
so where is the handle? Why does the operating system use this handle? The purpose of the handle is to avoid directly modifying the kernel object in the application layer, and the handle is an index. Through this index, I can easily find the address of the corresponding kernel object structure in the kernel.
note that I emphasize here that the handle is for the 3-ring, not for the kernel. So when writing drivers, don't do anything fancy. For all Windows API s involving handles, once they reach the real function implementation part, they immediately use ObReferenceObjectByHandle to convert it into a real pointer to the kernel object. For example, due to the length, only the beginning part is listed:
; 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 ; ---------------------------------------------------------------------------
the following is Microsoft's official explanation of 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.
Handle table
the above describes what handles are. Let's introduce the handle table. Without special instructions, the handle table we are talking about is the process handle table. So where is the handle table? Look at the following structure:
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
we have contacted this structure before. Because it is very large, only part of it is shown. In 0xc4, this offset is where the handle table is stored. Let's take a look at its structure:
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 is the so-called handle table, but the handle table has a structure. Let's take a look at its structure:
①: there are two bytes in this block. The high-order byte is used for the SetHandleInformation function. For example, if it is written in the following form, this position will be written to 0x02:
`SetHandleInformation(Handle,HANDLE_FLAG_PROTECT_FROM_CLOSE,HANDLE_FLAG_PROTECT_FROM_CLOSE);`
HANDLE_ FLAG_ PROTECT_ FROM_ The value of the close macro is 0x00000002, the lowest byte is taken, and the final ① is 0x0200.
②: This is the access mask, which is used for the OpenProcess function. The specific stored value is the value of the first parameter of the function.
③ and ④ are four bytes in total, in which bit0-bit2 stores the attributes of the handle, in which bit2 and bit0 are 0 and 1 by default; bit1 indicates whether the handle can be inherited. The second parameter of OpenProcess is related to bit1,
bit31-bit3 is the specific address of the kernel object stored in the kernel.
Handle table structure
the structure of the handle table is still complex. The specific structure is as follows:
TableCode can be seen from the above figure, indicating the level of the handle table. If the lower two bits are 0, the real handle is stored in the handle table pointed to; If the lower two bits are 1, each member of the first handle table pointed to is an address, and each address points to each real handle table, and so on.
This section exercises
The answers in this section will be explained in the next section. Be sure to read the next explanation after completing this section. Don't be lazy. Experiment is a shortcut to learn this tutorial.
as the saying goes, you can only speak without practicing the fake skill. The following are the relevant exercises in this section. If you don't do well in the exercise, don't look at the next tutorial. If you don't do the exercise, it's easy to get mixed up. At first, you still understand, and then you really don't understand at all. There are not many exercises in this section. Please complete it with quality and quantity.
one ️⃣ When I open a kernel object with a function, if I use CloseHandle, will the kernel object be destroyed? Suppose I open an existing process with OpenProcess, but after opening, the process is closed. Does the kernel object still exist?
two ️⃣ Use the loop to open a kernel object 100 times to analyze the structure of the handle table; Then open a kernel object 1000 times and continue to analyze the above operations.
Next
handle table -- global handle table