CVE-2016-0040 滥用GDI 内核提权漏洞
漏洞背景
本人技术有限,有很多可能没有分析到位的地方请各位大佬指点。
GDI利用技术很早就流行了,不过在国内 文章搜出来基本就几篇。正好前段时间看到了利用GDI 提权漏洞,赶紧学习一波。
在2016年2月份,微软修复了CVE-2016-0040 内核级别的特权提升漏洞。该漏洞成因为在 ntoskrnl 的WMI子系统中利用未初始化的堆栈变量。
漏洞名称:Windows 特权提升漏洞
漏洞编号: CVE-2016-0040
漏洞等级: 严重
影响版本:
根据 微软官方给出的资料
Windows 7 x32 Windows 7 x64
Windows Vista sp2 WindowsServer 2008sp2
漏洞分析
一般分析一个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。
typedef struct {
BASEOBJECT64 BaseObject; // 0x00
SURFOBJ64 SurfObj; // 0x18
[...]
} SURFACE64;
typedef struct {
ULONG64 dhsurf; // 0x00
ULONG64 hsurf; // 0x08
ULONG64 dhpdev; // 0x10
ULONG64 hdev; // 0x18
SIZEL sizlBitmap; // 0x20
ULONG64 cjBits; // 0x28
ULONG64 pvBits; // 0x30
ULONG64 pvScan0; // 0x38
ULONG32 lDelta; // 0x40
ULONG32 iUniq; // 0x44
ULONG32 iBitmapFormat; // 0x48
USHORT iType; // 0x4C
USHORT fjBitmap; // 0x4E
} SURFOBJ64; // sizeof = 0x50
在 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 下断,
fffff800`03e7449d 4881eca0010000 sub rsp,1A0h
fffff800`03e744a4 448b31 mov r14d,dword ptr [rcx]
fffff800`03e744a7 448b2a mov r13d,dword ptr [rdx]
fffff800`03e744aa 4c8d6118 lea r12,[rcx+18h]
fffff800`03e744ae 498bd8 mov rbx,r8
fffff800`03e744b1 488bf9 mov rdi,rcx
fffff800`03e744b4 c78424e00100000d0000c0 mov dword ptr [rsp+1E0h],0C000000Dh
fffff800`03e744bf 4c89642468 mov qword ptr [rsp+68h],r12
fffff800`03e744c4 4183fe10 cmp r14d,10h //比较Handle count
fffff800`03e744c8 0f87c5590000 ja nt! ?? ::FNODOBFM::`string'+0x428c0 (fffff800`03e79e93)
fffff800`03e744ce 488db424a0000000 lea rsi,[rsp+0A0h] //RSI 指向 本地数组
fffff800`03e744d6 33c9 xor ecx,ecx
fffff800`03e744d8 e827c30100 call nt!WmipEnterCritSection (fffff800`03e90804)
结合源码去看,很容易去理解,可以看到 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 和源码附上。(点击阅读原文下载查看)
看雪ID:Cestlavie呀
bbs.pediy.com/user-806349
本文由看雪论坛 Cestlavie呀 原创
转载请注明来自看雪社区
热门技术文章推荐: