免责声明:内容仅供学习参考,请合法利用知识,禁止进行违法犯罪活动!
本次游戏没法给
内容参考于:微尘网络安全
上一个内容: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;
}
