Mickey Jin
This blog entry details our investigation of CVE-2019-8561, a vulnerability that exists in the macOS PackageKit framework, a component used to install software installer packages (PKG files). We discuss how Apple patched it, how we exploited this vulnerability after it was addressed, and how Apple patched it again.
We also disclosed more than 15 critical SIP-bypass vulnerabilities to Apple and talked about some of them at the Power of Community 2022 Security Conference (POC2022). This is the first of a series of blog entries where we will discuss our SIP-related vulnerability discoveries.
A brief history of CVE-2019-8561
On March 25, 2019, Apple patched CVE-2019-8561, a bug that could lead to root privilege escalation, signature bypassing, and ultimately, the bypassing of Apple's System Integrity Protection (SIP).
Just three months after its disclosure, researcher Jaron Bradley talked about this vulnerability at the second Objective by the Sea security conference. His presentation, titled “Bad Things in Small Packages,” had us wondering how Apple addressed CVE-2019-8561. After conducting a deep dive into the PackageKit framework, we discovered that CVE-2019-8561 still affected what was then the latest version of Apple’s desktop operating system, macOS (Monterey). Similar to how it affected macOS 10.14 (Mojave), this vulnerability could have been exploited to get privilege escalation and bypass SIP restrictions.
Apple addressed this vulnerability again in macOS 13 (Ventura) as CVE-2022-32895 on Oct. 24, 2022.
Apple’s SIP
SIP is a security feature introduced in OS X EI Capitan, which is designed to protect the Mac operating system from potentially malicious software that can modify protected files and folders. It restricts the root user account, which is also referred to as “rootless,” and limits the actions that the root user can perform on protected parts of the operating system.
Before the introduction of SIP, root users had no permission restrictions, which means that they could access any system folder or app on the machine. But because of this security feature, only Apple-signed processes and those that have special entitlements can modify protected system files. These processes include Apple software updates and installers.
system_installd
The Mac operating system updates system files via special entitlements such as com.apple.rootless.install and com.apple.rootless.install.heritable, which grant the permission to modify system files for special purposes.
These special entitlements can only be used by Apple, and only a few system commands are signed with them. The system_installd is one of these entitled daemon services, and it is responsible for installing Apple-signed packages. Its core logic is implemented in the PackageKit framework. Because of its com.apple.rootless.install.heritable special entitlement, the child processes it spawns will be executed in a SIP-bypass context.
Technical analysis of CVE-2019-8561
A look at CVE-2019-8561
CVE-2019-8561 abuses a classic time-of-check-to-time-of-use (TOCTTOU) issue that is triggered by installing an Apple-signed PKG file.
Figure 1 shows an illustration of the attack:
Figure 1. CVE-2019-8561 attack chain
1. An Apple-signed PKG file with pre-install or post-install scripts is installed.
2. The system_installd daemon service will handle the install request and verify the package signature. If the service determines that the package is not signed by Apple, it will abort the installation process.
3. Once the service passes the verification process, the attacker can replace the PKG file with a malicious one.
4. The service will extract and run malicious pre-install or post-install scripts inside the fake, unsigned PKG file.
5. The spawned scripts will be executed in a SIP-bypass context with root privilege.
It should be noted that the bug itself is very easy to understand and exploit. However, we were curious about how Apple addressed it.
Analyzing CVE-2019-8561
When we tried exploiting the old issue in the then latest operating system, macOS Monterey, we received an error message from the “xar_open_digest_verify” function.
Figure 2 shows the call stack trace for the digest verification:
Figure 2. Call stack of the digest verification
It stated that the Table of Contents (toc) digest did not match what was expected.
After reversing the caller functions from the call stack inside the PackageKit framework, we found that the digest is passed from the installation client. It will also cache the returned extensible archive format (XAR) pointer into its member variable. So, when it needs to access the PKG file next time, it will return the cached XAR pointer from the member variable directly, instead of opening the XAR archive again.
Figure 3. The implementation of the function -[PKXARArchive _xar]
At this point, it seemed that the patch had worked since it verifies the digest before extracting. However, the issue didn’t stop here.
Double and triple fetch
A PKG file could be very large, so it is not advisable to read all its contents into memory in a single fetch. Thus, it will read and extract components on demand.
After reversing the “-[PKExtractInstallOperation _extractAllSpecifiersOnceAndReturnFailingSpecifier:andError:]” function, we found that it calls three different methods to extract the bill of materials (BOM) file, payloads, and scripts accordingly.
Figure 4. The implementation of the _extractAllSpecifiersXXX method
The first method, “-[PKExtractInstallOperation _extractBomForPackageSpecifier:error:]”, will verify the digest before extracting the BOM file, as seen in Figure 2.
The second method, “-[PKExtractInstallOperation _extractScriptsForPackageSpecifier:error:]”, will internally call the “-[PKLeopardPackage scriptsExtractorWithDestination:error:]” function.
Figure 5. The implementation of the scriptsExtractorXXX method
We also looked into the internal function “-[PKPayloadCopier initWithArchivePath:offset:destination:]”.
Figure 6. The implementation of the initWithArchivePathXXX method
We can see in Figure 6 that the function opens the PKG file directly at line 20, seeks the returned file descriptor with a fixed offset, and puts the file descriptor into the BOMCopier with "inputFD" as the key input.
Next, we analyzed the "-[PKPayloadCopier run]" function.
Figure 7. The implementation of the -[PKPayloadCopier run] method
This function calls the API “BOMCopierCopyWithOptions” to extract from the untrusted “inputFD.”
The API “xar_open_digest_verify” is safe to use for opening an untrusted PKG file, while the API “open” is not. This is a classic “double fetch” issue: The “open” API opens the untrusted PKG file directly without verifying the digest again, so the PKG file could be replaced with a malicious one, and this makes the old issue exploitable again.
The third method, “-[PKExtractInstallOperation _extractPayloadForPackageSpecifier:error:]”, will internally call the function “-[PKLeopardPackage payloadExtractorWithDestination:externalRoot:error:]”.
Figure 8. The implementation of the payloadExtractorXXX method
At line 28, if the offset value of the payload subpath inside the PKG file is not equal to zero, the “-[PKLeopardPackage payloadExtractorWithDestination:externalRoot:error:]” function will call the “-[PKPayloadCopier initWithArchivePath:offset:destination:]” function. Similar to the second method, there is a “triple fetch” issue.
If the offset value is equal to zero, it will extract the payload from a special external root path, which seems to be unrestricted and can be controlled by an attacker. This means that an attacker could put malicious payloads in the external root path. However, as of writing, we are not able to find an Apple-signed PKG file with an external root path.
Exploit
Compared to the older exploitation, the time window for this race condition issue is smaller. We needed to restore the PKG file to the original Apple-signed one after the extraction to pass any possible verifications later. And there is one more challenge that needs to be overcome: The offset value of the scripts or payload subpath component inside the newly crafted PKG file must be equal to that of the original one.
To exploit the issue again, we first prepared a crafted PKG file that contains our payload. After expanding the original Apple-signed PKG file, we cleaned up the old scripts and put our payload into the post-install script.
pkgutil --expand /Volumes/Pro\ Video\ Formats/ProVideoFormats.pkg /tmp/ProVideoFormats
rm -rf /tmp/ProVideoFormats/MXFPlugIns.pkg/Scripts/*
echo '#!/bin/bash' > /tmp/ProVideoFormats/MXFPlugIns.pkg/Scripts/postinstall
echo 'touch /Library/Apple/sip_bypass' >> /tmp/ProVideoFormats/MXFPlugIns.pkg/Scripts/postinstall
chmod +x /tmp/ProVideoFormats/MXFPlugIns.pkg/Scripts/postinstall
Next, to address the offset value of the scripts subpath component challenge, we wrote a Python script to build the new PKG file in a dead loop until the offset value met the demand.
while True:
os.system('pkgutil --flatten /tmp/ProVideoFormats /tmp/ProVideoFormats.fake.pkg')
f=open('/tmp/ProVideoFormats.fake.pkg', 'rb')
f.seek(scriptsOffsetInPkg) # the offset value from the original PKG
if f.read(4)=='\x1f\x8b\x08\x00': break
f.close()
Once the crafted PKG file was ready, it was time to exploit the vulnerability via the following steps:
1. An Apple-signed PKG file with post-install scripts is installed.
2. The system_installd daemon service will handle the install request.
3. In the function “-[PKLeopardPackage scriptsExtractorWithDestination:error:]”, the PKG file will be replaced with a crafted one after line 8 and before line 16.
4. After the service calls the API “BOMCopierCopyWithOptions” to extract the malicious scripts inside our crafted PKG file, the PKG file will be restored to the original one.
5. The extracted malicious scripts will then be spawned by the system_installd in a SIP-Bypass context with root privilege.
Apple’s new patch on macOS Ventura
Apple patched the vulnerability again in macOS Ventura via the following steps:
First, the new patch code gets the expected checksum property of the PKG file’s subpath via the trusted XAR pointer, which is returned by the safe API “xar_open_digest_verify”.
Figure 9. The new patch code gets the expected checksum
Then, instead of reading from the “inputFD” directly, it will use an instance of the ObjC class “IASInputStream” to read the “inputStream”. During the extraction, it will update the “inputStream” digest at the same time.
Figure 10. Extract from the “inputStream”
After the extraction, it will check whether the real checksum of “inputStream” is equal to the expected value. If so, it will continue the installation. Otherwise, it will abort the whole process.
Figure 11. Check the real checksum
We believe that the best solution could be to copy the PKG file to a safe place before it is installed. Apple-signed packages should be copied to a SIP-protected location. Other packages should be copied to a root-owned location.
However, Apple’s latest patch has successfully addressed this vulnerability.
Security recommendations
Apple issued patches addressing this vulnerability and successfully addressed it this year in macOS Ventura. Users who fail to update their operating systems can be vulnerable to root privilege escalation, signature bypassing, and SIP bypassing. It is therefore imperative for users to install all updates to keep their systems secure.