DEF CON 29 - Zhipeng Huo, Yuebin Sun, Chuanda Ding - Reveal and Exploit IPC Logic Bugs in Apple

Aug 5, 2021 17:35 · 4885 words · 23 minute read

- [Presenter] Hello everyone. Another year to see you in this special mode.

00:10 - Welcome to our talk, “Caught You: Reveal and Exploit IPC Logic Bugs Inside Apple. “ First, a self-introduction.

00:20 - Zhipeng Huo is a senior security researcher.

00:23 - He’s a member of EcoSec Team at Tencent Security Xuanwu Lab.

00:27 - His research focuses on macOS, iOS, and Windows platform security.

00:33 - He has found and reported many vulnerability to Apple and Microsoft.

00:37 - He’s a speaker of Black Hat Europe 2018, and DEF CON 28.

00:43 - Yuebin Sun is a co-author of this presentation.

00:46 - He’s a senior security researcher of EcoSec Team.

00:49 - His focus is on macOS and iOS platform security.

00:55 - Chuanda Ding is also a co-author of this presentation.

00:58 - He leads the EcoSec Team and is a speaker of Black Hat Europe 2018, DEF CON China 2018, and DEF CON 28.

01:07 - This is the agenda of our talk. First, an introduction to IPC and logic vulnerability.

01:14 - Then we talk about the IPC mechanisms on Apple platforms.

01:19 - After that, we will share some interesting logic vulnerabilities we found in Preferences and App Store.

01:24 - And finally, we’ll give a conclusion on IPC logic vulnerabilities on Apple platform.

01:31 - Let’s talk about some background knowledge.

01:33 - What is IPC? IPC means Inter-Process Communication.

01:38 - It’s a set of techniques provided by the operating system.

01:41 - It allows stand-alone processes to communicate with each other.

01:45 - The communication could be about process notifying, and then the process about some event or transferring of data from one process to another.

01:54 - The processes involved in IPC could have two roles, client and server.

02:00 - The client request the server, and server may respond to the client if needed.

02:05 - The OS kernel provides the IPC channel to allow the client and server processes to send or reply to messages.

02:13 - There are three advantages that IPC provides.

02:17 - First, modularity. Modern software is more and more complex.

02:23 - With the help of IPC, developers could divide the complex system into separated modules and reduce the complexity of software and avoids re-inventing the wheel.

02:36 - Second, stability. If all data and code are inside one process, a little error would probably crash the entire system.

02:45 - By dividing a complex system into separated modules, a module crash would not crash the entire system, which makes the system more stable.

02:54 - And third, privilege separation. With IPC, developers not just separate functionality, but also separate the privilege, isolating sensitive operations into separated processes, giving the processes the least privilege.

03:11 - Then if part of the system is hacked, it will not compromise the entire system, and increase the security and protection from attacks.

03:21 - Let’s see an example of IPC usage. The web browser.

03:26 - It’s nearly impossible to build a rendering engine that never crashes, and hence, it’s also nearly impossible to build a rendering engine that’s perfectly secure.

03:36 - To solve this issue, modern browser mostly use a multi-process architecture.

03:42 - For example, it may divide the components into rendering process and networking process.

03:49 - So different processes of browser would communicate with each other through IPC.

03:54 - And all of them also needs to request the operating system’s system service.

04:00 - You may not feel it, but IPC is everywhere.

04:05 - The using of IPC divides the entire system into separated processes.

04:11 - Different processes have different privileges.

04:14 - Process may have low privilege or high privilege.

04:18 - A process may be sandboxed or non-sandboxed.

04:23 - There is a security boundary between them and IPC may break it.

04:28 - IPC is a bridge between different processes.

04:31 - So it is also a window between different privileges.

04:35 - IPC vulnerability is a key to high privilege.

04:38 - So it is one of the most valuable targets for privilege escalation.

04:44 - Logic vulnerability is different from memory corruption vulnerability.

04:49 - We do not want to play with memory corruption since they are boring to us.

04:53 - We like to find logic flaws. There are two kinds of logic flaws.

04:58 - One that is introduced during design phase, and one is introduced during implementation phase.

