确定大致方向

首先先用微软官方函数获取句柄信息


验证ProcessObject可以发现+0x480的位置刚好就是ImageFileName, 说明这就是EPROCESS对象

可以正确获取EPORCESS对象和句柄的权限, 但是尝试修改权限会发现并不会被修改成功, 因为它只是将权限copy出来了, 并不是真正的权限地址
于是即可开始逆向这个函数, 目标: 找出权限是从哪里取出来的? 以及如何根据句柄取到EPROCESS对象
逆向分析如何得到OBJECT_HANDLE_INFORMATION

在此处给info下一个访问断点ba w8 info


发现在mov dword ptr [rax+4],esi断了下来, 此时查看寄存器rsi刚好等于0x12345是设置的权限, 此时需要找到是什么地方写入rsi


继续跟踪发现mov rsi,qword ptr [rsp+50h]对rsi进行了写入, 并且进一步跟踪发现是由上面jne跳转过来的, 继续向上寻找


在这里发现rsi被rdx赋值, 然后又回到了rsi, 继续向上跟踪发现rsi来自[rax+8], 继续向上发现rax是来自nt!ExpLookupHandleTableEntry

分析nt!ExpLookupHandleTableEntry可以发现这就是取句柄信息的核心算法, 首先判断传入句柄是否合法, 其次根据句柄表后2位判断是几级结构的句柄表, 共有三个层次
1 2 3 | 一级结构算法: poi(poi(poi(gs:[0x188]+0x0B8)+0x418)+0x8)+handle*0x4+0x8
二级结构算法: poi(poi(poi(poi(gs:[0x188]+0x0B8)+0x418)+0x8)+(handle>>0x10)*0x8-0x1)+(handle&0x3FF)*0x4+0x8
三级结构算法: poi(poi(poi(poi(poi(gs:[0x188]+0x0B8)+0x418)+0x8)+((handle>>0xA)>>0x9)*0x8-0x2)+((handle>>0xA)&0x1FF)*0x8)+(handle&0x3FF)*0x4+0x8
|
根据分析结果回推可以得到三级结构的各个算法如上所示
逆向分析如何得到_EPROCESS

继续分析如何得到_EPROCESS对象, 对ProcessObject下一个访问断点

可以发现断点被触发, 在mov qword ptr [r12],rax对ProcessObject进行了写入, 明显当前r12=ProcessObject=EPROCESS, 继续向上跟踪发现rax被lea rax,[rbx+30h]赋值, 知道EPROCESS-30刚好就回到了_OBJECT_HEADER头部, 所以rbx=_OBJECT_HEADER, 需要继续向上跟踪rbx的由来...

继续跟踪发现rbx由rax赋值, 并且经过sar rbx,10h和and rbx,0FFFFFFFFFFFFFFF0h运算, 这也是重要的算法, 需要一并反推记录, 继续向上追踪rax由来

rax被r14赋值, 继续追踪r14

继续跟踪得知r14来自[rsp+30], [rsp+30]又来自rcx, rcx又来自[rax], 而rax来自call nt!ExpLookupHandleTableEntry, 经过上面分析ExpLookupHandleTableEntry得知[rax]+8=OBJECT_HANDLE_INFORMATION, 而对于EPROCESS则是用的[rax], 于是可以反解出算法为((poi(rax)>>0x10)&0x0FFFFFFFFFFFFFFF0)=_OBJECT_HEADER
代码实现
下面直接上代码实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 | //一级表
// poi(poi(poi(gs:[0x188]+0x0B8)+0x418)+0x8)+handle*0x4
LONG_PTR GetObjectHandleInformation0(ULONG_PTR handle)
{
return poi(poi(poi(__readgsqword(0x188)+0x0B8)+0x418)+0x8)+handle*0x4;
}
//二级表
//poi(poi(poi(poi(gs:[0x188]+0x0B8)+0x418)+0x8)+(handle>>0x10)*0x8-0x1)+(handle&0x3FF)*0x4
LONG_PTR GetObjectHandleInformation1(ULONG_PTR handle)
{
return poi(poi(poi(poi(__readgsqword(0x188)+0x0B8)+0x418)+0x8)+(handle>>0x10)*0x8-0x1)+(handle&0x3FF)*0x4;
}
//三级表
// poi(poi(poi(poi(poi(gs:[0x188]+0x0B8)+0x418)+0x8)+((handle>>0xA)>>0x9)*0x8-0x2)+((handle>>0xA)&0x1FF)*0x8)+(handle&0x3FF)*0x4
LONG_PTR GetObjectHandleInformation2(ULONG_PTR handle)
{
return poi(poi(poi(poi(poi(__readgsqword(0x188)+0x0B8)+0x418)+0x8)+((handle>>0xA)>>0x9)*0x8-0x2)+((handle>>0xA)&0x1FF)*0x8)+(handle&0x3FF)*0x4;
}
//获取权限地址
void GetObjectHandleInformation(ULONG_PTR handle, PVOID** object,OBJECT_HANDLE_INFORMATION** info)
{
//句柄表地址
ULONG_PTR handleTable = GetHandleTable(handle);
//表等级
ULONG level = handleTable & 0x3;
LONG_PTR var1 = 0;
if (level == 0) {
PZY_PRINT("level=0");
var1 = GetObjectHandleInformation0(handle);
}
else if (level == 1) {
PZY_PRINT("level=1");
var1 = GetObjectHandleInformation1(handle);
}
else if (level == 2) {
PZY_PRINT("level=2");
var1 = GetObjectHandleInformation2(handle);
}
//处理info
UINT_PTR tempInof = (UINT_PTR)((UCHAR*)var1 + 0x8);
*info = tempInof;
//处理Object
UINT_PTR tempObject = (((INT64)poi(var1) >> 0x10) & 0x0FFFFFFFFFFFFFFF0);
*object = tempObject;
}
|
1 2 3 4 5 | //自己实现的获取权限地址方法
OBJECT_HANDLE_INFORMATION* myInfo= { 0 };
PVOID* EPROCESS = 0;
GetObjectHandleInformation(handle, &EPROCESS, &myInfo);
PZY_PRINT("EPROCESS=%llX GrantedAccess=%llX HandleAttributes=%llX", EPROCESS,myInfo->GrantedAccess, myInfo->HandleAttributes);
|
取出的地址就是储存权限的真是地址, 修改此地址权限直接影响当前句柄权限
总结
经过后续观察发现, 系统先会得到全局句柄表 方式: 获取当前线程_KTHREAD, 再由当前线程+0x220 Process找到当前进程, 当前进程+0x418 ObjectTable得到句柄表_HANDLE_TABLE, 句柄表继续偏移+0x008 TableCode得到句柄入口_HANDLE_TABLE_ENTRY, 继续偏移+0x000 InfoTable得到一个加密后的地址, 这个地址解密后是一个具体的对象, 这个具体对象是根据传入的参数而定的, 常用的有事件对象/文件对象/进程对象/线程对象, 其他的我还没摸到, 参考下图:

这些e开头的数据就是解密前的数据, 这些都是一个个_HANDLE_TABLE_ENTRY

[培训]Windows内核深度攻防:从Hook技术到Rootkit实战!
最后于 2026-1-19 14:49
被mb_binusgki编辑
,原因: 补充内容