原poc
看雪分析
漏洞的成因是调用CreateWindowA函数创建窗口的过程中,接着调用ReferenceClass克隆tagCLS结构时,未另分配分页pool内存保存重新创建的tagCLS->lpszMenuName,而是直接克隆tagCLS结构指向源tagCLS>lpszMenuName地址,导致doublefree,
我们先来看CreateWindowA函数对于tagCLS结构的克隆操作部分:
ReferenceClass是造成漏洞最关键的函数,现在来分析补丁更新前后函数的变化来了解漏洞的成因,补丁对比如下
更新前:
更新后
可见更新后为克隆tagCLS结构的lpszMenuName重新申请了重新申请了pool内存,在调用DestroyWindow和NtUserUnregisterClass释放tagCLS结构时,导致每次释放释放的都是是新申请的内存,修复了Double-free问题.
其实这个lpszMenuName对象在调用SetClassLongPtrA函数时已经被被释放和重新申请了一次,而在ReferenceClass克隆tagCLS结构指向的还是原来的lpszMenuName对象,结构又被释放了一次.下面通过分析代码来解释释放过程.
此时原pCls->lpszMenuName第一次释放,在poc中调用NtGdiSetLinkedUFIs占位释放的内存.
接着调用DestroyWindow第二次释放对象,以NtUserDestroyWindow->xxxDestroyWindow-> xxxFreeWindow->DereferenceClass->DestroyClass的顺序最后释放克隆的pCls对象
接着调用NtUserUnregisterClass->UnregisterClass->DestroyClass顺序释放原pCls对象,原pCls->lpszMenuName和克隆的pCls->lpszMenuName指向的是同一内存区域,所以肯定会被释放,是否3次释放??
在poc中先申请了10000个100大小的AcceleratorTable(以下简称acc),然后释放前3000个,并创建3000个e00大小的acc,部分e00和2个100的acc会占满一页,然后再释放1500个100的acc和创建1500个200大小acc,这样原释放100和新创建的200会填满池空隙,有些e00和200的acc会占满一页,也存在e00和2个100的acc占满一页情况,又由于e00的acc数量大于200的acc,会出现大量的e00和200大小free的页面空洞,用于放置poc中要创建的lpszMenuName,最后又把最后4000个100的acc释放,导致更多相同空洞出现.效果如下图:
下面我们来看下poc运行过程内核对象池风水的实际布局情况,具体过程如图:
对于这个漏洞关键对象pCls->lpszMenuName内核地址获取可以通过以下方式查看:
bp win32k!ReferenceClass+0x6b "p;"
也就是ReferenceClass函数中其中
pclsClone = (tagCLS *)ClassAlloc((__int64)hheapDesktop, (Src->CSF_flags & 8u) + Src->cbclsExtra + 160)这行代码,采用调试脚本如下:
poc运行流程顺序如图:
来看具体windbg调试过程,我的调试方法可以看我另一篇文章,:
笔者借鉴CVE-2018-8453布局思路,测试了一种新的布局方式,先申请创建4000个C10大小的块,位于堆顶部,然后创建4000个200大小的块,位于堆底部,这样就在堆中间留出了1F0大小的空隙,再创建5000个1F0大小的小块,把池堆中的空隙填满,然后每间隔2个1F0大小释放其中一个,这样就在堆中留出大量1F0大小的空隙用于放置lpszMenuName,这样正好把空隙控制在1F0大小,200大小的块不会覆盖1F0大小的块也填满了1F0之前的空隙使其剩余空隙保留在小于200大小,不会影响之后的GDI和PALETTE也不会跑到这些空隙去,第一次释放用GDI占位,第二次释放先释放C10用C00占位,然后创建2w个100大小PALETTE,填充二次释放区域,经测试布局成功率大于90%,池风水布局后如图:
整个doublefree占位过程如图:
之后的利用方式与原poc相同这里略过,下面有详细解释
参考PALETTE滥用这篇文章为exp达到内核内存任意位置读写的方式,poc使用NtGdiSetLinkedUFIs函数把写入的指定HDC对象数据对准了PALETTE +1c也就是PALETTE64->cEntries位置值为0xfff构造了一个越界的PALETTE实现
NtGdiSetLinkedUFIs主要实现为XDCOBJ::bSetLinkedUFIs内部过程,在x64系统下如果之前未申请内存就新申请内存在对象0x138位置保存了申请内存的地址然后拷贝 8 Count大小内存,如果之前申请过内存就直接拷贝传入的 8 Count大小内存,这里buf可控,count也可控
构造好了越界的PALETTE就可以构造hManager、hWorker两个Palette object,其中hManager->pFirstColor指针指向hWorker的内核地址,具体方法是通过GetPaletteEntries和SetPaletteEntries,内部通过GreGetPaletteEntries调用XEPALOBJ::ulGetEntries和GreSetPaletteEntries调用实现.
在poc中对于hManager设置GetPaletteEntries的istart=0x1b,SetPaletteEntries=的istart=0x3C,0x1b 4=6c对齐后为0x70,0x3C 4=0xf0,0xf0-0x70=0x80正好是hWorker->pFirstColor指针指向的地址,写入任意目标内核地址后,对于hWorker调用GetPaletteEntries就就可以读取这个地址4*icount大小的任意内容,下面我们来看调试验证结果:
同理对hWorker调用SetPaletteEntries实现任意内存写入,实现替换进程SYSTEM权限的token,为了避免退出进程后HDC句柄释放失败导致蓝屏,把Palette改回原来大小这样就会调用GetPaletteEntries失败,从而判断出是哪个HDC改写了越界Palette,最后通过偏移量找到他内核句柄的地址,清零最后成功退出exp,成功后获得一个system的cmd,本exp成功率90%,效果如图:
引用
我的poc地址
作者来自ZheJiang Guoli Security Technology
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课
最后于 2019-9-5 20:09
被王cb编辑
,原因: 添加调试方法