CVE-2016-0165从补丁到exploit

阅读量    42190 | 评论 5   稿费 300

分享到: QQ空间 新浪微博 微信 QQ facebook twitter

1.前言

本来是想分析其他漏洞的,不小心分析错了,就将错就错分析了。分析的环境是Win7 x86。

2.补丁分析以及POC构造

漏洞发生在win32k.sys模块的RGNMEMOBJ::vCreate函数中。通过对比打补丁前后可以发现在打补丁之后,在调用ExAllocatePoolWithTag之前添加了ULongAdd和ULongLongToULong这两个函数。

如果之前有分析过windows内核整型溢出漏洞的应该会知道ULongAdd和ULongLongToULong这两个函数是用来防止整型溢出的。因此可以判定这也是一个申请内存时发生的整型上溢漏洞。

接下去要做的就是成功调用存在漏洞的函数。查看调用RGNMEMOBJ::vCreate的函数,可以选择的函数非常的多,在尝试了FillRgn,CreateEllipticRgn等函数后发现可以触发RGNMEMOBJ::vCreate函数的调用,但是却无法执行到申请内存处的代码。

只好在RGNMEMOBJ::vCreate中调用ExAllocatePoolWithTag处下断点,观察正常使用系统时什么函数可以到达该处。发现FrameRgn可以成功到达漏洞触发点。但是新的问题又出现了,无法精确的控制申请内存的大小。

最后还是选择了PathToRegion函数,调用该函数可以到达漏洞触发点,并且申请的内存大小可以由PolyLineTo函数的第二个参数POINT数组的大小来决定。
我们希望的POINT数量是0x6666667个(系统会再添加一个坐标为(0,0)的点,一共是0x6666668个点)。
(0x6666667+1+1)* 0x28Bytes = 4GB + 70 Bytes , 这样最终分配的内存大小是70Bytes,70Bytes是一个比较好的值,过小或者过大都不利于后续的内存布局。

但是PolyLineTo的参数POINT数组的大小是有限制的,NtGdiPolyPolyDraw函数中对该值做了限制,如果大于该值将会调用失败。因此可以将POINT数组长度减小,分多次调用PolyLineTo函数。

最终的POC代码如下:

HRGN hRgn;

HDC hdc = GetDC(NULL);

static POINT Points[SIZE];

for (UINT idx = 0; idx < SIZE; idx++)
{
    Points[idx].x = idx;
    Points[idx].y = idx * 2;
}

BeginPath(hdc);
for (UINT cnt = 0; cnt < 8069; cnt++)
{
    PolylineTo(hdc, Points, SIZE);
}
EndPath(hdc);
hRgn = PathToRegion(hdc);`

3.编写利用代码

前面的POC代码已经触发了整型溢出,接下去在AddEdgeToGET函数中会触发堆溢出。AddEdgeToGET函数调用流程为RGNMEMOBJ::vCreate->vConstructGET->AddEdgeToGET。

AddEdgeToGET函数会将相邻的两个点(就是前文提到的PolyLineTo函数的POINT数组)转化为一个EDGE结构,并将该EDGE结构依次拷贝到之前申请的内存中,最后一个点会与系统添加的坐标为(0,0)转化为一个EDGE结构。
EDGE结构如下,大小为0x28。

这里有一点非常重要的是相邻两个点的纵坐标y值不能相同,如果相同会直接返回,而不会将EDGE结构拷贝到申请的内存中。就无法造成堆溢出。但是如果所有相邻的两个点纵坐标y值都不相同,又会导致大量的页面被破坏,因此必须仅让部分相邻点纵坐标不同,仅覆盖想要覆盖的值,尽可能的少破坏内存页。

在AddEdgeToGET函数中有大量的将EDGE对象中的成员赋值为-1(0xFFFFFFFF)的情况。


最让人容易想到的利用就是覆盖SURFOBJ的sizlBitmap成员,来使位图可读写的内存变大。

在RGNMEMOBJ::vCreate函数结尾对之前申请的空间进行了释放。所以我们希望我们这70Bytes能分配到某一页的末尾。因为操作系统在释放分配的对象时,会校验相邻对象的POOL_HEADER结构,如果发现该结构被破坏就会引发系统崩溃,如果被释放的对象在某一页的末尾,操作系统就不会执行校验操作。

首先我们先分配5000个大小的大小为0xF90的Bitmap,再分配7000个大小为0x70的AcceleratorTable。分配的AcceleratorTable的数量比Bitmap的数量多的原因是尽可能的将内存中原先就存在的合适的内存空洞占据了。

接着再释放掉这5000个Bitmap。重新申请大小为0xB98的矩形,大小为0x3F8的Bitmap。
0x3F8+0xb98=0xF90,所以刚好占满了刚释放掉的Bitmap。

最后释放掉7000个AcceleratorTable。调用函数触发漏洞。可以看到刚申请的0x70Bytes恰好占据了之前AcceleratorTable所在的位置。

AddEdgeToGET函数中的拷贝操作结束后,下一页的Bitmap的宽度和长度都被修改了。

因为这个Bitmap的Height被修改为0xFFFFFFFF,那么这个Bitmap就可以当作Manger,下一页的Bitmap可以当作worker,利用SetBitmapBits修改Worker的pvscan0为想要读写的地址,再对Worker调用SetBitmapBits,GetBitmapBits来进行任意地址读写操作。

在获取System权限之前,我们首先要对因为堆溢出被破坏的POOL_HEADER结构进行修复,否则在退出当前进程时会引发系统崩溃。接着再利用漏洞读取System进程的token,并将该token替换当前进程的token,完成提权。
提权成功。

4.参考资料

https://www.coresecurity.com/system/files/publications/2016/10/Abusing-GDI-Reloaded-ekoparty-2016_0.pdf

https://www.anquanke.com/post/id/85302

https://www.coresecurity.com/blog/ms16-039-windows-10-64-bits-integer-overflow-exploitation-by-using-gdi-objects

https://security.tencent.com/index.php/blog/msg/117

分享到: QQ空间 新浪微博 微信 QQ facebook twitter
|推荐阅读
|发表评论
|评论列表
加载更多