05:05 - In fact, most of the time, abusing existing features are enough for us to compromise the entire system.

05:14 - Apple’s new MacBook and iPad Pro have been combined with the new Apple M1 chip.

05:21 - The new chip brings many additional security features such as system integrity, data protection, and Pointer Authentication Code.

05:30 - The Pointer Authentication Code or PAC is a hardware-level security mechanism against the memory bug, which makes memory games much harder.

05:42 - Logic vulnerability is not playing with memory corruption and may not be affected.

05:48 - So is the Spring of logic vulnerability finally coming? Before introducing the IPC logic vulnerabilities, let’s start with some fundamental knowledge of IPC on Apple platforms.

06:03 - Apple IPC has different specific implementation methods.

06:08 - This includes shared files, shared memory, and sockets, which other systems supports as well.

06:16 - There are also some that are unique to Apple, such as Mach messages, Apple events, distributed notifications, and so on.

06:26 - However, the latest and the most advanced IPC methods on Apple platform currently are XPC and NSXPC.

06:36 - Apple implements them on top of Mach message.

06:41 - Next, we will walk you through the principle and usages of them.

06:46 - Mach port is an endpoint of unidirectional communication channel, which is one of the fundamental parameters of XNU kernel for messaging.

06:57 - Messages can be sent or received from it. Users of Mach port never actually sees the port itself, but access it through a type of indirection called Port Rights.

07:09 - The sender of the message can send the message to Mach port through the SEND right.

07:15 - The receiver of the message can get the received message the Mach port or through the RECEIVE right.

07:25 - The message transmitted through the Mach port is called Mach message.

07:30 - The system trap level API, mach_msg and mach_msg_overwrite, can be used to send or receive Mach messages.

07:39 - The structure contains a header, optional complex data, and message buffer.

07:45 - The header contains the sender and receiver of the message.

07:50 - The optional complex data part can transfer complex data such as a file handle, shared memory, and Mach port.

07:58 - Message buffer is used to send binary data.

08:02 - Mach message is low level and powerful, but it is also ancient and poorly documented.

08:10 - Developers needs to construct entire structure to transmit data and handle different data type by themselves.

08:17 - It is difficult to use, and Apple also does not recommend developers to use it directly.

08:24 - Instead, Apple provides high-level IPC mechanisms that are easier to use.

08:31 - On top of Mach messages, Apple built another communication mechanism called XPC.

08:38 - XPC is managed by launchd process. Launchd is a naming server.

08:46 - The XPC server register with launchd and declares that it will handle message sent to its endpoint.

08:54 - The client looks up an endpoint name via launchd and sends a message request.

09:00 - The server will receive the request, handle it, and reply to the client if needed.

09:07 - Behind the scene, launchd starts and terminates the target server process on demand.

09:15 - The message sent through XPC is called XPC message, which is a more structured dictionary format.

09:24 - XPC users do not need to pay attention to the details of the underlying Mach message processing.

09:33 - Through the xpc_dictionary_set APIs, we can easily construct an XPC dictionary message.

09:42 - Besides supporting the transmission of basic types of data such as typical string integer and Boolean, XPC message also supports complex data types such as file descriptor and shared memory.

09:56 - XPC message is serialized into Mach message and transmitted to the other end of the IPC via the XNU kernel.

10:06 - The received Mach messages is unserialized to XPC message and then can be used through the xpc_dictionary_get APIs.

10:19 - At the API level, the XPC server calls xpc_connection_create_mach_service API with the xpc_connection_mach_service_listener flag set to register itself to launchd as an XPC service.

10:34 - And then the client can connect to the XPC service through the same API.

10:40 - The message sending is completed by the xpc_connection_send_message.

10:47 - After the process received the XPC message, the registered message handler via xpc_connection_set_event_handler will be called to process the message.

11:03 - The mainstream languages for app developers are the object-oriented Objective-C and Swift.

11:11 - So Apple has encapsulated a layer of object-oriented implementation on top of XPC called NSXPC.

11:22 - NSXPC provides a set of remote procedure call interface implementations instead of caring about the underlying message like XPC.

11:33 - After establishing the connection, the client can directly call the open interface of the NSXPC server across processes, just like calling local methods.

