iOS 0-Click, CVE-2021-30860, sounds familiar. An unreleased write-up: one year later

A 0-click vulnerability that was identified by Jamf Threat Labs is reproduced, alongside a breakdown of how it works and why it is critical to protect your iOS-based mobile fleet from CVE-2021-30860.

September 14 2021 by

Jamf Threat Labs

Microscope examining virus strain upclose

TLDR;

Jamf identified and reproduced an Out-Of-Bounds (OOB) write vulnerability that can be triggered by opening a malformed PDF. This vulnerability reminded us of the FORCEDENTRY vulnerability exploited by NSO/Pegasus according to The Citizen Lab’s blog.

As a brief background, Jamf analyzed several devices of Al-Jazeera journalists in the summer of 2020 and automatically and successfully found compromised devices without relying on any IOC. These attacks were later attributed to NSO/Pegasus.

Also of note, although these two vulnerabilities are different – they are similar and worth a deeper read.

Timeline

  • We reported this vulnerability on September 1st, 2020 – iOS 14 beta was vulnerable at the time.
  • The vulnerability was patched on September 14th, 2020 – iOS 14 beta release.
  • Apple contacted us on October 20, 2020 – claiming that the bug was already fixed – (“We were unable to reproduce this issue using any current version of iOS 14. Are you able to reproduce this issue using any version of iOS 14? If so, we would appreciate any additional information you can provide us, such as an updated proof-of-concept.”).
  • No CVE was assigned.

It is possible that NSO noticed this incremental bug fix and dived deeper into CoreGraphics.

Background

Earlier last year, we obtained a PDF file that cannot be previewed on iOS. The PDF sample crashes previewUI with a segmentation fault, meaning that a memory corruption was triggered by the PDF. When opening the PDF, previewUI flashes and shows nothing.

The important question is: how do we find out the source of the memory corruption?

The macOS preview works fine, with no crash. Meaning that it’s the iOS library that might have an issue. We confirmed the assumption with the iPhone Simulator since the crash happened on the iPhone Simulator.

It’s great news since Simulator on macOS provides better debug tools than iOS. However, debug capability is not enough since the process crashes only when the corrupted memory is being used, after the actual memory corruption.

We need to find a way to trigger the crash right at the point the memory corruption happens.

The idea is to leverage Guard Malloc or Valgrind, making the process crash right as memory corruption occurs.

“Guard Malloc is a special version of the malloc library that replaces the standard library during debugging. Guard Malloc uses several techniques to try and crash your application at the specific point where a memory error occurs. For example, it places separate memory allocations on different virtual memory pages and then deletes the entire page when the memory is freed. Subsequent attempts to access the deallocated memory cause an immediate memory exception rather than a blind access into memory that might now hold other data.”

Environment Variables Injection

In this case, we cannot simply add an environment variable with the command line since the previewUI launches on clicking the PDF – which does not launch from the terminal. We need to inject libgmalloc before the launch.

The process launchd_sim launches Simulator XPC services with a trampoline process called xpcproxy_sim. The xpcproxy_sim launches target processes with a posix_spawn system call, allowing us to inject environment variables into the target process, in this case, com.apple.quicklook.extension.previewUI.

The following lldb command process attach –name xpcproxy_sim –waitfor allows us to attach xpcproxy_sim and then set a breakpoint on posix_spawn once it’s launched.

Once the posix_spawn breakpoint is hit, we are able to read the original environment variables by reading the address stored in the $r9 register.

With a few simple lldb expressions, we are able to overwrite one of the environment variables into DYLD_INSERT_LIBRARIES=/usr/lib/libgmalloc.dylib, injection complete.

Continuing execution, the process crashed almost right away.

Analyzing the Crash

Finally, we got the Malloc Guard working as expected, the previewUI crashes right at the memmove function that triggers the memory corruption.

After libgmalloc injection we have the following backtrace that shows an Out-Of-Bounds write occurs in <strong>CGDataProviderDirectGetBytesAtPositionInternal</strong>.

With the same method, we can take one step further, with the MallocStackLogging flag libgmalloc provides, we can track the function call stack at the time of each allocation.

After setting the MallocStackLoggingNoCompact=1, we got the following backtrace showing that the allocation was inside <strong>CGDataProviderCreateWithSoftMaskAndMatte</strong>.

Vulnerability

The OOB-Write vulnerability happens in the function CGDataProviderDirectGetBytesAtPositionInternal of the CoreGraphics library. The allocation of the target memory was inside the function <strong>CGDataProviderCreateWithSoftMaskAndMatte</strong>.

It allocates 16 bytes of memory if the bits_per_pixel equals or less than 1 byte, which is less than copy length.

We came out with a minimal PoC and reported to Apple on September 1st, 2020, the issue was fixed on the iOS 14 release. We will release this POC soon.

Jamf protects against this and numerous other security threats impacting your enterprise fleet.

Subscribe to the Jamf Blog

Have market trends, Apple updates and Jamf news delivered directly to your inbox.

To learn more about how we collect, use, disclose, transfer, and store your information, please visit our Privacy Policy.