属性列表解析错误:通过格式错误的二进制plist使MacOS崩溃

2020-10-28 13:25:16

在这篇客座博客文章中,@OSCartoography背后的安全研究员(也是好朋友)描述了一个与解析属性列表相关的有趣的MacOS错误。这篇文章最初发布在他的个人网站上。谢谢分享!🤩。

最近,我决定是时候进入另一个操作系统兔子洞了,MacOS看起来足够有趣了,特别是因为我对它的内部结构知之甚少,尽管我经常使用苹果的产品。学习一个新的操作系统永远是一次冒险,但我通常需要一个具体的目标来为我的探索奠定基础,并以某种方式衡量成功与否。因此,我设定了通过将单个文件放在MacOS系统上来进行本地攻击的目标,并让分析挑战开始。仅仅几周后,我就发现了一个与属性列表相关的有趣的bug,本文将对此进行更详细的介绍。该漏洞于2020年5月报告给苹果公司,并为…打了补丁。虽然没有发出CVE。

属性列表(Plist)是存储序列化对象的文件,在Apple操作系统中非常流行,类似于Microsoft Windows使用注册表存储配置数据的方式。MacOS应用程序Automator的基于XML的属性列表示例如下所示。此属性列表存储应用程序的版本信息以及其他有用数据:

1<;?xml版本=";1.0";编码=";UTF-8";?>;2<;!DOCTYPE plist public";-//APPLIST//DTD plist 1.0//en";...>;3<;plist版本=";1.0";>;4<;dict>;5<;key>;BuildAliasOf<;/key<;6<;string>;Automator<;/string>;7<;key>;BuildVersion<;/key>;8<;string>;383<;/string>;9<;key>;CFBundleShortVersionString<;/key>;10<;string>;2.10<;/string>;11<;key>;CFBundleVersion<;/key>;12<;string>;492<;/string>;13<;key>;项目名称<;/key>;14<;string>;Automator_Executable<;/string>;15<;key>;SourceVersion<;/key>;16<;string>;492000000000000<;/string>;17<;/dict>;18<;/plist>;;

属性列表也可以是二进制形式,也称为bplist。顾名思义,这些是二进制格式的属性列表,支持一些额外的对象类型和关系,包括字典。下面是bplist00标记可以识别的示例bplist,尽管存在其他标记来支持该格式的其他版本:

00000000:6270 6c69 7374 3030 d401 0203 0405 060a bplist00.00000010:0b5f 1014 4e53 5374 6f72 7962 6f61 7264._.NSStoryboard00000020:4d61 696e 4d65 6e75 5f10 254e 5356 6965 MainMenu_.%NSVie00000030:7743 6f6e 7472 6f6c 6c65 7249 6465 6e74 wControllerIdent00000040:6966 6965 7273 546f 4e69 624e 616d 6573 TobNibStoryboard000050:5f10 134e 5353 746f 7279 626f 6172_NStoryboardV0060:6543 6f6e 7772 6f6c 6532c65 7249 6465 6e74 wControllerIdent00000040:6966 6965 7273 546f 4e69 624e 616d 6573 TobNibNamers624e 616d 6573。NSViewC00000070:6f6e 7472 6f6c 6c65 7249 6465 6e74 6966控件标识符Identif00000080:6965 7273 546f 5555 4944 7358 4d61 696e iersToUIDsXMain00000090:4d65 6e75 d207 0508 095f 1013 7369 6d75 Menu._.simu000000a0:6c61 746f 724d 6169 6169 6e57 696e 646f 775f latorMainWindow_000000b0:1013 7369 6d75 746d 724d 657。

通过查看苹果的开源代码可以更好地理解bplist格式,我也发现这个参考资料非常有帮助。苹果开源代码中定义的bplist格式如下:

二进制plist格式的属性列表提供了一个有趣的模糊目标,因为它们易于创建,并且可以由操作系统的许多部分使用,包括更高特权的进程。苹果的开源代码使我能够创建任意的bplist,并开始模糊文件格式,同时使用内置的MacOS Plutil命令行工具确保正确的语法。

