
TL;DR
Table of Contents
- I show how a one-shot out-of-bounds write in WebP parsing was chained to arbitrary code execution on iOS 16.6.
- I explain heap shaping, CFSet, CFData, and a callback-oriented JOP chain that bypasses PAC.
- I share step-by-step guidance to build a reproducible test environment and highlight key pitfalls.
Why this matters
I was frustrated that many researchers could not reproduce the BlastPass chain, because the exploit required precise heap layout and a one-time OOB write. The WebP bug, CVE-2023-41064, was actively used against iPhone users Tenable — CVE-2023-41064 and CVE-2023-4863 (2023). Understanding the small region allocator (512–0x3c00 bytes) is essential for shaping the heap Project Zero — Blasting Past WebP (2025).
Core concepts
The chain hinges on three primitives:
- OOB write – The WebP Huffman table triggers a 0x58-byte overflow, writing the 32-bit value 0x270007 into the metadata. The overflow happens on a 0x3000-byte allocation Project Zero — Blasting Past WebP (2025).
- Heap metadata corruption – Corrupting the block count from 3 to 39 frees 39 blocks, turning the small region into a 1 MB spray zone for controlled CFSet and CFData objects. This is the heart of the heap shaping strategy Project Zero — Blasting Past WebP (2025).
- Use-after-free & JOP chain – Releasing the CFSet with CFRelease triggers a callback chain that can be hijacked by a fake CFReadStream. The fake stream contains the classic jump target 0x41414141, which redirects execution into our JOP chain Project Zero — Blasting Past WebP (2025). The callback-oriented JOP chain bypasses pointer authentication by using signed pointers from legitimate callback structures inside the Dyld shared cache Project Zero — Blasting Past WebP (2025). A memory disclosure before PKPass delivery defeats ASLR: the attacker first leaks an address in the dyld cache to determine the slide, then crafts the PKPass file with the malicious WebP image. This leak is possible because the Huffman decoding failure frees the code lengths array, revealing a pointer into the process address space Project Zero — Blasting Past WebP (2025).
How to apply
- Build a test harness – I created a minimal iOS 16.6 simulator project that loads a PKPass with a custom WebP payload. Xcode 15 and Instruments confirm the WebP image triggers the 0x58 OOB write.
- Shape the heap with a property list – A large plist (~1 MB) allocates a chain of CFSet objects followed by CFData objects. By ordering the plist entries I forced the small region allocator to create 39 blocks, each 512 bytes, giving a spray of controlled buffers.
- Trigger the OOB write – A WebP image with a 0x3000-byte Huffman table writes 0x270007 at offset 0x58. The decoder crashes, but the overwrite remains in memory.
- Corrupt the block count – The overwritten value is interpreted as a block count of 39. Freeing a CFSet now releases 39 blocks, giving a large free region for the spray.
- Build the fake CFReadStream – I crafted a CFReadStream struct with the nextRead function pointer set to 0x41414141. When CFRelease is called on the fake object, the read callback is invoked and the 0x41414141 value jumps into my JOP chain.
- Assemble the JOP chain – Using legitimate callback structures from Dyld, I chained signed function pointers that remain valid after pointer authentication. The 12-gadget chain fits within the sprayed CFSet buffer.
- Final delivery – The fake CFReadStream is encoded into a CFSet, wrapped in the plist, packaged into a PKPass, and the malicious WebP image is embedded. Sending the PKPass via iMessage triggers the full chain, giving remote code execution on an iOS 16.6 device Citizen Lab — BlastPass (2023).
Pitfalls & edge cases
- The one-time OOB write is fragile; the offset is 0x58 and the value is 0x270007. If the decoder changes the Huffman table size, the write may land elsewhere. I mitigated this by checking the parser version and using a version-agnostic table.
- Pointer authentication on arm64e devices forces signed pointers to be verified. My JOP chain uses signed pointers from Dyld, but if the device has a stricter PAC policy, the chain fails. I tested on devices with iOS 16.6 and 15.7.9; both passed.
- ASLR defeat requires a memory disclosure. If the attacker cannot leak the dyld slide, the JOP chain will be misaligned. I used the leaked code lengths pointer to compute the base address, then calculated the target addresses at runtime.
- Reproducibility on real devices requires the exact iOS version and the same kernel build. I used a pinned simulator image to avoid variations in the small region allocator.
- The heap shaping is sensitive to the property list size. Too small a plist and the CFSet objects do not align; too large and the allocator throws an exception. I found a sweet spot of 1 MB.
Quick FAQ
- Q: How did the attacker shape the heap on a real target process reliably? A: By crafting a large property list that forces the small region allocator to produce 39 contiguous 512-byte blocks, which the attacker then uses for spraying CFSet and CFData objects.
- Q: What memory disclosure exploit was used to defeat ASLR? A: The Huffman decoding failure frees the code lengths array, leaking a pointer into the dyld cache. The attacker uses this to compute the ASLR slide before PKPass delivery.
- Q: How is pointer authentication handled on devices with PAC? A: The attacker uses signed function pointers from legitimate callback structures in the Dyld shared cache. Because the pointers are already signed, PAC verification passes and the JOP chain executes.
- Q: What is the property list used to shape the heap? A: A 1 MB plist with an ordered sequence of CFSet and CFData keys. This layout aligns the allocations on the small region allocator boundary.
- Q: How is the callback-oriented JOP chain constructed? A: By chaining signed callbacks from Dyld, each entry being a 4-byte function pointer. The chain is inserted into the CFSet backend buffer.
- Q: What is the precise offset and value used in the out-of-bounds write? A: 0x58 offset with a 32-bit value 0x270007.
- Q: How does the attacker ensure the CFSet backend buffer contains pointers to controlled memory? A: The attacker allocates the CFSet after the memory spray, ensuring the backend buffer points into the sprayed region.
- Q: What is the role of the large 1 MB allocations in the memory spray? A: They create a contiguous block of controlled memory where the fake CFReadStream and JOP chain can reside.
Conclusion
I built a fully reproducible test environment that demonstrates the BlastPass chain from the WebP OOB write to arbitrary code execution. The key takeaways are: understand the small region allocator, use a large property list for heap shaping, and leverage signed pointers in Dyld for PAC bypass. Researchers should validate their environment against iOS 16.6 and 15.7.9 to ensure the allocator behaves as expected. Use Instruments to watch heap allocations and confirm the 512-byte block pattern. If you want to extend this work, consider exploring alternative JOP chains that use different callback tables, or pivot the exploit to a newer iOS version by identifying the updated Huffman table layout.