11:45 - Apple provides many NSXPC classes for developers.

11:51 - The server registers the service with launchd through the NSXPCListener, and specifies a NSXPCListenerDelegate to handle the connection request.

12:04 - The connection between a client and server is managed by NSXPCConnection.

12:12 - What method can a client call in a server? The NSXPC server defines this through the Objective-C protocol, which defines programmatic interface between the calling application and service.

12:28 - In interfaces definitions, common arithmetic types and basic types such as strings and arrays are directly supported as parameters.

12:40 - The interface also supports custom defined class objects to be parsed as parameters.

12:47 - The custom object needs to implement the NSSecureCoding protocol.

12:52 - Here is an example. Here is an architectural diagram for an app.

13:02 - The server registered the service through an NSXPCListener.

13:07 - The app establishes a connection with the service through the NSXPCConnection.

13:16 - In this process, both parties can directly call remote methods across process boundary without caring about the underlying implementation details.

13:28 - We will now share some interesting logic vulnerabilities we found and exploited on Apple platforms.

13:36 - We found and reported three logic bugs in Preferences components.

13:43 - With these vulnerabilities, a local user may be able to modify protected parts of the file system.

13:50 - What are Preferences? Preferences are user-defined settings.

13:55 - They are persistent data stored in Preferences file.

13:59 - Its format is property list also called plist.

14:03 - The service, cfprefsd, has the responsibility to manage Preferences.

14:09 - It would read from or write to Preferences file according to client requests.

14:15 - Apple provides two kinds of high level Preferences API.

14:19 - Using the Foundation APIs, apps could use the NSUserDefaults class to access its Preferences.

14:27 - Each app has a single instance of this class accessible from the standardUserDefaults class method.

14:36 - Through the shared user defaults object, apps could get and set individual Preferences values.

14:44 - Apps can also use many of the underlying core Foundation APIs.

14:49 - For example, CFPreferencesSetAppValue and CFPreferencesCopyAppValue could be used to get or set the Preferences data.

15:04 - After reverse engineering cfprefsd, we found it create the XPC service named com. apple. cfprefsd. daemon, which runs with root privilege and without sandbox.

15:19 - When the client requests cfprefsd, the event handler will be scheduled to handle the request.

15:27 - As a foundational service, Preferences could be accessed from almost everywhere.

15:32 - Even the most restricted process needs to access Preferences.

15:39 - Here is a sandbox profile for Safari web browsers.

15:43 - It allows the sandbox process to access com. apple. cfprefsd. daemon.

15:52 - Because cfprefsd is an XPC service, we could also request it by sending XPC message directly.

16:01 - It is a low level method that could control the operation more precisely.

16:07 - Here is a sample request. First, we use the xpc_connection_create_mach_service to create a service connection.

16:18 - Then we create the XPC dictionary and set the key value that is used to control the operation.

16:25 - Finally, we send the XPC message through the xpc_connection_send_message API.

16:33 - Where does cfprefsd save the Preferences data? The Preferences file path is constructed from multiple components.

16:44 - Part of it is a fixed value in cfprefsd. Parts of it come from the client through XPC message.

16:53 - First, let’s see the Preferences Directory where the Preferences file is stored in.

16:59 - There are some predefined locations to store the file.

17:04 - PreferencesDomain is a value that is parsed through the CFPreferencesDomain key.

17:10 - By default, the file path is composed of PreferencesDirectory, PreferencesDomain, and the surface string. plist.

17:19 - How is the file path constructed? There are two main components.

17:25 - First, format the file path with CFStringCreateWithFormat which concatenate the PreferencesDomain with. plist, then using CFURLCreateWithFileSystemPathRelativeToBase to generate the full path.

17:44 - The baseURL is the PreferencesDirectory, and the filePath is the path returned above.

17:52 - Logic vulnerabilities always combine some features.

17:57 - And we found that CFURLCreateWithFileSystemPathRelativeToBase have two features that may be abused.

18:05 - First, this function has path traversal feature.

18:10 - If filePath contains. . /, the returned filePath could traverse to any path we want.

