作者:ze0r @360A-TEAM
公众号:360ESG CERT

在本篇文章中,我们将对CVE-2019-0623进行深入分析并得到利用EXP。

这个漏洞是微软在2019年2月份的补丁日中发布的(漏洞补丁),由腾讯的湛沪实验室提交给微软官方。该漏洞是一个存在于win32k.sys中的提权漏洞。分析后知道,这个漏洞居然异常简单!而且最早在1993年漏洞代码就已经被写出来了,所以至今已经存在了至少25年之久!下面我们来看看这个漏洞。

从微软的官方介绍上可知,此漏洞存在于win32k.sys中。在官方下载了补丁文件后解出更新后的win32k.sys后,与未更新的版本对比(win 7 32位系统):

从图中可以看出,更改较大的有sfac_ReadGlyphHorMetrics和ReferenceClass。这里说一下,由于微软是在修补漏洞,而漏洞处的前后逻辑(比如漏洞函数本身的参数和之后返回的值)是不会改动的,一般漏洞都是没有校验XX或者没有检查XX参数造成XX超长,所以微软一般是在某处新增一段代码。看了sfac_ReadGlyphHorMetrics函数比较乱,便去查看ReferenceClass函数,查看流程图,发现是新增加了一段代码,差异如下:

明显中间是新增加了中间粉红色的代码。所以很可能问题出在此函数上。拉近看看:

中间能看到多了一次ExAllocatePoolWithQuotaTag函数的调用,图形看具体代码不方便,我们在IDA里看:

可以看到,都是在ClassAlloc返回的内存块赋值给一个变量(v4),并且使用memcpy给整个v4结构体全部赋值。而差异在于,新版的是又多申请了一块内存(调用ExAllocatePoolWithQuotaTag在内核中申请),并且把申请到的内存赋值给了v4的一个成员。如果这里就是漏洞所在,那么应该就是v4的一个成员( v4+0x50)和a1的成员指向了同一段内存,而指向同一段内存可能会导致内存值被另外改变、释放等。那么我们有必要弄清楚,这个V4和V4的这个成员到底是谁,又是什么作用。我们去翻看一下win2000的源代码尝试寻找答案:

很幸运,泄露的部分源代码中,包含此函数:

这里只截图了该函数中对应diff结果的代码部分,从代码和IDA F5结果来看,这段代码完全一致。仔细看代码本身,1677行ClassAlloc函数是申请一个Class结构大小的内存,那么v4(pclsClone)其实是class结构,之后使用RtlCopyMemory给整个新申请的CLASS初始化,而赋值的来源则是调用该函数的第一个参数,所以该函数的功能基本就是根据参数传进来的CLASS再“克隆”出来一个CLASS,并且之后对克隆出来的新CLASS的成员进行一些设置。我们查看一下Class结构:

可以看到,在该结构中,0x50的地方是lpszMenuName,对应于用户态的WNDCLASS结构的lpszClassName成员。回头来看源代码,发现新申请的Class pclsClone竟然没有被重新设置!也就正是补丁所做的:重新申请了一段内存给lpszMenuName!而新旧两个CLASS的menuname成员的值一样的话,则意味着,如果我们能想办法操纵一个CLASS释放掉这块内存,而另一个CLASS也销毁的时候,会引起double-free的问题!

那么漏洞位置找到了,现在需要触发漏洞。这存在如下问题:

  1. 哪里会调用漏洞函数?

  2. 如何通过操纵其中一个CLASS释放掉MenuName内存?

  3. 如何通过操纵另一个CLASS来再次释放这块内存?

我们先来看第一个问题,在IDA的交叉引用中,查看引用ReferenceClass函数的地方:

只有两处引用该函数,而其中一个就是xxxCreateWindowEx,该函数是在创建一个窗口的时候直接会被调用,而引用处:

完全不需要任何条件!所以这里我们可以直接让流程调用进入到漏洞函数ReferenceClass。以下分析使用WIN 2008 R2 X64系统:

可以看到,我们直接进入到了漏洞函数。回头再看一下WIN2000的源代码:

在第1657行,我们看到如果是源CLASS(以下称baseCLS)的桌面和当前要创建的WINDOW所在的桌面一样的话,就会直接增加CLASS的引用计数后返回,也就是不需要再克隆一个新CLASS了。那么我们想要触发漏洞,就需要注册CLASS时和使用注册的CLASS创建窗口时处在不同的桌面中:

之后可直接进入到克隆新CLASS的流程:

