导语:概述最近,我们的漏洞检测平台发现了一个新的类型混淆漏洞,该漏洞影响到所有Acrobat Reader(Acrobat DC、Acrobat Reader DC、Acrobat 2017、Acrobat Reader 2017、Acrobat XI、Reader XI)的最新版本。该漏洞的编号为CVE-2017-16379/CY
概述
最近,我们的漏洞检测平台发现了一个新的类型混淆漏洞,该漏洞影响到所有Acrobat Reader(Acrobat DC、Acrobat Reader DC、Acrobat 2017、Acrobat Reader 2017、Acrobat XI、Reader XI)的最新版本。该漏洞的编号为CVE-2017-16379/CY-2017-011。
这个漏洞的核心问题可以总结为一句话,“我们该如何检查void *的类型?”。
我们都知道,由于void指针被定义为无类型,所以可以指向任何类型的对象,并且任何类型对象的指针都可以转换为这种类型的指针。那么,当我们把一个对象指针转换为一个void指针后,仍然有可能通过某种方式检测出它的原类型,对吧?这个问题的答案:不完全是这样。
对象被转换为void指针后,再检测它的原来类型简直就是不可能的。那么,我们该如何检查void *的类型?答案是别这样做。因为当你这样做的时候,可能会引发一个安全漏洞。
归根到底,该漏洞是由AcroRd32.dll中的IsAVIconBundleRec6函数引起的。该函数会接收一个void指针,并尝试用魔法(常量)值来确定它的类型。
实际上,这种做法不仅无法确定其类型,而且还会导致类型混淆漏洞,攻击者可以利用这个漏洞对多种Adobe Reader产品实施远程执行代码攻击。
漏洞分析
函数IsAVIconBundleRec6接收一个任意的void指针,并试图确定它是否是一个指向AVIconBundleRec6对象的指针。
该函数假定,如果一个对象以特定的魔法值开头的话,那么它就是一个AVIconBundleRec6对象。
具体来说,该函数会进行相应测试看看是否满足以下条件:
1.该对象是有效的可读指针。
2.该对象的0-7字节是魔术值“CIVAUBNO”。
3.该对象的8-11字节是一个版本号(必须>= 0x5000)。
在大多数情况下,该函数会接收的是一个指向AVIconBundleRec6对象的有效指针,并且能够成功识别,这不会引起任何问题。
但有些时候,函数会接收到一个HBITMAP类型的对象。 与大多数的Windows句柄(通常是有效指针,例如HMODULE)相反,HBITMAP可以是0x0-0xffffffff之间的任意值,并且不被视为有效的指针。
由于HBITMAP可以是0x0-0xffffffff之间的任意值,所以,有时它可能会在无意中指向有效的内存位置,从而满足第1项测试(在0x601141D2处的测试)。
一旦通过了第1项测试,类型混淆就会发生,因为该函数混淆了HBITMAP的类型,并假定它是一个有效的指针,而不管它到底是不是有效指针。
那么,在什么情况下,用户才会因为使用类型为HBITMAP的对象来调用这个函数而受到攻击呢?实际上,AcroRd32Res.dll中就有一组图标资源,这些资源将被渲染器作为HBITMAP对象来加载。所以,这些HBITMAP对象每次被渲染时(每秒渲染多次),都会作为参数发送给IsAVIconBundleRec6。
渲染器在进行初次渲染时,会从资源中加载图标。并且,渲染器只会加载一次图标,当它再次渲染图标时,将使用第一次渲染时加载的HBITMAP(即渲染器缓存的HBITMAP)。这意味着,如果攻击者能够渲染其中一个图标,那么渲染器就会将其加载为HBITMAP对象,并且在每一秒内都会调用IsAVIconBundleRec6许多次。
如果HBITMAP对象无意中指向了一个有效的内存位置,那么IsAVIconBundleRec6会把它当做一个指针,从而导致类型混淆问题。
漏洞利用
为了利用这个漏洞,我们需要:
1.创建一个指向处于我们控制下的内存位置的HBITMAP。
2.以HBITMAP为参数调用IsAVIconBundleRec6。
3.利用IsAVIconBundleRec6搞定相应测试,使其返回true值(即,使其将指向处于我们控制下的存储器位置的HBITMAP验收为有效的AVIconBundleRec6对象)。
4.一旦该函数验收了HBITMAP,它就会将它作为一个有效的AVIconBundleRec6对象来使用,包括使用AVIconBundleRec6对象的AVIconHandler成员(一个类似vtable的指针)来执行各种函数调用。这样,攻击者就能够接管这个类vtable的成员,从而在下一次调用该函数时完全控制EIP。
第1步
为了创建一个HBITMAP对象,我们只需渲染上面列出的一个图标就行了。
为此,我们创建一个PDF文件,并将相应的图标放到该文件的起始页面即可。
一旦这个图标被渲染,渲染器就会加载相应的图标资源,并为其创建一个HBITMAP对象。
这个HBITMAP对象可以包含0x0-0xffffffff之间的任意值。
然后,利用处于我们控制之下的内存块进行堆喷射,从统计上来说,我们就有机会让HBITMAP指向处于我们控制下的一个内存块。
由于每个图标只能作为HBITMAP加载一次,所以我们可以渲染所有图标,这样就能得到8个不同的HBITMAP值,并且每个值都指向一个随机位置。如果至少有一个指向处于我们控制之下的内存位置,我们就可以继续执行我们接下来的漏洞利用流程。
生成8个随机值后,其中一个或多个值很可能指向处于我们控制之下的内存位置——我们的第1步就算完成了。
第2步
渲染器会自动使用8个HBITMAP对象来调用IsAVIconBundleRec6,并且每秒都会执行许多次。
如果HBITMAP无法通过IsAVIconBundleRec6的各项测试,则该函数会“优雅地”结束。这意味着,只要图标正处于渲染过程中,IsAVIconBundleRec6就会重复调用这8个HBITMAP值。
即使我们最初的堆喷射错过了其中一个HBITMAP,但是由于我们会连续进行堆喷射和释放动作,并且每秒重复多次,所以,我们很有可能会捕获到一个HBITMAP对象。这里不必担心崩溃,因为任何失败的尝试都会“优雅地”结束。
只要有一个以上的HBITMAP指向有效的用户模式地址,我们就能“捕获”到它。 如果所有HBITMAP都指向我们无法控制的内存位置(例如内核空间),那么该攻击就会以失败而告终。
第3步
这一步需要做的就是,设法满足IsAVIconBundleRec6的检查条件,以确保堆喷中包含符合AVIconBundleRec6对象结构要求的内存块(即以“CIVAUBNO”开头)。
第4步
IsAVIconBundleRec6一旦上当,即相信我们的内存块是一个有效的AVIconBundleRec6对象,(在IsAVIconBundleRec6被调用之后不久)它就会使用AVIconHandler成员来调用一个函数调用。当我们控制了整个对象,包括AVIconHandler成员后,就可以将其替换,这样在下一次调用函数时就能够完全控制EIP了。
POC
我们的POC代码可以根据要求再现这个漏洞(而不是利用这个漏洞)。
结束语
我们知道,根据void指针的定义来说,它是无类型的。
如果您确实需要使用void指针并且想知道该指针的类型,只需另外添加一个参数来说明其类型,或者将该指针包装到自定义结构中,然后通过一个结构成员来说明其类型即可。
如果应用程序无视“void指针是无类型的”这一事实,而试图检测其类型的话,肯定会引起不必要的麻烦,在最糟糕的情况下,甚至会导致可以被攻击者利用的安全漏洞。
备注
您一定想知道引发该漏洞的8个图标长什么样吧?那好,请看: