The Recent iOS 0-Click, CVE-2021-30860, Sounds Familiar. An Unreleased Write-up: One Year Later


Follow zecops


ZecOps identified and reproduced an Out-Of-Bounds 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 CitizenLabs blog.

As a brief background: ZecOps have analyzed several devices of Al-Jazeera journalists in the summer 2020 and automatically and successfully found compromised devices without relying on any IOC. These attacks were later attributed to NSO / Pegasus.
ZecOps Mobile EDR and Mobile XDR are available here.

Noteworthy, although these two vulnerabilities are different – they are close enough and worth a deeper read.


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

The Background

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

Open 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, 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, having debug capability is not enough since the process crashes only when the corrupted memory is being used, which is 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 at the 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, which gives us an opportunity to inject environment variables into the target process, in this case “”.

The following lldb command “process attach –name xpcproxy_sim –waitfor” allows us to attach xpcproxy_sim 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.

By 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 “CGDataProviderDirectGetBytesAtPositionInternal”.

Thread 3 Crashed:: Dispatch queue: PDFKit.PDFTilePool.workQueue
0   libsystem_platform.dylib      	0x0000000106afc867 _platform_memmove$VARIANT$Nehalem + 71
1        	0x0000000101b44a98 CGDataProviderDirectGetBytesAtPositionInternal + 179
2        	0x0000000101d125ab provider_for_destination_get_bytes_at_position_inner + 562
3        	0x0000000101b44b09 CGDataProviderDirectGetBytesAtPositionInternal + 292
4        	0x0000000101c6c60c provider_with_softmask_get_bytes_at_position_inner + 611
5        	0x0000000101b44b09 CGDataProviderDirectGetBytesAtPositionInternal + 292
6        	0x0000000101dad19a get_chunks_direct + 242
7        	0x0000000101c58875 img_raw_read + 1470
8        	0x0000000101c65611 img_data_lock + 10985
9        	0x0000000101c6102f CGSImageDataLock + 1674
10        	0x0000000101a2479e ripc_AcquireRIPImageData + 875
11        	0x0000000101c8399d ripc_DrawImage + 2237
12        	0x0000000101c68d6f CGContextDrawImageWithOptions + 1112
13        	0x0000000101ab7c94 CGPDFDrawingContextDrawImage + 752

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 CGDataProviderCreateWithSoftMaskAndMatte.

ALLOC 0x6000ec9f9ff0-0x6000ec9f9fff [size=16]:
0x7fff51c07b77 (libsystem_pthread.dylib) start_wqthread |
0x7fff51c08a3d (libsystem_pthread.dylib) _pthread_wqthread |
0x7fff519f40c4 (libdispatch.dylib) _dispatch_workloop_worker_thread |
0x7fff519ea044 (libdispatch.dylib) _dispatch_lane_invoke |
0x7fff519e9753 (libdispatch.dylib) _dispatch_lane_serial_drain |
0x7fff519e38cb (libdispatch.dylib) _dispatch_client_callout |
0x7fff519e2951 (libdispatch.dylib) _dispatch_call_block_and_release |
0x7fff2a9df04d ( __71-[PDFPageBackgroundManager forceUpdateActivePageIndex:withMaxDuration:]_block_invoke |
0x7fff2a9dfe76 ( -[PDFPageBackgroundManager _drawPageImage:forQuality:] |
0x7fff2aa23b85 ( -[PDFPage imageOfSize:forBox:withOptions:] |
0x7fff2aa23e1e ( -[PDFPage _newCGImageWithBox:bitmapSize:scale:offset:backgroundColor:withRotation:withAntialiasing:withAnnotations:withBookmark:withDelegate:] |
0x7fff2aa22a40 ( -[PDFPage _drawWithBox:inContext:withRotation:isThumbnail:withAnnotations:withBookmark:withDelegate:] |
0x7fff240bdfe0 ( CGContextDrawPDFPage |
0x7fff240bdac4 ( CGContextDrawPDFPageWithDrawingCallbacks |
0x7fff244bb0b1 ( CGPDFScannerScan | 0x7fff244bab02 ( pdf_scanner_handle_xname |
0x7fff2421e73c ( op_Do |
0x7fff2414dc94 ( CGPDFDrawingContextDrawImage |
0x7fff242fed6f ( CGContextDrawImageWithOptions |
0x7fff2431999d ( ripc_DrawImage |
0x7fff240ba79e ( ripc_AcquireRIPImageData |
0x7fff242f6fe8 ( CGSImageDataLock |
0x7fff242f758b ( img_image |
0x7fff24301fe2 ( CGDataProviderCreateWithSoftMaskAndMatte |
0x7fff51bddad8 (libsystem_malloc.dylib) calloc |
0x7fff51bdd426 (libsystem_malloc.dylib) malloc_zone_calloc 

The Vulnerability

The OOB-Write vulnerability happens in the function “CGDataProviderDirectGetBytesAtPositionInternal” of CoreGraphics library, the allocation of the target memory was inside the function “CGDataProviderCreateWithSoftMaskAndMatte“.

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 minimum PoC and reported to Apple on September 1st 2020, the issue was fixed on the iOS 14 release. We will release this POC soon.

ZecOps Mobile EDR & Mobile XDR customers are protected against NSO and are well equipped to discover other sophisticated attacks including 0-days attacks.

reverse bounty

ZecOps Mobile EDR

Perform automated investigations in minutes to uncover cyber-espionage on smartphones and tablets.

Learn more >

Partners, Resellers, Distributors and Innovative Security Teams

ZecOps provides the industry-first automated crash forensics platform across devices, operating systems and applications.

Learn more >