Zero-click RCE bugs on phones are worth millions of dollars. Because of the requirements of "zero-click" and the small attack surface, they are pretty rare. This details a zero-click RCE bug on the iPhone within the EXR image parser.
Apple's EXR parser allocates a destination buffer sized for a three-channel RGB image. Twelve bytes per pixel for red, green, and blue. Each of these values is a 32-bit float. When the routine goes to write the data to a buffer, it writes four channels instead of three. So, there is a four byte buffer overrun per pixel for the entire image. A small 448 by 488 pixel image is 800 KB of heap past the allocation. So, it's a bug where the code never worked that would have been trivially caught by ASAN.
This primitive is huge, and takes data directly from the pixel information for a write. The first twelve are user-controlled, but the final byte is a constant 0x3f800000. The LLM dissemabled the code and immediately saw the flaw. To trigger this bug, they built an LLM-based fuzzer to uncover edge cases. The crash gave them a nice write-only primitive that crashes in a loop during the write with a properly constructed image.
They wrote AppleScript to have it parse the image with slight variations. By changing the size of the data and the pixel values, they were able to learn how the bug leads to crashes. They also tested the crash on their iPad. Importantly, they needed to do some heap grooming to overwrite something useful. After playing around with the primitive some more, they were able to craft it into a write-what-where primitive. They used this to overwrite a function pointer to control the flow of execution.
It appears that you need an ASLR leak and a PAC-signed pointer to exploit this. The bug was triaged as 0-click code execution via iMessage EXR image payload still. Getting these is still a tall order imo. They have some good takeaways on exploitability that actually contradict this though... a static bug is a lead until proven exploitable. Just because something looks like a bug, doesn't mean that it's triggerable. The next lesson was making the exploit better. Triggering the crash took a day, but finding the no-interaction path with the proper grooming took a long time.
The article is very straight forward with what the primitive provides. It doesn't exaggerate, which I greatly appreciate. The bug is simple and is explained as such. I appreciate the time spent turning the primitive into something more useful, and the candid promises about the bug's exploitability. Good post!