25.实现过TP保护ACE保护NP保护BE保护EAC保护-内存读写检测(私有句柄表和全局句柄表的句柄提权)-Windows驱动

免责声明:内容仅供学习参考,请合法利用知识,禁止进行违法犯罪活动!

本次游戏没法给

内容参考于:微尘网络安全

上一个内容:24.IDA逆向句柄表算法-Windows驱动

效果图:

不管什么检测,在句柄上的处理都是一样的,下方是提权代码,原理就是找到要提权进程的_EPROCESS结构,然后读取它里面的私有句柄表,然后从0开始遍历,一直到找不到(NULL),把它们的权限全部设置为0x1FFFFF,也就是最高权限就可以了,当前操作系统版本是Windows11的10.0.26200版本,如果其它版本结构体可能会有变化,需要去https://www.vergiliusproject.com/这里找,注意这里给的结构体没问题,但是union会有问题,怎样修改参考下方的结构体,一般变化不大,或者复制windbg里的结构信息,让ai生成,所以电脑设置好后一定不能更新,更新了可能就没法用了,或者也可以不用结构体,可以直接使用偏移的方式遍历私有句柄表

c++ 复制代码
// 【核心宏1】句柄值递增步长:Windows句柄表中句柄值按4递增(系统规定,避免句柄冲突)
#define HANDLE_VALUE_INC 4

// 【EXHANDLE结构体】- 系统底层句柄表示结构(8字节)
// 作用:封装句柄的核心信息(Tag标记位 + Index索引),是访问句柄表的"索引钥匙"
//0x8 bytes (sizeof)
typedef struct  _EXHANDLE
{
	union
	{
		struct
		{
			ULONG TagBits : 2;        //0x0  句柄标记位(2位):用于系统内部校验句柄有效性
			ULONG Index : 30;         //0x0  句柄索引(30位):句柄在句柄表中的位置索引(核心)
		};
		VOID* GenericHandleOverlay;   //0x0  通用句柄覆盖指针:兼容不同类型句柄的指针表示
		ULONGLONG Value;              //0x0  句柄的64位数值(整合TagBits+Index)
	};
} EXHANDLE, * PEXHANDLE;

// 【HANDLE_TABLE_ENTRY结构体】- 句柄表项(每个句柄对应一个该结构体,存储句柄核心属性)
// 作用:记录单个句柄的权限、引用计数、指向的内核对象(如进程/线程/文件)等信息
typedef struct _HANDLE_TABLE_ENTRY
{
	union
	{
		LONG_PTR VolatileLowValue;    // 易失性低32位值(多线程场景下的低值)
		LONG_PTR LowValue;            // 低32位值(核心存储区)
		PVOID InfoTable;              // 指向句柄信息表的指针
		LONG_PTR RefCountField;       // 句柄引用计数字段(记录多少个地方引用该句柄)
		struct
		{
			ULONG_PTR Unlocked : 1;       // 1位:句柄是否未锁定(0=锁定,1=未锁定)
			ULONG_PTR RefCnt : 16;       // 16位:句柄引用计数(最大值65535)
			ULONG_PTR Attributes : 3;    // 3位:句柄属性(如是否继承、是否保护)
			ULONG_PTR ObjectPointerBits : 44; // 44位:指向内核对象的指针(核心!需转换为真实指针)
		};
	};
	union
	{
		LONG_PTR HighValue;               // 高32位值
		struct _HANDLE_TABLE_ENTRY* NextFreeHandleEntry; // 指向下一个空闲句柄表项(内存管理用)
		EXHANDLE LeafHandleValue;         // 叶子节点句柄值(多层句柄表用)
		struct
		{
			ULONG32 GrantedAccessBits : 25; // 25位:句柄的实际授权权限(核心!对应之前的0x1FFFFF)
			ULONG32 NoRightsUpgrade : 1;    // 1位:是否禁止权限升级(0=允许,1=禁止)
			ULONG32 Spare1 : 6;             // 6位:预留位(无实际作用)
		};
		ULONG32 Spare2;                   // 备用32位值
	};
} HANDLE_TABLE_ENTRY, * PHANDLE_TABLE_ENTRY;