我花了几天时间生成bplist来练习这种格式,并很快发现某些对象类型在被常见的MacOS二进制文件(如Finder,甚至包括启动服务守护程序(LSD)在内的更高特权的二进制文件)解析时会导致异常。系统崩溃日志表明Core Foundation框架中存在一个问题,但正如我们稍后将看到的,此错误存在于多个位置。

大多数生成的崩溃似乎是由Core Foundation解析bplist的方式以及随后试图使用创建的对象引起的。在ObjectTable中包含非字符串类型(Date、Data、Bool等)的对象的任何bplist。导致在调用不存在的字符串相关选择器时解析过程崩溃。结果是,使用Core Foundation读取属性列表的任何进程都可能崩溃,并出现无法识别的选择器异常。以下是易受攻击的代码路径示例:

CFBundleGetMainBundle<;--导出函数_CFBundleCreate CFBundleGetInfoDictionary_CFBundleRefresh InfoDictionaryAlreadyLocked_CFBundleCopyInfoDictionaryInDirectoryWithVersion_CFBundleInfoPlistProcessInfoDictionary CFStringFind CFStringFindWithOptionsAndLocale Crash!<;--调用无法识别的选择器。

使用测试应用程序所在目录中的以下C代码和名为Info.plist的恶意属性列表,可以轻松到达此位置:

1#import<;CoreFoundation/CoreFoundation.h>;2 int main(int argc,const char*argv[]){3 CFBundleRef myAppsBundle=null;4 myAppsBundle=CFBundleGetMainBundle();5 return 0;6}。

生成此错误的崩溃可以通过编程方式完成,也可以通过将修改后的bplist放在系统上自动解析来完成。事实上,这个错误的最初迹象之一是,当我试图用修改后的属性列表注册应用程序时,LSD在我的系统上反复崩溃。

目标-See有一篇很棒的博客文章,详细介绍了通过LSD注册应用程序和自动解析属性列表。阅读:";点击文件,应用程序打开";

下图是控制台输出,显示了在我的桌面上伪装成合法的Info.plist的修改后的bplist发生崩溃的频率。还要注意用户级和系统级进程崩溃。

通过修改单个字节将ASCII字符串对象(类型0x5X)更改为另一个对象类型(如DATE(类型0x33)),可以完成创建恶意bplist的LSD崩溃。修改后的bplist示例如下:

00000000:6270 6c69 7374 3030 d401 0203 0405 060a bplist00.00000010:0b5f 1014 4e53 5374 6f72 7962 6f61 7264._.NSStoryboard00000020:4d61 696e 4d65 6e75 5f10 254e 5356 6965 MainMenu_.%NSVie00000030:7743 6f6e 7472 6f6c 6c65 7249 6465 6e74 wControllerIdent00000040:6966 6965 7273 546f 4e69 624e 616d 6573 TobNibStoryboard000050:5f10 134e 5353 746f 7279 626f 6172_NStoryboardV0060:6543 6f6e 7772 6f6c 6532c65 7249 6465 6e74 wControllerIdent00000040:6966 6965 7273 546f 4e69 624e 616d 6573 TobNibNamers624e 616d 6573。NSViewC00000070:6f6e 7472 6f6c 6c65 7249 6465 6e74 6966控件标识符Identif00000080:6965 7273 546f 5555 4944 7358 4d61 696e iersToUIDsXMain00000090:4d65 6e75 d207 0508 0933 1013 7369 6d75 Menu...3.simu000000a0:6c61 746f 724d 6169 6169 6e57 696e 646f 775f latorMainWindow_000000b0:1013 7369 6d675 746d 6169。

这个小小的一个字节的改变现在可以用来对MacOS系统和大概的iOS造成严重破坏,尽管该平台在本研究期间没有经过测试。这种方法还会影响多个数据库,包括Spotlight数据库,它会被此恶意Info.plist污染,甚至在重新启动后也会反复导致崩溃。

凭借轻松重现崩溃的能力,我深入了解了这个错误实际存在的地方。跟踪此问题的一种简单方法是查看崩溃进程的堆栈跟踪。下面是来自前面显示的使用Core Foundation读取恶意属性列表的测试应用程序的崩溃日志。

