- [Presenter] Hello everyone.
Another year to see you in this special mode.
Welcome to our talk, "Caught You:
Reveal and Exploit IPC Logic Bugs Inside Apple."
First, a self-introduction.
Zhipeng Huo is a senior security researcher.
He's a member of EcoSec Team at Tencent Security Xuanwu Lab.
His research focuses on macOS, iOS,
and Windows platform security.
He has found and reported many vulnerability
to Apple and Microsoft.
He's a speaker of Black Hat Europe 2018,
and DEF CON 28.
Yuebin Sun is a co-author of this presentation.
He's a senior security researcher of EcoSec Team.
His focus is on macOS and iOS platform security.
Chuanda Ding is also a co-author of this presentation.
He leads the EcoSec Team
and is a speaker of Black Hat Europe 2018,
DEF CON China 2018, and DEF CON 28.
This is the agenda of our talk.
First, an introduction to IPC and logic vulnerability.
Then we talk about the IPC mechanisms on Apple platforms.
After that, we will share some interesting
logic vulnerabilities we found in Preferences and App Store.
And finally, we'll give a conclusion
on IPC logic vulnerabilities on Apple platform.
Let's talk about some background knowledge.
What is IPC?
IPC means Inter-Process Communication.
It's a set of techniques provided by the operating system.
It allows stand-alone processes
to communicate with each other.
The communication could be about process notifying,
and then the process about some event
or transferring of data from one process to another.
The processes involved in IPC could have two roles,
client and server.
The client request the server,
and server may respond to the client if needed.
The OS kernel provides the IPC channel
to allow the client and server processes
to send or reply to messages.
There are three advantages that IPC provides.
First, modularity.
Modern software is more and more complex.
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.
Second, stability.
If all data and code are inside one process,
a little error would probably crash the entire system.
By dividing a complex system into separated modules,
a module crash would not crash the entire system,
which makes the system more stable.
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.
Then if part of the system is hacked,
it will not compromise the entire system,
and increase the security and protection from attacks.
Let's see an example of IPC usage.
The web browser.
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.
To solve this issue, modern browser
mostly use a multi-process architecture.
For example, it may divide the components
into rendering process and networking process.
So different processes of browser
would communicate with each other through IPC.
And all of them also needs to request
the operating system's system service.
You may not feel it, but IPC is everywhere.
The using of IPC divides the entire system
into separated processes.
Different processes have different privileges.
Process may have low privilege or high privilege.
A process may be sandboxed or non-sandboxed.
There is a security boundary between them
and IPC may break it.
IPC is a bridge between different processes.
So it is also a window between different privileges.
IPC vulnerability is a key to high privilege.
So it is one of the most valuable targets
for privilege escalation.
Logic vulnerability is different
from memory corruption vulnerability.
We do not want to play with memory corruption
since they are boring to us.
We like to find logic flaws.
There are two kinds of logic flaws.
One that is introduced during design phase,
and one is introduced during implementation phase.
In fact, most of the time,
abusing existing features
are enough for us to compromise the entire system.
Apple's new MacBook and iPad Pro
have been combined with the new Apple M1 chip.
The new chip brings many additional security features
such as system integrity, data protection,
and Pointer Authentication Code.
The Pointer Authentication Code or PAC
is a hardware-level security mechanism
against the memory bug,
which makes memory games much harder.
Logic vulnerability is not playing with memory corruption
and may not be affected.
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.
Apple IPC has different specific implementation methods.
This includes shared files, shared memory, and sockets,
which other systems supports as well.
There are also some that are unique to Apple,
such as Mach messages, Apple events,
distributed notifications, and so on.
However, the latest and the most advanced
IPC methods on Apple platform
currently are XPC and NSXPC.
Apple implements them on top of Mach message.
Next, we will walk you through the principle
and usages of them.
Mach port is an endpoint
of unidirectional communication channel,
which is one of the fundamental parameters
of XNU kernel for messaging.
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.
The sender of the message can send the message to Mach port
through the SEND right.
The receiver of the message can get the received message
the Mach port or through the RECEIVE right.
The message transmitted through the Mach port
is called Mach message.
The system trap level API,
mach_msg and mach_msg_overwrite,
can be used to send or receive Mach messages.
The structure contains a header,
optional complex data,
and message buffer.
The header contains the sender and receiver of the message.
The optional complex data part can transfer complex data
such as a file handle, shared memory, and Mach port.
Message buffer is used to send binary data.
Mach message is low level and powerful,
but it is also ancient and poorly documented.
Developers needs to construct
entire structure to transmit data
and handle different data type by themselves.
It is difficult to use,
and Apple also does not recommend
developers to use it directly.
Instead, Apple provides high-level IPC mechanisms
that are easier to use.
On top of Mach messages,
Apple built another communication mechanism called XPC.
XPC is managed by launchd process.
Launchd is a naming server.
The XPC server register with launchd
and declares that it will handle message
sent to its endpoint.
The client looks up an endpoint name via launchd
and sends a message request.
The server will receive the request,
handle it, and reply to the client if needed.
Behind the scene, launchd starts and terminates
the target server process on demand.
The message sent through XPC is called XPC message,
which is a more structured dictionary format.
XPC users do not need to pay attention to the details
of the underlying Mach message processing.
Through the xpc_dictionary_set APIs,
we can easily construct an XPC dictionary message.
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.
XPC message is serialized into Mach message
and transmitted to the other end of the IPC
via the XNU kernel.
The received Mach messages is unserialized to XPC message
and then can be used through the xpc_dictionary_get APIs.
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.
And then the client can connect to the XPC service
through the same API.
The message sending is completed
by the xpc_connection_send_message.
After the process received the XPC message,
the registered message handler
via xpc_connection_set_event_handler
will be called to process the message.
The mainstream languages for app developers
are the object-oriented Objective-C and Swift.
So Apple has encapsulated a layer
of object-oriented implementation
on top of XPC called NSXPC.
NSXPC provides a set of remote procedure
call interface implementations
instead of caring about the underlying message like XPC.
After establishing the connection,
the client can directly call the open interface
of the NSXPC server across processes,
just like calling local methods.
Apple provides many NSXPC classes for developers.
The server registers the service with launchd
through the NSXPCListener,
and specifies a NSXPCListenerDelegate
to handle the connection request.
The connection between a client and server
is managed by NSXPCConnection.
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.
In interfaces definitions,
common arithmetic types and basic types
such as strings and arrays
are directly supported as parameters.
The interface also supports custom defined class objects
to be parsed as parameters.
The custom object needs to implement
the NSSecureCoding protocol.
Here is an example.
Here is an architectural diagram for an app.
The server registered the service
through an NSXPCListener.
The app establishes a connection with the service
through the NSXPCConnection.
In this process,
both parties can directly call remote methods
across process boundary without caring about
the underlying implementation details.
We will now share some interesting logic vulnerabilities
we found and exploited on Apple platforms.
We found and reported three logic bugs
in Preferences components.
With these vulnerabilities,
a local user may be able to modify
protected parts of the file system.
What are Preferences?
Preferences are user-defined settings.
They are persistent data stored in Preferences file.
Its format is property list also called plist.
The service, cfprefsd,
has the responsibility to manage Preferences.
It would read from or write to Preferences file
according to client requests.
Apple provides two kinds of high level Preferences API.
Using the Foundation APIs,
apps could use the NSUserDefaults class
to access its Preferences.
Each app has a single instance of this class
accessible from the standardUserDefaults class method.
Through the shared user defaults object,
apps could get and set individual Preferences values.
Apps can also use many of the underlying
core Foundation APIs.
For example, CFPreferencesSetAppValue
and CFPreferencesCopyAppValue
could be used to get or set the Preferences data.
After reverse engineering cfprefsd,
we found it create the XPC service
named com.apple.cfprefsd.daemon,
which runs with root privilege and without sandbox.
When the client requests cfprefsd,
the event handler will be scheduled to handle the request.
As a foundational service,
Preferences could be accessed from almost everywhere.
Even the most restricted process
needs to access Preferences.
Here is a sandbox profile for Safari web browsers.
It allows the sandbox process
to access com.apple.cfprefsd.daemon.
Because cfprefsd is an XPC service,
we could also request it by sending XPC message directly.
It is a low level method that could control
the operation more precisely.
Here is a sample request.
First, we use the xpc_connection_create_mach_service
to create a service connection.
Then we create the XPC dictionary and set the key value
that is used to control the operation.
Finally, we send the XPC message
through the xpc_connection_send_message API.
Where does cfprefsd save the Preferences data?
The Preferences file path
is constructed from multiple components.
Part of it is a fixed value in cfprefsd.
Parts of it come from the client through XPC message.
First, let's see the Preferences Directory
where the Preferences file is stored in.
There are some predefined locations to store the file.
PreferencesDomain is a value
that is parsed through the CFPreferencesDomain key.
By default, the file path is composed
of PreferencesDirectory, PreferencesDomain,
and the surface string .plist.
How is the file path constructed?
There are two main components.
First, format the file path with CFStringCreateWithFormat
which concatenate the PreferencesDomain with .plist,
then using CFURLCreateWithFileSystemPathRelativeToBase
to generate the full path.
The baseURL is the PreferencesDirectory,
and the filePath is the path returned above.
Logic vulnerabilities always combine some features.
And we found that
CFURLCreateWithFileSystemPathRelativeToBase
have two features that may be abused.
First, this function has path traversal feature.
If filePath contains ../,
the returned filePath could traverse to any path we want.
The second feature is that
if the filePath is an absolute path,
it will return the filePath
no matter what the baseURL is.
We could use the path traversal feature
or absolute path feature to control the Preferences filePath
and return any filePath we want.
What if the controllable filePath
does not exist on the file system?
The function name, cacheActualPathCreatingIfNecessary,
is very interesting.
Seems it will create the file path if it does not exist.
Firstly, it will try try to open a file.
If the file path exists, it will return path.
But if the file path does not exist,
it will get the directory part of the file path,
and it will create the directory.
After it created the directory,
it tries to open the file path again,
and then return the file path.
The CFPrefsCreatePreferenceDirectory is the function
used to create the now existing directories.
It first split the path into multiple sub items by slice
and then creates them recursively with mkdirat.
After the directory is created,
its ownership will be modified through a file change owner.
But where does the user ID and group ID came from?
The owner of the newly created directories
is determined by several factors.
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.
However, the XPC client can also specify the expected user.
Therefore, the ownership of the newly created directory
is also under our control.
We can let cfprefsd help us create
any directory with controlled owner.
There are many ways to convert arbitrary directory creation
to code execution with root privilege.
Here we share a method mentioned by Csaba Fitzl
through periodic script.
Periodic script is a mechanism to schedule script execution.
The daily directory does not exist by default
but the operating system will periodically
scan and execute the files in it.
By exploiting the vulnerability,
we can create the daily directory
and set the owner of the directory to the current user.
And then we can write any script to the daily directory.
Wait a day, and the script will execute with root privilege.
The patch for the vulnerability
is simple and straightforward.
It can be explained even by looking at the function name.
The cacheActualPathCreatingIfNecessary function
has been replaced with cacheFileInfoForWriting.
There is no creating anymore.
In fact, cfprefsd will no longer help the user
create the non-existing Preferences Directory.
If client parse in a directory which does not exist,
cfprefsd will ask the client to create it.
How does cfprefsd read the Preferences data?
When the client wants to get the data,
the client needs to request cfprefsd.
By default, it will read the data
and then return them in the reply directly.
But if it decides that the file is too large
to fit in a XPC message,
it will just return the file descriptor.
To avoid subsequent changes to the file,
it will clone a copy before return the file descriptor.
This function is the implementation of cfprefsd
to process large Preferences file.
First, it gets the file path,
then it determines whether the file size
exceeds one megabyte.
Then it generates a random temporary file path
according to the rules,
and then clone the original file to a temporary file.
Finally, open a temporary file,
and return its file descriptor.
It calls clonefile to clone the file to a temporary file
with a random name.
The temporary file and the original file
are in the same directory.
The path of the temporary file
is generated by mktemp according to a rule.
The rule is that the file path is spliced
with .cfp and seven random characters.
So the final temporary file name is random.
But can it guarantee it to be random?
The mktemp will replace the seven X at the end of the row
with random characters.
The space for random characters is very large.
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.
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.
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.
And mktemp will generate a fixed temporary file path.
Before clonefile is called,
there's a file check.
In exploit code,
we can first parse in a normal file path.
This will ensure that cfprefsd
can successfully pass this check
and enter the subsequent clonefile process.
After the file check and before the clonefile,
there is a window for race condition.
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.
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.
But after the mktemp return successfully
and before the clonefile,
this is also a window for race condition.
We could replace the temporary file with a symbolic link.
In this way, we can achieve arbitrary file write.
In the patch, Apple added an overflow checking
and the subsequent clone file will not be executed
if overflow occurs.
The previous method of forcing fixed file name
no longer works.
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.
And then it renamed the temporary file
back to the target file.
When saving data, cfprefsd verifies
the client has write permission to target file.
The client needs to parse in a file descriptor
with write permission.
After the previous check,
cfprefsd would generate a temporary file
and write data to it.
Then it will rename this temporary file
back to the target file.
Can normal user replace the source file
if the rename was a symbolic link?
No, this temporary file is in root-owned directory.
A normal user does not have permission
to write to this directory.
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.
So even if it can be replaced with a symbolic link,
it will not work.
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.
But wait, the document says that
if the final component of the target is a symbolic link,
rename will delete it first.
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.
Before renaming the temporary file,
it verifies the caller has write permission.
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.
In renaming files, it will move the temporary file
with controllable content to arbitrary path.
The patch for the rename vulnerability is very simple.
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.
So even if we replace the directory with a symbolic link,
it will not work anymore.
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.
First,
we used Preferences vulnerabilities
to achieve root privilege.
We could see the system integrity protection is enabled.
The current user is a normal user, not root.
We could get the root privileges very quickly.
Next, we use Preferences vulnerabilities
to read privacy data on iOS.
You can see our demo application is not allowed
to access photos and contacts.
However, when we take a photo,
then the open the demo application.
Our demo application can steal the photo
and send it to our server.
Last year, we found and reported an interesting
logic vulnerabilities in macOS App Store component.
An application could abuse it to gain elevated privilege.
The vulnerability exists in NSXPC server,
com.apple.storedownloadd.daemon.
The server is implemented in storedownloadd application,
which runs with root privilege.
Fortunately, this process is run in the sandbox.
But unfortunately, as an app download service,
it must have the ability to write
to some sensitive locations.
For example, it is allowed to write to applications
and keychains directory.
As an NXSPC server, it provides many interfaces for client.
Here we listed the setStoreClient
and performDownload interface.
The performDownload interface is very interesting.
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.
SSDownload is such an object.
This object only have one property, assets.
And it is an array.
The element in the assets array is SSDownloadAsset object.
It is also an Objective-C object,
and it has three properties.
The URL, download paths, and hashes.
The SSDownloadAsset object is passed from client to server.
The properties of SSDownloadAsset
will be serialized in client.
The function, encodeWithCoder, will be called
to serialize the specified properties to XPC message.
At the server side,
the XPC message would be unserialized with initWithCoder,
and construct the SSDownloadAsset object
with specified properties.
The properties of the object would be fully controllable.
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.
And finally, it writes the contents
to the specified download path.
There is a hash verification in the download logic.
This function will calculate
the hash of the response content,
and then compare it with the hashes the client specified.
But it's not a security check,
just a data integrity verification.
Because we can control the download URL,
the content hash and download path,
we could write arbitrary file path
with the storedownloadd's privilege.
Attackers could say, "Hi, it's storedownloadd.
Please help me download the file from this URL.
Its hash is this...
and then write the contents to this download path.
Thanks!"
Then the storedownloadd would do
all the jobs well for the attacker.
How did Apple fix this vulnerability?
"The issue was addressed
by removing the vulnerable code," they said.
To be more precise,
they completely removed the service.
There is no com.apple.storedownloadd.daemon anymore.
Here is the demo of App Store vulnerability.
We just downloaded the test.keychain in tmp directory,
to keychains directory,
with the help of storedownloadd.
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.
There are many security mechanisms on Apple platform
that tries to make vulnerabilities harder to exploit
like DEP, ASLR, PAC and so on.
But you probably noticed that logic bugs
are not affected by these security features.
That's just awesome.
Logic vulnerabilities has many advantages.
Though it is hard to find, it is easy to exploit.
The exploitation is always stable.
The logic vulnerabilities exist across platforms often,
so one exploit could rule them all.
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.
Apple is also working hard to try to reduce
the IPC attack surfaces.
For example, by adding more restrictive sandbox rules,
it reduced the IPC services accessible to applications.
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.
It hard to make sure everything is perfectly secure,
so Apple is also trying to limit the damage.
For example, they are putting IPC services in sandboxes
and give them the least privilege.
They are also using Rootless to limit the root privilege.
In this presentation,
we talked about the latest IPC mechanism
on Apple platforms, XPC and NSXPC.
Then we walked you through some of the interesting
IPC logic vulnerabilities;
three logic vulnerabilities in Preferences,
and one in App Store.
We detailed the design logic
and the implementation of these components,
the flaws inside them
and how we exploit them to elevate privilege.
And we also talked about the advantage
of IPC logic vulnerability,
and the state of Apple IPC security.
Logic bugs are always fun to hunt for.
We think you will love it just as we do.
We would like to thank Csaba Fitzl, Ian Beer, and Zhi Zhou
for their previous work and shares.