// 【HANDLE_TABLE_FREE_LIST结构体】- 句柄表空闲链表(0x40字节)
// 作用:管理句柄表中的空闲表项,提高句柄分配/释放效率
typedef struct _HANDLE_TABLE_FREE_LIST
{
	ULONG_PTR FreeListLock;              //0x0  空闲链表锁(防止多线程操作冲突)
	PHANDLE_TABLE_ENTRY FirstFreeHandleEntry; //0x8  第一个空闲句柄表项指针
	PHANDLE_TABLE_ENTRY LastFreeHandleEntry;  //0x10 最后一个空闲句柄表项指针
	LONG HandleCount;                    //0x18 空闲句柄数量
	ULONG HighWaterMark;                 //0x1c 句柄表使用的高水位标记(记录最大使用量)
}HANDLE_TABLE_FREE_LIST,* PHANDLE_TABLE_FREE_LIST;

// 【HANDLE_TABLE结构体】- 进程句柄表的根结构(0x80字节)
// 作用:每个进程有且仅有一个该结构体,是整个句柄表的"总控台"
//0x80 bytes (sizeof)
typedef struct _HANDLE_TABLE
{
	ULONG NextHandleNeedingPool;         //0x0  下一个需要分配内存池的句柄值(句柄表扩容用)
	LONG ExtraInfoPages;                 //0x4  额外信息页数量(句柄表扩展页)
	volatile ULONGLONG TableCode;        //0x8  句柄表代码(核心!存储句柄表层级+基地址)
	struct _EPROCESS* QuotaProcess;      //0x10 关联的配额进程(权限配额管控)
	struct _LIST_ENTRY HandleTableList;  //0x18 句柄表链表节点(系统串联所有进程句柄表)
	ULONG UniqueProcessId;               //0x28 进程PID(句柄表所属进程的ID)
	union
	{
		ULONG Flags;                     //0x2c 句柄表标记位
		struct
		{
			UCHAR StrictFIFO : 1;            //0x2c 严格FIFO分配句柄(0=否,1=是)
			UCHAR EnableHandleExceptions : 1;//0x2c 是否启用句柄异常(0=否,1=是)
			UCHAR Rundown : 1;               //0x2c 句柄表是否正在清理(0=否,1=是)
			UCHAR Duplicated : 1;            //0x2c 句柄表是否被复制(0=否,1=是)
			UCHAR RaiseUMExceptionOnInvalidHandleClose : 1; //0x2c 关闭无效句柄时是否抛用户层异常
		};
	};
	ULONG_PTR HandleContentionEvent;     //0x30 句柄竞争事件(多线程操作句柄时的同步事件)
	ULONG_PTR HandleTableLock;           //0x38 句柄表锁(保护句柄表读写)
	union
	{
		HANDLE_TABLE_FREE_LIST FreeLists[1]; //0x40 空闲链表(管理空闲句柄表项)
		struct
		{
			UCHAR ActualEntry[32];          //0x40 实际句柄表项(初始32个)
			struct _HANDLE_TRACE_DEBUG_INFO* DebugInfo; //0x60 调试信息指针
		};
	};
} HANDLE_TABLE, * PHANDLE_TABLE;

// 【核心宏2】Win11系统中EPROCESS结构体里句柄表的偏移量(0x300)
// 作用:通过EPROCESS+0x300能找到进程的句柄表根结构(不同系统版本偏移不同,Win11固定0x300)
#define HANDLE_TABLE_OFFSET_WIN11	0x300
// 【核心宏3】页大小(等于系统PAGE_SIZE,通常0x1000=4096字节)
#define TABLE_PAGE_SIZE	PAGE_SIZE
// 【核心宏4】低层级句柄表项数量 = 页大小 / 单个句柄表项大小(计算单层句柄表能存多少个句柄)
#define LOWLEVEL_COUNT (TABLE_PAGE_SIZE / sizeof(HANDLE_TABLE_ENTRY))
// 【核心宏5】中间层级句柄表项数量 = 页大小 / 句柄表项指针大小(多层句柄表用)
#define MIDLEVEL_COUNT (PAGE_SIZE / sizeof(PHANDLE_TABLE_ENTRY))