2020-07-06 09:31:14.433 OpenInfo[79624:2895718]*未捕获异常导致APP终止';NSInvalidArgumentException';原因:';-[__NSDate Length]:无法识别的选择器发送到实例0x7fa546d033e0';***First throw call stack:(0 CoreFoundation 0x00007fff2c0058ab__exceptionPreprocess+250 1 libobjc.A.dylib 0x00007fff62126805 objc_exception_throw+48 2 CoreFoundation 0x00007fff2c084b61-[NSObject(NSObject)__retain_OA]+0 3 CoreFoundation 0x00007fff2bf69adf___forwarding___+1427 4 CoreFoundation 0x00007fff2bf694b8_CF_forwarding_prep_0+120 5 CoreFoundation 0x00007fff2bf27676 CFStringFind+45 6 CoreFoundation 0x00007fff2bf26cc8_CFBundleInfoPlistProcessInfoDictionary+194 7 CoreFoundation 0x00007fff2bf1faff_CFBundleCopyInfoDictionaryInDirectoryWithVersion+1117 8 CoreFoundation 0x00007fff2bf1f349_CFBundleRefreshInfoDictionaryAlreadyLocked+111 9 CoreFoundation 0x00007fff2bf1f2c8 CFBundleGetInfoDictionary+33 10 CoreFoundation 0x00007fff2c027d4f_CFBundleCreate+715 11 CoreFoundation 0x00007fff2bf102aa CFBundleGetMainBundle+148。12 OpenInfo 0x0000000109061f83 main+35 13 libdyld.dylib 0x00007fff634947fd start+1 14??0x0000000000000001 0x0+1)libc++abi.dylib:终止,类型为NSExceptionAbort Trap:6。

阅读下面的文章有助于理解Core Foundation如何处理无法识别的选择器异常,并阐明堆栈跟踪中的_CF_FORWARING_PREP_0的作用。有了这个信息,我将此之前的返回地址视为CFStringFind…中异常的可能来源。特别是在调用_CFStringGetLength之后。下面的反汇编说明了此调用:

CFStringFind反汇编我遍历了LLDB中的CFStringFind,直到调用_CFStringGetLength检查寄存器之前。从Apple的_CFStringGetLength文档中,我们知道第一个参数应该是字符串,这样我们就可以使用以下LLDB命令检查RDI寄存器。

对啰!。第一个参数的对象类型不是字符串,而是恶意bplist中的_NSDate对象。下面的_CFStringGetLength的反编译说明了这可能会出错的地方:

_CFStringGetLength反编译我们可以看到,在此函数的第一个参数上调用了长度选择器,我们知道这对于_NSDate对象是失败的,因为它没有这个选择器。这一理论也与坠机日志相符。

如果我们继续执行此函数,我们最终将在Objective-C异常处理的内部遇到异常,这表明我们已经找到了这些崩溃的根本原因。

我继续使用非字符串对象生成bplist,并且能够在Core Foundation中从其他无法识别的选择器生成额外的崩溃。下面的崩溃日志来自LSD,它使用了带有单个__NSCFData对象的恶意bplist:

Application Specific Backtrace 1:0 CoreFoundation 0x00007fff356568ab__exceptionPreprocess+2501 libobjc.A.dylib 0x00007fff6b777805 objc_exception_throw+482 CoreFoundation 0x00007fff356d5b61-[NSObject(NSObject)__retain_OA]+03 CoreFoundation 0x00007fff355baadf___forwarding___+14274 CoreFoundation 0x00007fff355ba4b8_CF_forwarding_prep_0+1205 CoreFoundation 0x00007fff355615f9 CFStringFindWithOptionsAndLocale+2656 CoreFoundation 0x00007fff35578697 CFStringFind+787 CoreFoundation 0x00007fff35577cc8_CFBundleInfoPlistProcessInfoDictionary+1948 CoreFoundation 0x00007fff35570aff_CFBundleCopyInfoDictionaryInDirectoryWithVersion+11179 CoreFoundation 0x00007fff35570349_CFBundleRefreshInfoDictionaryAlreadyLocked+11110 CoreFoundation 0x00007fff355702c8 CFBundleGetInfoDictionary+3311 CoreFoundation 0x00007fff35678d4f_CFBundleCreate+71512 LaunchServices 0x00007fff36d2b762-[FSNode(Bundles)CFBundleWithError:]+8813 LaunchServices 0x00007fff36e34e62。_LSCreateRegistrationData+29414启动服务0x00007fff36d861e8__104-[_LSDModifyClient registerItemInfo:alias:diskImageAlias:bundleURL:installationPlist:completionHandler:]_block_invoke+19115 libdispatch.dylib 0x00007fff6ca8b583_DISPATCH_CALL_BLOCK_AND_RELEASE+1216libDispatch.dylib 0x00007fff6ca8c50e_DISPATCH_CLIENT_CALOUT+817libDispatch.dylib 0x00007ffpw线程+59718 libDispatch.dylib0x007fff6ca92485_DISPATCH_41419 libDispatch.dylib 0x007fff6ca9ca9ba9e_Dispatch_Worklop_Work_Worker线程+59820 libDispatch.dybsystem_preadddy.libdispatch.dyx007ffccecwtHREAD_29021 SYSTEM_DISPATCH_LANE_SERIAL_DRANER+59718 libDispatch.dylib 0x007fff6ca92485_DISPATION_41419 libDispatch.dylib 0x007ffff6ca92485_DISPATCH_WORKORE_Worker_THREAD+59820 libDispatch.dybsystem。

