阅读:1935回复:0
Hacking Team攻击代码分析Part5 Adobe Font Driver内核权限提升漏洞第二弹+Win32k KALSR绕过漏洞
作者:360Vulcan Team成员: MJ0011、pgboy
◆0 前言 继360Vulcan上周分析了Hacking Team泄露信息中曝光的三个Flash漏洞和一个Adobe Font Driver内核漏洞后(链接见文后)。 Hacking Team泄露信息中仍在不断被发现存在新的攻击代码和0day漏洞。7月12日,Twitter上安全研究人员@vlad902公布了Hacking Team的邮件(https://wikileaks.org/hackingteam/emails/emailid/974752)中可能的一处Windows权限提升漏洞,并将其攻击代码上传到Github上(https://github.com/vlad902/hacking-team-windows-kernel-lpe)。 经过我们的分析,该攻击代码中包含了两个Windows内核模式驱动的0day漏洞,其中一个是针对Windows内核驱动Win32k.sys的一处安全特性(KASLR)的绕过漏洞,另一个是针对Adobe字体驱动(atmfd.dll)的一处内核池溢出引发的内核代码执行漏洞。 ◆1 漏洞原理分析: 通过简单浏览攻击代码,我们知道攻击代码运用了一处Win32k.sys中的KASLR绕过漏洞获得Win32k的基地,并组织ROP链,同时,加载一个字体文件(font-data.bin)来利用字体驱动漏洞,触发ROP链,最终完成攻击。 ◆2 Win32k.sys KASLR绕过漏洞 在Windows8.1以上的系统上,微软增强了针对KALSR的缓和能力,对于低完整性级别及以下的程序,禁止获得系统内核模块的地址信息,来缓和内核漏洞针对IE沙盒等安全机制的攻击。在360Vulcan Team 5月的博客《谈谈15年5月修复的三个0day》(http://blogs.360.cn/blog/fixed_three_0days_in_may/)中,我们比较详细地介绍了这类问题的的背景,以及一个和本次漏洞类似的CNG.sys KASLR绕过漏洞CVE-2015-1674。 这里Hacking Team所使用的是一个win32k处理字体信息时的栈未初始化导致的信息泄露漏洞。 我们结合源代码的win32k_infoleak()函数中可以了解, win32k用于获取文本字体信息的内核调用NtGdiGetTextMetricsW->GreGetTextMetrics->bGetTextMetrics会针对DC对象返回一个内部结构到存放tagTEXTMETRIC结构的输出缓存中。 通过分析bGetTextMetrics的实现我们可以得知,该函数首先检查字体对象中用于缓存tagTEXTMETRIC结构的一处指针是否为空,如果不为空,就直接使用这里保存的字体信息,这样可以加快频繁调用的GetTextMetricsW的性能。 如果缓存的结构为空,那么该函数会调用bIFIMetricsToTextMetricW来获取字体信息,并且使用PALLOCMEM2分配一块缓存结构内存,保存到字体对象中,以供下次查询加快速度。 这套逻辑在复制0x38偏移时,存在一处对齐引发的栈信息泄露问题,我们来看MSDN中对于tagTEXTMETRIC的定义(https://msdn.microsoft.com/en-us/library/aa911401.aspx),可以看到 0x38偏移就是这个数据结构的最后一个成员tmCharSet,它的类型是BYTE,长度1个字节,而这里数据结构为了对齐,会补充7个字节,以便实现8字节对齐(x86系统上补充3个字节),就是这个数据结构对齐问题引发了这里的信息泄露。 在bIFIMetricsToTextMetricW函数中,会使用外部bGetTextMetrics提供的栈空间来保存获得的tagTEXTMETRIC结构,在存储前,函数并没有将栈中数据全部初始化,因此补齐的7个字节仍是其他函数遗留在栈空间中的,在后面复制到分配的用于缓存的堆内存中时,也将这部分数据一起复制了过去。 这就导致之前在栈中存放的其它函数的信息,被存入缓存的tagTEXTMETRIC结构中, 下次程序再通过NtGdiGetTextMetricsW获取时,就会获取到这些信息,如果栈中的信息恰好是内核地址信息,就会导致内核模块的信息泄露。 经过调试发现,目前最新补丁的Windows8.1 x64上,在首次调用并存储缓存结构时,这里的栈位置恰好存储了win32k!SetOrCreateRectRgnIndirectPublic+0x42函数的一处返回地址, 由于这里只有7个字节的地址信息,低8位会被修改为tmCharSet的数值(一般是0),因此最后通过NtGdiGetTextMetricsW获取的,会是再往上一点的RGNOBJ::UpdateUserRgn这个函数结尾处的垃圾对齐空间的位置。 这个漏洞显然远不如之前我们提到的CNG.SYS的泄露漏洞好用: 首先,栈上的信息可能因为调用路径或其他原因改变而不稳定,经过我们测试,这里的栈位置在某些调用路径下,并不是返回地址,而是其他的垃圾数据,这就会直接导致这个漏洞失效; 其次,Win32k的版本过多代码变动复杂,这个RGNOBJ::UpdateUserRgn的位置随时在变动,在低完整性级别下攻击代码还可以通过识别win32k.sys的版本做调整,在AppContainers(EPM)或Untrust级别下,就无法做到这点,只能硬猜,这也是为什么目前Github上的攻击代码不能在最新的全补丁Windows 8.1 x64上工作的原因:这个函数的位置发生了变动。 鉴于目前看到的这个攻击代码同上一个Hacking Team泄露的Windows内核权限提升漏洞的已经成熟“商用”的攻击代码不同,还只是出于演示目的的、存在很多硬编码的示例代码,因此很可能以后攻击代码的作者会使用更稳定、更好用的地址泄露漏洞来替换这个漏洞。毕竟,微软才刚刚意识到这类问题的严重性,内核中还存在很多类似的漏洞和问题。 #◆3 Adobe Font Driver(atmfd.dll)内核池溢出漏洞 接下来我们来分析这个攻击代码中的重头戏:字体漏洞, 我们可以看到,在攻击代码中使用了AddFontMemResourceEx函数来加载了font-data.bin这个OTF字体文件,我们尝试在Windows 7系统上将这个文件改名为.otf文件(Explorer在渲染这个文件时也会加载它),系统立即蓝屏崩溃,但是在Windows8.1 x64系统上,则不会出现这个情况,是不是Windows 8.1中已经修补了这个漏洞? 我们再进一步验证,在Windows7系统上蓝屏崩溃时,我们看到蓝屏的代码是0x19 BAD_POOL_HEADER,看上去这似乎是一个Windows内核池的溢出漏洞,那么是不是在Windows 8.1上这个漏洞所溢出的内核池恰好没有被用到而导致不容易触发崩溃呢? 我们打开Driver Verifier工具,针对win32k.sys打开Speical Pool功能。关于驱动校验器的这个功能,可以参考微软MSDN的介绍:https://msdn.microsoft.com/en-us/library/windows/hardware/ff551832(v=vs.85).aspx,这个功能类似用户模式中的Page Heap功能,会将指定模块分配的Windows内核池放入特殊的内存位置,使得这类内核池的溢出在第一时间被发现,开启了这个功能后,我们如愿地在Windows 8.1 x64上100%触发这个漏洞的蓝屏崩溃。 我们可以看到这个崩溃的栈(这里是在桌面浏览字体文件触发,因此是 NtGdiAddFontResourceW函数) atmfd….win32k!atmfdLoadFontFilewin32k!PDEVOBJ::LoadFontFilewin32k!vLoadFontFileViewwin32k!PUBLIC_PFTOBJ::bLoadFontswin32k!GreAddFontResourceWInternalwin32k!NtGdiAddFontResourceW这里可以看得很清楚,这是在win32k.sys驱动加载这个字体文件,在PUBLIC_PFTOBJ::bLoadFonts函数中,win32k.sys会将字体映射到内核中,进行一些字体对象的处理后,会调用这个字体对应的字体驱动,这里的这个adobe OTF字体最终就触发了atmfdLoadFontFile这个函数,这个函数atmfd.dll会输出的加载字体接口的封装,最后就进入atmfd.dll的代码中执行,实现最终的字体加载过程。 atmfd.dll是Adobe的代码编译的驱动,这里微软并没有给这个驱动提供符号文件,因此针对它的分析相对困难一些。我们通过分析代码的执行流程,结合内核调试和字体分析工具(如T2F Analyzer),进一步深入分析atmfd.dll中的处理这个字体,最终触发漏洞的过程(以Windows 8.1 x64上最新补丁的atmfd.dll为例): [*]通过win32k!atmfdLoadFontFile进入atmfd中的+0x13DE0位置函数,我们称其为AtmfdLoadFont,这里是atmfd.dll中的加载字体接口,会识别字体的格式类型,填充相关的字体结构,并交给下面的进一步加载字体的函数来处理 [*]通过AtmfdLoadFont进入偏移+0x178F0的函数,我们称其为AtmfdLoadFontInternal,该函数进一步分析和处理字体的信息(如字体的文件时间),并通过EngMapFontFile等函数映射字体文件到内核,映射完成后,这个函数判断字体的类型为OTF,接着会进入一个专门处理OTF字体信息的函数中。 [*]通过AtmfdLoadFontFileInternal进入偏移+0x17D55的位置,我们称其为ProcessOTFFontInfo,该函数开始分析字体文件各个表的内容,接着我们看到有一个专门处理’GPOS‘这个表的FeatureList中标记为”kern”(kerning,用于处理字距)的FeatureTag的FeatureRecord的相关函数。 [*]进入这个专门处理”kern“的FeatureRecord的函数,偏移+0x23128,我们称其为ProcessKerningRecord。到了这个函数就进入了这里比较关键和复杂的细节。 5.刚才说到当PosFormat= 2 时会进入专门的处理SubTable函数,这里偏移是0x22A9C,我们称其为ProcessFormat2SubTable,这里也就是这个漏洞的本质原因的地方了,在这个函数中,会根据SubTable的Class1Count和Class2Count计算需要的长度,计算的方式是0x20 * ClassXCount = 内存长度,即0x20长度一个item,然后分配对应长度的内存,接着偏移0x21d08的函数(我们称其为CopyClassDefData)会调用将SubTable的ClassDef1和ClassDef2中的数据复制到这些内存中,同时,在这些内存的第一个item会复制到一个0x20字节的特殊数据。 这段逻辑的反编译代码如下: #!c+01 Class1DefBuf = AllocMemory(32i64 * (unsigned int)Class1Count, v23, 1, 1);02 if ( Class1DefBuf )03 {04 Class2DefBuf = AllocMemory(32i64 * Class2Count, v24, 1, 1);05 if ( Class2DefBuf )06 {07 Class1DefSrc = *(_BYTE *)(SubTableObject + 9) | (unsigned __int16)(*(_WORD *)(SubTableObject + 8) |
|