这里说一下,在tagCLS结构中,存在一个pclsBase成员,这个成员会指向baseCLS,再注册一个窗口类时,CLASS结构的这个成员会被初始化为指向自己。而pclsClone成员则会指向自己复制出来的新CLASS:

分别查看一下baseCLS和CloneCLS的MenuName成员:

可以看到两个CLASS的MenuName成员指向了同一块内存:

至此,我们已经得到了两个CLASS,接下来的问题是如何释放掉其中一个CLASS的lpszMenuName成员?也就是问题2。要解决这个问题,当然是往CLASS的MenuName方向考虑。熟悉WINDOWS GUI编程的都知道,有一个API叫SetClassLongPtrA,该API可以完成更改一个窗口所使用的类的属性,当调用该API第二个参数为GCLP_MENUNAME时,可更改(替换)CLASS的menu name:

这里就透露了一个很重要的信息:既然是menu name String,是个字符串,那么就是一段内存,那么如果新字符串比原字符串长呢?那肯定是要重新申请内存的,那么原内存肯定会释放!而泄露的源代码也印证了这一点:

可以看到,当为GCLP_MENUNAME时,会在1567行释放掉旧内存!所以我们就有了第2个问题的解决方案,让MenuName指向的内存块释放掉:

可以看到,释放了lpszMenuName成员指向的内存块:

然而,在baseCLS中的lpszMenuName成员,还是依然指向这块内存:

可以看到,baseCLS中的MenuName成员指向的数据,已经不再是UNICODE的xxxxxx了!如何还剩下最后一个问题:如何让baseCLS的MenuName释放呢?也就是问题3。我们知道,tagCLS结构,在内核中就是一个内核结构,产生可以RegisterClassExA,那么销毁也自然简单:UnregisterClass就行!但在测试中,发现直接调用该API,在系统调用完成后,user32.dll中的代码会导致在用户态的某处内存错误,所以这里是直接调用了系统调用的销毁API:NtUserUnregisterClass

调用即触发重复释放内存块:

可以看到该内存被再次释放!继而引发BSOD(这里由于为了后补一张截图,重新运行了POC,所以RCX并不完全一样):

至此,我们已经成功触发了漏洞!

接下来,就是如何利用此漏洞。我们经过上面的分析可知,该漏洞就是个double-free,所以利用流程如下:第一次释放->重新占用刚刚释放的内存->触发第二次释放->再次占用内存->使用第一次占用对象句柄来更改内存内容->获取一个损坏的对象!这个流程不再多说,有兴趣的可以参照本人之前的文章《从补丁diff到EXP--CVE-2018-8453漏洞分析与利用》。这里仅贴出关键代码和简单流程说明:

  1. 创建触发环境:

  2. 触发第一次释放:

  3. 马上占用被释放的内存:

  4. 触发第二次释放:

  5. 创建palette对象,再次占用被释放的内存:

  6. 使用第4步中创建的DC对象来重写Palette结构,我们这里更改Palette结构中的大小,造成越界访问,从而更改紧临后面的Palette对象的地址指针等:

  7. 使用越界的Palette(Manger)来控制后面的Palette(worker)地址指针成员,完成获取token、替换token工作。然后创建CMD进程实现提权:

最后,由于上面第4步中,有一个DC对象的成员是被释放了的,如果退出EXP,则会导致释放这个DC对象的时候,导致系统BSOD(重复释放内存)。所以我们需要修正这个成员:只要把这个成员的值改为0即可,但获取该DC对象的地址却相当麻烦。

查看win32k的转换过程,发现如下:

所以重点是如何获取gpentHmgr表,win32k.sys+固定偏移的方式固然可以,但是兼容性太不好,只能是在本环境中可以,这是本人所不愿看到的。最后采用函数搜索的办法:

Win32k.sys导出了EngCopyBits函数,该函数内引用了gGdiInPageErrors全局变量:

而该变量的附近就是gpentHmgr:

所以只要从win32k.sys中,从导出表找到这个位置,然后减固定偏移就是gpentHmgr的指针!而寻找内存中win32k.sys模块的基址,本人从nt!PsLoadedModuleList中找到win32k.sys的基址,然后加上上面获取到的偏移获得gpentHmgr指针的地址,然后读取该地址通过换算即得到DC的地址,之后将目的成员值为0即可:

之后即可直接退出进程:


Paper 本文由 Seebug Paper 发布,如需转载请注明来源。本文地址:https://paper.seebug.org/832/