文章总结: 本文详述了利用AI辅助分析CVE-2026-20805漏洞的全过程,从补丁比对定位DWM组件UAF漏洞,到构建GetRECT堆喷技术及创新利用链绕过CFG防护,最终实现本地权限提升至SYSTEM。文章展示了LLM显著降低漏洞利用门槛的能力,证明了AI在逆向工程与漏洞开发中的实战价值,并呼吁防御者积极利用AI加强安全建设。 综合评分: 94 文章分类: 漏洞分析,实战经验,二进制安全,漏洞POC,AI安全
利用大模型分析二进制漏洞-从漏洞补丁到获取SYSTEM权限(CVE-2026-20805)
幻泉之洲
2026年3月9日 11:17 北京
本文记录了如何利用AI辅助工具,分析2026年1月Windows补丁中的一个DWM组件漏洞(CVE-2026-20805),从补丁比对、漏洞分析、再到开发出可用本地权限提升漏洞的完整过程。文章展示了LLM如何降低了利用程序开发的门槛,并详细剖析了漏洞成因、利用技巧(如GetRECT堆喷)和最终获取代码执行权限的复杂链。
补丁比对一直吸引着我。其中的乐趣部分在于与时间的赛跑,逆向分析,尝试赶在补丁日当天或次日就实现“一日漏洞”的成就。对于高端的Windows目标,Valentina Palmiotti和Ruben Boonen在近三年前就证明了这是可能的。但他们是这个世界上最有才华的利用程序开发者之一。LLM能否为我们这些普通人提高能力下限?幸运,或者说略带一丝警觉地说,答案是肯定的。
寻找目标
2026年1月补丁星期二的安全公告一发布,我就开始了搜索,目标是识别一个已修复的漏洞,并希望能够为其开发出可用的漏洞利用程序。目标列表里优先级最高的,是那些已知在野被利用的漏洞。一月份的补丁中包含一个存在于桌面窗口管理器中的信息泄露漏洞,这引起了我的注意。公告里还提到了第二个可能导致本地权限提升的DWM漏洞。历史上,DWM一直是本地权限提升的热门目标。有时要精确识别被修补的组件可能很棘手,但就DWM而言,dwmcore.dll总是个稳妥的选择。
在用Ghidra处理完文件并为每个函数提取BSim向量后,高亮显示它们之间的差异变得非常容易。更不用说,微软的补丁常常伴随着新的功能标记。不用说,DeepSeek-V3模型(Opus 4.5)很快就完成了补丁比对工作,并在几分钟内就识别出了其中一个漏洞。
======================================================================
BSim PATCH DIFF REPORT
File 1: dwmcore_vuln.dll
File 2: dwmcore_patched.dll
TOP 10 MOST MODIFIED FUNCTIONS
dwmcore_vuln.dll dwmcore_patched.dll Sim Jaccard
FUN_1802e7842 FUN_1802e7842 0.1191 0.0632 FUN_1802e92d6 FUN_1802e92d6 0.1470 0.0722 FUN_1802e5faa FUN_1802e5faa 0.1741 0.0769 ~CDelegatedInkCanvas ~CDelegatedInkCanvas 0.7556 0.6047 GetBufferedOutputTransformed GetBufferedOutputTransformed 0.7628 0.6154 FrameStarted FrameStarted 0.7833 0.6429 ~CSynchronousSuperWetInk ~CSynchronousSuperWetInk 0.8018 0.6667 FUN_1802f5aa2 FUN_1802f5aa2 0.9127 0.8393
FUN_1802f57d2 FUN_1802f5d72 0.9127 0.8393
从这开始,我得说,构建一个功能性的漏洞利用程序所花费的时间,比我预想的要痛苦得多。我熬了很多个通宵和周末,不断地与模型纠缠和调试。这很大程度上源于我个人对这个漏洞类别和子系统的不熟悉。最终,我们取得了胜利,成功从低权限进入DWM并最终抵达SYSTEM权限。在这个过程中,我发现了多个新颖的利用技术,比如GetRECT堆喷、新的链,以及一条DWM-to-SYSTEM的路径。然而,在掌握了这些技术以及其他一些工具,再加上更新版模型(如Opus 4.6)的帮助下,从发现DWM中的一个UAF漏洞到开发出功能性漏洞利用程序的时间,从3周缩短到了几个小时。
漏洞剖析
漏洞是CSynchronousSuperWetInk类中一个释放后使用。析构函数根据IsSuperWetCompatible()的返回值,有条件地从CSuperWetInkManager中移除对象。
void CSynchronousSuperWetInk::~CSynchronousSuperWetInk(CSynchronousSuperWetInk *this) { this->vtable = &_vftable_; bool bVar2 = IsSuperWetCompatible(this); if (bVar2) { CSuperWetInkManager::RemoveSource(this->composition->superWetInkManager, this); } // … cleanup continues }
这是dwmcore.dll版本10.0.26100.7309中的脆弱析构函数。
bool CSynchronousSuperWetInk::IsSuperWetCompatible(CSynchronousSuperWetInk *this) { if ((this->LookupMode == 2 || this->notifier1 != NULL) && this->clipEntry != NULL && this->comObject != NULL) { return true; } return false; }
这是dwmcore.dll版本10.0.26100.7309中的IsSuperWetCompatible条件。
该函数仅在LookupMode等于2,或者notifier1非空,**并且**clipEntry和comObject都非空时才返回true。
攻击者可以:
-
向管理器注册一个
CSynchronousSuperWetInk(在Draw()期间需要LookupMode=2) -
通过
CMD_SET_PROPERTY将LookupMode更改为0 -
通过
CMD_RELEASE_RESOURCE触发析构 -
IsSuperWetCompatible()返回
FALSE→ 跳过RemoveSource() -
一个悬垂指针留在了
CSuperWetInkManager::localStrokesVector中
当DWM稍后遍历这个向量时,它会解引用已释放对象的虚函数表,导致受控的代码执行。
修复方案
补丁增加了一个功能标记Feature_1732988217。启用时,无论IsSuperWetCompatible()结果如何,都会无条件调用RemoveSource()。这确保了对象在析构期间总能被正确地从管理器中注销,消除了悬垂指针。
void CSynchronousSuperWetInk::~CSynchronousSuperWetInk(CSynchronousSuperWetInk *this) { *(undefined ***)this = &_vftable_; bool bVar2 = wil::details::FeatureImpl::__private_IsEnabled(&impl); if (!bVar2) { bVar2 = IsSuperWetCompatible(this); if (!bVar2) goto LAB_1802a9b1a; // 只有在功能禁用 且 !compatible时跳过RemoveSource } CSuperWetInkManager::RemoveSource(…, this); LAB_1802a9b1a: // … cleanup continues }
这是dwmcore.dll版本10.0.26100.7623中修复后的析构函数。
漏洞利用
UAF可以通过DirectComposition API从一个普通的用户模式应用程序触发。该攻击无需特殊权限。
先决条件
- 创建具有BGRA支持的D3D11设备,以及用于可见窗口的交换链。
- 通过
DCompositionCreateDevice()并使用DXGI设备初始化DirectComposition设备。 - 拦截或直接通过
win32u.dll调用NtDCompositionProcessChannelBatchBuffer和NtDCompositionCommitChannel来注入原始批处理缓冲区命令。
触发序列
-
创建墨迹轨迹(分配CSynchronousSuperWetInk):
从DirectComposition设备查询
IDCompositionInkTrailDevice,然后调用CreateDelegatedInkTrailForSwapChain()或CreateDelegatedInkTrail()。这会在dwm.exe的堆上分配一个CSynchronousSuperWetInk对象。 -
创建视觉对象并设置LookupMode=2:
注入批处理缓冲区命令来创建关联的视觉对象,并将其连接到墨迹源,然后将该源上的
LookupMode设置为2,最后将其关联到合成树。LookupMode=2确保在Draw()期间IsSuperWetCompatible()返回TRUE,从而将对象注册到管理器的内部向量中。 -
渲染帧以在管理器中注册:
呈现多个帧并提交DirectComposition更改。这会触发DWM的渲染循环,该循环调用墨迹基础设施并将
CSynchronousSuperWetInk指针注册到管理器的内部向量中。 -
设置LookupMode=0(绕过移除检查):
注入
CMD_SET_PROPERTY将LookupMode更改为0。现在IsSuperWetCompatible()将返回FALSE。 -
释放墨迹轨迹(创建悬垂指针):
断开视觉引用,然后释放
IDCompositionDelegatedInkTrail接口。当析构函数运行时,它会调用IsSuperWetCompatible(),该函数返回FALSE,于是RemoveSource()被跳过,对象被释放但其指针仍保留在管理器的向量中。 -
触发DirtyActiveInk(释放后使用):
继续呈现帧并使窗口无效。DWM的合成循环调用
CSuperWetInkManager::DirtyActiveInk(),该函数遍历向量并解引用悬垂指针。
如果没有堆喷,DWM在访问已释放内存时会崩溃。通过控制什么数据占用了已释放的内存分配,攻击者可以伪造一个虚函数表,并通过位于vtable+0x50的虚调用实现任意代码执行。
堆喷技术
为了利用UAF,我们必须用包含伪造虚函数表的攻击者控制的数据来“回收”已释放的CSynchronousSuperWetInk分配。这一节记录了我们称之为GetRECT的CRegionGeometry RECT缓冲区喷射技术。
目标对象属性:CSynchronousSuperWetInk,大小为0x120字节,通过GetProcessHeap()分配,位于低碎片化堆的桶34。
喷射基础: 喷射使用CRegionGeometry资源与RECT数组数据。18个RECT正好是288字节,与目标大小完美匹配。RECT缓冲区通过CMD_SET_BUFFER_PROPERTY命令写入。
18个RECT为我们提供了对被“回收”内存的完全控制,关键偏移量用于伪造虚表指针。
static void SetU64(int32_t* lo, int32_t* hi, uint64_t val) { *lo = (int32_t)(val & 0xFFFFFFFF); *hi = (int32_t)(val >> 32); }
利用原语
UAF给了我们一个受控的虚表调用,其中RCX指向我们喷射的虚假对象。当DirtyActiveInk遍历悬垂指针时,它会从[[spray]+0x50]读取函数指针并调用它,RCX作为参数传递。
目标函数的限制
最初我们有两个限制:目标必须在控制流防护位图中标记为有效的调用目标,并且必须有一个指向它的指针存在于可读内存中,我们不能直接调用任意地址。
链:__fnINSTRING + CStdAsyncStubBuffer2_Disconnect
有了受控的虚表调用,剩下的挑战是将CFG有效的链连接起来以实现任意代码执行。我们开发了一条新颖的链,但它需要2次成功的利用尝试,降低了可靠性。因此,我们转向使用两个Windows系统DLL的两个公开技术:__fnINSTRING和CStdAsyncStubBuffer2_Disconnect。
第一阶段:__fnINSTRING – 无需泄露的Kernel Callback Dispatch
Windows内核通过位于PEB偏移量+0x58处的内核回调表与用户模式通信。表中的每个条目都指向user32.dll中的一个__fn*处理程序。这些函数是CFG有效的调用目标,并且在可读内存中有指向它们的指针。我们将伪造的虚表指向&KCT[fnINSTRING_index] - 0x50,当解引用时,它会读取KCT条目并分发到__fnINSTRING。
__fnINSTRING内部会将其参数视为一个_CAPTUREBUF结构,并在分发内部函数之前调用FixupCallbackPointers。这个函数从缓冲区读取一个修正表,并通过添加缓冲区的基地址将相对偏移量转换为绝对地址。这消除了对堆地址泄露的需求。在修复之后,__fnINSTRING分派内部函数指针。
第二阶段:CStdAsyncStubBuffer2_Disconnect – 两个链式的虚表调用
CStdAsyncStubBuffer2_Disconnect从combase.dll导出,是一个CFG有效的稳定地址。它的反汇编揭示了一个有用的原语:两个具有保留参数寄存器的顺序虚表分派。
-
虚表调用 #1:VirtualProtect → RWX:
我们构建一个自引用的假对象。
CStdAsyncStubBuffer2_Disconnect中的第一个虚表调用使用VirtualProtect的参数进行设置。这个调用将喷射缓冲区的内存页标记为RWX,并更新CFG位图以允许从该区域执行。 -
虚表调用 #2:内联Shellcode:
在
VirtualProtect返回后,Disconnect为第二次虚表分派加载[this+0x10]。指针链逐步解析,最终分发到位于base+0xD0的我们的shellcode。
Shellcode布局
Shellcode分为两个阶段,因为VirtualProtect地址数据位于+0xE8(被第一次调用用作vtable_1[0x20]),这在我们的可执行区域中间留下了一个空档。
阶段1: 保存寄存器,分配影子空间,加载SW_SHOW,通过movabs RAX加载WinExec的绝对地址,然后跳转到阶段2。
阶段2: 使用RIP相对指针调用WinExec执行cmd.exe,对喷射进行无害化处理以实现安全重入,然后修复堆栈以直接返回到DWM的合成循环。add rsp, 0xB8是关键,它跳过中间帧直接返回。
安全重入:无害化处理
DWM的DirtyActiveInk可能多次遍历悬垂指针。如果没有无害化处理,每次重入都会重新触发完整的链并导致崩溃。Shellcode重写喷射的虚表指针,使后续的解引用走一个无害的路径:指向一个ret指令。
完整的链总结
DirtyActiveInk遍历悬垂指针 → [[spray+0x00]+0x50] = __fnINSTRING(spray) → FixupCallbackPointers: 8个相对偏移 → 绝对地址 → Dispatch: CStdAsyncStubBuffer2_Disconnect(…) → 虚表调用 #1: VirtualProtect(…) → 喷射缓冲区页现在变为RWX,CFG位图更新 → 虚表调用 #2: shellcode at base+0xD0 → WinExec(“cmd.exe”, SW_SHOW) → 无害化处理:为安全重入重写虚表 → 堆栈修复: add rsp, 0xB8 以跳过Disconnect + __fnINSTRING帧 → 直接RET到DWM合成循环 → DirtyActiveInk重入: [[base]+0x50] = ret → 干净返回
最终思考
我们现在拥有的模型在那些历史上需要多年深耕才能获得的专业领域,已经具有了非常强的能力。这包括逆向工程、漏洞发现和利用程序开发。它们的能力分布并不均匀,目前还无法与这些领域的世界顶尖高手匹敌。然而,模型的进步步伐目前看起来没有放缓的迹象。
这为防御者提供了公平的竞技场,但也提升了攻击者的能力。虽然一直以来都存在着对抗性的猫鼠游戏,但这方面并无新意,但至少在短期内,攻击者拥有不对称的优势来利用这些工具进行破坏。攻击者可以更快地行动,而几乎不用担心AI系统的安全性。
防御者必须利用AI对其代码(查找漏洞)、安全产品(查找检测漏洞)和其企业(对手模拟)进行攻击性用途,抢在攻击者之前发现弱点并迭代改进防御措施。不幸的是,那些没有安全团队的小型组织可能会在近期承受大部分的痛苦。
我的希望是,长期来看,安全社区能够共同在攻防研究上比攻击者投入更多,最终我们走出这个时代时,处境能比开始时更好。
免责声明:
本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。
任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。
本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我。
本文转载自:幻泉之洲 《利用大模型分析二进制漏洞-从漏洞补丁到获取SYSTEM权限(CVE-2026-20805)》
版权声明
本站仅做备份收录,仅供研究与教学参考之用。
读者将信息用于其他用途的,全部法律及连带责任由读者自行承担,本站不承担任何责任。









评论