查看原文
其他

CVE-2016-0040 滥用GDI 内核提权漏洞

Cestlavie呀 看雪学院 2019-05-26

漏洞背景


本人技术有限,有很多可能没有分析到位的地方请各位大佬指点。


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呀 原创

转载请注明来自看雪社区





热门技术文章推荐:







戳原文,看看大家都是怎么说的?

    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存