温馨提示
本文的内容均在Windows 11 Enterprise(22000.466)版本下测试
不同版本的部分内容可能存在差异,但万变不离其中
[Upadate 20220803]经测试,本文内容目前向下兼容
正文
Part1.理论知识
PspCidTable是一个指向类型为_HANDLE_TABLE
的指针
0: kd> dp Pspcidtable
fffff801`6bf195d0 ffffb10c`bd635180 ffffd087`1bec6da0
fffff801`6bf195e0 00000000`00000000 00010000`00000000
fffff801`6bf195f0 00000000`00001000 00000000`00000000
fffff801`6bf19600 00000000`00000000 0000a503`00000000
fffff801`6bf19610 00000000`00000000 00000000`00000000
fffff801`6bf19620 00000000`00000000 00000000`00000000
fffff801`6bf19630 00000000`00000000 00000000`00000000
fffff801`6bf19640 ffffd087`1bef4bc0 fffff801`6c263000
我们可以通过WinDbg
查看该结构
0: kd> dt _handle_table ffffb10c`bd635180
nt!_HANDLE_TABLE
+0x000 NextHandleNeedingPool : 0x1c00
+0x004 ExtraInfoPages : 0n0
+0x008 TableCode : 0xffffb10c`c118e001
+0x010 QuotaProcess : (null)
+0x018 HandleTableList : _LIST_ENTRY [ 0xffffb10c`bd635198 - 0xffffb10c`bd635198 ]
+0x028 UniqueProcessId : 0
+0x02c Flags : 1
+0x02c StrictFIFO : 0y1
+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)
其中,+0x8 TableCode
就是我们所需的全局句柄表的地址
关于该表,有如下命名规则
满足条件 | 含义 | 翻译成人话 |
---|
(TableCode & 3) == 0 | 该指针所指句柄表为一级句柄表 | TableCode的二进制低两位都是0 ,遍历此表可以直接得到我们要的信息 |
(TableCode & 3) == 1 | 该指针所指句柄表为二级句柄表 | TableCode的二进制低两位为01 ,此表中表项指向一级句柄表 |
(TableCode & 3) == 2 | 该指针所指句柄表为三级句柄表 | TableCode的二进制低两位为10 ,此表中的表项指向二级句柄表 |
关于各级句柄表的关系,如图所示:
!!!更正上图!由于忘记保存EXCEL文件,口头补充说明,对于一级表每16bytes才有一次数据、有八字节空;二三级表则每隔8bytes都有一次数据!!!
关于这个句柄表,微软这个糟老头子在Win7 x32还是没有加密的
但是到Win7x64以后开始就开始加密了,我们可以通过反汇编查看PsLookUpProcessByProcessId => PspReferenceCidTableEntry
函数的实现过程来解决
以22000.466为例,如下为IDA反汇编结果
_BYTE *__fastcall PspReferenceCidTableEntry(__int64 a1, char a2)
{
volatile signed __int64 *v3;
volatile signed __int64 *v4;
__int64 v5;
signed __int64 v6;
__int64 v7;
unsigned __int128 v8;
unsigned __int8 v9;
unsigned __int64 v10;
_BYTE *v11;
int v13;
bool v14;
signed __int64 v15;
signed __int64 v16;
__int64 v17;
_QWORD *v18;
unsigned __int64 v19;
int v20[8];
unsigned __int128 v21;
__int128 v22;
if ( (a1 & 0x3FC) == 0 )
return 0i64;
v3 = (volatile signed __int64 *)ExpLookupHandleTableEntry(PspCidTable, a1);
v4 = v3;
if ( !v3 )
return 0i64;
v5 = PspCidTable;
_m_prefetchw((const void *)v3);
*(_QWORD *)&v21 = *v3;
v6 = *((_QWORD *)v3 + 1);
*((_QWORD *)&v21 + 1) = v6;
v7 = v21;
if ( (v21 & 0x1FFFE) == 0 )
{
LABEL_10:
v13 = 0;
if ( !(unsigned __int8)ExLockHandleTableEntry(PspCidTable, v4) )
return 0i64;
v11 = (_BYTE *)((*(__int64 *)v4 >> 16) & 0xFFFFFFFFFFFFFFF0ui64);
if ( (*v11 & 0x7F) == a2 )
{
if ( a2 == 3 )
v14 = (*(_DWORD *)(((*(__int64 *)v4 >> 16) & 0xFFFFFFFFFFFFFFF0ui64) + 0x464) & 0x400000C) == 0x4000000;
else
v14 = (*(_DWORD *)(((*(__int64 *)v4 >> 16) & 0xFFFFFFFFFFFFFFF0ui64) + 0x560) & 3) == 2;
if ( v14 )
v13 = ExSlowReplenishHandleTableEntry(v4);
_m_prefetchw(v11 - 48);
v15 = *((_QWORD *)v11 - 6);
if ( v15 )
{
while ( 1 )
{
v16 = v15;
v15 = _InterlockedCompareExchange64((volatile signed __int64 *)v11 - 6, (unsigned int)(v13 + 1) + v15, v15);
if ( v16 == v15 )
break;
if ( !v15 )
goto LABEL_25;
}
if ( ObpTraceFlags )
ObpPushStackInfo((_DWORD)v11 - 48);
LABEL_20:
v17 = PspCidTable;
_InterlockedExchangeAdd64(v4, 1ui64);
v18 = (_QWORD *)(v17 + 48);
_InterlockedOr(v20, 0);
if ( *v18 )
ExfUnblockPushLock(v18, 0i64);
return v11;
}
LABEL_25:
v19 = *v4 & 0xFFFFFFFFFFFE0001ui64;
v22 = v19;
*v4 = v19;
}
v11 = 0i64;
goto LABEL_20;
}
while ( 1 )
{
if ( (v7 & 1) == 0 )
{
ExpBlockOnLockedHandleEntry(v5, v4, v7);
_m_prefetchw((const void *)v4);
v6 = *((_QWORD *)v4 + 1);
*(_QWORD *)&v21 = *v4;
v7 = v21;
*((_QWORD *)&v21 + 1) = v6;
goto LABEL_27;
}
*(_QWORD *)&v8 = v7;
*((_QWORD *)&v8 + 1) = v6;
v9 = _InterlockedCompareExchange128(v4, v6, v7 - 2, (signed __int64 *)&v8);
v6 = v8 >> 64;
v10 = v8;
v7 = v8;
v21 = v8;
if ( v9 )
break;
LABEL_27:
if ( (v7 & 0x1FFFE) == 0 )
goto LABEL_10;
}
if ( (unsigned __int16)(v10 >> 1) == 16 )
v7 = ((unsigned int)v7 ^ (2 * (unsigned int)(v10 >> 1) - 2)) & 0x1FFFE ^ (unsigned __int64)v7;
v11 = (_BYTE *)((v7 >> 16) & 0xFFFFFFFFFFFFFFF0ui64);
if ( (*v11 & 0x7F) == a2 )
return v11;
ObfDereferenceObject(v11);
return 0i64;
}
我们根据对原函数的分析,可以发现在成功的情况下应该是返回v11
这个变量,通过分析代码可以分析出他的解密方式,其他系统的解密方法同理,这里在下面给出。
系统 | 解密方式(v4是PEPROCESS*) |
---|
Win7 | (_BYTE *)(*(__int64 *)v4)& 0xFFFFFFFFFFFFFFF0ui64 |
Win8 | (_BYTE *)(*(__int64 *)v4 >> 19) & 0xFFFFFFFFFFFFFFF0ui64) |
Win10 - Win11(截至20220803) | (_BYTE *)(*(__int64 *)v4 >> 16) & 0xFFFFFFFFFFFFFFF0ui64) |
为了保证正确,我们进入WinDbg选取一个表项进行测试,这里顺带说一声待会实现的时候要注意,在查询CidTableCode时低两位要抹掉零,不要问我怎么知道的,问就是看上面代码。
这一个系统的TableCode
为0xffffb10cc118e001
,由于低两位为1
可以确定为二级表,我们使用dp
指令查看抹除低两位后指向内存信息
0: kd> dp 0xffffb10c`c118e000
ffffb10c`c118e000 ffffb10c`bd6b1000 ffffb10c`c118f000
ffffb10c`c118e010 ffffb10c`c1a4b000 ffffb10c`c25fe000
ffffb10c`c118e020 ffffb10c`c2fff000 ffffb10c`c16de000
ffffb10c`c118e030 ffffb10c`c40fb000 00000000`00000000
ffffb10c`c118e040 00000000`00000000 00000000`00000000
ffffb10c`c118e050 00000000`00000000 00000000`00000000
ffffb10c`c118e060 00000000`00000000 00000000`00000000
ffffb10c`c118e070 00000000`00000000 00000000`00000000
上面每八个字节的指针都指向一个一级句柄表,我们选取ffffb10cbd6b1000
做测试
0: kd> dp ffffb10c`bd6b1000
ffffb10c`bd6b1000 00000000`00000000 00000000`00000000
ffffb10c`bd6b1010 d0871be5`f040f6a5 00000000`00000000
ffffb10c`bd6b1020 d0871bf9`3080ffef 00000000`00000000
ffffb10c`bd6b1030 d0871be7`f4800001 00000000`00000000
ffffb10c`bd6b1040 d0871bf7`90800001 00000000`00000000
ffffb10c`bd6b1050 d0871bf4`60800001 00000000`00000000
ffffb10c`bd6b1060 d0871bf9`b0800001 00000000`00000000
ffffb10c`bd6b1070 d0871bfc`91400001 00000000`00000000
这个便是一级表,可以发现每
16
b
y
t
e
s
16bytes
16bytes才有一次数据,按照上文提及的解密方法进行解密(这里偷懒直接扔程序算)
带入WinDbg进行检验
0: kd> dt _eprocess ffffd0871be5f040
nt!_EPROCESS
+0x000 Pcb : _KPROCESS
+0x438 ProcessLock : _EX_PUSH_LOCK
+0x440 UniqueProcessId : 0x00000000`00000004 Void
+0x448 ActiveProcessLinks : _LIST_ENTRY [ 0xffffd087`1bed74c8 - 0xfffff801`6be3af60 ]
+0x458 RundownProtect : _EX_RUNDOWN_REF
+0x460 Flags2 : 0xd000
+0x460 JobNotReallyActive : 0y0
+0x460 AccountingFolded : 0y0
+0x460 NewProcessReported : 0y0
+0x460 ExitProcessReported : 0y0
+0x460 ReportCommitChanges : 0y0
+0x460 LastReportMemory : 0y0
...
可以发现解密是正确的,此地址正是System进程的ERPCOESS对象
Part2.实现
Part2.1 查找PspCidTable表
众所周知,微软为提升系统稳定性,对于这种内核API的基函数、变量都是不导出的,但是我们通过刚才的反编译可以看到系统在PspReferenceCidTableEntry
函数中引用了PspCidTable
但是很遗憾他也不导出,但是PsLookupProcessByProcessId
他导出!所以我们可以枚举该函数的内存,跟随第一个call
访问到PspReferenceCidTableEntry
中再寻找PspCidTable
即可
Part2.2实现代码
#include"stdafx.h"
#include"Proc.h"
DWORD TargetPID;
ULONG64 ret = 0;
ULONG64 cidTableAddr = 0;
BOOLEAN found;
BOOLEAN GetPspCidTable(ULONG64* tableAddr)
{
UNICODE_STRING uc_funcName;
RtlInitUnicodeString(&uc_funcName, L"PsLookupProcessByProcessId");
ULONG64 func = MmGetSystemRoutineAddress(&uc_funcName);
if (func == NULL)
return FALSE;
ULONG64 Psp_func = 0;
for (int i = 0; i < 47; i++)
if (*(PUCHAR)(func + i) == 0xE8) {
Psp_func = func + i;
break;
}
if (Psp_func != 0)
{
int i_callCode = *(int*)(Psp_func + 1);
ULONG64 ul_callJmp = Psp_func + i_callCode + 5;
for (int i = 0; i < 0x3A; i++)
{
if (*(PUCHAR)(ul_callJmp + i) == 0x48 &&
*(PUCHAR)(ul_callJmp + i + 1) == 0x8b &&
*(PUCHAR)(ul_callJmp + i + 2) == 0x05)
{
int i_movCode = *(int*)(ul_callJmp + i + 3);
ULONG64 ul_movJmp = ul_callJmp + i + i_movCode + 7;
*tableAddr = ul_movJmp;
return TRUE;
}
}
}
return FALSE;
}
}
Part3.应用
可以用来遍历部分病毒/勒索程序 隐藏的进程来实现ARK
当然还有更多其他的功能
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)