To protect our users, TAG routinely hunts for 0-day vulnerabilities exploited in-the-wild. In late August 2021, TAG discovered watering hole attacks targeting visitors to Hong Kong websites for a media outlet and a prominent pro-democracy labor and political group. The watering hole served an XNU privilege escalation vulnerability (CVE-2021-30869) unpatched in macOS Catalina, which led to the installation of a previously unreported backdoor.
As is our policy, we quickly reported this 0-day to the vendor (Apple) and a patch was released to protect users from these attacks.
Based on our findings, we believe this threat actor to be a well-resourced group, likely state backed, with access to their own software engineering team based on the quality of the payload code.
In this blog we analyze the technical details of the exploit chain and share IOCs to help teams defend against similar style attacks.
The websites leveraged for the attacks contained two iframes which served exploits from an attacker-controlled server—one for iOS and the other for macOS.
The iOS exploit chain used a framework based on Ironsquirrel to encrypt exploits delivered to the victim's browser. We did not manage to get a complete iOS chain this time, just a partial one where CVE-2019-8506 was used to get code execution in Safari.
The macOS exploits did not use the same framework as iOS ones. The landing page contained a simple HTML page loading two scripts—one for Capstone.js and another for the exploit chain.
The parameter rid is a global counter which records the number of exploitation attempts. This number was in the 200s when we obtained the exploit chain.
The exploit chain combined an RCE in WebKit exploiting CVE-2021-1789 which was patched on Jan 5, 2021 before discovery of this campaign and a 0-day local privilege escalation in XNU (CVE-2021-30869) patched on Sept 23, 2021.
Remote Code Execution (RCE)
Loading a page with the WebKit RCE on the latest version of Safari (14.1), we learned the RCE was an n-day since it did not successfully trigger the exploit. To verify this hypothesis, we ran git bisect and determined it was fixed in this commit.
Sandbox Escape and Local Privilege Escalation (LPE)
It was interesting to see the use of Capstone.js, a port of the Capstone disassembly framework, in an exploit chain as Capstone is typically used for binary analysis. The exploit authors primarily used it to search for the addresses of dlopen and dlsym in memory. Once the embedded Mach-O is loaded, the dlopen and dlsym addresses found using Capstone.js are used to patch the Mach-O loaded in memory.
With the Capstone.js configured for X86-64 and not ARM, we can also derive the target hardware is Intel-based Macs.
After the WebKit RCE succeeds, an embedded Mach-O binary is loaded into memory, patched, and run. Upon analysis, we realized this binary contained code which could escape the Safari sandbox, elevate privileges, and download a second stage from the C2.
Analyzing the Mach-O was reminiscent of a CTF reverse engineering challenge. It had to be extracted and converted into binary from a Uint32Array.
Then the extracted binary was heavily obfuscated with a relatively tedious encoding mechanism--each string is XOR encoded with a different key. Fully decoding the Mach-O was necessary to obtain all the strings representing the dynamically loaded functions used in the binary. There were a lot of strings and decoding them manually would have taken a long time so we wrote a short Python script to make quick work of the obfuscation. The script parsed the Mach-O at each section where the strings were located, then decoded the strings with their respective XOR keys, and patched the binary with the resulting strings.
After downloading the payload, it removes the quarantine attribute of the file to bypass Gatekeeper. It then elevated privileges to install the payload.
N-day or 0-day?
Before further analyzing how the exploit elevated privileges, we needed to figure out if we were dealing with an N-day or a 0-day vulnerability. An N-day is a known vulnerability with a publicly available patch. Threat actors have used N-days shortly after a patch is released to capitalize on the patching delay of their targets. In contrast, a 0-day is a vulnerability with no available patch which makes it harder to defend against.
Despite the exploit being an executable instead of shellcode, it was not a standalone binary we could run in our virtual environment. It needed the address of dlopen and dlsym patched after the binary was loaded into memory. These two functions are used in conjunction to dynamically load a shared object into memory and retrieve the address of a symbol from it. They are the equivalent of LoadLibrary and GetProcAddress in Windows.
To run the exploit in our virtual environment, we decided to write a loader in Python which did the following:
- load the Mach-O in memory
- find the address of dlopen and dlsym
- patch the loaded Mach-O in memory with the address of dlopen and dlsym
- pass our payload url as a parameter when running the Mach-O
For our payload, we wrote a simple bash script which runs id and pipes the result to a file in /tmp. The result of the id command would tell us whether our script was run as a regular user or as root.
Having a loader and a payload ready, we set out to test the exploit on a fresh install of Catalina (10.15) since it was the version in which we were served the full exploit chain. The exploit worked and ran our bash script as root. We updated our operating system with the latest patch at the time (2021-004) and tried the exploit again. It still worked. We then decided to try it on Big Sur (11.4) where it crashed and gave us the following exception.
The exception indicates that Apple added generic protections in Big Sur which rendered this exploit useless. Since Apple still supports Catalina and pushes security updates for it, we decided to take a deeper look into this exploit.
Elevating Privileges to Root
The Mach-O was calling a lot of undocumented functions as well as XPC calls to mach_msg with a MACH_SEND_SYNC_OVERRIDE flag. This looked similar to an earlier in-the-wild iOS vulnerability analyzed by Ian Beer of Google Project Zero. Beer was able to quickly recognize this exploit as a variant of an earlier port type confusion vulnerability he analyzed in the XNU kernel (CVE-2020-27932). Furthermore, it seems this exact exploit was presented by Pangu Lab in a public talk at zer0con21 in April 2021 and Mobile Security Conference (MOSEC) in July 2021.
In exploiting this port type confusion vulnerability, the exploit authors were able to change the mach port type from IKOT_NAMED_ENTRY to a more privileged port type like IKOT_HOST_SECURITY allowing them to forge their own sec_token and audit_token, and IKOT_HOST_PRIV enabling them to spoof messages to kuncd.
After gaining root, the downloaded payload is loaded and run in the background on the victim's machine via launchtl. The payload seems to be a product of extensive software engineering. It uses a publish-subscribe model via a Data Distribution Service (DDS) framework for communicating with the C2. It also has several components, some of which appear to be configured as modules. For example, the payload we obtained contained a kernel module for capturing keystrokes. There are also other functionalities built-in to the components which were not directly accessed from the binaries included in the payload but may be used by additional stages which can be downloaded onto the victim's machine.
Notable features for this backdoor include:
- victim device fingerprinting
- screen capture
- file download/upload
- executing terminal commands
- audio recording
Our team is constantly working to secure our users and keep them safe from targeted attacks like this one. We continue to collaborate with internal teams like Google Safe Browsing to block domains and IPs used for exploit delivery and industry partners like Apple to mitigate vulnerabilities. We are appreciative of Apple’s quick response and patching of this critical vulnerability.
For those interested in following our in-the-wild work, we will soon publish details surrounding another, unrelated campaign we discovered using two Chrome 0-days (CVE-2021-37973 and CVE-2021-37976). That campaign is not connected to the one described in today’s post.
- cbbfd767774de9fecc4f8d2bdc4c23595c804113a3f6246ec4dfe2b47cb4d34c (capstone.js)
- bc6e488e297241864417ada3c2ab9e21539161b03391fc567b3f1e47eb5cfef9 (mac.js)
Sandbox escape / LPE
- cf5edcff4053e29cb236d3ed1fe06ca93ae6f64f26e25117d68ee130b9bc60c8 (2021 sample)
- f0b12413c9d291e3b9edd1ed1496af7712184a63c066e1d5b2bb528376d66ebc (2019 sample)