Threat Analysis Group

Analyzing a watering hole campaign using macOS exploits

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.

Watering Hole

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.


iOS Exploits

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.

macOS Exploits

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.

While the javascript starting the exploit chain checks whether visitors were running macOS Mojave (10.14) or Catalina (10.15) before proceeding to run the exploits, we only observed remnants of an exploit when visiting the site with Mojave but received the full non-encrypted exploit chain when browsing the site with Catalina.

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.


Embedded Mach-O

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.

decoded strings

Once we had all of the strings decoded, it was time to figure out what capabilities the binary had. There was code to download a file from a C2 but we did not come across any URL strings in the Mach-O so we checked the javascript and saw there were two arguments passed when the binary is run–the url for the payload and its size.


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.

exception type

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.

MACMA Payload

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
  • keylogging


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.

Related IOCs

Delivery URLs

  • http://103[.]255[.]44[.]56:8372/6nE5dJzUM2wV.html
  • http://103[.]255[.]44[.]56:8371/00AnW8Lt0NEM.html
  • http://103[.]255[.]44[.]56:8371/SxYm5vpo2mGJ?rid=<redacted>
  • http://103[.]255[.]44[.]56:8371/iWBveXrdvQYQ?rid=?rid=<redacted>
  • https://appleid-server[.]com/EvgSOu39KPfT.html
  • https://www[.]apple-webservice[.]com/7pvWM74VUSn2.html
  • https://appleid-server[.]com/server.enc
  • https://amnestyhk[.]org/ss/defaultaa.html
  • https://amnestyhk[.]org/ss/4ba29d5b72266b28.html
  • https://amnestyhk[.]org/ss/mac.js


  • cbbfd767774de9fecc4f8d2bdc4c23595c804113a3f6246ec4dfe2b47cb4d34c (capstone.js)
  • bc6e488e297241864417ada3c2ab9e21539161b03391fc567b3f1e47eb5cfef9 (mac.js)
  • 9d9695f5bb10a11056bf143ab79b496b1a138fbeb56db30f14636eed62e766f8

Sandbox escape / LPE

  • 8fae0d5860aa44b5c7260ef7a0b277bcddae8c02cea7d3a9c19f1a40388c223f
  • df5b588f555cccdf4bbf695158b10b5d3a5f463da7e36d26bdf8b7ba0f8ed144


  • cf5edcff4053e29cb236d3ed1fe06ca93ae6f64f26e25117d68ee130b9bc60c8 (2021 sample)
  • f0b12413c9d291e3b9edd1ed1496af7712184a63c066e1d5b2bb528376d66ebc (2019 sample)