
杀毒软件的沙箱本该隔离不可信的进程,丹麦安全团队 SAFA 在 Avast 身上找到了一个反向剧本:漏洞代码只对「已经被关进沙箱的进程」开放,攻击者要先想办法把自己关进去,才能触发这个最终通向 Windows 内核 SYSTEM 权限的缺陷。2026 年 9 月 18 日,SAFA 发布了这组研究的第二篇,完整公开了从内核内存越界写到 SYSTEM 令牌窃取的全部利用链。这组漏洞共用一个编号 CVE-2025-13032,影响 Avast 和 AVG 的全线杀毒产品,NVD 给出的厂商评分是 9.9(CRITICAL)。
一个先要「攻进去」的沙箱
故事从攻击面分析开始。SAFA 把目标锁定在 Avast 的内核驱动上——杀毒软件在内核里挂了一堆驱动做实时防护,其中 aswSnx 暴露的 IOCTL 处理器数量最多。IOCTL 是用户态程序向驱动发指令的通道,DeviceIoControl API 调进去之后,驱动代码就在内核态执行,这里历来是本地提权研究的富矿。
反编译显示 aswSnx 的字符串与 Avast、AVG、Norton、Avira 多个品牌共用,这些驱动来自同一套代码(Gen 数字旗下产品),潜在影响面比单一品牌更大。SAFA 没有逐一验证其他品牌,但代码同源的线索摆在那里。
静态审计很快有了收获。一个编号 0x82AC0204 的 IOCTL 会处理用户传入的字符串,驱动用 ProbeForRead 检查了指针指向的用户态地址,却没有把数据先复制进内核——内核代码直接读着用户态内存干活。用户态进程随时可以改自己内存里的值,于是「先读一次定长度、再读一次做拷贝」的常规写法就出了事:_UNICODE_STRING 结构里的 Length 字段被读了两次,第一次决定内核缓冲区分配多大,第二次决定往里拷多少字节。
这两个值之间隔着一个系统调用的时间窗口。攻击者开一个线程在这个窗口里把 Length 从小值改成大值,内核就会按小的分配、按大的拷贝,多出来的字节带着攻击者控制的内容冲出缓冲区边界。这就是教科书级的 double fetch,落到 Windows 内核的 paged pool 上就是一次可控的池溢出——分配大小、溢出量、溢出内容三者全部由攻击者说了算,是利用条件最好的那一类溢出。
真正有意思的地方在下一步。SAFA 拿着正确的参数去调这个 IOCTL,代码纹丝不动——调试器显示那行代码压根没执行。逆向之后答案浮出水面:aswSnx 是 Avast 自动沙箱的实现载体,这个漏洞 IOCTL 只对「被沙箱收管的进程」开放。普通进程跑在沙箱外面,反而够不着这段代码。
要让攻击程序进沙箱,得改沙箱的注册配置。配置文件 snx_lconfig.xml 在 %ProgramData%\Avast Software\Avast\ 下,由另一个过滤驱动保护着,普通用户读不到也写不了。SAFA 在配置相关的 IOCTL 里找到了出口:编号 0x82AC0054 的接口专管把新进程注册进沙箱,而且只需要读权限就能调用。填上 Flags=0x1C、ScanHandle=1 和目标可执行文件名,攻击者的进程就拿到了沙箱居民身份,漏洞 IOCTL 随叫随到。
杀毒厂商给产品加沙箱,图的是把可疑进程关起来限制它碰系统资源;这段设计却让沙箱本身变成了特权限定区,把最脆弱的内核代码圈在里面只给「被怀疑的进程」用。隔离机制的访问控制写反了方向,这是这组漏洞在工程层面最值得记住的教训。
同一个处理器里的四个变体
SAFA 在同一个 IOCTL 处理器里前后挖出四个内核堆溢出加两个本地 DoS,全部源于相似的字符串处理模式:
- UNICODE_STRING double fetch:上面已述,
Length两次读取之间被篡改; - pString 双循环 double fetch:对宽字符串先循环找 NULL 终止符算长度、分配,再循环一遍拷贝,两轮之间字符串变长就溢出;
- pData double fetch:同一模式换个字段再来一遍;
- 进程注销时的 snprintf 误用:进程退出沙箱时,先前存下的用户字符串要拷进固定 0x420 字节的内核缓冲区,代码却把「源字符串长度」当成
_wsnprintf的尺寸参数传了进去——目标缓冲区大小这个该传的值根本没参与,拷贝上限完全由攻击者控制。
四个溢出里三个是 double fetch 变体,根因是同一个:内核不信任用户态的原则只落实了一半,ProbeForRead 检查了地址合法性,却没处理「数据会在检查之后变化」这个并发事实。
从时间线看,厂商的响应速度倒是值得肯定。SAFA 在 2025 年 3 月 14 日通过 Bugcrowd 提交报告,4 月 1 日修复版就发布了,从受理到上线只用了 12 天。修复方案也对路:UNICODE_STRING 在处理前整体复制进内核,其余 double fetch 删掉第二次循环改用第一次的计算结果,snprintf 误用补上了对固定尺寸的校验,所有用户指针 dereference 前加验证。有个小插曲是厂商最初把四个变体标成重复提交,SAFA 申诉后各自独立计件,赏金在 5 月底发放。CVE 编号拖到 2025 年 11 月 11 日才公开,SAFA 则等到补丁确认覆盖全部漏洞后,才在 2026 年分两篇发布技术细节。
从池溢出到任意读写:IORing 成了跳板