18:17 - The second feature is that if the filePath is an absolute path, it will return the filePath no matter what the baseURL is.

18:27 - We could use the path traversal feature or absolute path feature to control the Preferences filePath and return any filePath we want.

18:37 - What if the controllable filePath does not exist on the file system? The function name, cacheActualPathCreatingIfNecessary, is very interesting.

18:49 - Seems it will create the file path if it does not exist.

18:53 - Firstly, it will try try to open a file. If the file path exists, it will return path.

18:59 - But if the file path does not exist, it will get the directory part of the file path, and it will create the directory.

19:07 - After it created the directory, it tries to open the file path again, and then return the file path.

19:15 - The CFPrefsCreatePreferenceDirectory is the function used to create the now existing directories.

19:23 - It first split the path into multiple sub items by slice and then creates them recursively with mkdirat.

19:34 - After the directory is created, its ownership will be modified through a file change owner.

19:40 - But where does the user ID and group ID came from? The owner of the newly created directories is determined by several factors.

19:52 - By default, the owner is the identity of the XPC client and cfprefsd get the identity of the client user through xpc_connection_get_euid and xpc_connection_get_egid.

20:08 - However, the XPC client can also specify the expected user.

20:14 - Therefore, the ownership of the newly created directory is also under our control.

20:19 - We can let cfprefsd help us create any directory with controlled owner.

20:26 - There are many ways to convert arbitrary directory creation to code execution with root privilege.

20:33 - Here we share a method mentioned by Csaba Fitzl through periodic script.

20:39 - Periodic script is a mechanism to schedule script execution.

20:44 - The daily directory does not exist by default but the operating system will periodically scan and execute the files in it.

20:53 - By exploiting the vulnerability, we can create the daily directory and set the owner of the directory to the current user.

21:01 - And then we can write any script to the daily directory.

21:05 - Wait a day, and the script will execute with root privilege.

21:11 - The patch for the vulnerability is simple and straightforward.

21:15 - It can be explained even by looking at the function name.

21:19 - The cacheActualPathCreatingIfNecessary function has been replaced with cacheFileInfoForWriting.

21:27 - There is no creating anymore. In fact, cfprefsd will no longer help the user create the non-existing Preferences Directory.

21:38 - If client parse in a directory which does not exist, cfprefsd will ask the client to create it.

21:47 - How does cfprefsd read the Preferences data? When the client wants to get the data, the client needs to request cfprefsd.

22:00 - By default, it will read the data and then return them in the reply directly.

22:05 - But if it decides that the file is too large to fit in a XPC message, it will just return the file descriptor.

22:13 - To avoid subsequent changes to the file, it will clone a copy before return the file descriptor.

22:22 - This function is the implementation of cfprefsd to process large Preferences file.

22:28 - First, it gets the file path, then it determines whether the file size exceeds one megabyte.

22:37 - Then it generates a random temporary file path according to the rules, and then clone the original file to a temporary file.

22:45 - Finally, open a temporary file, and return its file descriptor.

22:51 - It calls clonefile to clone the file to a temporary file with a random name.

22:59 - The temporary file and the original file are in the same directory.

23:04 - The path of the temporary file is generated by mktemp according to a rule.

23:12 - The rule is that the file path is spliced with. cfp and seven random characters.

23:20 - So the final temporary file name is random.

23:23 - But can it guarantee it to be random? The mktemp will replace the seven X at the end of the row with random characters.

23:37 - The space for random characters is very large.

23:42 - Is there any other way to make mktemp generate a fake file name? One key point is that snprintf specifies a maximum length as 0x400 for generating rules.

23:58 - If plist path is very long, what happens if the splicing row exceeds 0x400? Snprintf will overflow and the characters exceeding 0x400 will not be written into the written buffer.

24:17 - To be more precise, if the length of the filePath pass the cfp string is exactly 0x400-1, the rule generated by snprintf will not contain X at the end.

24:30 - And mktemp will generate a fixed temporary file path.

24:36 - Before clonefile is called, there’s a file check.

24:40 - In exploit code, we can first parse in a normal file path.

24:44 - This will ensure that cfprefsd can successfully pass this check and enter the subsequent clonefile process.

