SKREAM(二):内存地址的随机分配
字数 1854 2025-08-19 12:42:38
SKREAM技术:内存地址随机分配防御机制详解
0x01 关于SKREAM技术背景
SKREAM是一种针对Windows内核池溢出漏洞的防御工具包,在前一篇文章中已经讨论了针对Windows 7和8系统的内核池溢出缓解技术。虽然Windows 8.1已经缓解了特定攻击手法(如在0xbad0b0b0构建恶意OBJECT_TYPE结构),但内存溢出漏洞仍然存在且攻击手法不断进化。
溢出攻击的必要前提条件
- 攻击者必须找到关键地址构建溢出缓冲区
- 必须准确知道应写入哪些数据
- 必须保持其余数据不变
- 需要精确计算ObjectHeader从溢出缓冲区开始的准确距离和TypeIndex偏移量
0x02 PoolSlider(内存隔离)技术
技术原理
- x64架构内存分配器分配的字节长度必须对齐到16字节(x86为8字节)
- 不足16字节整数倍的请求会被填充字符串以达到对齐要求
- 通过随机化返回给调用者的指针位置,混淆内存池开头的填充字节
实现机制
- 监听图像加载事件并在每个新加载驱动的ExAllocatePoolWithTag上放置IAT钩子
- 计算需要填充的字节数
- 生成1到可用填充量之间的随机数n
- 将返回给调用者的指针向前推进n位
内存释放处理
- 在ExFreePoolWithTag上放置IAT钩子
- 释放前将指针重新对齐到16字节边界
- 防止因指针不对齐导致的BAD_POOL_HEADER错误
技术挑战与解决方案
-
分配/释放函数不匹配问题
- 同时在ExAllocatePool和ExFreePool上放置钩子
- 在Ex{Allocate,Free}PoolWithTag处进行同样的随机/重新对齐处理
-
字符串分配释放问题
- 在RtlFree{Ansi,Unicode}String上放置IAT钩子
- 使用与ExFreePool(WithTag)相同的手法重新对齐指针
-
跨驱动分配释放问题
- 当一个驱动分配内存遇上另一个驱动(如NTOS)释放内存时
- 目前尚无完美解决方案,可能导致0xC2错误
-
无填充字节情况
- 当请求大小正好是16的整数倍时无法推进指针
- 可能的解决方案:人为添加1字节请求,强制产生15字节填充
0x03 PoolBloater(资源浪费者)技术
技术原理
- 不改变分配的基本地址
- 随机增加请求池分配的大小("膨胀")
- 通过破坏攻击精度来防御溢出
实现优势
- 实现简单,只需在ExAllocatePool(WithTag)放置钩子
- 不需要处理指针对齐问题
- 有效避免内存碰撞问题
- 溢出大小随机化使攻击难以预测
技术缺点
- 内存占用率可能显著增加
- 需要在防御效果和资源占用之间权衡:
- 设置较高上限:防御效果好但资源占用大
- 设置较低下限:资源占用少但防御效果差
0x04 技术测试与验证
使用HEVD(HackSys Extreme Vulnerable Driver)测试
-
PoolSlider技术测试结果:
- 返回给调用者的指针被移动(如图5.1中移动5字节)
- 溢出无法正确覆盖下一个池块头(图5.2)
- 最终因破坏池头完整性导致崩溃(图5.3)
-
防御效果对比:
- 有SKREAM时溢出被有效阻止(图7上图)
- 无SKREAM时溢出成功(图7下图)
0x05 技术局限性
当前版本的限制
- 仅能保护非Windows操作系统的一部分驱动程序
- 仅能保护在SKREAM之后加载的驱动
- 仅能保护通过ExAllocatePool(WithTag)直接执行的内存分配
- 无法保护系统自身的内存分配(如IOCTLs中的SystemBuffer)
- 仅能保护与文中描述相似的分配方式(大跨度分配由nt!ExpAllocateBigPool处理)
部署注意事项
- 直接卸载SKREAM可能导致系统崩溃
- 启用内存隔离技术时,SKREAM服务不能伴随系统启动,只能自启
0x06 技术实现要点总结
PoolSlider关键点
- 随机化指针位置破坏攻击可预测性
- 必须正确处理内存释放时的对齐问题
- 需要处理各种特殊情况(字符串分配、跨驱动操作等)
PoolBloater关键点
- 通过随机增加分配大小破坏攻击精度
- 实现简单但资源消耗较大
- 需要在防御强度和资源消耗间找到平衡点
0x07 未来发展方向
- 扩大保护范围至更多系统组件
- 解决跨驱动分配释放问题
- 优化资源消耗问题
- 开发更安全的部署和卸载机制
- 探索不放钩子的防御实现方式
通过这两种技术,SKREAM为内核池溢出攻击提供了有效的防御机制,尽管目前还存在一些限制,但已经显著提高了攻击者利用此类漏洞的难度。