// 【ExpLookupHandleTableEntry函数】- 核心!根据句柄值查找对应的句柄表项
// 入参1:HandleTable - 进程句柄表根结构指针
// 入参2:tHandle - 要查找的EXHANDLE句柄
// 返回值:PHANDLE_TABLE_ENTRY - 找到的句柄表项(NULL=未找到)
// 作用:把"句柄值"转换成"句柄表项指针",是操作句柄权限的前提
PHANDLE_TABLE_ENTRY ExpLookupHandleTableEntry(
	IN PHANDLE_TABLE HandleTable,
	IN EXHANDLE tHandle
)
{
	ULONG_PTR i, j, k;                  // 临时变量:存储句柄表层级索引
	ULONG_PTR CapturedTable;            // 句柄表基地址(剥离层级后的地址)
	ULONG TableLevel;                   // 句柄表层级(0/1/2层,系统最多3层句柄表)
	PHANDLE_TABLE_ENTRY Entry = NULL;   // 要返回的句柄表项指针
	EXHANDLE Handle;                    // 处理后的句柄(清空TagBits)

	PUCHAR TableLevel1;                 // 1级句柄表基地址(最底层)
	PUCHAR TableLevel2;                 // 2级句柄表基地址(中间层)
	PUCHAR TableLevel3;                 // 3级句柄表基地址(最高层)

	ULONG_PTR MaxHandle;                // 进程句柄表的最大句柄值(超过则无效)

	PAGED_CODE();                       // 标记该函数可在分页内存中执行(内核规范)
	//__debugbreak();                  // 调试断点(可注释)

	// 步骤1:清理句柄的TagBits(只保留Index索引,避免标记位干扰查找)
	Handle = tHandle;
	Handle.TagBits = 0;

	// 步骤2:检查句柄值是否超出进程句柄表的最大范围(超出则无效)
	MaxHandle = *(volatile ULONG*)&HandleTable->NextHandleNeedingPool;
	if (Handle.Value >= MaxHandle)
	{
		return NULL;
	}

	// 步骤3:获取句柄表基地址+层级(TableCode=基地址+层级(低2位))
	CapturedTable = *(volatile ULONG_PTR*)&HandleTable->TableCode;
	TableLevel = (ULONG)(CapturedTable & 3); // 低2位是层级(0/1/2)
	CapturedTable = CapturedTable - TableLevel; // 剥离层级,得到纯基地址

	// 步骤4:根据句柄表层级查找对应的句柄表项(系统句柄表分0/1/2层,层数越多存储的句柄越多)
	switch (TableLevel)
	{
	// 层级0:单层句柄表(小进程,句柄少)
	case 0:
	{
		TableLevel1 = (PUCHAR)CapturedTable; // 1级表基地址

		// 计算句柄表项位置:基地址 + 句柄值 * (表项大小/递增步长)
		Entry = (PHANDLE_TABLE_ENTRY)&TableLevel1[Handle.Value *
			(sizeof(HANDLE_TABLE_ENTRY) / HANDLE_VALUE_INC)];

		break;
	}

	// 层级1:双层句柄表(中等进程,句柄较多)
	case 1:
	{
		TableLevel2 = (PUCHAR)CapturedTable; // 2级表基地址

		// 计算低层级索引i + 中间层索引j
		i = Handle.Value % (LOWLEVEL_COUNT * HANDLE_VALUE_INC);
		Handle.Value -= i;
		j = Handle.Value / ((LOWLEVEL_COUNT * HANDLE_VALUE_INC) / sizeof(PHANDLE_TABLE_ENTRY));

		// 先找2级表→再找1级表→最后找具体表项
		TableLevel1 = (PUCHAR) * (PHANDLE_TABLE_ENTRY*)&TableLevel2[j];
		Entry = (PHANDLE_TABLE_ENTRY)&TableLevel1[i * (sizeof(HANDLE_TABLE_ENTRY) / HANDLE_VALUE_INC)];

		break;
	}

	// 层级2:三层句柄表(大进程,句柄极多)
	case 2:
	{
		TableLevel3 = (PUCHAR)CapturedTable; // 3级表基地址

		// 计算低层级索引i + 中间层索引j + 高层级索引k
		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;

		// 先找3级表→再找2级表→再找1级表→最后找具体表项
		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; // 返回找到的句柄表项(NULL=未找到)
}

// 【内核导出函数声明】ObGetObjectType - 获取内核对象的类型(如Process/Thread/File)
// 作用:判断句柄指向的对象是不是"进程"类型(核心!只修改进程句柄的权限)
NTKERNELAPI POBJECT_TYPE ObGetObjectType(PVOID Object);

// 【RestoreObjectAccess函数】- 核心!遍历目标进程的句柄表,修改所有Process类型句柄的权限为0x1FFFFF
// 入参:EProcess - 目标进程的EPROCESS指针
// 返回值:NTSTATUS - 执行状态(STATUS_SUCCESS=成功,其他=失败)
NTSTATUS RestoreObjectAccess(PEPROCESS EProcess/*,ULONG32 PassiveId */)
{
	NTSTATUS Status = STATUS_UNSUCCESSFUL; // 默认返回失败
	ULONG_PTR Handle = 0;                 // 遍历用的句柄值(从0开始递增)
	PHANDLE_TABLE_ENTRY Entry = NULL;     // 当前遍历到的句柄表项
	PVOID Object = NULL;                  // 句柄指向的内核对象(如进程对象)
	POBJECT_TYPE ObjectType = NULL;       // 内核对象类型(如Process)

	// 步骤1:遍历目标进程的所有句柄(按4递增,符合HANDLE_VALUE_INC)
	for (Handle = 0;; Handle += HANDLE_VALUE_INC)
	{
		// 步骤2:根据句柄值查找对应的句柄表项
		// 关键:(PUCHAR)EProcess + HANDLE_TABLE_OFFSET_WIN11 → 找到进程的句柄表根结构
		Entry = ExpLookupHandleTableEntry(*(PHANDLE_TABLE*)((PUCHAR)EProcess + HANDLE_TABLE_OFFSET_WIN11), *(PEXHANDLE)&Handle);
		if (Entry == NULL) // 句柄表项为空(遍历到末尾),退出循环
		{
			break;
		}

		// 步骤3:从句柄表项的ObjectPointerBits还原真实的内核对象指针
		// ObjectPointerBits是44位,需左移4位 + 高位补0xFFFF + 偏移0x30(内核对象地址转换规则)
		*(ULONG_PTR*)&Object = Entry->ObjectPointerBits;
		*(ULONG_PTR*)&Object <<= 4;          // 左移4位(补全指针位)
		if (Object == NULL)                  // 对象指针无效,跳过
		{
			continue;
		}
		*(ULONG_PTR*)&Object |= 0xFFFF000000000000; // 高位补0xFFFF(64位指针规则)
		*(ULONG_PTR*)&Object += 0x30;        // 偏移0x30(指向真实的内核对象)

		// 步骤4:获取对象类型(判断是不是Process类型)
		ObjectType = ObGetObjectType(Object);
		if (ObjectType == NULL)              // 对象类型无效,跳过
		{
			continue;
		}

		// 步骤5:判断对象类型是否是"Process"(进程)
		// 关键:ObjectType+0x18是对象类型名(Unicode字符串,如L"Process")
		if (wcscmp(*(PCWSTR*)((PUCHAR)ObjectType + 0x18), L"Process") == 0)
		{
			// 调试打印:确认找到Process类型句柄,开始修改权限
			DbgPrintEx(77, 0, "R0 GrantedAccessBits\n\n");
			// 核心操作:将句柄的实际授权权限设为0x1FFFFF(PROCESS_ALL_ACCESS,最大权限)
			// GrantedAccessBits是25位,刚好能容纳0x1FFFFF(21位),无溢出
			Entry->GrantedAccessBits = 0x1FFFFF;
		}
	}

	// 步骤6:设置返回状态为成功,并释放进程对象引用(内核规范)
	Status = STATUS_SUCCESS;
	ObDereferenceObject(EProcess); // 释放EPROCESS引用(避免内存泄漏)

	return Status;
}

// 【进程句柄前置回调函数】- 所有进程句柄创建/复制操作前触发
// 核心逻辑:
// 1. 给"aaaa.exe"的句柄请求设置最大权限(0x1FFFFF);
// 2. 检测目标PID(18184)的进程,强制遍历其句柄表修改Process句柄权限为0x1FFFFF;
OB_PREOP_CALLBACK_STATUS PobPreOperationCallback(
	PVOID RegistrationContext,  // 注册回调时的自定义参数(此处为NULL)
	POB_PRE_OPERATION_INFORMATION OperationInformation  // 句柄操作详情
)
{
	// 步骤1:获取发起句柄操作的进程(操作方)EPROCESS
	PEPROCESS pep = IoGetCurrentProcess();
	// 步骤2:提取操作方进程名(如"aaaa.exe")
	char* nName = PsGetProcessImageFileName(pep);

	// 步骤3:判断操作方是否是"aaaa.exe"(不区分大小写)
	if (_strnicmp(nName, "aaaa.exe", strlen("aaaa.exe")) == 0) {
		// 调试打印:确认匹配到aaaa.exe
		DbgPrintEx(DPFLTR_IHVDRIVER_ID, 0, "进入nName = %s\n", nName);
		
		// 给aaaa.exe的句柄请求设置最大权限(创建/复制句柄都生效)
		OperationInformation->Parameters->CreateHandleInformation.DesiredAccess = 0x1FFFFF;
		OperationInformation->Parameters->DuplicateHandleInformation.DesiredAccess = 0x1FFFFF;
	}

	// 步骤4:处理目标PID(18184)的进程,强制修改其句柄权限
	PEPROCESS npep = NULL;
	// 提取被操作进程的PID(OperationInformation->Object是被操作进程的EPROCESS)
	UINT32  目标进程PID = (UINT32)(UINT_PTR)PsGetProcessId((PEPROCESS)OperationInformation->Object);
	// 判断被操作进程是否是目标PID(18184)
	if (目标进程PID == 18184)
	{
		//DbgPrintEx(77, 0, "进入目标进程 成功\n");
		//DbgBreakPoint();
		//DbgPrintEx(DPFLTR_IHVDRIVER_ID, 0, " 进入目标进程PID=%s \n", nName);
		
		// 根据PID查找目标进程的EPROCESS(成功则返回STATUS_SUCCESS)
		if (PsLookupProcessByProcessId((HANDLE)目标进程PID, &npep) == STATUS_SUCCESS)
		{
			// 核心调用:遍历目标进程句柄表,修改所有Process句柄权限为0x1FFFFF,提权
			RestoreObjectAccess(npep);
		}
	}

	// 【致命坑点】:必须返回回调状态!补充后才不会蓝屏
	// return OB_PREOP_SUCCESS;
}

相关推荐
程序猿_极客3 小时前
【2025 年最新版】Java JDK 安装与环境配置教程(附图文超详细,Windows+macOS 通用)
java·开发语言·windows·macos·jdk
C++ 老炮儿的技术栈6 小时前
在C++ 程序中调用被 C编译器编译后的函数,为什么要加 extern “C”声明?
c语言·c++·windows·git·vscode·visual studio
pVPkTAeVGPdZ8 小时前
码垛机器人分析与仿真:基于Matlab Simulink Simscape的探索
windows
一点事9 小时前
windows:安装rabbitMQ
windows·rabbitmq·ruby
SunkingYang9 小时前
Windows系统怎么修改系统文件如user32.dll的控制权限(无法保存对xxx权限所做的更改,拒绝访问,怎么处理)
windows·系统文件·控制权限修改·无法保存·拒绝访问
yaoxin5211239 小时前
296. Java Stream API - 二元操作符与“单位元“
java·服务器·windows
用什么都重名11 小时前
Conda 虚拟环境安装配置路径详解
windows·python·conda
万行14 小时前
企业级前后端认证方式
前端·windows
cws20040114 小时前
MFA双因素用户使用手册
运维·windows·网络安全·github·邮件·邮箱
颜子鱼14 小时前
Linux驱动-INPUT子系统
linux·c语言·驱动开发