注意,在Objective-C异常处理之前,崩溃位置不是来自CFStringFind,而实际上是CFStringFindWithOptionsAndLocale,它调用_CFStringGetCStringPtrInternal,最后由于调用了不正确的selector_fast CStringContents而死得很惨。这是因为__NSCFData类型实际上有一个长度选择器,因此它成功地通过了我们前面看到的第一个崩溃位置,并进一步进入Core Foundation,直到它调用另一个无法识别的选择器。

在本研究的早期,我使用Plutil从恶意bplist生成崩溃,然后再编写我自己的代码以命中必要的代码路径。以下命令设置一个LLDB会话,通过使用Plutil作为目标进程和print plist标志开始调试此崩溃,该标志将只打印属性列表的人类可读版本。

在执行了几次之后,很明显,Plutil实际上在不同的位置崩溃,而不是在Core Foundation中。下面的输出说明它试图调用__NSDate类型上的长度选择器,这会导致无法识别的选择器异常,但此错误存在于Plutil中,而不是Core Foundation中。

*线程#1,队列=';com.apple.main-线程';,停止原因=断点3.1帧#0:0x0000000100006aa0插头`_lldb_unname_symbol 67$$Plutil:->;0x100006aa0<;+21>;:movq 0x5e91(%rip),%rsi;";长度";0x100006a7<;+28>;:movq 0x45b2(%rip),%robjc_msgSend 0x106ae<;+35>;:callq*%R12 0x100006ab1<;+38>;用法:movq%rax,%r15目标1:(Plutil)已停止。(Lldb)po[$RDI类]__NSDate

似乎在许多MacOS应用程序中都存在类似的错误,这些应用程序假定bplist将只包含字符串对象类型。来自崩溃的LSD进程的堆栈跟踪如下,它也位于Core Foundation之外:

