我们经常能看到在编写C++程序时由于编程错误而引起的漏洞。然而,当程序员编写正确的C ++程序但由于编辑器在编译过程中将代码编译出漏洞是十分少见的。在去年10月份,我就经历了类似的事情。当我写的工具出现崩溃时,我发现故障来源于于Visual C++
编译器。 微软将我们的漏洞报告称为CVE-2019-0546
。然而这个漏洞在正式版本中仍没有被修补。
事件起源
当时我正使用由Borland编译的x86模块进行工具编写工作。
检测框架在运行中调用了一个回调函数,该函数将在目标模块中调用原函数。然而目标函数的调用约束与Microsoft Visual C++
不兼容,因此我的回调需要包含自定义__asm
代码。 为了简化问题的复杂度,我将回调定义为lambda
,如下所示:
lambda
定义了一个回调函数,它有一个参数,并指定原始函数的地址。 回调将参数从获取的变量处复制到原始函数中(m,s)
,并按照原始函数的内容将它们放入寄存器中。 (注意,第一个参数进入@eax。这与Microsoft并不是兼容的,因此需要__asm
。)接下来,它调用原始函数。 最后,它将原始函数的返回值从@eax
处复制到变量r中。
编译器对这个代码进行了编译并没有报错,但奇怪的是,编译后的代码没有按预期工作。 生成的指令未访问变量的正确堆栈位置。 读取变量时,它访问了错误的堆栈位置,然而这个操作可能泄漏敏感的堆栈数据。 写入捕获的变量r时,如果我们写入到堆栈上的位置不正确,那么可能会破坏数据或控制流。
该错误由满足以下两个条件的lambda
表达式触发:
1 lambda可通过引用或通过复制进行内部获取。
2 lambda包含一个__asm
块。
PoC详情
很快我就编写了一个独立的PoC
。 这适用于Visual Studio 2015
,其目的为Release x86
配置进行编译:
请注意,由于x是全局变量而不是基于堆栈的变量,lambda
可正确访问变量。但是,当它写入变量y时,它会写入错误的堆栈地址并破坏框架上的@ebp
值。 当控制返回main
时,@ebp
包含0xdeadbeef
的错误值。
这是导致崩溃的截图:
Visual Studio 2017也受到影响。
补丁详情
然而,虽然这个bug会影响Visual Studio 2015
和Visual Studio 2017
(可能还有其他我们尚未测试过的版本),但Microsoft
只发布了Visual Studio 2017
的补丁。上面显示的漏洞仍然存在于Visual Studio
的最新更新中。。 当被问及原因时,微软表示:
“这个
CVE-2019-0546
报告是关于禁止C ++ lambda
内部的内联汇编。 该漏洞是关于下载并运行不受信任的代码,在支持lambdas的VS2017 Update 9之前的所有版本中始终存在该漏洞。 该漏洞利用场景并不常见。考虑到我们在所有先前版本中均有这种情况,并且没有看到任何利用漏洞的威胁发生,所以为VS版本打补丁是没有意义的。”
此外,我发现微软对Visual Studio 2017
的修复是删除lambda
中对__asm
块的支持。 现在,如果用户尝试在Visual Studio 2017
上编译上述PoC
代码,则会出现以下编译器错误:
所以,我现在是Visual C ++
编译器CVE以及全新的CXXXX
编译器错误的唯一拥有者。:)
虽然这次漏洞发现不同寻常,但我们仍然认为它值得修补并希望微软在未来重新考虑补丁计划。 还应该注意的是,虽然Microsoft将此错误评为中等,但Visual Studio
中的其他错误已经达到严重的等级。 该漏洞可以允许攻击者的代码在登录用户级别执行。
结论
长期以来,人们一直认为编译器可能会在编译时会将后门或易受攻击的漏洞引入软件中。 在实践中,编译器将漏洞引入100%正确的非恶意代码是十分罕见的。 然而,也许在平凡的日子里,我们还是有可能幸运的偶然发现漏洞的存在。
您可以在Twitter上找到我@HexKitchen
,并跟随团队获取最新的漏洞利用技术和安全补丁。
本文为翻译文章,来自:https://www.zerodayinitiative.com/blog/2019/2/28/finding-unicorns-when-the-c-compiler-writes-the-vuln