CVE-2018-8453 利用(下)

上一篇文章中我们在探索漏洞利用的过程中意外发现了一个新的方法,即一个任意地址减一的原语,本文将延续这个方法,通过任意地址减一这个原语实现漏洞的利用。

本文利用环境依然为 win10 x64 1703

参考:CVE-2018-8453:针对中东地区的Windows内核提权漏洞利用分析

首先回顾一下上次的流程。这个漏洞是一个 double free 漏洞,在第一次释放后我们通过 NtGdiSetLinkedUFIs 去在原始被释放的位置分配了一块新的内存,而在第二次释放前,win32k 对 sbtrack 中的字段有 unlock 的操作,由于 sbrack 的内存现在是我们可以控制的,所以此时我们有了一个任意地址减一的原语。

那么我们用这个任意地址减一的原语去干什么?首先想到的那就是通过两个相邻的 palette,修改前一个 palette 的 cEntries 为 0 的高位(减一后溢出变为 0xff),实现增大 cEntries 的效果,然后覆盖后一个 palette 的 apalColor 实现任意读写。

如果要这么实现,那么我们需要进一步解决两个问题,第一要知道这两个 palette 的地址,第二是要让这两个 palette 相邻(或者比较足够近)。

首先还是通过窗口 lpszMenuName 预测下一次分配一个页时的地址,因为这个预测方法需要 palette 必须大于一页(large pool 分配机制),所以相邻的 palette 一定在第二页中。因此我们设计第一个 palette 大小为 0x1b00。在 large pool 分配后,系统会在分配的块后面加一个 frag chunk,然后将多余的块作为待分配的 poolchunk 管理。 因此第二个 palette 的大小便是 0x4e0。为了防止还有其他的待分配的 0x4e0 的块,这里我们可以分配大量的 palette,然后通过 Manager palette 改写后面 palette cEntries 来确定哪个是真正分配在 Manager 后面的块。这样就实现了两个并排的 palette,并且我们知道他们俩个的地址。

0: kd> ??MgrAddr
unsigned int64 0xffffa088`c2557000
0: kd> !pool 0xffffa088`c2557000
Pool page ffffa088c2557000 region is Paged session pool
ffffa088c2557000 is not a valid large pool allocation, checking large session pool...
*ffffa088c2557000 : large page allocation, tag is Gh08, size is 0x1b00 bytes
		Pooltag Gh08 : GDITAG_HMGR_PAL_TYPE, Binary : win32k.sys
0: kd> !pool 0xffffa088`c2557000+1b00+20
Pool page ffffa088c2558b20 region is Paged session pool
ffffa088c2558000 is not a valid large pool allocation, checking large session pool...
 ffffa088c2558b00 size:   20 previous size:    0  (Allocated)  Frag
*ffffa088c2558b20 size:  4e0 previous size:   20  (Allocated) *Gh08
		Pooltag Gh08 : GDITAG_HMGR_PAL_TYPE, Binary : win32k.sys

同样我们继续使用 NtGdiSetLinkedUFIs 在 sbtrack 被释放后分配内存,在原始思路中,文章作者使用了 bitmap 来占位刚被释放的 sbtrack,这是因为作者的利用环境为 1709,此时 bitmap 已经开启了 type isolation,数据与 bitmap 头做了分离,因此变相实现了 NtGdiSetLinkedUFIs 的效果。

虽然使用任意地址减一的方法不需要 sbtrack 的那个 chunk 与之后的 chunk 合并,但是可能是有由于 sbtrack 的 chunk size 太小,导致利用不稳定(之后重新分配的时候经常分配不到 sbtrack 的那个块),因此还是将 chunk 合并为大块(大块数量少)。

0: kd> !dppal 0xffffa088`c1db7000
EPALOBJ structure at 0xffffa088c1db7000:
--------------------------------------------------
FLONG      flPal         0x501
ULONG      cEntries      0xffff069c
ULONG      ulTime        0x2082
HDC        hdcHead       0x0000000000000000
HDEVPPAL   hSelected     0x0000000000000000
ULONG      cRefhpal      0x0
ULONG      cRefRegular   0x0
PTRANSLATE ptransFore    0x0000000000000000
PTRANSLATE ptransCurrent 0x0000000000000000
PTRANSLATE ptransOld     0x0000000000000000
PPALETTE   ppalColor     0xffffa088c1db7088
PAL_ULONG  apalColor     0x0000000000000000
--------------------------------------------------

将 cEntries 高位减一后变为 0xffff,此时 palette 就可以实现越界读写了,然后再改写后面的 palette。

写到这才想起来 0xffff0000 是一个很大的数,可以越界读写 0xffff0000*4(约3gb) 的内存空间,实际上我们根本不需要让两个 palette 并排放置,只要多申请几个 palette 然后确定两个 palette 之间的距离在这个范围内就可以了。

之后的流程就是一模一样了,从内核中删除 dc object 的 handle entry 防止二次释放,然后替换进程 token 提权。


从刚开始想了解内核漏洞去读《Kernel Attacks through User-Mode Callbacks》到现在完整分析完一个漏洞,已经过去了半年时间。这样的速度是快还是慢呢?类似的疑问我常常会想,但我又知道这样的问题是无所谓回答不回答的。曾经刚入门安全的时候我曾在笔记中留下这样的话,现在回过头看还是会不禁赞叹一下自己,哈哈。

有点忙,能学东西的时间不多,有点苦恼于自己什么都不会,但是转念一想才开始学习3个月不到,这也正常。我已经见过不少的例子,只要保持学习,就可以从什么都不会到小有所成