句柄表篇——总结与提升
写在前面
此系列是本人一个字一个字码出来的,包括示例和实验截图。由于系统内核的复杂性,故可能有错误或者不全面的地方,如有错误,欢迎批评指正,本教程将会长期更新。 如有好的建议,欢迎反馈。码字不易,如果本篇文章有帮助你的,如有闲钱,可以打赏支持我的创作。如想转载,请把我的转载信息附在文章后面,并声明我的个人信息和本人博客地址即可,但必须事先通知我。
你如果是从中间插过来看的,请仔细阅读 羽夏看Win系统内核——简述 ,方便学习本教程。
看此教程之前,问几个问题,基础知识储备好了吗?保护模式篇学会了吗?练习做完了吗?没有的话就不要继续了。
🔒 华丽的分割线 🔒
小节
本篇的知识挺简单的,就介绍了两个表:进程句柄表和全局句柄表。进程句柄表归属进程私有,每一个进程一个句柄表。上一篇的课后思考题答案先不急的给你,下面我们来扩展一下句柄表的相关知识,你在来看看你写的代码。
句柄表结构体扩展
如下是WRK
里面的句柄表结构,重点是看注释,介绍扩展一下我讲解基础没讲到知识点:
typedef struct _HANDLE_TABLE {
//
// A pointer to the top level handle table tree node.
//
ULONG_PTR TableCode;
//
// The process who is being charged quota for this handle table and a
// unique process id to use in our callbacks
//
struct _EPROCESS *QuotaProcess;
HANDLE UniqueProcessId;
//
// These locks are used for table expansion and preventing the A-B-A problem
// on handle allocate.
//
#define HANDLE_TABLE_LOCKS 4
EX_PUSH_LOCK HandleTableLock[HANDLE_TABLE_LOCKS];
//
// The list of global handle tables. This field is protected by a global
// lock.
//
LIST_ENTRY HandleTableList;
//
// Define a field to block on if a handle is found locked.
//
EX_PUSH_LOCK HandleContentionEvent;
//
// Debug info. Only allocated if we are debugging handles
//
PHANDLE_TRACE_DEBUG_INFO DebugInfo;
//
// The number of pages for additional info.
// This counter is used to improve the performance
// in ExGetHandleInfo
//
LONG ExtraInfoPages;
//
// This is a singly linked list of free table entries. We don't actually
// use pointers, but have each store the index of the next free entry
// in the list. The list is managed as a lifo list. We also keep track
// of the next index that we have to allocate pool to hold.
//
ULONG FirstFree;
//
// We free handles to this list when handle debugging is on or if we see
// that a thread has this handles bucket lock held. The allows us to delay reuse
// of handles to get a better chance of catching offenders
//
ULONG LastFree;
//
// This is the next handle index needing a pool allocation. Its also used as a bound
// for good handles.
//
ULONG NextHandleNeedingPool;
//
// The number of handle table entries in use.
//
LONG HandleCount;
//
// Define a flags field
//
union {
ULONG Flags;
//
// For optimization we reuse handle values quickly. This can be a problem for
// some usages of handles and makes debugging a little harder. If this
// bit is set then we always use FIFO handle allocation.
//
BOOLEAN StrictFIFO : 1;
};
} HANDLE_TABLE, *PHANDLE_TABLE;
然后我再把WinDbg
获取的结构体拿出来:
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
可以说是一模一样的,编制操作系统时为了保持兼容性,结构体的每一个成员的含义是一样的。
有没有注意到HandleCount
这个成员,这个是我们做思考题的一个坑,就是我知道遍历有效句柄的次数,就得需要这个值,其实也可以不用,因为句柄是按物理页进行的。当然,如何获取一个进程的句柄数可以通过ObGetProcessHandleCount
这个函数。这个函数是未导出的,如下是我整理好的伪代码:
int __stdcall ObGetProcessHandleCount(PEPROCESS Process)
{
_HANDLE_TABLE *objtable; // eax
int handleCount; // esi
objtable = ObReferenceProcessHandleTable(Process);
if ( !objtable )
return 0;
handleCount = objtable->HandleCount;
ObDereferenceProcessHandleTable(Process);
return handleCount;
}
_HANDLE_TABLE *__stdcall ObReferenceProcessHandleTable(_EPROCESS *Process)
{
_HANDLE_TABLE *objtable; // edi
objtable = 0;
if ( ExAcquireRundownProtection(&Process->RundownProtect) )
{
objtable = Process->ObjectTable;
if ( !objtable )
ExReleaseRundownProtection(&Process->RundownProtect);
}
return objtable;
}
void __stdcall ObDereferenceProcessHandleTable(_EPROCESS *eprocess)
{
ExReleaseRundownProtection(&eprocess->RundownProtect);
}
ObReferenceObjectByHandle 浅析
我们来看看操作系统是如何使用句柄的,定位到我们熟悉的函数ObReferenceObjectByHandle
:
NTSTATUS __stdcall ObReferenceObjectByHandle(HANDLE Handle, ACCESS_MASK DesiredAccess, POBJECT_TYPE ObjectType, KPROCESSOR_MODE AccessMode, PVOID *Object, POBJECT_HANDLE_INFORMATION HandleInformation)
{
// [COLLAPSED LOCAL DECLARATIONS. PRESS KEYPAD CTRL-"+" TO EXPAND]
currentThread = KeGetCurrentThread();
attachedProcess = currentThread;
*Object = 0;
if ( Handle >= 0 )
{
objHandleTable = currentThread->Tcb.ApcState.Process->ObjectTable;
}
else
{
if ( Handle == -1 )
{
if ( ObjectType == PsProcessType || !ObjectType )
{
attachedProcess = currentThread->Tcb.ApcState.Process;
if ( (~attachedProcess->GrantedAccess & DesiredAccess) == 0 || !AccessMode )
{
if ( HandleInformation )
{
HandleInformation->GrantedAccess = attachedProcess->GrantedAccess;
HandleInformation->HandleAttributes = 0;
}
if ( ObpTraceEnabled )
ObpPushStackInfo(&attachedProcess[-1u].584, 1);
++attachedProcess[-1u].Flags;
LABEL_12:
*Object = attachedProcess;
return 0;
}
return STATUS_ACCESS_DENIED;
}
return STATUS_OBJECT_TYPE_MISMATCH;
}
if ( Handle == -2 )
{
if ( ObjectType == PsThreadType || !ObjectType )
{
if ( (~currentThread->GrantedAccess & DesiredAccess) == 0 || !AccessMode )
{
if ( HandleInformation )
{
HandleInformation->GrantedAccess = currentThread->GrantedAccess;
HandleInformation->HandleAttributes = 0;
}
if ( ObpTraceEnabled )
ObpPushStackInfo(¤tThread[-1].ReadClusterSize, 1);
++attachedProcess[-1].Flags;
goto LABEL_12;
}
return STATUS_ACCESS_DENIED;
}
return STATUS_OBJECT_TYPE_MISMATCH;
}
if ( AccessMode )
return STATUS_INVALID_HANDLE;
Handle = (Handle ^ 0x80000000);
objHandleTable = ::ObpKernelHandleTable;
}
--attachedProcess->WorkingSetLock.Contention;
ObpKernelHandleTable = objHandleTable;
handleEntry = ExMapHandleToPointerEx(objHandleTable, Handle, AccessMode);
if ( handleEntry )
{
v12 = (handleEntry->InfoTable & 0xFFFFFFF8);
BackTraceHash = v12;
if ( v12[1].Object == ObjectType || !ObjectType )
{
if ( (NtGlobalFlag & 0x2000) != 0 )
{
if ( AccessMode || HandleInformation )
{
v14 = ObpTranslateGrantedAccessIndex(handleEntry->GrantedAccessIndex);
v12 = BackTraceHash;
v15 = v14;
}
else
{
v15 = Handle;
}
}
else
{
v15 = ~ObpAccessProtectCloseBit & handleEntry->GrantedAccess;
}
if ( (~v15 & DesiredAccess) == 0 || !AccessMode )
{
if ( *(ObpKernelHandleTable + 44) )
{
v16 = ExpGetHandleInfo(ObpKernelHandleTable, Handle, 1);
v12 = BackTraceHash;
ObjectTypea = v16;
}
else
{
ObjectTypea = 0;
}
if ( HandleInformation )
{
HandleInformation->GrantedAccess = v15;
if ( (ObpAccessProtectCloseBit & handleEntry->GrantedAccess) != 0 )
v17 = handleEntry->ObAttributes & 6 | 1;
else
v17 = handleEntry->ObAttributes & 6;
HandleInformation->HandleAttributes = v17;
}
if ( (handleEntry->Object & 4) != 0
&& ObjectTypea
&& ObjectTypea->Mutex.SystemResourcesList.Flink
&& DesiredAccess
&& AccessMode )
{
ObpAuditObjectAccess(Handle, ObjectTypea, v12[1].ObAttributes + 64, DesiredAccess);
v12 = BackTraceHash;
}
if ( ObpTraceEnabled )
ObpPushStackInfo(v12, 1);
++BackTraceHash->ObAttributes;
ExUnlockHandleTableEntry(ObpKernelHandleTable, handleEntry);
v19 = attachedProcess->WorkingSetLock.Contention++ == 4294967295;
if ( v19 && attachedProcess->Pcb.ActiveProcessors != &attachedProcess->Pcb.ActiveProcessors )
{
LOBYTE(v18) = 1;
BYTE1(attachedProcess->Pcb.SwapListEntry.Next) = 1;
HalRequestSoftwareInterrupt(v18);
}
*Object = &BackTraceHash[3];
return 0;
}
v13 = STATUS_ACCESS_DENIED;
}
else
{
v13 = STATUS_OBJECT_TYPE_MISMATCH;
}
ExUnlockHandleTableEntry(ObpKernelHandleTable, handleEntry);
}
else
{
v13 = STATUS_INVALID_HANDLE;
}
v19 = attachedProcess->WorkingSetLock.Contention++ == 4294967295;
if ( v19 && attachedProcess->Pcb.ActiveProcessors != &attachedProcess->Pcb.ActiveProcessors )
{
LOBYTE(v10) = 1;
BYTE1(attachedProcess->Pcb.SwapListEntry.Next) = 1;
HalRequestSoftwareInterrupt(v10);
}
return v13;
}
当然上面的翻译的伪代码也是不准确的,有些函数我不熟悉,逆向不太明白,如下是我逆向的结果,由于汇编太长了,请点击折叠观看:
🔒 点击查看代码 🔒
; NTSTATUS __stdcall ObReferenceObjectByHandle(HANDLE Handle, ACCESS_MASK DesiredAccess, POBJECT_TYPE ObjectType, KPROCESSOR_MODE AccessMode, PVOID *Object, POBJECT_HANDLE_INFORMATION HandleInformation)
public _ObReferenceObjectByHandle@24
_ObReferenceObjectByHandle@24 proc near ; CODE XREF: IopUnloadDriver(x,x)+1B8↑p
; MmCreateSection(x,x,x,x,x,x,x,x)+221↑p ...
ObpKernelHandleTable= dword ptr -8
OBJHeader = dword ptr -4
Handle = dword ptr 8
DesiredAccess = dword ptr 0Ch
ObjectType = dword ptr 10h
AccessMode = byte ptr 14h
Object = dword ptr 18h
HandleInformation= dword ptr 1Ch
mov edi, edi
push ebp
mov ebp, esp
push ecx
push ecx
push ebx
push esi
push edi
mov eax, large fs:_KPCR.PrcbData.CurrentThread
mov edi, [ebp+Object]
xor ebx, ebx ; EBX = 0
cmp [ebp+Handle], ebx ; CMP Handle , 0
mov esi, eax ; ESI = CurrentThread
mov [edi], ebx
jge NotFakeHandle ; >=0 JMP
cmp [ebp+Handle], -1
jnz short CheckIsThreadFake ; != -1 JMP
mov eax, [ebp+ObjectType]
cmp eax, _PsProcessType ; 判断是否为 Process 类型
jz short CurrentProcessHandle ; 是的话跳走
cmp eax, ebx
jnz short loc_4D9B4F
CurrentProcessHandle: ; CODE XREF: ObReferenceObjectByHandle(x,x,x,x,x,x)+31↑j
mov esi, [esi+_KTHREAD.ApcState.Process]
mov ecx, [esi+_EPROCESS.GrantedAccess]
mov eax, ecx ; 至此 esi = AttachedProcess , eax = ecx = GrantedAccess
not eax ; 按位取反 Access
test [ebp+DesiredAccess], eax ; 如果是0的话,说明一致
jz short DesiredIsGranted ; 一致的话跳走
cmp [ebp+AccessMode], KernelMode
jnz short IsUserMode
DesiredIsGranted: ; CODE XREF: ObReferenceObjectByHandle(x,x,x,x,x,x)+47↑j
mov eax, [ebp+HandleInformation]
cmp eax, ebx ; CMP HandleInformation , 0
lea edx, [esi-18h] ; _OBJECT_HEADER
mov [ebp+OBJHeader], edx
jz short HandleInformationIsNULL
mov [eax+_OBJECT_HANDLE_INFORMATION.GrantedAccess], ecx
mov [eax+_OBJECT_HANDLE_INFORMATION.HandleAttributes], ebx
HandleInformationIsNULL: ; CODE XREF: ObReferenceObjectByHandle(x,x,x,x,x,x)+5A↑j
cmp _ObpTraceEnabled, 0
jz short ObpTraceEnabledIsFalse
push 1 ; char
push edx ; BackTraceHash
call _ObpPushStackInfo@8 ; ObpPushStackInfo(x,x)
ObpTraceEnabledIsFalse: ; CODE XREF: ObReferenceObjectByHandle(x,x,x,x,x,x)+68↑j
mov eax, 1
mov ecx, [ebp+OBJHeader]
xadd [ecx+_OBJECT_HEADER.PointerCount], eax ; 引用计数加1
loc_4D9B33: ; CODE XREF: ObReferenceObjectByHandle(x,x,x,x,x,x)+F0↓j
mov [edi], esi
jmp loc_4D9D25
; ---------------------------------------------------------------------------
CheckIsThreadFake: ; CODE XREF: ObReferenceObjectByHandle(x,x,x,x,x,x)+26↑j
cmp [ebp+Handle], -2
jnz short NotCurrentThread ; Handle != -2 跳走
mov eax, [ebp+ObjectType]
cmp eax, _PsThreadType
jz short IsThreadType
cmp eax, ebx ; cmp eax , 0
jz short IsThreadType
loc_4D9B4F: ; CODE XREF: ObReferenceObjectByHandle(x,x,x,x,x,x)+35↑j
mov eax, STATUS_OBJECT_TYPE_MISMATCH
jmp ProcEnding
; ---------------------------------------------------------------------------
IsThreadType: ; CODE XREF: ObReferenceObjectByHandle(x,x,x,x,x,x)+93↑j
; ObReferenceObjectByHandle(x,x,x,x,x,x)+97↑j
mov ecx, [esi+_ETHREAD.GrantedAccess]
mov eax, ecx
not eax
test [ebp+DesiredAccess], eax
jz short DesiredIsGranted_0
cmp [ebp+AccessMode], KernelMode
jz short DesiredIsGranted_0
IsUserMode: ; CODE XREF: ObReferenceObjectByHandle(x,x,x,x,x,x)+4D↑j
mov eax, STATUS_ACCESS_DENIED
jmp ProcEnding
; ---------------------------------------------------------------------------
DesiredIsGranted_0: ; CODE XREF: ObReferenceObjectByHandle(x,x,x,x,x,x)+B0↑j
; ObReferenceObjectByHandle(x,x,x,x,x,x)+B6↑j
mov eax, [ebp+HandleInformation]
cmp eax, ebx ; CMP HandleInformation , 0
lea edx, [esi-18h] ; _OBJECT_HEADER
mov [ebp+OBJHeader], edx
jz short loc_4D9B8A
mov [eax+_OBJECT_HANDLE_INFORMATION.GrantedAccess], ecx
mov [eax+_OBJECT_HANDLE_INFORMATION.HandleAttributes], ebx
loc_4D9B8A: ; CODE XREF: ObReferenceObjectByHandle(x,x,x,x,x,x)+CD↑j
cmp _ObpTraceEnabled, 0
jz short ObpTraceEnabledIsFalse_0
push 1 ; char
push edx ; BackTraceHash
call _ObpPushStackInfo@8 ; ObpPushStackInfo(x,x)
ObpTraceEnabledIsFalse_0: ; CODE XREF: ObReferenceObjectByHandle(x,x,x,x,x,x)+DB↑j
mov eax, 1
mov ecx, [ebp+OBJHeader]
xadd [ecx+_OBJECT_HEADER.PointerCount], eax ; 引用计数加1
jmp short loc_4D9B33
; ---------------------------------------------------------------------------
NotCurrentThread: ; CODE XREF: ObReferenceObjectByHandle(x,x,x,x,x,x)+88↑j
cmp [ebp+AccessMode], KernelMode
jnz short IsUserMode_1
xor [ebp+Handle], 80000000h
mov eax, _ObpKernelHandleTable
jmp short loc_4D9BCF
; ---------------------------------------------------------------------------
IsUserMode_1: ; CODE XREF: ObReferenceObjectByHandle(x,x,x,x,x,x)+F6↑j
mov eax, STATUS_INVALID_HANDLE
jmp ProcEnding
; ---------------------------------------------------------------------------
NotFakeHandle: ; CODE XREF: ObReferenceObjectByHandle(x,x,x,x,x,x)+1C↑j
mov eax, [esi+_KTHREAD.ApcState.Process]
mov eax, [eax+_EPROCESS.ObjectTable]
loc_4D9BCF: ; CODE XREF: ObReferenceObjectByHandle(x,x,x,x,x,x)+104↑j
push dword ptr [ebp+AccessMode] ; char
dec [esi+_ETHREAD.Tcb.KernelApcDisable]
push [ebp+Handle] ; BugCheckParameter1
mov [ebp+ObpKernelHandleTable], eax
push eax ; BugCheckParameter2
call _ExMapHandleToPointerEx@12 ; ExMapHandleToPointerEx(x,x,x)
mov edi, eax
cmp edi, ebx
jz InvalidHandleProc
mov edx, [edi+_HANDLE_TABLE_ENTRY.___u0.InfoTable]
mov eax, [ebp+ObjectType]
and edx, 0FFFFFFF8h
cmp [edx+8], eax
mov [ebp+OBJHeader], edx
jz short loc_4D9C09
cmp eax, ebx
jz short loc_4D9C09
mov ebx, STATUS_OBJECT_TYPE_MISMATCH
jmp short ErrorProc
; ---------------------------------------------------------------------------
loc_4D9C09: ; CODE XREF: ObReferenceObjectByHandle(x,x,x,x,x,x)+146↑j
; ObReferenceObjectByHandle(x,x,x,x,x,x)+14A↑j
test byte ptr _NtGlobalFlag+1, 20h
jz short loc_4D9C30
cmp [ebp+AccessMode], KernelMode
jnz short IsUserMode_0
cmp [ebp+HandleInformation], ebx
jz short loc_4D9C3E
IsUserMode_0: ; CODE XREF: ObReferenceObjectByHandle(x,x,x,x,x,x)+160↑j
xor eax, eax
mov ax, [edi+4]
push eax
call _ObpTranslateGrantedAccessIndex@4 ; ObpTranslateGrantedAccessIndex(x)
mov edx, [ebp+OBJHeader]
mov ebx, eax
jmp short loc_4D9C41
; ---------------------------------------------------------------------------
loc_4D9C30: ; CODE XREF: ObReferenceObjectByHandle(x,x,x,x,x,x)+15A↑j
mov eax, _ObpAccessProtectCloseBit
mov ebx, [edi+4]
not eax
and ebx, eax
jmp short loc_4D9C41
; ---------------------------------------------------------------------------
loc_4D9C3E: ; CODE XREF: ObReferenceObjectByHandle(x,x,x,x,x,x)+165↑j
mov ebx, [ebp+Handle]
loc_4D9C41: ; CODE XREF: ObReferenceObjectByHandle(x,x,x,x,x,x)+178↑j
; ObReferenceObjectByHandle(x,x,x,x,x,x)+186↑j
mov eax, ebx
not eax
test [ebp+DesiredAccess], eax
jz short loc_4D9C63
cmp [ebp+AccessMode], 0
jz short loc_4D9C63
mov ebx, STATUS_ACCESS_DENIED
ErrorProc: ; CODE XREF: ObReferenceObjectByHandle(x,x,x,x,x,x)+151↑j
push edi ; BugCheckParameter2
push [ebp+ObpKernelHandleTable] ; int
call _ExUnlockHandleTableEntry@8 ; ExUnlockHandleTableEntry(x,x)
jmp loc_4D9D2E
; ---------------------------------------------------------------------------
loc_4D9C63: ; CODE XREF: ObReferenceObjectByHandle(x,x,x,x,x,x)+192↑j
; ObReferenceObjectByHandle(x,x,x,x,x,x)+198↑j
mov eax, [ebp+ObpKernelHandleTable]
cmp dword ptr [eax+2Ch], 0
jz short loc_4D9C7F
push 1
push [ebp+Handle]
push eax
call _ExpGetHandleInfo@12 ; ExpGetHandleInfo(x,x,x)
mov edx, [ebp+OBJHeader]
mov [ebp+ObjectType], eax
jmp short loc_4D9C83
; ---------------------------------------------------------------------------
loc_4D9C7F: ; CODE XREF: ObReferenceObjectByHandle(x,x,x,x,x,x)+1B4↑j
and [ebp+ObjectType], 0
loc_4D9C83: ; CODE XREF: ObReferenceObjectByHandle(x,x,x,x,x,x)+1C7↑j
mov eax, [ebp+HandleInformation]
test eax, eax
jz short loc_4D9CA7
mov [eax+4], ebx
mov ecx, _ObpAccessProtectCloseBit
test [edi+4], ecx
mov ecx, [edi]
jz short loc_4D9CA2
and ecx, 6
or ecx, 1
jmp short loc_4D9CA5
; ---------------------------------------------------------------------------
loc_4D9CA2: ; CODE XREF: ObReferenceObjectByHandle(x,x,x,x,x,x)+1E2↑j
and ecx, 6
loc_4D9CA5: ; CODE XREF: ObReferenceObjectByHandle(x,x,x,x,x,x)+1EA↑j
mov [eax], ecx
loc_4D9CA7: ; CODE XREF: ObReferenceObjectByHandle(x,x,x,x,x,x)+1D2↑j
test byte ptr [edi], 4
jz short loc_4D9CDA
mov eax, [ebp+ObjectType]
test eax, eax
jz short loc_4D9CDA
cmp dword ptr [eax], 0
jz short loc_4D9CDA
cmp [ebp+DesiredAccess], 0
jz short loc_4D9CDA
cmp [ebp+AccessMode], 0
jz short loc_4D9CDA
push [ebp+DesiredAccess]
mov ecx, [edx+8]
add ecx, 40h ; '@'
push ecx
push eax
push [ebp+Handle]
call _ObpAuditObjectAccess@16 ; ObpAuditObjectAccess(x,x,x,x)
mov edx, [ebp+OBJHeader]
loc_4D9CDA: ; CODE XREF: ObReferenceObjectByHandle(x,x,x,x,x,x)+1F4↑j
; ObReferenceObjectByHandle(x,x,x,x,x,x)+1FB↑j ...
cmp _ObpTraceEnabled, 0
jz short loc_4D9CEB
push 1 ; char
push edx ; BackTraceHash
call _ObpPushStackInfo@8 ; ObpPushStackInfo(x,x)
loc_4D9CEB: ; CODE XREF: ObReferenceObjectByHandle(x,x,x,x,x,x)+22B↑j
mov eax, 1
mov ecx, [ebp+OBJHeader]
xadd [ecx+_OBJECT_HANDLE_INFORMATION.HandleAttributes], eax
push edi ; BugCheckParameter2
push [ebp+ObpKernelHandleTable] ; int
call _ExUnlockHandleTableEntry@8 ; ExUnlockHandleTableEntry(x,x)
inc [esi+_ETHREAD.Tcb.KernelApcDisable]
jnz short MovObject
lea eax, [esi+_ETHREAD.Tcb.ApcState]
cmp [eax+_KAPC_STATE.ApcListHead.Flink], eax
jz short MovObject
mov cl, 1
mov byte ptr [esi+49h], 1
call ds:__imp_@HalRequestSoftwareInterrupt@4 ; HalRequestSoftwareInterrupt(x)
MovObject: ; CODE XREF: ObReferenceObjectByHandle(x,x,x,x,x,x)+24F↑j
; ObReferenceObjectByHandle(x,x,x,x,x,x)+256↑j
mov eax, [ebp+OBJHeader]
mov ecx, [ebp+Object]
add eax, 18h
mov [ecx], eax
loc_4D9D25: ; CODE XREF: ObReferenceObjectByHandle(x,x,x,x,x,x)+7F↑j
xor eax, eax
jmp short ProcEnding
; ---------------------------------------------------------------------------
InvalidHandleProc: ; CODE XREF: ObReferenceObjectByHandle(x,x,x,x,x,x)+132↑j
mov ebx, STATUS_INVALID_HANDLE
loc_4D9D2E: ; CODE XREF: ObReferenceObjectByHandle(x,x,x,x,x,x)+1A8↑j
inc [esi+_ETHREAD.Tcb.KernelApcDisable]
jnz short loc_4D9D49
lea eax, [esi+_ETHREAD.Tcb.ApcState]
cmp [eax+_KAPC_STATE.ApcListHead.Flink], eax
jz short loc_4D9D49
mov cl, 1
mov [esi+_ETHREAD.Tcb.ApcState.KernelApcPending], 1
call ds:__imp_@HalRequestSoftwareInterrupt@4 ; HalRequestSoftwareInterrupt(x)
loc_4D9D49: ; CODE XREF: ObReferenceObjectByHandle(x,x,x,x,x,x)+27E↑j
; ObReferenceObjectByHandle(x,x,x,x,x,x)+285↑j
mov eax, ebx
ProcEnding: ; CODE XREF: ObReferenceObjectByHandle(x,x,x,x,x,x)+9E↑j
; ObReferenceObjectByHandle(x,x,x,x,x,x)+BD↑j ...
pop edi
pop esi
pop ebx
leave
retn 18h
_ObReferenceObjectByHandle@24 endp
为了方便讲解,我就用伪代码,具体细节请参考我的汇编注释。我们把注意力放到如下代码上:
if ( Handle == -1 )
{
if ( ObjectType == PsProcessType || !ObjectType )
{
attachedProcess = currentThread->Tcb.ApcState.Process;
if ( (~attachedProcess->GrantedAccess & DesiredAccess) == 0 || !AccessMode )
{
if ( HandleInformation )
{
HandleInformation->GrantedAccess = attachedProcess->GrantedAccess;
HandleInformation->HandleAttributes = 0;
}
if ( ObpTraceEnabled )
ObpPushStackInfo(&attachedProcess[-1u].584, 1);
++attachedProcess[-1u].Flags;
LABEL_12:
*Object = attachedProcess;
return 0;
}
return STATUS_ACCESS_DENIED;
}
return STATUS_OBJECT_TYPE_MISMATCH;
}
if ( Handle == -2 )
{
if ( ObjectType == PsThreadType || !ObjectType )
{
if ( (~currentThread->GrantedAccess & DesiredAccess) == 0 || !AccessMode )
{
if ( HandleInformation )
{
HandleInformation->GrantedAccess = currentThread->GrantedAccess;
HandleInformation->HandleAttributes = 0;
}
if ( ObpTraceEnabled )
ObpPushStackInfo(¤tThread[-1].ReadClusterSize, 1);
++attachedProcess[-1].Flags;
goto LABEL_12;
}
return STATUS_ACCESS_DENIED;
}
return STATUS_OBJECT_TYPE_MISMATCH;
}
看到函数会提前判断句柄的值是否为负数,再判断是否是-1
还是-2
。通过伪代码可知,如果是-1
就是获取了当前进程的结构体,如果是-2
就会找当前线程的结构体,这个值被称之为伪句柄。
我敢说只要你在三环与进程线程相关打交道的时候,肯定用到过伪句柄。用没用过GetCurrentProcess
和GetCurrentThread
这俩函数?
; HANDLE __stdcall GetCurrentProcess()
public _GetCurrentProcess@0
_GetCurrentProcess@0 proc near ; CODE XREF: UnhandledExceptionFilter(x)+81↓p
; UnhandledExceptionFilter(x)+69E↓p ...
or eax, 0FFFFFFFFh
retn
_GetCurrentProcess@0 endp
; HANDLE __stdcall GetCurrentThread()
public _GetCurrentThread@0
_GetCurrentThread@0 proc near ; DATA XREF: .text:off_7C802654↑o
push 0FFFFFFFEh
pop eax
retn
_GetCurrentThread@0 endp
上面的函数是不是挺弱智的,为啥我必须调用这样的函数获取伪句柄呢,直接一个宏定义不就行了?为什么不能用CloseHandle
关闭伪句柄是不是明白了?
好,下面我继续接着扩展。当我们的句柄为负值时,操作系统就会使用ObpKernelHandleTable
这个表,这个是通过逆向得到的结果。我们使用WinDbg
看看里面有什么东西:
kd> dd ObpKernelHandleTable
8055a7d8 e1001cc8 00000000 00000000 00000000
8055a7e8 00000000 00000000 00000000 00000000
8055a7f8 00000000 00000000 00000000 00000000
8055a808 00000000 00000000 00000000 00000000
8055a818 00000000 00000000 00000000 00000000
8055a828 00000000 00000000 00000000 00000000
8055a838 00000000 00000000 00000000 00000000
8055a848 00000000 00000000 00000000 00000000
kd> dt _HANDLE_TABLE e1001cc8
ntdll!_HANDLE_TABLE
+0x000 TableCode : 0xe1002000
+0x004 QuotaProcess : (null)
+0x008 UniqueProcessId : 0x00000004 Void
+0x00c HandleTableLock : [4] _EX_PUSH_LOCK
+0x01c HandleTableList : _LIST_ENTRY [ 0xe100f384 - 0x8055c448 ]
+0x024 HandleContentionEvent : _EX_PUSH_LOCK
+0x028 DebugInfo : (null)
+0x02c ExtraInfoPages : 0n0
+0x030 FirstFree : 0x64c
+0x034 LastFree : 0
+0x038 NextHandleNeedingPool : 0x800
+0x03c HandleCount : 0n221
+0x040 Flags : 0
+0x040 StrictFIFO : 0y0
kd> dq 0xe1002000
ReadVirtual: e1002000 not properly sign extended
e1002000 fffffffe`00000000 001f0fff`89fb09e9
e1002010 00000000`89fb0329 000f003f`e13c9119
e1002020 00000000`e1011449 00020019`e13d6731
e1002030 00020019`e13d2791 00020019`e101f421
e1002040 0002001f`e13de479 00020019`e13c3079
e1002050 00020019`e13d1419 0002001f`e13bee51
e1002060 00020019`e13d84d1 001f0003`89fa7a11
e1002070 00000040`00000000 000000a8`00000000
关于该函数的扩展,就这些。
句柄表扩展
为了方便大家进行学习句柄表,我们给出一个小结论,我在WinDbg
找到了一个进程的句柄表,如下所示:
kd> dq 0xe10ec000
e10ec000 fffffffe`00000000 00000000`00000000
e10ec010 00000004`00000000 00000008`00000000
e10ec020 0000000c`00000000 00000010`00000000
e10ec030 00000014`00000000 00000018`00000000
e10ec040 0000001c`00000000 00000020`00000000
e10ec050 00000024`00000000 00000028`00000000
e10ec060 0000002c`00000000 00000030`00000000
e10ec070 00000034`00000000 00000038`00000000
每一个QWORD
,发现了什么规律了吗?每一个项目展示的前半部分是索引,后面就是我们对应的结构体地址,每一个索引都是4的倍数。
其实每一个句柄都是一个结构体,名为_HANDLE_TABLE_ENTRY
,我们可以看一下它的结构:
kd> dt _HANDLE_TABLE_ENTRY
nt!_HANDLE_TABLE_ENTRY
+0x000 Object : Ptr32 Void
+0x000 ObAttributes : Uint4B
+0x000 InfoTable : Ptr32 _HANDLE_TABLE_ENTRY_INFO
+0x000 Value : Uint4B
+0x004 GrantedAccess : Uint4B
+0x004 GrantedAccessIndex : Uint2B
+0x006 CreatorBackTraceIndex : Uint2B
+0x004 NextFreeTableEntry : Int4B
可以看出,这是个用共用体复杂嵌套出的结构体,我们可以看看WRK
的定义:
typedef struct _HANDLE_TABLE_ENTRY {
//
// The pointer to the object overloaded with three ob attributes bits in
// the lower order and the high bit to denote locked or unlocked entries
//
union {
PVOID Object;
ULONG ObAttributes;
PHANDLE_TABLE_ENTRY_INFO InfoTable;
ULONG_PTR Value;
};
//
// This field either contains the granted access mask for the handle or an
// ob variation that also stores the same information. Or in the case of
// a free entry the field stores the index for the next free entry in the
// free list. This is like a FAT chain, and is used instead of pointers
// to make table duplication easier, because the entries can just be
// copied without needing to modify pointers.
//
union {
union {
ACCESS_MASK GrantedAccess;
struct {
USHORT GrantedAccessIndex;
USHORT CreatorBackTraceIndex;
};
};
LONG NextFreeTableEntry;
};
} HANDLE_TABLE_ENTRY, *PHANDLE_TABLE_ENTRY;
目前掌握的细节已经在进程句柄表里解释了,也就扩展这些。如想更好的掌握这些细节,推荐潘爱民的《Windows内核原理与实现》,注意这本书基于WRK
,仅供参考。
PsLookupProcessByProcessId 浅析
对于操作系统,它是如何实现通过句柄查找对应的结构体地址呢?我们可以通过以逆向PsLookupProcessByProcessId
为例子进行:
NTSTATUS __stdcall PsLookupProcessByProcessId(HANDLE ProcessId, PEPROCESS *Process)
{
_KTHREAD *v2; // esi
struct _EPROCESS **v3; // eax
int v4; // ecx
ULONG_PTR v5; // ebx
struct _EPROCESS *v6; // edi
bool v7; // zf
HANDLE ProcessIda; // [esp+10h] [ebp+8h]
v2 = KeGetCurrentThread();
--v2->KernelApcDisable;
v3 = ExMapHandleToPointer(PspCidTable, ProcessId);
v5 = v3;
ProcessIda = STATUS_INVALID_PARAMETER;
if ( v3 )
{
v6 = *v3;
if ( (*v3)->Pcb.Header.Type == 3 && v6->GrantedAccess && ObReferenceObjectSafe(*v3) )
{
ProcessIda = 0;
*Process = &v6->Pcb;
}
ExUnlockHandleTableEntry(PspCidTable, v5);
}
v7 = v2->KernelApcDisable++ == -1;
if ( v7 && v2->ApcState.ApcListHead[0].Flink != &v2->ApcState )
{
LOBYTE(v4) = 1;
v2->ApcState.KernelApcPending = 1;
HalRequestSoftwareInterrupt(v4);
}
return ProcessIda;
}
我们可以看出这个函数功能主要是由ExMapHandleToPointer
实现的,点击去看看:
_EPROCESS *__stdcall ExMapHandleToPointer(int pspcidtable, int processid)
{
_EPROCESS *eprocess; // eax
_EPROCESS *eprocess_1; // esi
int v5; // ebx
_EPROCESS *v8; // eax
int *v9; // esi
ULONG v10; // eax
int v11; // [esp+4h] [ebp-8h]
_EPROCESS *v12; // [esp+8h] [ebp-4h]
_DWORD *pspcidtablea; // [esp+14h] [ebp+8h]
if ( (processid & 0x7FC) == 0 )
return 0;
eprocess = ExpLookupHandleTableEntry(pspcidtable, processid);
eprocess_1 = eprocess;
if ( eprocess )
{
v12 = eprocess;
while ( 1 )
{
v5 = *&eprocess_1->Pcb.Header.Type;
v11 = *&eprocess_1->Pcb.Header.Type;
if ( (*&eprocess_1->Pcb.Header.Type & 1) != 0 )
{
_ECX = v12;
_EDX = v5 - 1;
__asm { cmpxchg [ecx], edx }
if ( v11 == v5 )
return eprocess_1;
}
else if ( !v5 )
{
break;
}
ExpBlockOnLockedHandleEntry(pspcidtable, eprocess_1);
}
}
pspcidtablea = *(pspcidtable + 40);
if ( pspcidtablea )
{
v8 = (*pspcidtablea)++;
v9 = &pspcidtablea[20 * ((v8 + 1) & 0xFFF) + 1];
*v9 = *&KeGetCurrentThread()[1].DebugActive;
v9[2] = processid;
v9[3] = 3;
v10 = RtlWalkFrameChain(&pspcidtablea[20 * ((v8 + 1) & 0xFFF) + 5], 0x10u, 0);
RtlWalkFrameChain(&v9[v10 + 4], 16 - v10, 1u);
}
return 0;
}
这个函数是由ExpLookupHandleTableEntry
实现的,继续:
unsigned int __stdcall ExpLookupHandleTableEntry(_HANDLE_TABLE *pspcidtable, int processid)
{
unsigned int index; // eax
int pageCount; // ecx
_HANDLE_TABLE *tablebase; // esi
int base; // ecx
unsigned int processindex; // [esp+Ch] [ebp+Ch]
processindex = processid & 0xFFFFFFFC;
index = processindex >> 2;
if ( processindex >= pspcidtable->NextHandleNeedingPool )
return 0;
pageCount = pspcidtable->TableCode & 3;
tablebase = (pspcidtable->TableCode & 0xFFFFFFFC);
if ( !pageCount )
return tablebase + 8 * index;
if ( pageCount == 1 )
{
base = *(&tablebase->TableCode + (processindex >> 11));
}
else
{
index = (processindex >> 2) - (processindex >> 21 << 19);
base = *(*(&tablebase->TableCode + (processindex >> 21)) + 4 * (index >> 9));
}
return base + 8 * (index & 0x1FF);
}
这个就是操作系统实现的伪C代码了,上面的pageCount
就是句柄表的级数,我们重点分析一下它是怎样查找的:
pageCount 值为 0
tablebase
就是我们的真正的句柄表项目的地址,这个很简单,正如其实现:tablebase + 8 * index
。
你可能会有疑问,为什么有这条代码:processindex = processid & 0xFFFFFFFC;
这个代码就是去掉余数,保证这个就是4的倍数。processindex >> 2
就是我们之前所谓的CID / 4
。剩下的应该没有理解上的难度了。
pageCount 值为 1
我们之前介绍过句柄表是按物理页的,如果是一级,那么第一层一个项目可以存储4 * 1024 / 8
个句柄,也就是 29,由于还要除以4,所以就是 211,我们的Base
算出来了,那么真正的index
还没有计算,但是操作系统是这样算的:base + 8 * (index & 0x1FF)
。
为什么index & 0x1FF
就是我们的真正的索引呢?0x200
十进制为512
,也就是级数为1的一个项目存储的句柄数,我只需取出余数即可,由于这个是二进制,除数又是2,我们就可以用与运算进行替代。
pageCount 值为 2
这个更复杂一些,但思想是统一的。如果值为2,第一层一个项目存储的项目为4096 / 4 * 4096 / 8
,也就是 219,第二层一个项目就是29,第一层的base
就是*(&tablebase->TableCode + (processindex >> 21)
,只需要再找第二层就是真正的Base
了,也就是所谓的4 * (index >> 9)
偏移。
剩下就是Index
了,是不是对于这个代码非常懵:(processindex >> 2) - (processindex >> 21 << 19)
?
上面的就是为了重用上一种情况的公式,把它转换到第二层,作用就是看看第一层(级数为2的层数)剥削后剩下的句柄个数。如果读懂这一块,就没问题了,如果实在看不懂,我换一种写法:index - index >> 19 << 19
,这样是不是明白了?
遍历全局句柄表练习参考
有了上面一大波铺垫,那么我们就开始了写上一篇的思考题参考了:
🔒 点击查看代码 🔒
#include <ntddk.h>
typedef struct _HANDLE_TABLE {
ULONG_PTR TableCode;
struct _EPROCESS* QuotaProcess;
HANDLE UniqueProcessId;
#define HANDLE_TABLE_LOCKS 4
EX_PUSH_LOCK HandleTableLock[HANDLE_TABLE_LOCKS];
LIST_ENTRY HandleTableList;
EX_PUSH_LOCK HandleContentionEvent;
int* DebugInfo;
LONG ExtraInfoPages;
ULONG FirstFree;
ULONG LastFree;
ULONG NextHandleNeedingPool;
LONG HandleCount;
union {
ULONG Flags;
BOOLEAN StrictFIFO : 1;
};
} HANDLE_TABLE, * PHANDLE_TABLE;
unsigned int* __stdcall ExpLookupHandleTableEntry(struct _HANDLE_TABLE* pspcidtable, int cid)
{
unsigned int index;
int pageCount;
int tablebase;
int base;
unsigned int processindex;
processindex = cid & 0xFFFFFFFC;
index = processindex >> 2;
if (processindex >= pspcidtable->NextHandleNeedingPool)
return 0;
pageCount = pspcidtable->TableCode & 3;
tablebase = (pspcidtable->TableCode & 0xFFFFFFFC);
if (!pageCount)
return *(int*)(tablebase + 8 * index);
if (pageCount == 1)
{
base = *(int*)(tablebase + (processindex >> 11));
}
else
{
index = (processindex >> 2) - (processindex >> 21 << 19);
base = *((char*)*(int*)(tablebase + (processindex >> 21)) + 4 * (index >> 9));
}
return *(int*)(base + 8 * (index & 0x1FF));
}
VOID UnloadDriver(PDRIVER_OBJECT DriverObject)
{
DbgPrint("Unloaded Successfully!");
}
UNICODE_STRING process, thread;
void ShowInfo(int* TableCode, int cid)
{
int* addr = ((int)ExpLookupHandleTableEntry(TableCode, cid) & ~3);
if (addr && MmIsAddressValid(addr))
{
int* type = addr[-4];
if (!type)
{
return;
}
PUNICODE_STRING str = &type[16];
if (!RtlCompareUnicodeString(str, &process, FALSE))
{
UCHAR* imgname = &addr[93];
DbgPrint("PID : %d , Type : %wZ , Name : %s\n", cid, str, imgname);
return;
}
if (!RtlCompareUnicodeString(str, &thread, FALSE))
{
DbgPrint("TID : %d , Type : %wZ\n", cid, str);
return;
}
}
}
NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath)
{
DriverObject->DriverUnload = UnloadDriver;
DbgPrint("Loaded Successfully!");
RtlInitUnicodeString(&process, L"Process");
RtlInitUnicodeString(&thread, L"Thread");
UNICODE_STRING PsLookupProcessThreadByCid;
RtlInitUnicodeString(&PsLookupProcessThreadByCid, L"PsLookupProcessThreadByCid");
UCHAR* f = MmGetSystemRoutineAddress(&PsLookupProcessThreadByCid);
DWORD32* PspCidTable = *(DWORD32*)(f + 32);
UINT32 TableCode = *PspCidTable;
switch (TableCode & 3)
{
case 0:
for (int i = 0; i < 512; i++)
{
ShowInfo(TableCode, 4 * i);
}
break;
case 1:
for (int i = 0; i < 512 * 1024; i++)
{
ShowInfo(TableCode, 4 * i);
}
break;
case 2:
for (int i = 0; i < 512 * 1024 * 1024; i++)
{
ShowInfo(TableCode, 4 * i);
}
break;
}
return STATUS_SUCCESS;
}
效果如下图所示:
到最后,我们看看WRK
是如何写的ExpLookupHandleTableEntry
代码:
🔒 点击查看代码 🔒
PHANDLE_TABLE_ENTRY
ExpLookupHandleTableEntry (
IN PHANDLE_TABLE HandleTable,
IN EXHANDLE tHandle
)
/*++
Routine Description:
This routine looks up and returns the table entry for the
specified handle value.
Arguments:
HandleTable - Supplies the handle table being queried
tHandle - Supplies the handle value being queried
Return Value:
Returns a pointer to the corresponding table entry for the input
handle. Or NULL if the handle value is invalid (i.e., too large
for the tables current allocation.
--*/
{
ULONG_PTR i,j,k;
ULONG_PTR CapturedTable;
ULONG TableLevel;
PHANDLE_TABLE_ENTRY Entry = NULL;
EXHANDLE Handle;
PUCHAR TableLevel1;
PUCHAR TableLevel2;
PUCHAR TableLevel3;
ULONG_PTR MaxHandle;
PAGED_CODE();
//
// Extract the handle index
//
Handle = tHandle;
Handle.TagBits = 0;
MaxHandle = *(volatile ULONG *) &HandleTable->NextHandleNeedingPool;
//
// See if this can be a valid handle given the table levels.
//
if (Handle.Value >= MaxHandle) {
return NULL;
}
//
// Now fetch the table address and level bits. We must preserve the
// ordering here.
//
CapturedTable = *(volatile ULONG_PTR *) &HandleTable->TableCode;
//
// we need to capture the current table. This routine is lock free
// so another thread may change the table at HandleTable->TableCode
//
TableLevel = (ULONG)(CapturedTable & LEVEL_CODE_MASK);
CapturedTable = CapturedTable - TableLevel;
//
// The lookup code depends on number of levels we have
//
switch (TableLevel) {
case 0:
//
// We have a simple index into the array, for a single level
// handle table
//
TableLevel1 = (PUCHAR) CapturedTable;
//
// The index for this level is already scaled by a factor of 4. Take advantage of this
//
Entry = (PHANDLE_TABLE_ENTRY) &TableLevel1[Handle.Value *
(sizeof (HANDLE_TABLE_ENTRY) / HANDLE_VALUE_INC)];
break;
case 1:
//
// we have a 2 level handle table. We need to get the upper index
// and lower index into the array
//
TableLevel2 = (PUCHAR) CapturedTable;
i = Handle.Value % (LOWLEVEL_COUNT * HANDLE_VALUE_INC);
Handle.Value -= i;
j = Handle.Value / ((LOWLEVEL_COUNT * HANDLE_VALUE_INC) / sizeof (PHANDLE_TABLE_ENTRY));
TableLevel1 = (PUCHAR) *(PHANDLE_TABLE_ENTRY *) &TableLevel2[j];
Entry = (PHANDLE_TABLE_ENTRY) &TableLevel1[i * (sizeof (HANDLE_TABLE_ENTRY) / HANDLE_VALUE_INC)];
break;
case 2:
//
// We have here a three level handle table.
//
TableLevel3 = (PUCHAR) CapturedTable;
i = Handle.Value % (LOWLEVEL_COUNT * HANDLE_VALUE_INC);
Handle.Value -= i;
k = Handle.Value / ((LOWLEVEL_COUNT * HANDLE_VALUE_INC) / sizeof (PHANDLE_TABLE_ENTRY));
j = k % (MIDLEVEL_COUNT * sizeof (PHANDLE_TABLE_ENTRY));
k -= j;
k /= MIDLEVEL_COUNT;
TableLevel2 = (PUCHAR) *(PHANDLE_TABLE_ENTRY *) &TableLevel3[k];
TableLevel1 = (PUCHAR) *(PHANDLE_TABLE_ENTRY *) &TableLevel2[j];
Entry = (PHANDLE_TABLE_ENTRY) &TableLevel1[i * (sizeof (HANDLE_TABLE_ENTRY) / HANDLE_VALUE_INC)];
break;
default :
_assume (0);
}
return Entry;
}
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
我们把关注点放到Type
中,之前我们也涉及过,用来判断该对象体的类型,再看一看其结构:
kd> dt _OBJECT_TYPE
ntdll!_OBJECT_TYPE
+0x000 Mutex : _ERESOURCE
+0x038 TypeList : _LIST_ENTRY
+0x040 Name : _UNICODE_STRING
+0x048 DefaultObject : Ptr32 Void
+0x04c Index : Uint4B
+0x050 TotalNumberOfObjects : Uint4B
+0x054 TotalNumberOfHandles : Uint4B
+0x058 HighWaterNumberOfObjects : Uint4B
+0x05c HighWaterNumberOfHandles : Uint4B
+0x060 TypeInfo : _OBJECT_TYPE_INITIALIZER
+0x0ac Key : Uint4B
+0x0b0 ObjectLocks : [4] _ERESOURCE
然后看到TypeInfo
这个成员,这个是一个结构体,dt
一下:
kd> dt _OBJECT_TYPE_INITIALIZER
ntdll!_OBJECT_TYPE_INITIALIZER
+0x000 Length : Uint2B
+0x002 UseDefaultObject : UChar
+0x003 CaseInsensitive : UChar
+0x004 InvalidAttributes : Uint4B
+0x008 GenericMapping : _GENERIC_MAPPING
+0x018 ValidAccessMask : Uint4B
+0x01c SecurityRequired : UChar
+0x01d MaintainHandleCount : UChar
+0x01e MaintainTypeList : UChar
+0x020 PoolType : _POOL_TYPE
+0x024 DefaultPagedPoolCharge : Uint4B
+0x028 DefaultNonPagedPoolCharge : Uint4B
+0x02c DumpProcedure : Ptr32 void
+0x030 OpenProcedure : Ptr32 long
+0x034 CloseProcedure : Ptr32 void
+0x038 DeleteProcedure : Ptr32 void
+0x03c ParseProcedure : Ptr32 long
+0x040 SecurityProcedure : Ptr32 long
+0x044 QueryNameProcedure : Ptr32 long
+0x048 OkayToCloseProcedure : Ptr32 unsigned char
我们把注意力放到后面几个成员,也就是用来回调函数的地方:
+0x02c DumpProcedure : Ptr32 void
+0x030 OpenProcedure : Ptr32 long
+0x034 CloseProcedure : Ptr32 void
+0x038 DeleteProcedure : Ptr32 void
+0x03c ParseProcedure : Ptr32 long
+0x040 SecurityProcedure : Ptr32 long
+0x044 QueryNameProcedure : Ptr32 long
+0x048 OkayToCloseProcedure : Ptr32 unsigned char
如果我们把函数地址挂到上面,当调用相应的内核函数的时候,就就会回调该函数,用WRK
代码片段来进行示例:
VOID
ObpDecrementHandleCount (
PEPROCESS Process,
POBJECT_HEADER ObjectHeader,
POBJECT_TYPE ObjectType,
ACCESS_MASK GrantedAccess
)
{
POBJECT_HEADER_HANDLE_INFO HandleInfo;
POBJECT_HANDLE_COUNT_DATABASE HandleCountDataBase;
POBJECT_HANDLE_COUNT_ENTRY HandleCountEntry;
PVOID Object;
ULONG CountEntries;
ULONG ProcessHandleCount;
ULONG_PTR SystemHandleCount;
LOGICAL HandleCountIsZero;
PAGED_CODE();
Object = (PVOID)&ObjectHeader->Body;
ProcessHandleCount = 0;
ObpLockObject( ObjectHeader );
SystemHandleCount = ObjectHeader->HandleCount;
HandleCountIsZero = ObpDecrHandleCount( ObjectHeader );
if ( HandleCountIsZero &&
(ObjectHeader->Flags & OB_FLAG_EXCLUSIVE_OBJECT)) {
OBJECT_HEADER_TO_QUOTA_INFO_EXISTS( ObjectHeader )->ExclusiveProcess = NULL;
}
if (ObjectType->TypeInfo.MaintainHandleCount) {
HandleInfo = OBJECT_HEADER_TO_HANDLE_INFO_EXISTS( ObjectHeader );
if (ObjectHeader->Flags & OB_FLAG_SINGLE_HANDLE_ENTRY) {
ASSERT(HandleInfo->SingleEntry.Process == Process);
ASSERT(HandleInfo->SingleEntry.HandleCount > 0);
ProcessHandleCount = HandleInfo->SingleEntry.HandleCount--;
HandleCountEntry = &HandleInfo->SingleEntry;
} else {
HandleCountDataBase = HandleInfo->HandleCountDataBase;
if (HandleCountDataBase != NULL) {
CountEntries = HandleCountDataBase->CountEntries;
HandleCountEntry = &HandleCountDataBase->HandleCountEntries[ 0 ];
while (CountEntries) {
if ((HandleCountEntry->HandleCount != 0) &&
(HandleCountEntry->Process == Process)) {
ProcessHandleCount = HandleCountEntry->HandleCount--;
break;
}
HandleCountEntry++;
CountEntries--;
}
}
else {
HandleCountEntry = NULL;
}
}
if (ProcessHandleCount == 1) {
HandleCountEntry->Process = NULL;
HandleCountEntry->HandleCount = 0;
}
}
ObpUnlockObject( ObjectHeader );
if (ObjectType->TypeInfo.CloseProcedure) {
#if DBG
KIRQL SaveIrql;
#endif
ObpBeginTypeSpecificCallOut( SaveIrql );
(*ObjectType->TypeInfo.CloseProcedure)( Process,
Object,
GrantedAccess,
ProcessHandleCount,
SystemHandleCount );
ObpEndTypeSpecificCallOut( SaveIrql, "Close", ObjectType, Object );
}
ObpDeleteNameCheck( Object );
InterlockedDecrement((PLONG)&ObjectType->TotalNumberOfHandles);
return;
}
为了突出主题,我把注释给删掉了,重点看这块代码:
if (ObjectType->TypeInfo.CloseProcedure) {
#if DBG
KIRQL SaveIrql;
#endif
ObpBeginTypeSpecificCallOut( SaveIrql );
(*ObjectType->TypeInfo.CloseProcedure)( Process,
Object,
GrantedAccess,
ProcessHandleCount,
SystemHandleCount );
ObpEndTypeSpecificCallOut( SaveIrql, "Close", ObjectType, Object );
}
明白这块地方的作用了吧?其他的我就不逐个列举了。
下一篇
本文来自博客园,作者:寂静的羽夏 ,一个热爱计算机技术的菜鸟
转载请注明原文链接:https://www.cnblogs.com/wingsummer/p/15864867.html