概述
在本文中,我们会探索句柄在windows内核中表现形式,帮助了解句柄在windows中的作用。通过调试与逆向内核代码的方式,介绍句柄是如何关联到内核对象,并在此基础上介绍一种利用句柄来保护我们的进程不被读写的技术方案
句柄是什么?
本节主要介绍句柄在R3的作用,如果你了解,可以跳过,不影响后面的阅读。
如果你经常使用windows的OpenProcess、CreateFile等API来操作进程或文件,那么你应该非常熟悉句柄的使用,这些函数的返回值类型都是HANDLE,即句柄。在《windows内核原理与实现》3.4.1节对句柄有如下描述:
当一个进程利用名称来创建或打开一个对象时,将获得一个句柄,该句柄指向所创建或打开的对象。以后,该进程无须使用名称来引用该对象,使用此句柄即可访问。这样做可以显著地提高引用对象的效率。句柄是一个在软件设计中被广泛使用的概念。例如,在C运行库中,文件操作使用句柄来表示,每当应用程序创建或打开一个文件时,只要此创建或打开操作成功,则C运行库返回一个句柄。以后应用程序对文件的读写操作都使用此句柄来标识该文件。而且,如果两个应用程序以共享方式打开了同一个文件,那么,它们将分别得到各自的句柄,且都可以通过句柄操作该文件。尽管两个应用程序得到的句柄的值并不相同,但是这两个句柄所指的文件却是同一个。因此,句柄只是一个对象引用,同一个对象在不同的环境下可能有不同的引用(句柄)值。
上文中的"对象"指的是内核对象,我们在R3中所使用的文件、进程、线程在内核中都有对应内核对象。应用层每次创建或打开进程、文件都会对相应的内核对象创建一个句柄。当多个进程同时打开一个文件时,该文件在内核中只会存在一个文件内核对象,但每个进程都有一个各自的文件句柄,每个句柄会增加内核对象的引用计数,只有当内核对象的引用计数为0时,内核对象才会释放。
句柄在内核中的存在形式
本节将通过实时内核调试结合逆向查看内核源代码的方式,来探索内核中的句柄结构。句柄结构经历各个windows版本到现在,变化非常大,这里使用的系统是Win10 19045。
Windows系统的句柄表主要有三种,第一种是系统全局PspCidTable句柄表,保存所有进程和线程的句柄,第二种是进程内部句柄表,保存该进程所打开的内核对象句柄,最后一种是系统进程system的全局句柄表,这几种句柄表对应的格式都是一样的。我们主要介绍进程中的句柄表。
_EPROCESS是进程在内核中内核对象结构,每个进程在内核中都有一个这样对应的内核结构来记录进程的信息。EPOCESS结构中的ObjectTable即该进程所有的句柄表,ObjectTable中的TableCode指向handle_table_entrys的首地址。
dt _EPROCESS
nt!_EPROCESS
+0x000 Pcb : _KPROCESS
+0x2d8 ProcessLock : _EX_PUSH_LOCK
+0x2e0 UniqueProcessId : Ptr64 Void
+0x2e8 ActiveProcessLinks : _LIST_ENTRY
+0x2f8 RundownProtect : _EX_RUNDOWN_REF
+0x300 Flags2 : Uint4B
......
+0x418 ObjectTable : Ptr64 _HANDLE_TABLE //我们要找的句柄表
......
+0x848 MmHotPatchContext : Ptr64 Void
0: kd> dt _HANDLE_TABLE
nt!_HANDLE_TABLE
+0x000 NextHandleNeedingPool : Uint4B
+0x004 ExtraInfoPages : Int4B
+0x008 TableCode : Uint8B //指向存放handle_table_entry的地方
+0x010 QuotaProcess : Ptr64 _EPROCESS
+0x018 HandleTableList : _LIST_ENTRY
+0x028 UniqueProcessId : Uint4B
+0x02c Flags : Uint4B
+0x02c StrictFIFO : Pos 0, 1 Bit
+0x02c EnableHandleExceptions : Pos 1, 1 Bit
+0x02c Rundown : Pos 2, 1 Bit
+0x02c Duplicated : Pos 3, 1 Bit
+0x02c RaiseUMExceptionOnInvalidHandleClose : Pos 4, 1 Bit
+0x030 HandleContentionEvent : _EX_PUSH_LOCK
+0x038 HandleTableLock : _EX_PUSH_LOCK
+0x040 FreeLists : [1] _HANDLE_TABLE_FREE_LIST
+0x040 ActualEntry : [32] UChar
+0x060 DebugInfo : Ptr64 _HANDLE_TRACE_DEBUG_INFO
以一个简单的图形先概述三者的结构如下
TableCode事实上比上图描述的要复杂一点点,句柄表是一个多层结构,TableCode的低两位代表了句柄表的层数。若低两位为0,则TableCode直接指向一个存放handle_table_entrys的表,若低两位为1则TableCode指向一个中间的句柄表,该表里存放的也是"TableCode";同理若TableCode低两位为3,则往后还有两层TableCode表。
如果还不是很明白的话,我们实际操作找一下。首先找到system进程的EPROCESS
2: kd> !process 4 0
Searching for Process with Cid == 4
PROCESS ffffd28ec18b0040
SessionId: none Cid: 0004 Peb: 00000000 ParentCid: 0000
DirBase: 001ad002 ObjectTable: ffffaf04d3a25c00 HandleCount: 2826.
Image: System
从EPROCESS中找到ObjectTable,即句柄表 _HANDLE_TABLE
2: kd> dt _EPROCESS ffffd28ec18b0040 -y object
nt!_EPROCESS
+0x570 ObjectTable : 0xffffaf04`d3a25c00 _HANDLE_TABLE
查看句柄表的TableCode
2: kd> dt 0xffffaf04`d3a25c00 _HANDLE_TABLE
nt!_HANDLE_TABLE
+0x000 NextHandleNeedingPool : 0x3400
+0x004 ExtraInfoPages : 0n0
+0x008 TableCode : 0xffffaf04`d5413001
+0x010 QuotaProcess : (null)
+0x018 HandleTableList : _LIST_ENTRY [ 0xffffaf04`d3a25658 - 0xfffff807`1ff2eb40 ]
+0x028 UniqueProcessId : 4
+0x02c Flags : 0
+0x02c StrictFIFO : 0y0
+0x02c EnableHandleExceptions : 0y0
+0x02c Rundown : 0y0
+0x02c Duplicated : 0y0
+0x02c RaiseUMExceptionOnInvalidHandleClose : 0y0
+0x030 HandleContentionEvent : _EX_PUSH_LOCK
+0x038 HandleTableLock : _EX_PUSH_LOCK
+0x040 FreeLists : [1] _HANDLE_TABLE_FREE_LIST
+0x040 ActualEntry : [32] ""
+0x060 DebugInfo : (null)
这里TableCode为0xffffaf04`d5413001,最低为1,则表示后续还有一层TableCode表。去除TableCode的低两位,输出的结果就是一张二级表,里面存储的就是一级表指针。
2: kd> dp 0xffffaf04`d5413000
ffffaf04`d5413000 ffffaf04`d3aad000 ffffaf04`d5414000
ffffaf04`d5413010 ffffaf04`d770d000 ffffaf04`d7cfa000
ffffaf04`d5413020 ffffaf04`d80b2000 ffffaf04`d85e4000
ffffaf04`d5413030 ffffaf04`d8cd5000 ffffaf04`d9615000
ffffaf04`d5413040 ffffaf04`dacff000 ffffaf04`dc0ff000
ffffaf04`d5413050 ffffaf04`dd9d0000 ffffaf04`dd9d3000
ffffaf04`d5413060 ffffaf04`ddbdb000 00000000`00000000
ffffaf04`d5413070 00000000`00000000 00000000`00000000
继续查看第一张一级表,输入dp ffffaf04`d3aad000命令,我们知道一级句柄表是根据进程或线程ID来索引的,且以4累加,所以第一行对应id = 0,第二行对应id = 4。根据尝试,PID = 4的进程是System。
2: kd> dp ffffaf04`d3aad000
ffffaf04`d3aad000 00000000`00000000 00000000`00000000
ffffaf04`d3aad010 d28ec18b`0010ff43 00000000`001fffff
ffffaf04`d3aad020 d28ec19c`b1100001 00000000`001fffff
ffffaf04`d3aad030 d28ec355`b010fffb 00000000`001fffff
ffffaf04`d3aad040 d28ec188`6250fe7d 00000000`001f0001
ffffaf04`d3aad050 af04d3a6`27100001 00000000`000f000f
ffffaf04`d3aad060 af04d3a5`1ca0fc5f 00000000`000f000f
ffffaf04`d3aad070 d28ec18c`13f00001 00000000`001f0003
查看handle_table_entry的结构
2: kd> dt _handle_table_entry
nt!_HANDLE_TABLE_ENTRY
+0x000 VolatileLowValue : Int8B
+0x000 LowValue : Int8B
+0x000 InfoTable : Ptr64 _HANDLE_TABLE_ENTRY_INFO
+0x008 HighValue : Int8B
+0x008 NextFreeHandleEntry : Ptr64 _HANDLE_TABLE_ENTRY
+0x008 LeafHandleValue : _EXHANDLE
+0x000 RefCountField : Int8B
+0x000 Unlocked : Pos 0, 1 Bit
+0x000 RefCnt : Pos 1, 16 Bits
+0x000 Attributes : Pos 17, 3 Bits
+0x000 ObjectPointerBits : Pos 20, 44 Bits
+0x008 GrantedAccessBits : Pos 0, 25 Bits
+0x008 NoRightsUpgrade : Pos 25, 1 Bit
+0x008 Spare1 : Pos 26, 6 Bits
+0x00c Spare2 : Uint4B
从进程的handle找到对应的handle_table_entry
通过上文,我们知道了EPROCESS与handle_table_entry的关系,那么句柄的作用是什么呢?如何通过句柄找到对应的handle_table_entry呢。这里有一个函数ExpLookupHandleTableEntry,它刚好是通过句柄从handle_table中找对应的handle_table_entry。看伪代码:
unsigned __int64 __fastcall ExpLookupHandleTableEntry(_HANDLE_TABLE *handle_table, __int64 handle)
{
unsigned __int64 my_handle; // rdx
volatile unsigned __int64 TableCode; // r8
__int64 v4; // rax
my_handle = handle & 0xFFFFFFFFFFFFFFFCui64; // 从这里句柄的使用,我们可以得出句柄是以4为增长单位的结论
if ( my_handle >= handle_table->NextHandleNeedingPool )
return 0i64;
TableCode = handle_table->TableCode;
if ( (TableCode & 3) == 1 ) //TableCode低两位为1,代表下一层是TableCode表
{
v4 = *(_QWORD *)(TableCode + 8 * (my_handle >> 10) - 1);
return v4 + 4 * (my_handle & 0x3FF);
}
if ( (TableCode & 3) != 0 ) //TableCode低两位为2,代表下两层都是TableCode表
{
v4 = *(_QWORD *)(*(_QWORD *)(TableCode + 8 * (my_handle >> 19) - 2) + 8 * ((my_handle >> 10) & 0x1FF));
return v4 + 4 * (my_handle & 0x3FF);
}
return TableCode + 4 * my_handle; //TableCode低两位为0
}
通过伪代码中的注释描述,我们知道了句柄实际上是下标,在最简单的情况下,使用时"handle×4+TableCode"即为对应的handle_table_entry所在的位置。到这里,我们清楚的知道了句柄的值在进程中的作用,使用它我们可以找到handle_table_entry,也就能找到对应的内核对象。
从handle_table_entry找到句柄对应的内核对象
通过上一节,我们知道了如何从进程的Handle找到进程内核中的handle_table_entry,本节我们将介绍如何从handle_table_entry找到对应的内核对象,完成从进程的handle找到对应内核对象的过程。
在Win7中,handle_table_entry结构中的object直接指向对应的内核对象地址,但在win10并没有这么简单。接着前文的调试内容继续查看ffffaf04`d3aad010地址处handle_table_entry结构的具体内容。通常我们会去google这些新结构的含义,但这些都是未公开的,假设在google找不到的情况下,我们如何去做呢。
2: kd> dt _handle_table_entry ffffaf04`d3aad010
nt!_HANDLE_TABLE_ENTRY
+0x000 VolatileLowValue : 0n-3274467076306043069
+0x000 LowValue : 0n-3274467076306043069
+0x000 InfoTable : 0xd28ec18b`0010ff43 _HANDLE_TABLE_ENTRY_INFO
+0x008 HighValue : 0n2097151
+0x008 NextFreeHandleEntry : 0x00000000`001fffff _HANDLE_TABLE_ENTRY
+0x008 LeafHandleValue : _EXHANDLE
+0x000 RefCountField : 0n-3274467076306043069
+0x000 Unlocked : 0y1
+0x000 RefCnt : 0y0111111110100001 (0x7fa1)
+0x000 Attributes : 0y000
+0x000 ObjectPointerBits : 0y11010010100011101100000110001011000000000001 (0xd28ec18b001)
+0x008 GrantedAccessBits : 0y0000111111111111111111111 (0x1fffff)
+0x008 NoRightsUpgrade : 0y0
+0x008 Spare1 : 0y000000 (0)
+0x00c Spare2 : 0
这个时候我们会去IDA查看内核中相关的实现,思路是找到handle与object相关联的函数。如ObFindHandleForObject,ObReferenceObjectByHandle
这里选择ObFindHandleForObject来介绍,因为它流程相对简单一些。函数声明来自官方。
BOOLEAN WINAPI ObFindHandleForObject(
_In_ PEPROCESS Process,
_In_ PVOID Object,
_In_opt_ PVOID Reserved1,
_In_opt_ PVOID Reserved2,
_Out_ PHANDLE Handle
);
从IDA中查看它的伪代码
bool __fastcall ObFindHandleForObject(struct _EX_RUNDOWN_REF *a1, __int64 a2, __int64 a3, __int64 a4, __int64 *a5)
{
bool v9; // bl
unsigned int *v10; // rcx
__int64 v12[5]; // [rsp+20h] [rbp-28h] BYREF
v9 = 0;
v10 = (unsigned int *)ObReferenceProcessHandleTable(a1);
if ( v10 )
{
if ( a2 )
v12[0] = a2 - 0x30;// 我使用的win10版本中,object_header的大小为0x30,因此这里用object-0x30是获取了object_header
else
v12[0] = 0i64;
v12[1] = a3;
v12[2] = a4;
v9 = (unsigned __int8)ExEnumHandleTable(
v10,
(__int64 (__fastcall *)(unsigned int *, signed __int64 *, __int64, __int64))ObpEnumFindHandleProcedure,
(__int64)v12,
a5) != 0;
ExReleaseRundownProtection_0(a1 + 0x8B);
}
return v9;
}
_HANDLE_TABLE *__fastcall ObReferenceProcessHandleTable(_EPROCESS *a1)
{
struct _EX_RUNDOWN_REF *p_RundownProtect; // rdi
_HANDLE_TABLE *ObjectTable; // rbx
p_RundownProtect = &a1->RundownProtect;
ObjectTable = 0i64;
if ( ExAcquireRundownProtection_0(&a1->RundownProtect) )
{
ObjectTable = a1->ObjectTable;
if ( !ObjectTable )
ExReleaseRundownProtection_0(p_RundownProtect);
}
return ObjectTable;
}
该函数首先使用ObReferenceProcessHandleTable从EPROCESS的中获取HANDLE_TABLE,然后使用ExEnumHandleTable遍历该HANDLE_TABLE中的handle_table_entry。关于ExEnumHandleTable这个未公开的函数,在WRK中有如下声明:
BOOLEAN
ExEnumHandleTable(
IN PHANDLE_TABLE HandleTable,
IN EX_ENUMERATE_HANDLE_ROUTINE EnumHandleProcedure,
IN PVOID EnumParameter,
OUT PHANDLE Handle OPTIONAL
);
结合伪代码,我们可以看出在ObFindHandleForObject函数中ExEnumHandleTable函数的EnumParameter参数为object_header;回调函数为ObpEnumFindHandleProcedure。接下来我们继续查看ObpEnumFindHandleProcedure函数中是如何使用object_header的。
在WRK中,ObpEnumFindHandleProcedure的声明如下:
BOOLEAN
ObpEnumFindHandleProcedure (
PHANDLE_TABLE_ENTRY ObjectTableEntry,
HANDLE HandleId,
PVOID EnumParameter
)
ObpEnumFindHandleProcedure的伪代码:
__int64 __fastcall ObpEnumFindHandleProcedure(
__int64 handle_table,
__int64 handle_table_entry,
__int64 a3,
_QWORD *object_header)
{
unsigned __int8 v5; // bl
__int64 v7; // rbx
_DWORD *v8; // rcx
__int64 v9; // r11
int v10[10]; // [rsp+0h] [rbp-28h] BYREF
if ( !*object_header || *object_header == ((*(__int64 *)handle_table_entry >> 0x10) & 0xFFFFFFFFFFFFFFF0ui64) )
{
v7 = object_header[1];
if ( !v7
|| v7 == ObTypeIndexTable[(unsigned __int8)ObHeaderCookie ^ *(unsigned __int8 *)(((*(__int64 *)handle_table_entry >> 0x10) & 0xFFFFFFFFFFFFFFF0ui64)
+ 0x18) ^ (unsigned __int64)(unsigned __int8)((unsigned __int16)(WORD1(*(_QWORD *)handle_table_entry) & 0xFFF0) >> 8)] )
{
v8 = (_DWORD *)object_header[2];
if ( !v8 )
goto LABEL_11;
v9 = (*(__int64 *)handle_table_entry >> 17) & 7;
if ( (*(_DWORD *)(handle_table_entry + 8) & 0x2000000) != 0 )
LOBYTE(v9) = v9 | 8;
if ( *v8 == (v9 & 7) && v8[1] == (*(_DWORD *)(handle_table_entry + 8) & 0x1FFFFFF) )
LABEL_11:
v5 = 1;
else
v5 = 0;
}
else
{
v5 = 0;
}
}
else
{
v5 = 0;
}
_InterlockedExchangeAdd64((volatile signed __int64 *)handle_table_entry, 1ui64);
_InterlockedOr(v10, 0);
if ( *(_QWORD *)(handle_table + 48) )
ExfUnblockPushLock(handle_table + 48, 0i64);
return v5;
}
对比伪代码与WRK中的声明发现,似乎多了一个a3参数,但是a3在整个代码中也没有被使用,暂且认为为保留值,我们不关心它。重点在于我们发现这样一个判断,找到了从handle_table_entry找到object_header的计算公式。
*object_header == ((*(__int64 *)handle_table_entry >> 0x10) & 0xFFFFFFFFFFFFFFF0ui64)
按照公式进行计算:
2: kd> dp ffffaf04`d3aad010 l1
ffffaf04`d3aad010 d28ec18b`0010ff43
(d28ec18b`0010ff43>>0x10)&0xFFFFFFFFFFFFFFF0=FFFFD28EC18B0010
2: kd> dt _object_header FFFFD28EC18B0010
nt!_OBJECT_HEADER
+0x000 PointerCount : 0n130643
+0x008 HandleCount : 0n4
+0x008 NextToFree : 0x00000000`00000004 Void
+0x010 Lock : _EX_PUSH_LOCK
+0x018 TypeIndex : 0x15 ''
+0x019 TraceFlags : 0 ''
+0x019 DbgRefTrace : 0y0
+0x019 DbgTracePermanent : 0y0
+0x01a InfoMask : 0 ''
+0x01b Flags : 0x2 ''
+0x01b NewObject : 0y0
+0x01b KernelObject : 0y1
+0x01b KernelOnlyAccess : 0y0
+0x01b ExclusiveObject : 0y0
+0x01b PermanentObject : 0y0
+0x01b DefaultSecurityQuota : 0y0
+0x01b SingleHandleEntry : 0y0
+0x01b DeletedInline : 0y0
+0x01c Reserved : 0
+0x020 ObjectCreateInfo : 0xfffff807`1fe538c0 _OBJECT_CREATE_INFORMATION
+0x020 QuotaBlockCharged : 0xfffff807`1fe538c0 Void
+0x028 SecurityDescriptor : 0xffffaf04`d3a78d62 Void
+0x030 Body : _QUAD
现在,我们可以做到从handle_table_entry找到对应的内核对象了,其实也就是可以做到从handle找到对应的内核对象了。
利用句柄来保护进程不被读写
本节介绍一种利用句柄降权来保护进程不被读写的技术。
我们知道在R3中,一个进程打开另一个进程进行读写,其过程为:A进程获取B进程句柄 -> A进程操作B进程句柄进行读/写。很明显,句柄是这里的关键介质。在阅读完上文后,我们知道了进程中的句柄对进程意味着什么。回顾handle_table_entry的结构,其中的GrantedAccessBits表示这个该进程可以通过该句柄所使用的权限。这意味着,如果我们修改A进程中的B进程句柄对应的handle_table_entry->GrantedAccessBits,便可以修改A进程对B进程的操作权限。
0: kd> dt _handle_table_entry
nt!_HANDLE_TABLE_ENTRY
+0x000 VolatileLowValue : Int8B
+0x000 LowValue : Int8B
+0x000 InfoTable : Ptr64 _HANDLE_TABLE_ENTRY_INFO
+0x008 HighValue : Int8B
+0x008 NextFreeHandleEntry : Ptr64 _HANDLE_TABLE_ENTRY
+0x008 LeafHandleValue : _EXHANDLE
+0x000 RefCountField : Int8B
+0x000 Unlocked : Pos 0, 1 Bit
+0x000 RefCnt : Pos 1, 16 Bits
+0x000 Attributes : Pos 17, 3 Bits
+0x000 ObjectPointerBits : Pos 20, 44 Bits
+0x008 GrantedAccessBits : Pos 0, 25 Bits // 该句柄的操作权限
+0x008 NoRightsUpgrade : Pos 25, 1 Bit
+0x008 Spare1 : Pos 26, 6 Bits
+0x00c Spare2 : Uint4B
思路清晰,就开始编码。我们这里使用CE打开notepad.exe,正常情况下,CE是可以修改notepad.exe进程的内存的。
首先我们找到NOTEPAD.EXE进程的内核对象,这里我们直接使用PID获取
NTSTATUS DriverEntry(PDRIVER_OBJECT pDriver, PUNICODE_STRING pReg)
{
PEPROCESS eprocess = NULL;
NTSTATUS status = PsLookupProcessByProcessId((HANDLE)0x1D24, &eprocess);
if (!NT_SUCCESS(status))
{
DbgPrintEx(DPFLTR_IHVDRIVER_ID, 0, "Open process unsuccessfully!\n");
return ;
}
ObDereferenceObject(eprocess);
ProtectProcessByEprocess(eprocess);
pDriver->DriverUnload = DriverUnload;
return STATUS_SUCCESS;
}
虽然我们已经会手工查找,但是还记得ObFindHandleForObject函数中如何遍历handle_table中的hanlde_table_entry吗,使用未公开的函数ExEnumHandleTable。
BOOLEAN
ExEnumHandleTable(
IN PHANDLE_TABLE HandleTable,
IN EX_ENUMERATE_HANDLE_ROUTINE EnumHandleProcedure,
IN PVOID EnumParameter,
OUT PHANDLE Handle OPTIONAL
);
这里我们使用的回调函数为enumRoutine,参数为protected_eprocess。在enumRoutine中,我们通过handle_table_entry找到对应的object_header,再找到对应的object。如果我们找到了notepad.exe进程对应的句柄,那么该object也就是notepad.exe的EPROCESS。这时,我们只需要将该handle_table_entry中的GrantedAccess权限修改即可达到进程保护的效果。
BOOLEAN NTAPI enumRoutine(
IN PVOID HandleTable,
IN PHANDLE_TABLE_ENTRY HandleTableEntry,
PVOID Unknow,
IN PVOID eprocess
)
{
BOOLEAN result = FALSE;
if (HandleTableEntry)
{
ULONG_PTR object_header = (*(PLONG_PTR)(HandleTableEntry) >> 0x10) & 0xFFFFFFFFFFFFFFF0;
ULONG_PTR object = object_header + 0x30;
// 若该对象类型为进程,则该对象为该进程的EPROCESS
if (object == (ULONG_PTR)eprocess)
{
//DbgBreakPoint();
DbgPrintEx(DPFLTR_IHVDRIVER_ID, 0, "[db]:HandleTableEntry= %llx \r\n", HandleTableEntry);
HandleTableEntry->GrantedAccess &= ~(PROCESS_VM_READ | PROCESS_VM_WRITE);
result = TRUE;
}
}
_InterlockedExchangeAdd64(HandleTableEntry, 1); // 注意 这里必须释放,参考ObpEnumFindHandleProcedure的实现
if (*(PULONG_PTR)((ULONG_PTR)HandleTable + 0x30)) {
ExfUnblockPushLock((ULONG_PTR)HandleTable + 0x30, 0); // 同上
}
return result;
}
VOID ProtectProcessByEprocess(PEPROCESS protected_eprocess)
{
PEPROCESS eprocess = NULL;
// 这里作为测试,直接写死CE的pid为0x464
NTSTATUS status = PsLookupProcessByProcessId((HANDLE)0x464, &eprocess);
if (!NT_SUCCESS(status))
{
KdPrint(("Open process unsuccessfully!\n"));
return;
}
// 在我使用的windows版本中 handle_table的偏移为0x418
PVOID handle_table = *(PVOID*)((LONG_PTR)eprocess + 0x418);
PVOID Handle = NULL;
ExEnumHandleTable(handle_table, enumRoutine, protected_eprocess, Handle);
ObDereferenceObject(eprocess);
}
执行后,notepad.exe的内存在CE中已经不可见了。完整的项目代码已上传至 git仓库
参考
《windows内核原理与实现》
句柄定义 - https://docs.microsoft.com/en-us/sysinternals/downloads/handle
反外挂技术整理 - https://www.freebuf.com/articles/system/255632.html
转载:https://tttang.com/archive/1682/