首页
社区
课程
招聘
[原创]CVE-2018-8639分析与复现
发表于: 2019-9-5 20:03 11151

[原创]CVE-2018-8639分析与复现

王cb 活跃值
11
2019-9-5 20:03
11151

原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编辑 ,原因: 添加调试方法
收藏
免费 7
支持
分享
打赏 + 2.00雪花
打赏次数 1 雪花 + 2.00
 
赞赏  Editor   +2.00 2019/09/06 精品文章~
最新回复 (4)
雪    币: 26205
活跃值: (63302)
能力值: (RANK:135 )
在线值:
发帖
回帖
粉丝
2
感谢分享!赞!
2019-9-6 14:34
0
雪    币: 188
活跃值: (626)
能力值: ( LV2,RANK:15 )
在线值:
发帖
回帖
粉丝
3
请问你的win32k.sys的符号表哪里来的?我的ida自动下载的都没有解析出来。
2020-3-26 12:08
0
雪    币: 11697
活跃值: (7194)
能力值: ( LV13,RANK:550 )
在线值:
发帖
回帖
粉丝
4
翻个墙就能下到
2020-3-27 12:12
0
雪    币: 188
活跃值: (626)
能力值: ( LV2,RANK:15 )
在线值:
发帖
回帖
粉丝
5
王cb 翻个墙就能下到
我下载到了,但是好多函数和数据结构都没有解析出来。
2020-3-27 14:11
0
游客
登录 | 注册 方可回帖
返回
//