24:53 - After the file check and before the clonefile, there is a window for race condition.

24:59 - Before we replace a controllable file with a symbolic link during this time, cfprefsd will call clonefile to help us copy arbitrary file to a temporary file path we control.

25:12 - We’ve got a fixed temporary file name, but can we link it to another place ahead of mktemp function? No, this file name is guaranteed not to exist at the time of function and location.

25:27 - But after the mktemp return successfully and before the clonefile, this is also a window for race condition.

25:38 - We could replace the temporary file with a symbolic link.

25:42 - In this way, we can achieve arbitrary file write.

25:46 - In the patch, Apple added an overflow checking and the subsequent clone file will not be executed if overflow occurs.

25:55 - The previous method of forcing fixed file name no longer works.

26:01 - How does cfprefsd write Preferences data? When cfprefsd saves Preferences data for the client, first it extract key and value from the XPC message, then it reads the original data from the target file, and it generates new data based on the incoming key and value data, and writes to the new data to a temporary file.

26:29 - And then it renamed the temporary file back to the target file.

26:33 - When saving data, cfprefsd verifies the client has write permission to target file.

26:40 - The client needs to parse in a file descriptor with write permission.

26:46 - After the previous check, cfprefsd would generate a temporary file and write data to it.

26:52 - Then it will rename this temporary file back to the target file.

26:57 - Can normal user replace the source file if the rename was a symbolic link? No, this temporary file is in root-owned directory.

27:07 - A normal user does not have permission to write to this directory.

27:13 - Can a normal user replace the rename target as a symbolic link? Normal user has write permission for the target, but the target file will be deleted first when the rename is called.

27:25 - So even if it can be replaced with a symbolic link, it will not work.

27:30 - Regarding this feature, the rename API documentation says, if the final component of the target is a symbolic link, the symbolic link is renamed, not the file or directory to which it points.

27:47 - But wait, the document says that if the final component of the target is a symbolic link, rename will delete it first.

27:57 - What if it’s not the final component? What if the middle component of plist_path is a symbolic link? Suppose the path of Preferences is /tmp/test/hello. plist? If we replaced the directory with a symbolic link, pointing to the LaunchDaemons directory, then the hello. plist is used as the target path of rename, what will happen? The symbolic link will be followed and the temporary file will be moved to the LaunchDaemons directory.

28:39 - Before renaming the temporary file, it verifies the caller has write permission.

28:45 - However, after the file check and before the rename function, there is a time window to replace the middle component of file path with symbolic link.

28:57 - In renaming files, it will move the temporary file with controllable content to arbitrary path.

29:05 - The patch for the rename vulnerability is very simple.

29:09 - Rename is replaced with renameat. Renameat locates the target file according to the directory descriptor, to ensure that the final move target must be in a certain directory.

29:23 - So even if we replace the directory with a symbolic link, it will not work anymore.

29:36 - Here is a demo. We used the vulnerabilities to achieve arbitrary file read and write, and then we put get root privileges on macOS and read privacy data on iOS.

29:48 - First, we used Preferences vulnerabilities to achieve root privilege.

29:56 - We could see the system integrity protection is enabled.

30:00 - The current user is a normal user, not root.

30:05 - We could get the root privileges very quickly.

31:09 - Next, we use Preferences vulnerabilities to read privacy data on iOS.

31:13 - You can see our demo application is not allowed to access photos and contacts.

31:19 - However, when we take a photo, then the open the demo application.

31:23 - Our demo application can steal the photo and send it to our server.

32:22 - Last year, we found and reported an interesting logic vulnerabilities in macOS App Store component.

32:30 - An application could abuse it to gain elevated privilege.

32:36 - The vulnerability exists in NSXPC server, com. apple. storedownloadd. daemon.

32:43 - The server is implemented in storedownloadd application, which runs with root privilege.

32:50 - Fortunately, this process is run in the sandbox.

32:55 - But unfortunately, as an app download service, it must have the ability to write to some sensitive locations.

33:02 - For example, it is allowed to write to applications and keychains directory.

33:08 - As an NXSPC server, it provides many interfaces for client.

33:12 - Here we listed the setStoreClient and performDownload interface.

