Mohamed GHANNAM (@_simo36)
WeightBufs achieves kernel memory read and write capabilities on some versions of iOS & iPadOS 15 and macOS 12.
As it would appear, the exploit itself supports all versions of iOS & iPadOS 15 on Neural Engine-equipped devices (A11 and newer) just shy of iOS & iPadOS 16; but iOS & iPadOS 15.6 patch the sandbox escape, which breaks the exploit chain used to achieve the WeightBufs hack. With that in mind, the full exploit chain is only currently viable on iOS & iPadOS 15.0-15.5 and macOS 12.0-12.4.
The problem above underscores one of the core issues impacting jailbreaking today, which is how techniques have become more important than the kernel exploits themselves. No single kernel exploit will make a jailbreak on modern iOS or iPadOS firmware since Apple continues to ratchet up iPhone and iPad security. Because of this, jailbreak developers need additional resources such as bypasses and sandbox escapes just to achieve one. The logic behind navigating all these mechanisms is what constitutes a technique.
At least four different vulnerabilities that have already been reported to Apple are used in @_simo36’s exploit chain, including the following:
- CVE-2022-32845: aned signature check bypass for model.hwx.
- CVE-2022-32948: DeCxt::FileIndexToWeight() OOB Read due to lack of array index validation.
- CVE-2022-42805: ZinComputeProgramUpdateMutables() potential arbitrary read due to integer overflow issue
- CVE-2022-32899: DeCxt::RasterizeScaleBiasData() Buffer underflow due to integer overflow issue.
Currently, @_simo36 says they’ve tested their exploit chain successfully on the following device and firmware combinations:
- iPhone 12 Pro (iPhone 13,3) running iOS 15.5
- iPad Pro (iPad 8,10) running iPadOS 15.5
- iPhone 11 Pro (iPhone 12,3) running iOS 15.4.1
- MacBook Air (10,1 with M1 chip) running macOS 12.4
Apple’s Neural Engine Architecture
The user interface
Tools & Frameworks
CoreML framework used to:
- Integrated trained models into Xcode apps.
- Load models and on-device training.
- Make predictions.
coremltools python library that:
- Creates models from scratch.
- Converts trained models from other ML tools into CoreML.
- Manipulates/Customizes network layers and operations.
- Loads models and makes predictions.
CoreML loads models through aned system service:
- XPC Interface : com.apple.appleneuralengine.
- Main broker for CoreML interactions with the kernel and the compiler service.
- Responsible for Model compilation and loading.
aned compiles a model through the ANE compiler ANECompilerService:
- XPC Interface : com.apple.ANECompilerService.
- Model Translation & Compilation. Entitled: com.apple.ANECompilerService.allow.
- Produces a binary model “model.hwx”.
Model Formats
MLModel is converted to MLModelc (ProtoBuf to JSON format):
- The compilation can also be done via coremlcompiler command.
MLModelc is translated to net.plist (from a set of JSON files to one PLIST file):
- The model translation is made by Espresso private framework.
net.plist is compiled to a binary model called “model.hwx”:
- The compilation is done by ANECompiler`ANECCompile()
- The model.hwx is a Mach-O file that starts with 0xfeedface or 0xbeefface.
- The model.hwx has segments / sections .. etc
The kernel interface
The kernel extension is AppleH11ANEInterface. The KEXT provides two UserClient classes: H11ANEInUserClient and H11ANEInDirectPathClient. H11ANEInUserClient: (for aned)
- Responsible for loading/unloading models.
- A lot of external methods with rich features (large attack surface).
- Entitled: com.apple.ane.iokit-user-access.
H11ANEInDirectPathClient: (for Xcode apps)
- Responsible for model predictions and on-device training.
- Allows apps to Send Procedure Calls Requests to the firmware.
- Reachable from the default app sandbox (an attractive target).
The Firmware interface
The firmware image can be found at ./Firmware/ane/ in IPSW files. CANEController::CmdProcessor() is the main function that parses ~70 commands.
References
- BlackHat ASIA 21 Wish Wu: Apple Neural Engine Internals
- George Hotz | Programming | tinygrad: triggering the Apple Neural Engine from C++
- Hollance : The Neural Engine — what do we know about it?
Vulnerabilities
CVE-2022-32840: ANE_ProgramSendRequest() OOB write
- Any validation for the total surface buffers ?
- No validation for total intermediate buffers
CVE-2022-42805: ANECValidateMutableProcedureInfo() integer overflow.
- ANECMutableProcedureInfo is a mapping of an IOSurface buffer.
- The IOSurface object is from structureInput->weightSurfaceId.
- ANECMutableProcedureInfo content is completely under user-control.
- ANECValidateMutableProcedureInfo is called to verify the safety of the object.
ANECMutableProcedureInfo is a user-controlled object
- ANECValidateMutableProcedureInfo is called to verify the safety of the object passed to ZinComputeProgramUpdateMutables.
- ANECValidateMutableProcedureInfo() validates the shared surface buffer.
- Integer overflow in the calculation at line 95.
- The security check could be bypassed by overflowing the calculation.
- ANECValidateMutableProcedureInfo() validated our object.
- ANECGetMutableWeight() is called to populate ANECMutableWeight.
- Because of the overflow, _weightBuf could point to any location outside the buffer range.
- This vulnerability could be turned into arbitrary memory read if the procedure_info address was known.
- ANECMutableProcedureInfo is allocated from KHEAP_DATA_BUFFERS.
- Groom KHEAP_DATA_BUFFERS with a lot of ipc_kmsg data buffers.
- Allocate the ANECMutableProcedureInfo object.
- Overflow the calculation : weightInfo->wi_off = (0 - 0x10000);
- Underflow the mw->_weightBuf location to point to an ipc_kmsg buffer.
Demo: Leak the ipc_kmsg content to user-space.
CVE-2022-32948: DeCxt::FileIndexToWeight() improper index validation
- DeCxt::FileIndexToWeigh() is reachable from H11ANEInDirectPathClient::_ANE_ProgramSendRequest().
- Both index and offset are user-controlled parameters.
- weight_objects class member is an array of ANECMutableWeight.
- ANECMutableWeight array allocation size is opsInfo->op_count from ANECMutableProcedureInfo (user controlled shared buffer).
- The lack of index validation allows reading out-of-bounds ANECMutableWeight objects.
- By grooming kernel memory, the attacker can read data from arbitrary kernel pointer with arbitrary size.
CVE-2022-32899: DeCxt::RasterizeScaleBiasData() OOB write
- The function converts floating-point values from single-precision to half-precision.
- param_1, param_2 and param_3 are user-controlled input.
- param_1 is 64-bit value and it’s user-controlled input.
- param_2 is 16-bit value and it’s user controlled input.
param_3 is deserialized by DeCxt::ParseOcgRasterizationInfo().
- MUTK_kernel_section is an IOSurface mapped buffer created by the kernel.
- Created by H11ANEIn::AllocateSharedMemorySurface().
- A sanity check for a potential integer overflow in the calculation at line 16.
- The sanity checks does NOT prevent from integer underflow.
- Buffer underflow that allows to write arbitrary data to any location prior the MUTK_kernel_section address.
- Up to 0x20000 bytes of user-controlled data could be written.
Kernel panic occurs because x25 = 0x4141414142424242
Could be turned into arbitrary kernel write if the location of MUTK_kernel_section was known.
- Another OOB read/write in DeCxt::RasterizeScaleBiasData().
- Because offset and pos are fully user-controlled and not validated before their usage.
How to reach H11ANEIn::patchMutableSurface() ?
Requirements:
- The model.hwx (Mach-O file ) must have some special flags in some mach sections.
- The model.hwx must have a procedure (Neural Network) with mutable features.
Failed to fulfill them because:
- No documentation available.
- To understand the compilation/translation options available, you need to RE some private frameworks yourself : mlcompiler, Espresso and ANECCompiler.
- Good luck reversing frameworks written in C++ and STL.
1. Patch an existing model.hwx file
- The model is a Mach-O file and easy parse and edit
2. Load a patched model.hwx via aned
- You can’t directly provide a native model by yourself to the kernel (unless you have a special entitlement).
- aned allows loading binary models without compilation (with some constraints) and can do the work on you behalf.
Load a malformed model.hwx (Mach-O) file
aned loads different model formats according to the given dictionary options:
- {kANEFModelType : nil } ⇒ Compiles + Loads .mlmodelc.
- {kANEFModelType : kANEFModelPreCompiled } ⇒ Loads model.hwx from arbitrary location.
- {kANEFIsInMemoryModelTypeKey : <model> } ⇒ Loads model.hwx from the cache directory.
aned can loads model.hwx from two different locations:
- Cache Directory: Loads an already compiled model.
- Arbitrary location: Loads a compiled model from a given directory.
From the cache directory using kANEFIsInMemoryModelTypeKey
- macOS: /Library/Caches/com.apple.aned/<build no>/InMemoryModelCache
- *OS: /var/mobile/Library/Caches/com.apple.aned/<build no>/InMemoryModelCache
- Even root can’t read its content. Security Features need to be disabled in macOS.
From the cache directory using kANEFIsInMemoryModelTypeKey
- Get the cache directory location.
- Append csIdentity to that cache directory.
- Append kANEFIsInMemoryModelTypeKey value.
- Append “model.hwx” string to the cache directory.
- The model.hwx is loaded from the cache directory by:
- aned’[_ANEStorageHelper memoryMapModelAtPath:isPrecompiled:modelAttributes:]
- Send a request to the kernel to create the program:
From any location using kANEFModelPreCompiled
- The model.hwx file is loaded from arbitrary location
- aned’[_ANEStorageHelper memoryMapModelAtPath:isPrecompiled:modelAttributes:]
- With isPrecompiled = True
- Send a request to the kernel to create the program with isPrecompiled=True.
Takeaways:
- Only the models that were compiled by Apple can be loaded from any location.
- Our (malicious) model needs to be located in the cache directory in order to be loaded.
- As a result: there’s no legitimate way to load a modified model.
Solution ?
- Find a vulnerability to trick aned to load our malicious (non-signed) model.hwx.
CVE-2022-32845: aned signature check bypass for model.hwx
From the cache directory using kANEFIsInMemoryModelTypeKey
- Get the cache directory location.
- Append csIdentity to that cache directory.
- Append kANEFIsInMemoryModelTypeKey value.
- If found, Append “model.hwx” string to the cache directory.
Directory Traversal in kANEFIsInMemoryModelTypeKey value
- kANEFIsInMemoryModelTypeKey value is not sanitized.
- Load a malformed model.hwx outside of the cache directory by exploiting the path traversal input.
- However the model’s cache directory path must first be created.
- We need to compile a mlmodelc to create the directory.
Proof Of Concept: Load a malformed model.hwx
Step 1: Create a model directory in the cache
- Send foo.mlmodelc as a model directory to aned for loading.
- aned calls ANECompilerService to compile the model under foo.mlmodelc directory.
- ANECompilerService creates foo directory in the cache directory then saves the compiled model.hwx for later use.
Step 2: Craft a traversal path to load malicious model.hwx
- Put the malformed model.hwx in a directory bar.
- Create an option dictionary with {kANEFIsInMemoryModelTypeKey : bar_path }. Where bar_path = ../../../../../../../../../../../../bar
- Call aned to load the model from kANEFIsInMemoryModelTypeKey path: -[_ANEInMemoryModelCacheManager cachedModelPathMatchingHash:csIdentity:] is called to retrieve the cache directory for that model
- +[_ANEStorageHelper memoryMapModelAtPath:isPrecompiled:modelAttributes:] to load malicious_model.hwx from the malicious path with isPrecompiled = False.
Exploitation
Build an arbitrary kernel r/w primitive
The exploit chains 4 vulnerabilities
- CVE-2022-32845: aned signature check bypass for model.hwx.
- CVE-2022-32948: DeCxt::FileIndexToWeight() improper index validation.
- CVE-2022-42805: ANECValidateMutableProcedureInfo() integer overflow.
- CVE-2022-32899: DeCxt::RasterizeScaleBiasData() OOB writes.
The exploitation could've been done with less amount of bugs.
- tfp0 techniques are dead since iOS 14.0
- Overwrite IOSurfaceClient reference in IOSurfaceRootUserClient for arbitrary r/w:
- First public appearance of the technique was in my oob_event kernel exploit for iOS 13.7
- Used to bypass zone_require() by corrupting corpse_task->map with kernel_map to gain tfp0
- Build a fake IOSurface and use external methods for kernel r/w
- John Åkerblom’s Zer0Con 2022 slides for more details
- Apple mitigated the technique in iOS 15.3
IOSurface Security Changes
Apple introduced IOSurfaceRootUserClient::getSurfaceClient()
which does the following:
- Pointer Authenticate IOSurfaceClient object when it’s looked-up via a given surface id.
- IOSurfaceClient->user client reference matches the calling UserClient.
- IOSurface->SurfaceRoot must match gIOSurfaceRoot value.
Strong validation checks for IOSurfaceClient objects:
- PAC Bypass is required to corrupt the array of IOSurfaceClient objects.
- IOSurfaceRootUserClient location is required to forge IOSurfaceClient.
Weak validation checks for IOSurface objects:
- IOSurfaceRoot location is required.
No checks at all for IOSurface->SharedRO/RW pointers.
To achieve kernel r/w, corrupt IOSurfaceClient->IOSurface location with a fake IOSurface. The attacker needs the following:
- A write primitive to overwrite IOSurfaceClient->IOSurface is needed.
- Leak an IOSurfaceClient object location that’s created by the attacker.
- Leak IOSurfaceRoot location to bypass IOSurfaceRootUserClient::getSurfaceClient() last check.
- A (Fake IOSurface) kernel pointer whose content is under the attacker’s control.
- For the write primitive, I used DeCxt::RasterizeScaleBiasData() OOB write to corrupt the target IOSurfaceClient object.
- To achieve this, the mutable kernel section (MUTK) address is required.
- The OOB write can be used as:
- Near Writes: Write into any offset near to MUTK.
- Far Writes: Perform arbitrary write to any kernel memory below `MUTK`.
- For Far Writes we need to locate the exact address of the MUTK buffer.
Leak ‘MUTK’ Section Mapping Address
What is the 'MUTK' from the attacker’s perspective?
- It’s a private IOSurface mapped buffer created by the kernel.
- The buffer is allocated from IOKit Pageable Maps.
- MUTK buffer is mapped in the kernel via H11ANEIn::patchMutableSurface().
- The mapping address is stored in ProgramBuffer object.
- All ‘MUTK’ information stored in H11ANESharedMemorySurfaceParamsStruct.
- Leak ProrgamBuffer->MUTK_Surface object to user-space to retrieve the MUTK buffer address.
What’s a programBuffer from the attacker’s perspective ?
- H11ANEProgramBufferParamsStruct size is 0x53e70.
- kalloc_type()’ed object.
- Big allocations don’t have a dedicated zone.
- Big allocations without zone fall into KHEAP_DEFAULT.
- Big allocations in KHEAP_DEFAULT with size > 0x8000 fall into kernel_map.
- H11ANEProgramBufferParamsStruct is allocated from kernel_map.
H11ANEProgramBufferParamsStruct object structure:
- H11ANESharedMemorySurfaceParamsStruct object holds interesting IOSurface information
- How to leak H11ANESharedMemorySurfaceParamsStruct content?
DeCxt::FileIndexToWeight() lack of array index validation:
- Use DeCxt::FileIndexToWeight() index to fetch programBuffer->MUTK_Surface[0] as a fake mutable weight buffer.
- Make one ProgramBuffer adjacent to ANECMutableWeight array.
- Because the ANECMutableWeight array allocation size is user-controlled, the attacker can direct the allocation to take place in kernel map.
- ANECMutableWeight allocations is a temporary.
- To make ProgramBuffer and ANECMutableWeight near to each other, kernel_map grooming is required.
Grooming kernel_map:
- Load multiple ProgramBuffer objects by creating several programs.
- Allocate an OSArray backing store of size 0x54000 between each 2 programBuffer objects.
- Release all the OSArray objects.
- Allocate ANECMutableWeight array with size of 0x54000.
- Use DeCxt::FileIndexToWeight() lack of array index validation.
- index = (sizeof(ANECMutableWeight[]) + offsetof(programBuffer, MUTK_Surface[0]) / sizeof(ANECMutableWeight).
- index = ( 0x54000 + 0x53E10) / 0x10 = 0x000a7e1.
- The selected weight buffer is passed to DeCxt::ParseTransform() for processing.
- Then DeCxt::RasterizeScaleBiasData() stores the elements to `MUTK` IOSurface buffer.
MUTK buffer cannot be read by user-space:
- `MUTK` IOSurface object is private therefore its content cannot be read.
- Because MUTK cannot be read, the OOB index bug is technically not exploitable.
- To make the OOB index exploitable, combine it with the OOB write vulnerability.
- Use the Near-Write to copy the leaked structure outside of the MUTK buffer.
- The target location to write into must be readable by our process.
- `MUTK` IOSurface buffer is allocated from IOKitPageableMaps.
- Groom IOKitPageableMaps is required.
Grooming IOKitPageableMaps requirements:
- Buffers that can be allocated in IOKitPageableMaps.
- Those buffers can be shared with (or copy data out to) user-space process.
- Shared memory FTW!
- The best option is : IOGPU shared buffers.
- IOGPU is reachable from the default app sandbox.
- Allocate MAX_SHMEMS ( =0x3000) shared memory objects.
- Shared memory object size = 0x4000.
- Map shared memory objects to the kernel via s_submit_command_buffers().
- Use Near Writes to copy H11ANESharedMemorySurfaceParamsStruct to one of our shared buffers
- Write H11ANESharedMemorySurfaceParamsStruct at MUTK_kernel_address - 0x280000
- Scan all the shared buffers to find the scratched one using lookup_scratched_shmem().
- For full implementation see groom_pageable_maps() in the exploit source code.
- If found, one of the shmem buffers holds H11ANESharedMemorySurfaceParamsStruct.
What we have so far
- A kernel address from IOKitPageableMaps (MUTK buffer).
- IOSurface address from IOSurface_zone.
- We need to find an IOSurfaceClient address to perform the arbitrary write.
Leak an IOSurfaceClient object location
Where & How to find an IOSurfaceClient object ?
- Can be found in IOSurfaceRootUserClient which created it.
- Can be found in IOSurface: in a queue that keeps track of IOSurfaceClient’s refs.
- An IOSurface address was leaked already but it doesn’t have an IOSurfaceClient object.
- The goal is to find an IOSurface object that’s owned by the attacker, thus has an IOSurfaceClient.
- Use ANECValidateMutableProcedureInfo() integer overflow to read 1 page from IOSurface_zone.
Read 1 page from IOSurface_zone
- Round down the address of IOSurface to get the page address.
- Leak 1-page of IOSurface_zone to user-space.
- The page must have at least one IOSurface created by us.
- Reading more than one page may result in a kernel panic.
- weightSurface (aka ANECMutableProcedureInfo) address is required to achieve arbitrary read.
- Because weightSurface can be in IOKitPageableMaps, its location can be deduced from the leaked MUTK address.
- 1 page contains ~15 IOSurface objects.
- 1 page may not necessarily have one of our IOSurface objects.
- Spray IOSurface_zone with our IOSurface objects to increase the odds.
- Release some IOSurface objects.
- Allocate a programBuffer so MUTK IOSurface overlaps with one of the freed IOSurface objects.
- IOSurface_zone_page = trunc_page(aneMemSurface.p_IOSurface);
- Scan the whole page to find a matching IOSurfaceID.
Exploit output:
- The second object in the page dump is a potential IOSurface target.
To achieve kernel r/w, corrupt IOSurfaceClient->IOSurface location a fake IOSurface. The attacker needs the following:
- Write primitive to overwrite IOSurfaceClient->IOSurface is needed ✔️
- Leak an IOSurfaceClient object location that’s created by the attacker ✔️
- Leak IOSurfaceRoot location to bypass IOSurfaceRootUserClient::getSurfaceClient() ✔️
- A (Fake IOSurface) kernel pointer whose content is under the attacker’s control
IOGPU Shared Buffers as a fake IOSurface object
For each shared buffer (shm) :
- Use IOSurface::get_use_count() to identify the kernel r/w shm_id.
- If the returned value is 0x41410AAA, it means that 0xAAA is the corresponding shm_id.
WeightBuf Kernel Exploit
- Kernel r/w exploits alone are not enough to fully hack iPhones nowadays.
- WeightBuf demonstrates that despite the challenges posed on by current mitigations, memory corruption bugs can still be exploited.
- WeightBuf works across all devices: macOS, iOS and iPadOS.
- The exploit reliability may differ from one device to another, some exploit tuning is required to increase the reliability for a particular device.
Conclusion
- iOS now is one of the hardest (if not the hardest) targets to hack.
- This is not the end of iOS exploitation, you just need a high quality bugs to pwn it. When there’s a will there’s a way.
- There are many excellent bugs out there just waiting to be found.
- As a security researcher, learn to follow the nudges of your intuition.
- Thanks to Apple SEAR for making the challenge super fun and more interesting than ever.