池溢出只是原料,把它变成可用的原语需要精心选一个「邻居」。Windows 内核的 paged pool 按 size class 分桶存放同类分配,这个特性让堆喷(heap spray)成为可能:塞满同尺寸的对象,再腾出几个空位,溢出的分配就会大概率落进紧邻攻击目标的槽位。
SAFA 选中的邻居是 I/O Ring 的 RegBuffers 数组。I/O Ring 是 Windows 11 引入的异步批量文件 I/O 机制,用户态注册一批缓冲区后,内核在 IORING_OBJECT 里用 RegBuffers 指针数组管理它们。这个目标有四个好处:对象本身住在 non-paged pool,但 RegBuffers 数组分配在 paged pool,正好挨着溢出发生的地界;数组大小完全由用户控制(注册 N 个缓冲区就是 N 个 8 字节指针);只要改写数组里的一个指针就能拿到完整的读写原语;这套玩法此前已有公开先例(windows-internals.com 的 "One I/O Ring to Rule Them All"),技术路线有据可依。
注册时内核会校验每个缓冲区条目,之后所有 I/O 操作直接复用注册结果,不再复查。SAFA 的操作是:大量创建 IORing 对象铺满 RegBuffers 分配,释放一部分造出正确尺寸的空位,触发溢出把 RegBuffers[0] 的指针改写,让它指向攻击者进程里一个伪造的 _IOP_MC_BUFFER_ENTRY 结构。内核后续用这个条目做 I/O 时,会直接解引用伪造结构里的 Address 字段——而那个字段住在用户态内存里,攻击者想改成几就改成几。这一步能成立,靠的是 Windows 内核至今没有全面启用 SMAP(Supervisor Mode Access Prevention),CPU 不拦内核去摸用户态内存的手。

于是两个普通的 I/O 操作变成了任意读写:


- 任意写:把伪造条目的
Address设成目标内核地址,提交IoRingReadFile,内核把文件数据写进那个地址; - 任意读:
Address设成要窥探的内核地址,提交IoRingWriteFile,内核从那里取数据写进攻击者的文件。
改地址在用户态一行赋值就够,每次读写不需要再碰漏洞。
补上泄漏、修好现场、换走令牌
任意读写原语在手,还差一个坐标:自己进程的 _EPROCESS 结构地址。_EPROCESS 是内核描述进程的数据库记录,令牌就存在里面。内核地址随机化让用户态无法猜,得漏一个出来。
泄漏路径藏在 I/O 的副产品里。内核对用户态缓冲区做 I/O 时会挂一个 MDL(Memory Descriptor List,把物理页钉住的描述结构),MDL 的 Process 字段记录着缓冲区属主的 _EPROCESS 指针。伪造条目本身在用户态,触发一次 I/O 后它的 Mdl 字段就能被攻击者直接读到;再用任意读顺着 Mdl 指针取回 Process 字段,_EPROCESS 地址到手。
拿到原语还不能急着庆祝。溢出破坏了两个内核一致性状态,不修好,进程一退出就蓝屏:
一是池块头的 ProcessBilled 字段。这个字段存着记账用的 _EPROCESS 指针,但做了混淆运算(与块地址、全局配额 cookie 异或)。SAFA 的办法是拿第二个干净的 IORing 对象当参照:读出干净块的 ProcessBilled,倒推 cookie 值,再给损坏块算出正确的混淆值写回去。
二是内核里残留的指向用户态伪造条目的引用。清理时提高伪造条目的引用计数,让内核的释放流程永远走不到那块用户态内存,拆卸过程就安全了。

最后一步没有悬念:用任意读遍历 _EPROCESS 双向链表找到 SYSTEM 进程(PID 4),读出它的 Token,再用任意写把这个 Token 抄进自己进程的 _EPROCESS。完成后当前进程就是 SYSTEM 权限,这是 Windows 用户态的最高等级,等于本机游戏结束。

对普通用户意味着什么
这组漏洞只影响 25.3 之前的 Avast 与 AVG 产品,覆盖 Avast Free/Premium/Ultimate、Avast One、AVG Free/Internet Security/Ultimate 的 Windows 版本。利用前提是攻击者已经能在你的机器上执行代码——它不会让远程攻击者凭空进门,但恶意程序一旦落地,这类漏洞能把「普通用户权限」一步抬到 SYSTEM,绕过 UAC 边界,让安全软件形同虚设。值得玩味的一点:用户装杀毒软件是为了加一层防护,而这次恰恰是防护软件的内核驱动成了提权跳板,攻击面与防御面在同一段代码里换了角色。
2025 年 4 月之后的更新版本已覆盖全部修复,NVD 在 2026 年 6 月 17 日最后一次更新条目。还在运行旧版 Avast/AVG 的机器,升级是唯一要做的事。对开发者而言,这组案例的价值在于它把三类高频内核漏洞模式(double fetch、snprintf 尺寸参数误用、注册后不复查的共享数据结构)连同各自的修法完整演示了一遍;Windows 内核正在推的 user-mode accessors(ProbeForRead 一类的访问封装)正是冲着 double fetch 这类问题去的——在内核侧的每一次用户态内存访问点做封装校验,压掉「检查之后数据又变了」的窗口。
顺带一提,这种「一个月内在单一 IOCTL 处理器里挖出四个变体」的产出,也说明了成熟商业软件的内核代码同样经不起耐心的手动审计。SAFA 在结项时点名批评了厂商把漏洞变体当重复件处理的做法——这种分类会拉长补丁周期,也让研究者的多份报告得不到独立承认。漏洞披露流程里,研究方与厂商的拉扯和漏洞本身一样,都是安全生态的真实组成部分。
参考来源
- SAFA Team: CVE-2025-13032 Part 1 — 漏洞发现与沙箱机制(2025-12-01)
- SAFA Team: CVE-2025-13032 Part 2 — 完整利用链(2026-09-18)
- NVD: CVE-2025-13032 详情(CNA: NortonLifeLock Inc.)
- windows-internals.com: One I/O Ring to Rule Them All
- Microsoft Learn: Kernel user-mode accessors