33:18 - The performDownload interface is very interesting.

33:21 - This interface performs downloading jobs for the client, but what can it download, and what is the SSDownload parameter? For NSXPC, the Objective-C object and implements NSSecureCoding protocol could be used as parameters.

33:39 - SSDownload is such an object. This object only have one property, assets.

33:45 - And it is an array. The element in the assets array is SSDownloadAsset object.

33:53 - It is also an Objective-C object, and it has three properties.

33:58 - The URL, download paths, and hashes. The SSDownloadAsset object is passed from client to server.

34:09 - The properties of SSDownloadAsset will be serialized in client.

34:13 - The function, encodeWithCoder, will be called to serialize the specified properties to XPC message.

34:19 - At the server side, the XPC message would be unserialized with initWithCoder, and construct the SSDownloadAsset object with specified properties.

34:32 - The properties of the object would be fully controllable.

34:40 - How does storedownloadd perform downloading tasks? It will perform download task according to the URL, then it verifies the response contents based on the hashes.

34:55 - And finally, it writes the contents to the specified download path.

35:00 - There is a hash verification in the download logic.

35:04 - This function will calculate the hash of the response content, and then compare it with the hashes the client specified.

35:12 - But it’s not a security check, just a data integrity verification.

35:18 - Because we can control the download URL, the content hash and download path, we could write arbitrary file path with the storedownloadd’s privilege.

35:30 - Attackers could say, “Hi, it’s storedownloadd.

35:33 - Please help me download the file from this URL.

35:37 - Its hash is this… and then write the contents to this download path.

35:41 - Thanks!” Then the storedownloadd would do all the jobs well for the attacker.

35:50 - How did Apple fix this vulnerability? “The issue was addressed by removing the vulnerable code,” they said.

36:00 - To be more precise, they completely removed the service.

36:04 - There is no com. apple. storedownloadd. daemon anymore.

36:11 - Here is the demo of App Store vulnerability.

36:16 - We just downloaded the test. keychain in tmp directory, to keychains directory, with the help of storedownloadd.

36:47 - There are other interesting logic vulnerabilities we found and detailed in our blog; an XPC service implementation flaw, which is a logic bug inside launchd, for managing the XPC service; and NSXPC vulnerability in Adobe Acrobat Reader for macOS.

37:10 - There are many security mechanisms on Apple platform that tries to make vulnerabilities harder to exploit like DEP, ASLR, PAC and so on.

37:22 - But you probably noticed that logic bugs are not affected by these security features.

37:28 - That’s just awesome. Logic vulnerabilities has many advantages.

37:36 - Though it is hard to find, it is easy to exploit.

37:40 - The exploitation is always stable. The logic vulnerabilities exist across platforms often, so one exploit could rule them all.

37:53 - Logic bugs in core frameworks like Preferences, let us rule whole Apple platforms, Intel and Apple Silicon alike, without changing one line of our exploit.

38:06 - Apple is also working hard to try to reduce the IPC attack surfaces.

38:12 - For example, by adding more restrictive sandbox rules, it reduced the IPC services accessible to applications.

38:21 - They keep deleting the unnecessary high privilege services, and they are adding more and more private entitlements to make many high privilege services only accessible to Apple applications.

38:34 - It hard to make sure everything is perfectly secure, so Apple is also trying to limit the damage.

38:42 - For example, they are putting IPC services in sandboxes and give them the least privilege.

38:49 - They are also using Rootless to limit the root privilege.

38:55 - In this presentation, we talked about the latest IPC mechanism on Apple platforms, XPC and NSXPC.

39:03 - Then we walked you through some of the interesting IPC logic vulnerabilities; three logic vulnerabilities in Preferences, and one in App Store.

39:13 - We detailed the design logic and the implementation of these components, the flaws inside them and how we exploit them to elevate privilege.

39:23 - And we also talked about the advantage of IPC logic vulnerability, and the state of Apple IPC security.

39:34 - Logic bugs are always fun to hunt for. We think you will love it just as we do.

39:42 - We would like to thank Csaba Fitzl, Ian Beer, and Zhi Zhou for their previous work and shares. .