译者:shan66
预估稿费:200RMB
投稿方式:发送邮件至linwei#360.cn,或登陆网页版在线投稿
macOS出现Bug密码提示直接显示密码
不久前,巴西软件开发者 Matheus Mariano发现了苹果公司最新操作系统High Sierra中的重大的编程Bug,系统的磁盘工具直接将加密APFS宗卷的密码明文显示出来。本应显示的是密码提示,但这个编程bug却直接将密码给显示出来了。这个严重的安全问题很快就成了各大技术网站的头条。
可怕的密码提示bug:这里的“dontdisplaythis”实际上就是密码。
之后,苹果开始通过App Store向客户提供macOS High Sierra补充更新,并确保服务器中的每个High Sierra版本都包含该更新。
通过逆向过程分析Bug成因
在本文中,我们将利用二进制文件对比技术来研究一下这个更新,深入剖析引起该bug的根本原因,并以此为鉴,防止将来犯同样的错误。
通过检查大小为51MB的软件包,我们可以看到Disk Utility和Keychain Access应用程序以及相关框架和命令行工具中都发生了变化:
由于本文只关注密码提示bug,所以我们的第一步是提取Applications/Utilities/Disk Utility.app/Contents/MacOS/Disk Utility ,并将其与之前留存的macOS 10.13 High Sierra中的相同二进制文件进行比较。为此,我写了一个Emacs扩展,每当我在缓冲区中加载Mach-O文件时,都会启动IDA,生成一个包含反编译函数的SQL数据库,然后加载修补过的二进制文件,最后显示由Diaphora生成的差别报告。 这种技术对于解构由次要修补程序版本更新的二进制文件非常有用,因为通常只有很少的变动,并且常用的试探法也很奏效。
两个版本的Disk Utility二进制文件在反编译后,两者之间并没有什么区别:
这通常意味着唯一的重大变化发生在某些链接的框架中。在这次调查过程中,最有可能的目标就是StorageKit了,这是一个私有的Apple框架,其作用是将APFS功能暴露给Disk Utility。 它有两个部分组成:客户端库和守护进程storagekitd。客户端使用苹果的标准XPC机制连接到守护程序。而守护进程则执行客户端请求的各种操作(即NSOperation的子类)。Disk Utility中的StorageKit有一个有趣的用法:
从Disk Utility中的控制器代码引用StorageKit结构。
这是在从Disk Utility界面添加新的APFS卷时运行的代码(具体来说,负责管理新的卷表的控制器)中的一部分。
通过比较StorageKit,我们可以得到更多感兴趣的结果:
[SKHelperClient addChildVolumeToAPFSContainer:name:caseSensitive:minSize:maxSize:password:passwordHint:progressBlock:completionBlock:] 是补充更新修改的函数之一。 检查反编译的差异对比,可以帮助我们揭示实际的bug:
在上图中,原先带有bug的StorageKit与更新后的明显不同。其中,删除的行我们以红色显示,添加的行用绿色显示,黄色表示发生变化的内容。上述函数的作用就是创建了一个NSMutableDictionary(Cocoa的哈希表)的实例,并填充有关该卷的信息。这个字典传作为optionsDictionary参数递给addChildVolumeToAPFSContainer:optionsDictionary:handlingProgressForOperationUUID:completionBlock:。
这个字典中最有趣的键是kSKAPFSDiskPasswordOption和kSKAPFSDiskPasswordHintOption,它们分别负责存储密码和提示密码。这里的bug是将包含密码的同一个变量(在反编译中表示为相同的虚拟寄存器v50)用作字典中两个键的值,这意味着明文密码通过XPC作为密码提示错误地发送了出去。通过重建Objective-C代码,该bug大致如下所示:
NSMutableDictionary *optionsDictionary = [NSMutableDictionary alloc] init];
[...]
optionsDictionary[kSKAPFSDiskPasswordOption] = password;
optionsDictionary[kSKAPFSDiskPasswordHintOption] = password;
以下是补充更新中经过纠正后的函数:
请注意,这里为密码和密码提示设置了不同的变量。
这是一种常见的bug:复制和粘贴使用了公共结构的代码后,开发人员忘记进行必要的修改,从而导致代码的行为发生了致命的变化。如果对这方面的内容感兴趣的话,这篇博客文章中提供了更多开源软件中“Last Line Effect”错误的示例。
需要重点强调的是,虽然这个特定的字典没有被存储到任何地方(它只是用来打包发送到storagekitd的信息),但是误将密码作为密码提示发送这一事实,意味着storagekitd信任客户端,并将其存储为明文,认为这是一个密码提示。
为什么在使用命令行时不会出现该bug?
这是一个非常常见的问题。显然,Disk Utility和命令行diskutil使用了不同的代码路径。StorageKit好像对diskutil没有直接依赖关系,或者间接的依赖关系。这里是otool -L的输出结果:
/usr/lib/libcsfde.dylib (compatibility version 1.0.0, current version 1.0.0)/usr/lib/libcsfde.dylib (compatibility version 1.0.0, current version 1.0.0)
/usr/lib/libCoreStorage.dylib (compatibility version 1.0.0, current version 1.0.0)
/System/Library/Frameworks/Foundation.framework/Versions/C/Foundation (compatibility version 300.0.0, current version 1443.14.0)
/System/Library/Frameworks/IOKit.framework/Versions/A/IOKit (compatibility version 1.0.0, current version 275.0.0)
/System/Library/PrivateFrameworks/DiskManagement.framework/Versions/A/DiskManagement (compatibility version 1.0.0, current version 1.0.0)
/System/Library/Frameworks/DiscRecording.framework/Versions/A/DiscRecording (compatibility version 1.0.0, current version 1.0.0)
/usr/lib/libncurses.5.4.dylib (compatibility version 5.4.0, current version 5.4.0)
/System/Library/Frameworks/DiskArbitration.framework/Versions/A/DiskArbitration (compatibility version 1.0.0, current version 1.0.0)
/usr/lib/libicucore.A.dylib (compatibility version 1.0.0, current version 59.1.0) /usr/lib/libobjc.A.dylib (compatibility version 1.0.0, current version 228.0.0)
/usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1252.0.0)
/System/Library/Frameworks/CoreFoundation.framework/Versions/A/CoreFoundation (compatibility version 150.0.0, current version 1443.13.0)
这种复制或多或少具有相同的功能,虽然有时是正当的,但肯定会增加出错的机会。
如何防止这种类型的bug?
有两个工程实践可以帮助我们应付这样的bug(但不能完全根除它们):
单位测试
单元测试是生成软件测试的一种实践方法,每次测试都是针对计算机程序中的单个单元的,其中“单元”通常是类或模块。有效的单元测试需要可靠地检测输出是否与预期的输出相符。在这个特定的bug中,需要用到与XPC服务的通信,因此将创建字典的逻辑与服务通信的部分分开将会有所帮助。当软件设计不容易测试时,如果公司过度依赖手动测试,那么这不是一种非常有效的测试方式,由于现代软件中典型的组合数量很多,人工难免会出现纰漏。
代码审查
代码审查是在软件项目中将代码纳入主要开发分支之前或之后审查代码的做法。代码审查的审查对象应该尽量较小,以便于审查人员可以集中注意力,从而提出更好的改进建议,甚至发现本文中提到的这种bug。如果一次审查大量代码的话,那么“last line”bug就很容易成为漏网之鱼。
小结
当苹果万众瞩目的High Sierra首次亮相的时候,竟然出现了一个惊人的bug,这确实有点尴尬。在本文中,我们对这个bug进行了深入剖析,通过分析其根本原因,帮助读者弄清楚其中到底发生了什么,最后给出了两种有效的软件开发实践(包括可测试的设计和严格的代码审查),帮助我们尽量少犯同样的错误。