翻译:ebeK9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4L8$3!0Y4L8r3g2H3M7X3!0B7k6h3y4@1P5X3g2J5L8#2)9J5k6h3u0D9L8$3N6K6M7r3!0@1i4K6u0W2j5$3!0E0i4K6u0r3x3U0l9I4y4#2)9J5c8U0p5H3i4K6u0r3N6i4y4A6L8X3N6Q4x3X3c8T1K9h3&6S2M7Y4W2Q4x3X3c8V1K9h3k6X3K9h3&6Y4i4K6u0V1N6r3!0Q4x3X3c8V1K9i4y4U0L8%4k6W2M7W2)9J5k6h3S2@1L8h3H3`.
补丁比较是比较相同代码的两次构建(一个已知有脆弱性,一个包含修补)的常用技术。通常通过该技术来发现措辞含糊的漏洞公告背后的技术细节,弄清漏洞根本原因、攻击代码和可能的攻击变种。近些年,该方法吸引了大量研究【1】【2】【3】,并且开发出了许多工具【4】【5】【6】,该方法在利用1-day漏洞攻击未及时打补丁用户上体现了价值。对于可以很容易地逆向的软件,已公布补丁的脆弱性被攻击者利用是不可避免的。
同样,若一个产品同时有多个版本在市面上,二进制比较也可以被用来发现它们之间的差异。比如Windows操作系统,当前微软还支持的版本有Windows 7、8和10。并且,当前Windows7在桌面市场仍然占有近50%的份额。微软对较新的版本引入和许多架构上的安全加固和补丁。这让旧版本系统的用户很不安全,使得他们可以被软件缺陷的脆弱性攻击,这些脆弱性可以简单地通过定位不同版本系统对应代码的微妙变化发现。
在后面的文章里,我们会展示如果通过二进制比较发现一个0-day例子,这是一个用户模式程序的未初始化内核内存泄露bug。这类bug在本地提权中很有用,也可以获取内核地址空间中存储的敏感数据。如果对这类bug不熟悉,我们建议你先看一下今年REcon和BlackHat上的演讲Bochspwn Reloaded【9】。
大多数内核内存泄露发送的原因是将一大块内存区域复制到用户模式时内存未初始化,这块内存可能是结构、联合体、数组或它们的结合。发生这种情况意味着,内核向ring-3程序提供了比有效数据更多的内容,可能的原因是:被编译器插入的填充部分,未使用的结构/联合体,为可变长度内容提供的固定长度大数组。这类脆弱性的修复工作通常只是换一个小的内存空间,原来的代码行为被完全保留,并且还要加一个memset调用来初始化输出内存以保证它不包含无关的数据。这使得通过逆向工程识别这类补丁非常容易。
当对134K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6T1N6h3N6K6i4K6u0W2j5$3S2J5L8$3#2A6N6h3#2Q4x3X3g2G2M7X3N6Q4x3V1k6H3i4K6u0r3M7s2u0G2K9X3g2U0N6q4)9J5k6s2A6W2M7X3!0Q4x3V1k6A6M7%4y4#2k6i4y4Q4x3V1k6V1k6i4c8S2K9h3I4Q4x3@1k6A6k6q4)9K6c8o6p5J5y4U0N6Q4x3U0k6S2L8i4m8Q4x3@1u0V1k6i4y4U0i4K6y4p5x3W2!0q4c8W2!0n7b7#2)9^5z5p5u0G2j5$3S2K6M7s2N6F1i4@1f1#2i4K6S2r3i4K6V1I4i4@1f1%4i4K6S2q4i4@1t1H3i4@1f1%4i4K6W2m8i4K6R3@1i4@1f1@1i4@1u0p5i4K6S2p5i4@1f1@1i4@1u0m8i4K6S2q4N6$3W2F1x3K6u0C8i4K6t1I4e0Y4c8s2k6r3W2s2k6i4c8s2L8s2W2H3K9p5!0#2N6r3I4A6L8X3g2Q4c8e0N6Q4z5f1q4Q4z5o6c8i4K9h3&6V1L8%4N6K6i4@1f1#2i4K6R3$3i4K6R3#2i4@1f1$3i4@1p5H3i4@1t1^5i4@1f1$3i4@1t1I4i4@1p5H3i4@1f1#2i4K6R3$3i4K6R3#2i4@1f1#2i4@1q4p5i4K6V1^5i4@1f1$3i4@1t1K6i4K6R3@1i4@1f1&6i4K6W2o6i4@1t1J5i4@1g2r3i4@1u0o6i4K6R3&6i4@1f1^5i4@1u0r3i4K6W2n7i4@1f1^5i4@1p5I4i4K6S2o6i4@1f1%4i4@1t1J5i4K6V1%4i4@1f1%4i4K6V1#2i4@1p5#2i4@1f1#2i4K6R3^5i4K6R3$3i4@1f1$3i4K6W2q4i4K6V1H3i4@1f1$3i4K6V1%4i4@1t1$3i4@1g2r3i4@1u0o6i4K6S2o6i4@1f1$3i4K6R3^5i4K6V1I4i4@1f1$3i4K6R3@1i4K6S2r3i4@1f1^5i4@1q4r3i4K6R3$3i4@1f1#2i4K6R3^5i4@1t1H3i4@1f1^5i4@1u0r3i4K6V1&6i4@1f1@1i4@1t1^5i4@1q4m8j5Y4g2Y4i4@1f1#2i4K6S2r3i4@1q4m8i4@1f1#2i4K6W2o6i4@1p5^5g2$3W2F1k6r3!0%4M7K6N6Q4c8e0g2Q4z5e0u0Q4z5p5x3^5i4@1f1@1i4@1t1^5i4@1q4p5i4@1f1#2i4@1q4p5i4K6V1^5i4@1f1#2i4K6W2o6i4@1p5^5i4@1g2r3i4@1u0o6i4K6S2o6i4@1f1#2i4@1u0q4i4@1q4q4i4@1f1^5i4@1u0p5i4@1q4r3i4@1f1#2i4@1t1%4i4@1t1J5i4@1f1%4i4@1u0n7i4K6S2r3i4@1f1#2i4K6W2o6i4@1p5^5g2$3W2F1k6r3!0%4M7K6p5H3i4@1f1@1i4@1t1^5i4@1q4p5i4@1f1#2i4@1t1%4i4@1t1J5i4@1f1%4i4@1u0n7i4K6S2r3i4@1f1#2i4@1t1H3i4K6R3$3i4@1f1#2i4K6R3#2i4@1t1$3i4@1f1#2i4K6W2o6i4@1p5^5i4@1f1#2i4K6R3$3i4K6R3#2i4@1f1&6i4K6R3K6i4@1p5^5i4@1f1@1i4@1u0r3i4@1q4q4i4@1f1#2i4@1p5@1i4K6S2p5i4@1f1K6i4K6R3H3i4K6R3J5i4@1f1@1i4@1t1^5i4K6S2n7i4@1f1#2i4K6W2n7i4@1u0q4i4@1f1$3i4K6V1^5i4@1u0q4i4@1f1%4i4@1p5@1i4@1u0m8i4@1f1@1i4@1u0r3i4@1q4q4i4@1f1#2i4@1p5@1i4K6S2p5i4@1f1#2i4K6R3&6i4K6S2p5i4@1f1#2i4K6V1H3i4K6S2q4i4@1f1@1i4@1u0n7i4@1p5K6i4@1f1%4i4@1p5H3i4K6R3I4i4@1f1%4i4K6W2m8i4K6R3@1i4@1f1@1i4@1t1^5i4K6S2p5i4@1f1#2i4K6V1H3i4K6S2o6i4@1g2r3i4@1u0o6i4K6S2o6i4@1f1%4i4K6V1@1i4@1t1I4d9r3g2^5i4K6u0V1f1X3q4&6M7#2!0q4y4g2)9^5c8W2)9^5c8q4!0q4y4#2!0n7b7#2)9&6y4W2!0q4z5q4!0m8c8W2)9&6x3g2!0q4c8W2!0n7b7#2)9^5b7#2!0q4y4#2)9&6y4q4!0n7x3f1c8A6j5i4m8Z5L8%4u0S2i4@1f1^5i4@1u0r3i4K6W2n7i4@1f1^5i4@1p5I4i4K6S2o6i4@1f1@1i4@1u0m8i4K6S2o6i4@1f1^5i4@1u0r3i4K6W2n7i4@1f1#2i4K6R3^5i4@1t1$3i4@1f1$3i4@1q4r3i4K6V1@1i4@1f1^5i4@1u0q4i4K6R3K6i4@1g2r3i4@1u0o6i4K6W2m8
![]()