*线程#2,队列=';com.apple.lsd.database';,停止原因=断点3.1*帧#0:0x00007fff384c5440核心基础`__Forwarding_PREP_0_帧#1:0x00007fff39c644d5 LaunchServices`_LSPlistCompactString(NSString*,Signed char*)+45 Frame#2:0x00007fff39c98b06 LaunchServices`___ZL22_LSPlistTransformValueP11objc_objectPFP8NSStringS2_PaES3__block_invoke.637+67 Frame#3:0x00007fff384a8f27 CoreFoundation`__NSDICTIONARY_IS_CALLING_OUT_TO_A_BLOCK__+7 Frame#4:0x00007fff384e8a85 CoreFoundation`-[__NSDictionaryM enumerateKeysAndObjectsWithOptions:usingBlock:]+230Frame#5:0x00007ff39c987c7 LaunchServices`___ZL17_LSPlistTransformP12NSDictionaryIP8NSStringP11objc_objectEPFS1_S1_PaES6__block_invoke+562 Frame#6:0x00007fff384a8f27 CoreFoundation`__NSDICTIONARY_IS_CALLING_OUT_TO_A_BLOCK。__+7帧#7:0x00007fff384e8a85核心基础`-[__NSDictionaryM enumerateKeysAndObjectsWithOptions:usingBlock:]+230Frame#8:0x00007fff39c64a60LaunchServices`_LSPlistTransform(NSDictionary*,NSString*(*)(NSString*,有符号字符*),Signed char*)+212 Frame#9:0x00007fff39c96dbb LaunchServices`_LSPlistCompact+65 Frame#10:0x00007fff39d019d9 Frame#10:0x00007fff39d019d9 LaunchServices`_LSPlistAdd+79 Frame#11:0x00007fff39c9d3b5 LaunchServices`-[LSBundleRecordBuilder buildBundleData:Error:]+2772 Frame#12:0x00007fff39c9e3a8 Frame#10:0x00007fff39c9e3a8 LaunchServices`-[LSBundleRecordBuilder#BundleServices`_LSServerBundleRegistry+1870 Frame#14:0x007ff39fd41LaunchSeres`。_2+107Frame#17:0x00007fff6f996583 libdispatch.dylib`_dispatch_call_block_and_release+12 Frame#18:0x00007fff6f99750e libdispatch.dylib`_DISPATCH_CLIENT_CALLOUT+8 Frame#19:0x00007fff6f9a4827 libdispatch.dylib`_dispatch_lane_concurrent_drain+1032Frame#20:0x00007fff6f99d4ec libDispatch.dylib`_DISPATCH_21:0x00007fff6f999202 libdispatch.dylib`_dispatch_queue_override_invoke+421 Frame#22:0x007fff6f9a57e2 lib调度_ROOT_QUEUE_326 Frame#23:0x007fff6f9a5a22 Frame#23:0x007fff6f6f999202 LIBDISTER#22:0x00007fff6f9a57e2 libDispatch_ROOT_QUEUE_326 Frame#23:0x007fff6f9f5a22 Frame#23:0x007fff6f99d4ec libDispatch.dyb`。_worker_thread2+92 Frame#24:0x00007fff6fbf16b6 libsystem_pthread.dylib`_pthread_wqthread+220 Frame#25:0x00007fff6fbf0827 libsystem_pthread.dylib`start_wqthread+15。

如果我们使用Ghidra来反汇编_LSPlistCompactString函数,我们可以看到偏移量45或0x2D会让我们对错误的对象类型进行另一个长度调用,这大概来自我们现在位于LSD数据库中的恶意bplist:

_LSPlistCompactString反汇编我们可以通过在_LSPlistCompactString上设置断点并使用以下断点命令打印第一个参数来验证这一点:

(Lldb)br com add 1输入您的调试器命令。键入';Done';以结束。>;po[$RDI类]>;c&>Done。

下面的输出说明LSD正在从恶意bplist获取__NSDate对象:

命令#2';c';继续目标。(Lldb)po[$RDI类]NSTaggedPointerString(Lldb)cProcess 576继续命令#2';c';继续目标。(Lldb)po[$RDI类]__NSDate。

这证实了我最初认为的一个bug实际上是跨越多个MacOS二进制文件的多个bug,并且都根源于bplist只包含String对象的假设。

根级别进程可能会从普通用户帐户崩溃,如果它们由操作系统重新生成,则会反复崩溃(例如LSD和MDS)。

当Finder或其他与UI相关的进程使用使进程崩溃所需的恶意bplist和crash0单击时,尤其会发生系统不稳定和拒绝服务,因为应用程序包、包等在写入磁盘时会自动处理。

可能会使正常用户帐户中与安全相关的进程崩溃,以删除安全边界(XProtect等)。尽管在这篇文章中并没有对它们进行充分的探索。

受此缺陷影响的系统组件包括使用Core Foundation解析bplist的任何组件,这是一个很大的百分比(快速搜索发现,在MacOS 10.15.3上安装了1000多个导入函数以达到此缺陷的二进制文件)。

许多应用程序通过Core Foundation解析bplist数据,但也会在其自己的代码中错误地访问生成的对象,这意味着bug数量可能要大得多。

希望这次探索能像我自己对MacOS内部结构的理解一样有趣,对社区也有帮助。向苹果安全社区喊话吧,他们对抨击苹果产品非常热情,内容也非常丰富。这篇文章还有一些后续工作,有望在未来几个月发表。一如既往,欢迎反馈,包括对任何不准确之处的更正或对实现相同目标的更有效方法的建议。