温馨提示

本文的内容均在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; // raxvolatile signed __int64 *v4; // rsi__int64 v5; // r14signed __int64 v6; // rcx__int64 v7; // rdiunsigned __int128 v8; // rt0unsigned __int8 v9; // ttunsigned __int64 v10; // rax_BYTE *v11; // rdiint v13; // ebxbool v14; // zfsigned __int64 v15; // raxsigned __int64 v16; // rtt__int64 v17; // rcx_QWORD *v18; // rcxunsigned __int64 v19; // raxint v20[8]; // [rsp+0h] [rbp-48h] BYREFunsigned __int128 v21; // [rsp+20h] [rbp-28h]__int128 v22; // [rsp+30h] [rbp-18h]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;elsev14 = (*(_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时低两位要抹掉零,不要问我怎么知道的,问就是看上面代码。

这一个系统的TableCode0xffffb10cc118e001,由于低两位为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

这个便是一级表,可以发现每16bytes16bytes16bytes才有一次数据,按照上文提及的解密方法进行解密(这里偷懒直接扔程序算)

带入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++) //The origin jmp address in 22000.466 is PsLook...+ 27if (*(PUCHAR)(func + i) == 0xE8) { // The First CallPsp_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++) // The origin jmp code in 22000.466 is 0x1A {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
当然还有更多其他的功能

【Windows内核编程】Win10/Win11通过PspCidTable取得EProcess相关推荐

  1. 寒江独钓Windows内核编程-双机调试1

    今天总结一下关于双机调试,前面一直使用的是DDK包进行NT式与WDM式驱动入门,至今已进入使用WDK包进行编程了,DDK包早已落后我只作为入门因为大体内容变化不大.我使用的书是<寒江独钓Wind ...

  2. 2、从汇编语言到Windows内核编程笔记(2)

    内核线程 在驱动中生成的线程一般是系统线程.系统线程所在的进程名为"System". NTSTATUS PsCreateSystemThread( OUT PHANDLE Thre ...

  3. Windows内核编程实现拦截Xuetr程序

    Windows内核编程实现拦截Xuetr程序 ----TTL 寒假的时候,开始学习windows内核编程,想走近windows的内部世界.由于微软对于windows并不开源,所以有些人开始质疑:学习w ...

  4. 【windows内核编程】vs2013+WDK8.1+winDbg+vmware win7虚拟机联调

    [我的]vs2013+WDK8.1+winDbg+vmwarewin7虚拟机联调 作者:zcr214 时间:2016/4/8 内核驱动开发,首先要配置开发环境,目前微软已经出到了vs2015+WDK1 ...

  5. Windows内核编程(三)-内核驱动运行与调试

    内核驱动运行与调试 驱动的运行 驱动的运行通过服务来实现. 微软规定,驱动文件必须经过微软的数字签名后,才可以运行在64位系统上,如果把没有经过签名的驱动直接放在64位操作系统中运行,结果是驱动加载失 ...

  6. Windows内核编程生成.sys文件需要签名才能加载

    作者最近因为在做内核驱动的作业,在本机WIndows10+WDK10上简单实现了一个驱动,满心欢喜放在Win7虚拟机里运行结果各种一直有提示需要签名,作者查了许多资料,包括开机F8取消强制签名,进入调 ...

  7. windows内核编程读书摘抄

    Windows API中的某些函数,比如Wi n E x e c和O p e n F i l e等,只是为了实现与1 6位Wi n d o w s程 序的向后兼容而存在,因此,应该避免使用.应该使用对 ...

  8. 西电网络攻防大赛Windows内核编程第3题

    一.题目: 编写程序,实现可注入dll模块到任意启动的程序,用户态界面模块完成选择dll和需要注入的程序,内核模块完成注入功能. 二.目前的思路: 1.先完成内核模块,实现注入dll模块到程序: 2. ...

  9. 寒江独钓-Windows内核安全编程(完整版).pdf

    寒江独钓-Windows内核安全编程(完整版).pdf   编写Windows内核程序,就意味着这个程序可以执行任意指令,可以访问计算机所有的软件.硬件资源.因此,稍有不慎就有可能将系统变得不稳定.W ...

最新文章

  1. 【cocos2d-x 仙凡奇缘-网游研发(2) 角色换线系统】
  2. 华为5720设置静态路由不通_如何设置静态路由与网关?一文了解清楚
  3. 全国python一级考试时间_2019年北京全国计算机一级考试时间
  4. Sql语句之递归查询
  5. *【CodeForces - 1088 ABC】套题比赛,A水题B模拟C构造D交互
  6. 《C++ Primer 4th》读书笔记 第7章-函数
  7. EfficientDet:COCO 51.0 mAP!谷歌大脑提出目标检测新标杆
  8. linux下node-webkit安装vlc插件
  9. 用Ruby读取Excel文件
  10. 百度文库下载器Python实现
  11. 科普下Tier1,Tier2,Tier3,Tier4 T1, T2, T3, T4
  12. 金融级云服务 平安云赋能保险业创新发展
  13. Jetson TK1 刷机步骤小记
  14. 如何在企业中从0-1建立一个数据/商业分析部门?
  15. 相濡以沫还是想忘于江湖
  16. Java利用HttpClient发送请求生成微信支付二维码、查询支付状态
  17. 电流、电压、电阻、电容、电感的通俗理解
  18. 认识TIA博途Portal软件平台
  19. 全球及中国醛酮树脂行业研究及十四五规划分析报告
  20. VC编程工具的灵活使用实验报告

热门文章

  1. 计算机操作系统:处理机调度相关
  2. 【ARM嵌入式】实验报告一 熟悉RealView MDK集成开发环境的使用
  3. Realview MDK 链接脚本文件详细解析(一)–链接符号
  4. ubuntu18.04 搭建ffmpeg踩坑
  5. Element ui 组件中用键盘事件
  6. 微信小程序商城篇(一)首页功能
  7. Word2003入门动画教程25:添加或改变Word页面边框
  8. win10连接文件服务器记住密码如何删除,win10如何删除局域网共享文件访问密码...
  9. 基于python的植物大战僵尸游戏开发
  10. 秀!如何搭建一个永久运行的个人服务器?