Windows10中补丁的特征非常明显(在syscall的顶层处理过程中一个新的memset调用),我猜测在老版本内核中会有其它相似的问题在新版本中被微软默默修复。为了验证这个猜测,我决定在Windows7和Windows10中比较所有顶层syscall处理函数(如以Nt作为前缀的函数,位于内核和图形子系统中)中memset的数量,然后在Windows8.1和Windows10间进行一样的比较。原理上这是个非常简单地分析,一个很简单地分析方法就可以得到期望的结果,我决定在IDA反汇编产生的代码列表上执行比较。
执行比较时,我很快发现内核中的内存清0操作都被编译为三种模式的一种:
![]()

![]()

![]()

最常见的两种情况(memset和rep stosd)都被Hex-Rays反编译为对memset的调用。
不幸的是,使用一个被清0的寄存器执行一系列mov指令没有被Hex-Rays识别为调用memset,所幸这种情况很少,所以在后续人工处理误报前可以忽略。最终,为了容易些,我决定基于反编译后的.c文件进行代码比对而不是反汇编文件。
获得最后结果的完整操作步骤如下,我们分别对Windws7/10和Windoes8.1/10各执行一次:
最后得到的结果统计如下:
直观看,Windows7/10之间的不同比Windows8.1/10之间大。另一个有趣的结果是图形子系统的变换相对小,但比核心内核的syscal处理过程多。在此结果之上,我们人工详细分析每一个有变换的函数,在 win32k!NtGdiGetFontResourceInfoInternalW和 win32k!NtGdiEngCreatePalette系统服务中发现了两个新的脆弱性。它们都在17年9月的补丁中被修复了,它们有一些相同的特征,接下来分别讨论:
表明存在bug的不一致memset如下:
![]()

这是一个0x5c字节的栈上的内核内存泄露。函数代码的结构遵循一个通常的Windows优化设计,使用一个栈上的本地缓存进行较短的syscall输出,并且池分配器直接使用一个比输出大的空间。相关的伪代码片段如下:
![]()

注意到,就算在存在脆弱性的过程中,内存泄露只在第一个分支中存在,所以需要的缓存尺寸(a4)最大是0x5c字节。因为动态的PALLOCMEM池分配内存时会执行清0操作:
![]()

幸运的是,介绍这个例子也顺便介绍了另一个安全漏洞(340K9s2c8@1M7q4)9K6b7g2)9J5c8W2)9J5c8X3Z5H3x3s2u0#2i4K6u0W2N6X3g2^5K9h3I4D9K9i4g2E0i4K6u0W2L8%4u0Y4i4K6u0r3M7$3I4A6k6r3g2K6i4K6u0r3x3U0l9I4y4#2)9J5c8Y4u0W2j5$3!0F1i4K6u0W2M7r3c8X3i4@1f1%4i4K6W2m8i4K6R3@1x3K6u0Q4x3X3b7K6x3#2!0q4z5g2!0m8x3g2!0n7y4g2!0q4c8W2!0n7b7#2)9^5z5g2!0q4y4q4!0n7z5q4!0m8c8q4!0q4y4q4!0n7z5q4)9^5c8g2!0q4y4#2)9&6y4q4!0m8z5q4!0q4y4W2)9^5z5q4!0n7y4#2!0q4y4W2!0m8z5q4!0m8x3g2!0q4y4g2!0n7b7#2)9^5c8W2!0q4y4q4!0n7b7g2!0m8y4q4!0q4y4q4!0n7b7g2)9&6x3W2!0q4y4#2)9&6b7g2)9^5y4q4!0q4y4W2)9&6y4W2!0n7z5g2!0q4y4g2!0n7b7#2)9^5c8W2!0q4x3#2)9^5x3q4)9^5x3W2!0q4y4g2)9^5z5q4!0m8z5g2!0q4y4#2)9&6y4q4!0m8z5q4!0q4y4W2!0n7b7#2)9^5c8W2!0q4y4W2!0n7y4q4)9&6c8g2!0q4y4#2)9&6b7g2)9^5y4q4!0q4y4q4!0n7b7W2!0m8x3#2!0q4y4#2!0m8x3q4)9^5x3g2!0q4y4W2!0m8z5q4!0m8x3g2!0q4y4g2!0n7b7#2)9^5c8W2!0q4y4g2!0m8y4W2)9^5x3W2!0q4y4q4!0n7z5q4)9^5b7W2!0q4c8W2!0n7b7#2)9&6b7b7`.`.
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课