如果你还没有阅读本系列的第一部分,请在此处查看。
我们的上一篇文章回顾了来自Pagefault提交的内容,详细介绍了Bitdefender 杀毒软件产品中的整数溢出问题。虽然仅依靠这些内容就足以向供应商提交一份漏洞报告,但Pagefault还通过提供一个概念验证(PoC)漏洞利用来进一步支持该报告。以下是实际中的利用:
Youtube视频连接:https://youtu.be/fPK8UjAogWg
Pagefault提供了关于漏洞如何工作的以下细节。
漏洞利用
由于终止条件是一个虚拟的越界读取,以及Bitdefender代码仿真器实现SEH的事实,我们可以多次重写覆盖。我们还可以通过修改执行的代码来调整被覆盖的长度和内容。
该vsserve.exe可执行文件的确会唤醒ASLR和DEP,然而通过在JIT页面内利用与位置无关的shellcode,漏洞利用绕过了这些缓解措施。实现代码执行需要操纵任意的内存地址。
Bitdefender仿真器为仿真程序提供虚拟地址空间。一个0x1000字节的仿真器页面具有一个0x1048字节的相应实际页面,其中包含几个帮助仿真在页面上操作的字段:
在仿真程序内部遇到的多个VirtualAlloc调用和相关的内存访问将导致创建多个0x1048结构,随后在遇到相应的VirtualFree调用,这些结构将被释放。
在内部,0x1048分配通过msvcrt的 malloc()执行,并落在低碎片堆(LFH)中。对于Windows 7及以下版本,继续利用的方法是分配多个虚拟页面,释放至少一个虚拟页面,触发分配并溢出0x1048缓冲区的脆弱功能,然后破坏这个虚拟页面。
对于Windows 8及以上版本,LFH增加了随机性,使得堆不那么确定。漏洞利用通过将LFH随机表位置重新设置为在重新分配尝试之间的0xFF分配,从而绕过了随机化。为了实现所需数量的内存分配并避免额外分配,必须为仿真代码创建JIT代码。这是通过执行一段代码至少34次来实现的。
在Windows 8及以上版本上实现漏洞利用的步骤示例:
l NUMPAGES(例如60个)虚拟页面被分配;
l 在LFH库中有一个随机的位置,最后一页被释放;
l 随后是0xFF分配(JIT被触发以精确地针对这个数字);
l 脆弱的功能被触发,脆弱的缓冲区被分配来代替最后一页;
l 触发8个字节的有限覆盖,并检查其他NUMPAGES-1虚拟页面,以查看其内容是否被修改;
l 如果检测到修改:
n 0xFF分配总数必须发生在脆弱缓冲区和下一个缓冲区之间的最后分配之间;
n 0x1048字节的另一个缓冲区被分配到与最后一个字节相同的位置,并且这一次覆盖了足够多的字节(0x1024),允许漏洞利用。
l 如果未检测到修改,漏洞利用则将分配另一组虚拟页面并重复这个过程。
重复这个过程直至检测到修改或达到重试限制。有限的覆盖是必需的,以避免碰到攻击保护页面。
一旦虚拟页面被修改,任何访问它的尝试都会受到影响。偏移量0x1020处的dword决定了用于计算被访问内存的真实地址所需要使用的目标读/写地址:
real_address = real_address_base + requested_virtual_address – dword[0x1020]
这使我们可以写入任意的偏移量。在这种情况下,可以修改位于已损坏缓冲区之前的第二个虚拟缓冲区的偏移量0x1020,从而允许对第二个虚拟缓冲区的写入偏移量进行重复控制。换句话来说,我们将损坏的缓冲区指向先前的缓冲区:
到目前为止所描述的所有技术都可以通过任意偏移量实现可靠的读/写原语。然而,这对于代码执行来说是不够的。
如果一段代码被仿真器解释至少34次,那么JIT编译就会启动,然后仿真器解释给定的操作码并构造相应的动态代码,以便对仿真代码执行以下调用。构造的代码被放置在一个可写和可执行的内存页面中。
对于每个JIT段,malloc()分析重复代码时都会创建一个内存结构。该内容结构的大小可以通过在仿真代码中放置计算指令来控制。通过多次调用具有多个push ecx/pop ecx对的函数,可以实现0x1048字节的大小。
重复调用的函数数组由相同的内容构成,并且只有在实现了任意读写功能后才会对每个函数执行第34次调用,并释放最后一个脆弱的缓冲区。
其中一个虚拟缓冲区占用的空间将被其中一个JIT结构占用,因此我们可以使用读/写偏移量来访问它。相对于控制的虚拟页面,JIT缓冲区被放置为0x1108*x字节(0x1108 = 0x1048,四舍五入到最近的LFH大小,为块标头添加了8个字节)。
JIT结构从几个有用的字段开始:
通过在偏移量0x2C处读取dword,我们可以提取损坏的虚拟页面的实际地址,从而可以进一步对任意地址进行读写,而不仅仅是对任意偏移量进行读写。新获得的能力接下来用于修改放置在JIT结构中第一个dword所指向的地址处的字节。
一个自定义的shellcode将包含的可执行文件转储到文件中并执行,它被放置在编译后的JIT代码中,,该代码在下一个调用对应的JIT编译函数的调用中执行。shellcode以一个TerminateProcess()结尾,避免了由于损坏的堆而导致的潜在崩溃。
结论
如果你想自己测试一下,PoC就在这里。它应该在73447之前的BitDefender版本上工作。
这个漏洞还表明,即使启用诸如ASLR和DEP这样的缓解措施,熟练的攻击者仍然可以找到执行代码的方法。如果你是一位开发人员,希望避免软件中出现整数溢出问题,CERT提供了一些关于如何避免在各种操作中出现带符号整数的溢出的优秀指南,这是绝对值得回顾。最后,感谢Pagefault的报告和Bitdefender对问题的及时解决。
审核人:yiwang 编辑:少爷
发表评论
您还未登录,请先登录。
登录