Windows7 6.1.7601.17514x86
可以看到在补丁后的ReferenceClass中对Clone Class流程加了如下一段代码,即在克隆窗口类时给tagCLS->lpszMenuName重新申请一块会话池,ReferenceClas函数可以参照NT 4源码,流程基本完全一致。由于没补丁前pclsClone->lpszMenuName和pclsBase->lpszMenuName指向同一块会话池,因此可能会造成double free,观察触发克隆类的流程可以知道:我们只需要在应用将一个线程绑定到新的桌面,然后在这个线程使用属于默认桌面的窗口类来创建窗口就会触发克隆类流程。

经过上面分析,我们可以知道应用层的代码该如何编写:

通过windbg可与观察到现在的pclsClone->lpszMenuName和pclsBase->lpszMenuName已经指向同一块会话池

起初我以为在调用UnregisterClass时,会将pclsClone->lpszMenuName和pclsBase->lpszMenuName同时释放,但是经过测试发现并没有蓝屏,通过观察NtUserUnregisterClass发现只释放了pclsBase->lpszMenuName,而pclsClone->lpszMenuName并没有被释放。

当时在这卡住了许久,后面看到这篇文章后才知道可以通过SetClassLongPtr修改lpszMenuName,我只知道有SetWindowLongPtr这个API,没有联想到类和窗口都有相关的API来修改 ??
现在知道如何将pclsClone->lpszMenuName和pclsBase->lpszMenuName释放后,就可以构造出蓝屏了


double free比较常见的利用方法就是每次释放时都用不同的对象来进行占位,并且其中一个对象拥有修改另外一个对象关键字段(例如palette->apalColor)的能力,因此就有可能转换为任意读写。
这是我分析的第一个double free类型的漏洞,因此主要还是参考的大佬的文章,其中最重要的是利用NtGdiSetLinkedUFIs进行占位,这篇文章主要是记录初次分析double free利用遇到的一些坑。
我通过阅读上面这个文章总结出一下几点:
Palette占位,然后第二次释放用NtGdiSetLinkedUFIs直接修改Palette->apalColor,因为在第一次释放Palette时会破坏Palette->BASEOBJECT字段,导致在后面进行任意读写调用SetPaletteEntries/GetPaletteEntries时,发生句柄校验错误,从而SetPaletteEntries/GetPaletteEntries函数会直接退出。NtGdiSetLinkedUFIs占位,在第二次释放时再通过Palette占位。double free的原因,这块会话池内存是有两个对象(hdc、palette)同时指向的,因此在关闭程序时由于进行清理工作,会将这两个对象进行回收,从而导致同一块内存被多次释放造成蓝屏,我这在里的处理方法与上文作者不太一样,详情看后文。通过上面总结的可以知道,我们需要在第一次释放后通过NtGdiSetLinkedUFIs进行占位,但是在实际测试过程中由于我没有进行pool fengshui操作,每次在通过SetClassLongPtr释放时发现这块内存发生了合并,从而导致与后面进行NtGdiSetLinkedUFIs占位的pool大小不一样,因此我们需要事先通过池布局,让lpszMenuName落入我们事先准备好的位置。
这里我使用了调色板和加速键这两个对象进行布局,注意布局的对象得位于session pool,分析一下这两个对象大小分配关系然后封装成函数方便使用:


然后我们现在就可以开始进行布局了,这里我准备将每个会话池页面布局成如下情况,更详细的pool fengshui资料请参考
pool fengshui原则 https://xz.aliyun.com/t/3146
[+] 当分配的pool size大于0x808的时候, 内存块会往前面挤
[+] 当分配的pool size小于0x808的时候, 内存块会往后面挤

因此可以编写如下代码,在windbg进行观察可以看到lpszMenuName已经落入我们事先准备好的位置。


lpszMenuName已经成功落入我们事先准备好的位置后,lpszMenuName就不会出现在释放后被合并的情况了,这时我们就可以通过NtGdiSetLinkedUFIs进行第一次占位,但是在测试时我发现了一个问题,由于lpszMenuName被释放后位于空闲链表尾部,因为后进先出的原则导致lpszMenuName所释放的内存始终会被NtGdiSetLinkedUFIs中第一次申请的临时会话池所占用,因为我们需要的是NtGdiSetLinkedUFIs第二次申请会话池时占位lpszMenuName所释放的内存,所以我们需要在lpszMenuName释放后在释放一个同样大小的会话池内存。

通过windbg可以发现成功占位,这里地址和上面不一样是因为测试时为了重现每个步骤改了代码,和上面不是同一次运行

第二次释放后通过palette占位时也遇到了一些小问题,发现CreatePalette直接创建不成功,在windbg中跟进NtGdiCreatePaletteInternal,发现是在如下代码片段返回了错误

后面通过请教琦哥后才知道每个进程能够申请的User、GDI对象是有上限的,这里引用一下琦哥博客的内容:
https://xiaodaozhi.com/exploit/42.html
在测试环境中执行验证代码时,发现执行到第
2次分配位图对象的后期阶段发生创建失败的错误,经过检查后发现是进程 GDI 对象数目已达到上限,随后适当调整验证代码的创建对象整体数目才得以继续执行。在实际应用时可随时根据进程句柄数、用户对象数、GDI 对象数具体情况随时调整选择分配的对象类型,以使利用逻辑能顺利进行下去。在当前系统环境下,用户对象和 GDI 对象的进程限额都是10000个,参见以下两个注册表路径的键值,如有必要可适当调整这两个键值的数值。HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Windows\GDIProcessHandleQuota HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Windows\USERProcessHandleQuota
调整之前pool fengshui的对象数量后发现CreatePalette可以申请成功了,因此就可以进行第二次释放后占位了。

其实这里还有一个坑,就是palette对象的tag有几种,不只是Gh18这种,导致我一直以为没有占位成功..........

palette->apalColor成功的进行两次占位后,我们就可以通过NtGdiSetLinkedUFIs修改palette->apalColor

然后就可以构造任意读写

接下来就是利用任意读写进行的常规套路,这里有很多种提权方式,我这里选择通过读取ntoskrnl导出的PsInitialSystemProcess进行活动进程链表搜索,找到当前进程后替换system令牌


到这里提权已经成功了,但是还是存在退出蓝屏的问题,具体见原因上面,我这里没有采用作者所使用的方法来进行修复,其实是因为他的方法我不会 ??
现在回到蓝屏的本质是因为进程结束时回收了两个指向同一个地址的对象,导致多次释放而蓝屏,这里一个对象是dc对象,一个是palette对象,因此我们只需要清空其中一个对象所在句柄表的项就可以了,但是通过分析NtGdiSetLinkedUFIs->XDCOBJ::XDCOBJ发现dc对象的内核地址是通过几个未导出的全局变量进行计算的,如果采用硬编码搜索等方式将会影响兼容性同时加大工作量,那么现在就只有对palette对象动手,众所周知palette对象的内核地址是通过peb->GdiSharedHandleTable进行泄露的,其指向一个GdiCell类型的数组,类似于句柄表,
typedef struct _GdiCell
{
PVOID pKernelAddress;
UINT16 wProcessIdl;
UINT16 wCount;
UINT16 wUpper;
UINT16 uType;
PVOID pUserAddress;
}GdiCell, * pGdiCell;
而这个句柄表在用户层是有映射的,只是没有写权限,因此我选择将这个页面改为可写后,在应用层清空这个句柄项

至此经过测试可以完美退出

https://xiaodaozhi.com/exploit/42.html
https://bbs.pediy.com/thread-249021.htm
利用代码:https://github.com/DreamoneOnly/CVE-2019-0623-32-exp/tree/master
原文:https://www.cnblogs.com/DreamoneOnly/p/12968718.html