0x00:前言
本篇文章从SSCTF中的一道Kernel Pwn题目来分析CVE-2016-0095(MS16-034),CVE-2016-0095是一个内核空指针解引用的漏洞,这道题目给了poc,要求我们根据poc写出相应的exploit,利用平台是Windows 7 x86 sp1(未打补丁)
0x01:漏洞原理
题目给了我们一个poc的源码,我们查看一下源码,这里我稍微对源码进行了修复,在VS上测试可以编译运行
1 | /** |
编译之后在win 7 x86中运行发现蓝屏,我们在windbg中回溯一下,可以发现我们最后问题出在在win32k模块中的bGetRealizedBrush
函数
1 | 3: kd> g |
我们在此时在windbg中查看一下byte ptr [eax+24h]
的内容,发现eax+24
根本没有映射内存,此时的eax为0
1 | 3: kd> dd eax+24 |
我们在IDA中分析一下该函数的基本结构,首先我们可以得到这个函数有三个参数,两个结构体指针,一个函数指针,中间的哪个参数我重命名了一下
1 | int __stdcall bGetRealizedBrush(struct BRUSH *a1, struct EBRUSHOBJ *EBRUSHOBJ, int (__stdcall *a3)(struct _BRUSHOBJ *, struct _SURFOBJ *, struct _SURFOBJ *, struct _SURFOBJ *, struct _XLATEOBJ *, unsigned int)) |
我们在汇编中找一下蓝屏代码的位置,继续追根溯源,可以发现eax是由[ebx+34h]
得到的
1 | loc_95D40543: |
我们在windbg中查询一下[ebx+34h]
的结构,发现 +1c 处确实是零,直接拿来引用就会因为没有映射内存而崩溃
1 | 3: kd> dd poi(ebx+34h) |
我们现在需要知道这个 +1c 处的内容是什么意思,根据刚才的回溯信息,我们在最外层的win32k!NtGdiFillRgn+0x339
的前一句,也就是调用EngPaint
之前下断点观察堆栈情况
1 | 0: kd> u win32k!NtGdiFillRgn+0x334 |
EngPaint
函数参数信息如下
1 | int __stdcall EngPaint(struct _SURFOBJ *a1, int a2, struct _BRUSHOBJ *a3, struct _POINTL *a4, unsigned int a5) |
根据参数信息我们可以得到下面这两个关键参数
- _SURFOBJ => fdeac018
- _BRUSHOBJ => 97ffaaf8
我们在bGetRealizedBrush
处下断,找到这两个参数的位置,根据计算由_BRUSHOBJ
推出了_SURFOBJ
1 | 3: kd> ba e1 win32k!bGetRealizedBrush |
我们在微软官方可以查询到_SURFOBJ的结构,总结而言就是_SURFOBJ->hdev
结构为零引用导致蓝屏
1 | typedef struct _SURFOBJ { |
0x02:漏洞利用
从上面的分析我们知道,漏洞的原理是空指针解引用,利用的话肯定是在零页构造内容从而绕过检验,最后运行我们的ShellCode,我们现在需要在bGetRealizedBrush
函数中寻找可以给我们利用的片段,从而达到call ShellCode
提权的目的,我们可以在IDA中发现以下可能存在的几个片段
- 第一处
- 第二处
看到第二个片段其实第一个片段都可以忽略了,因为[ebp+arg_8]的位置我们是不可以控制的,而第二个片段edi来自[eax+748h],所以我们是完完全全可以在零页构造这个结构的,我们只需要将[eax+748h]设置为我们shellcode的位置即可达到提权的目的,我们现在的目标已经清楚了,现在就是观察从漏洞触发点到我们 call edi 之间的一些判断,我们需要修改一些判断从而达到运行我们shellcode的目的,我们首先申请零页内存,运行代码查看函数运行轨迹
1 | int main(int argc, char* argv[]) |
我们单步运行可以发现,我们要到黄色区域必须修改第一处判断,不然程序就不会走到我们想要的地方,然而第一处判断我们只需要让[eax+590h]不为零即可,所以构造如下片段
1 | *(DWORD*)(0x590) = (DWORD)0x1; |
第二处判断类似,就在第一处的右下角
1 | *(DWORD*)(0x592) = (DWORD)0x1; |
最后一步就是放上我们的shellcode了,只是在构造的时候我们需要给他四个参数,当然也可以直接在shellcode里平衡堆栈
1 | ; IDA 里的片段 |
所以我们构造如下片段即可
1 | int __stdcall ShellCode(int parameter1,int parameter2,int parameter3,int parameter4) |
最后整合一下思路:
- 申请零页内存
- 绕过判断(两处)
- 放置shellcode
- 调用
Trigger_BSoDPoc
函数运行shellcode提权
提权的代码和验证在 => 这里
0x03:后记
因为是有Poc构造Exploit,所以我们这里利用起来比较轻松,win 7 x64利用也比较简单,修改相应偏移即可
参考资料:
[+] k0shl师傅的分析:https://whereisk0shl.top/ssctf_pwn450_windows_kernel_exploitation_writeup.html