漏洞背景
本人技术有限,有很多可能没有分析到位的地方请各位大佬指点。
GDI利用技术很早就流行了,不过在国内 文章搜出来基本就几篇。正好前段时间看到了利用GDI 提权漏洞,赶紧学习一波。
在2016年 2月份 微软 修复了 CVE-2016-0040 内核级别的特权提升漏洞。该漏洞成因为 在 ntoskrnl 的WMI子系统中利用未初始化的堆栈变量
漏洞分析
一般分析一个CVE 我都会先去Kali 里面看看
虽然 微软 说影响情况很多 不过在kali中只提供了 Win7 sp0 /sp1 X64的利用。最后在GitHub上找到了 EXP 和利用的源代码
利用成功情况
先对EXP 进行 裁剪 造成崩溃
可以看到裁剪 POC 在3环 通过控制码 对0环 WMIDataDevice进行通信
而发送 的数据为 WMIRECEIVENOTIFICATION 这个结构
刚开始再 32位下 分析
可以看到触发了漏洞 查看 堆栈 是在 nt!WMIPRECEIVENOTIFICATIONS函数中。之后在wrk 源码中发现了此函数 (为分析漏洞已删除 无关代码)
NTSTATUS WMIPRECEIVENOTIFICATIONS(
PWMIRECEIVENOTIFICATION RECEIVENOTIFICATION,
PULONG OUTBUFFERSIZE,
PIRP IRP
)
{
#DEFINE MANY_NOTIFICATION_OBJECTS 16
ULONG I;
PWMIGUIDOBJECT GUIDOBJECT;
ULONG HANDLECOUNT;
PHANDLE3264 HANDLEARRAY;
OBJECT_EVENT_INFO *OBJECTARRAY;
OBJECT_EVENT_INFO STATICOBJECTS[MANY_NOTIFICATION_OBJECTS]; //本地
#ENDIF
HANDLECOUNT = RECEIVENOTIFICATION->HANDLECOUNT; //攻击点 r14d
HANDLEARRAY = RECEIVENOTIFICATION->HANDLES;
//
// CREATE SPACE TO STORE THE OBJECT POINTERS SO WE CAN WORK WITH THEM
//
IF (HANDLECOUNT > MANY_NOTIFICATION_OBJECTS)
{
OBJECTARRAY = WMIPALLOC(HANDLECOUNT * SIZEOF(OBJECT_EVENT_INFO));
IF (OBJECTARRAY == NULL)
{
RETURN(STATUS_INSUFFICIENT_RESOURCES);
}
} ELSE {
OBJECTARRAY = STATICOBJECTS;
}
#IF DBG
RTLZEROMEMORY(OBJECTARRAY, HANDLECOUNT * SIZEOF(OBJECT_EVENT_INFO));
#ENDIF
} ELSE IF (RECEIVENOTIFICATION->ACTION == RECEIVE_ACTION_CREATE_THREAD) {
GUIDOBJECT = OBJECTARRAY[0].GUIDOBJECT;//
GUIDOBJECT->USERMODECALLBACK = (PUSER_THREAD_START_ROUTINE)(ULONG_PTR)RECEIVENOTIFICATION->USERMODECALLBACK.HANDLE;
GUIDOBJECT->EVENTQUEUEACTION = RECEIVE_ACTION_CREATE_THREAD;
GUIDOBJECT->USERMODEPROCESS = USERMODEPROCESS;
GUIDOBJECT->STACKSIZE = STACKSIZE;
GUIDOBJECT->STACKCOMMIT = STACKCOMMIT;
THREADLISTHEAD = &GUIDOBJECT->THREADOBJECTLIST;
INITIALIZELISTHEAD(THREADLISTHEAD);
从函数源码 可以看到 如果 当传入的HandleCount 为0时 就会使用本地一个数组 。可以看到 这个数组并没进行 初始化 ,往下看 看到 在CHK 版本中进行了 初始化(。。。) 之后函数 会进行检查一些操作 当action 为RECEIVE_ACTION_CREATE_THREAD 时 看到 对 ObjectArray 进行了使用 ,不过当设置 HandleCount 为0 时 ObjectArray 是未初始化 ,在下面部分 又对这个初始化的指针 进行了使用。在利用之后 就造成了典型的内核 任意内存 读写
GDI利用
GdiShareHandleTable Win32k!gpentHmgr的部分 对应进程中每个GDI对象
而其中每一项都使用 GDICELL64 此结构
而通过 一个GDI Handle 我们就可以知道表中的地址
在GDICELL64 结构中 pKernelAddress 指向 SURFACE
在 SURFOBJ 结构中 ,sizlBitmap 它是一个SIZEL 结构 系统 通过 该变量 来确定 bitMap位图的长和宽 而 pvScan0和pvBits成员变量都表示 指向bitMap 位图的指针
在该利用 还有两个 关键的 函数 GetBitmapBits 该函数 主要用于 读取 pvScan0 或者 pvBits 指针指向 的cBytes字节 的Bitmap位图内容 相反 SetBitmapBits 函数 则是对指向的bitMap位图 进行写入
在一些 paper 中 利用技巧为 创建两个BitMap对象 (Manager/Worker) 之后通过 控制 Manager Bitmap 对象 的 sizelBitmap或者 pvScan0 成员 去 控制 Worker BitMap 对象 pvScan0 成员 的目的 最终实现内核任意内存 读写
总结 来说 就是 Manager 控制 着 Worker 读取 和 写入 的地址,而Worker 则通过 Set和Get BitmapBits 去对 该地址 进行 读写
最关键一点 需要 把Manager 的 pvScan0 指向 Worker的 pvScan0 一般通过漏洞去设置
漏洞利用
结合 GDI 利用基本原理 去分析 该漏洞 (分析时 蓝屏真痛苦)
直接 对 nt!WmipReceiveNotifications 下断
结合源码 去看 很容易去理解 可以看到 RSI 为本地数组
因为又利用 内核栈喷射技巧 刚开始 调的时候 到后一直 BSOD 浪费了好长时间
可以看到 栈被喷成 HmangerAddress
在 该地址 加 50 的位置 就是 pvScan0
跟进去看一下 就是 利用代码中 创建的 位图
在当初寻找相关文章的时候,我一直在想漏洞与 该 利用技术 的关系 。文章都说 利用 漏洞 去写 pvScan0 。之后我一直调试去找这个地方 。一直没找到。最终还是在此漏洞 该EXP 并没有这么做 .
在 这位置 它把 pvBits 给了 pvScan0 .可以在看一下 SURFOBJ64结构
注意 现在 操作的Manager 结构
之后 走几步 该函数 就结束了
而此时 的情况 为 pvBits 里为它本身, pvScan0 为pvBits
那在哪把 hManger 的pvScan0 写成 hWorker 的 ,直接对 pvScan0 下写入断点
有时候双击去调试 打印的信息都不显示
因为调试过 ,为方便调试 插入了 _debugbreak
命中 了 硬件断点 查看 下 堆栈 是在 SetBitmapBits 中 进行了 memcopy 。此时看一下 hManger 和hWorker
可以看到 已经 写进去了
这里关于为什么能 改变 hManger pvScan0 扯一下
在内核函数 离开之后 可以看到上面的图 当时的 hManger pvScan0 为pvBits 而pvBits 里面又是它自己
当调用 setBitmapBits 时 会先去找到 hManger 的pvScan0---->pvBits--->pvBits 所以在写的时候 会直接 从pvBit地址覆盖 也把pvScan0 覆盖掉了
之后 的利用 就是 读取 system token 内容 写入 当前 进程 token 就不在详细 写了
最后
分析有错的地方 请大佬指点下 。之后 把 搜到的 paper 和 源码 附上
内核喷射技术
本文作者:Cestlavie呀(看雪ID)
原文链接:https://bbs.pediy.com/thread-246433.htm
看雪推荐阅读:
1、[翻译]绕过数据执行保护
2、[原创]某Unity3D游戏加固产品分析
3、Nexus6P 7.1.2 内核编译修改 TracerPid
4、[原创]阶乘算法性能分析与 DOUBLE FAULT 蓝屏故障排查 PART I
5、[翻译]VR头戴(HTC Vive)设备内的现实危险