Introduction
In this blog, we will be analyzing CVE-2024-23265, a kernel-level memory corruption vulnerability in iOS. This learning exercise will help us understand the process of IPSW diffing and identify where the fix likely patched the vulnerability.
CVE-2024-23265 is a kernel-level memory corruption vulnerability in iOS, patched in iOS 17.4. It was addressed by improving locking mechanisms to prevent race conditions that could lead to unexpected system termination or unauthorized kernel memory writes.
– Vulnerable Version: iOS 17.3.1 (Build 21D61)
– Patched Version: iOS 17.4 (Build 21E219)
We will explore how the underlying issue was addressed at the kernel level and how to analyze such fixes using practical diffing techniques.
Kernelcache's KEXTs Extraction and Diffing with IPSW
Continuing from the IPSW Walkthrough series published on the 8ksec blog, we will be using IPSW tool.
To begin, download the relevant IPSW files directly via the terminal using the following commands:
ipsw download ipsw --device iPhone15,4 --build 21D61
ipsw download ipsw --device iPhone15,4 --build 21E219
These correspond to the vulnerable (iOS 17.3.1) and patched (iOS 17.4) versions associated with CVE-2024-23265.
Now, to extract the kernelcache from the downloaded IPSW files, use the following commands:
ipsw extract --kernel iPhone15,4_17.3.1_21D61_Restore.ipsw
ipsw extract --kernel iPhone15,4_17.4_21E219_Restore.ipsw
Once extracted, we can inspect the kernel versions to confirm differences across builds:
ipsw kernel version 21D61__iPhone15,4/kernelcache.release.iPhone15,4 --json | jq .
{
"kernel": {
"darwin": "23.3.0",
"date": "2023-12-20T17:32:11Z",
"xnu": "10002.82.4~3",
"type": "RELEASE",
"arch": "ARM64",
"cpu": "T8120"
},
"llvm": {}
}
ipsw kernel version 21E219__iPhone15,4/kernelcache.release.iPhone15,4 --json | jq .
{
"kernel": {
"darwin": "23.4.0",
"date": "2024-02-16T20:44:20Z",
"xnu": "10063.102.14~10",
"type": "RELEASE",
"arch": "ARM64",
"cpu": "T8120"
},
"llvm": {}
}
We could also extract all KEXTs from both kernelcaches for detailed inspection using:
ipsw kernel extract kernelcache.release.iPhone15,4 --all --output KEXTs_21D61
ipsw kernel extract kernelcache.release.iPhone15,4 --all --output KEXTs_21E219
However, for the purpose of diffing, extracting every KEXT is unnecessary. A quick comparison between the two kernelcache versions can be done directly with:
ipsw kernel kexts --diff 21D61_21D61__iPhone15,4/kernelcache.release.iPhone15,4 21E219__iPhone15,4/kernelcache.release.iPhone15,4
com.apple.AGXFirmwareKextG15PRTBuddy (1)
-com.apple.AGXFirmwareKextRTBuddy64 (276.21)
-com.apple.AGXG15P (276.21)
+com.apple.AGXFirmwareKextRTBuddy64 (280.3.9)
+com.apple.AGXG15P (280.3.9)
com.apple.AUC (1.0)
com.apple.AppleFSCompression.AppleFSCompressionTypeZlib (1.0.0)
com.apple.IOTextEncryptionFamily (1.0.0)
com.apple.driver.AOPTouchKext (312)
-com.apple.driver.ASIOKit (11.61)
+com.apple.driver.ASIOKit (11.77)
…………
At this point, we could inspect those KEXTs that exhibit significant changes. For example, past vulnerabilities have been found in disk image handling, making com.apple.driver.AppleDiskImages2 a solid candidate for closer examination:
-com.apple.driver.AppleDiskImages2 (273)
+com.apple.driver.AppleDiskImages2 (276.100.16)
To extract this KEXT from both versions for detailed inspection:
ipsw kernel extract 21D61__iPhone15,4/kernelcache.release.iPhone15,4 com.apple.driver.AppleDiskImages2 --output AppleDiskImages2-21D61
ipsw kernel extract 21E219__iPhone15,4/kernelcache.release.iPhone15,4 com.apple.driver.AppleDiskImages2 --output AppleDiskImages2-21E219
Kernel Symbolication on Ghidra and Decompiled Code Dump
At this stage, we can proceed with static analysis. For this example, we will use Ghidra. The first step is to symbolicate the kernel using the symbolicator tool from blacktop.
Begin by cloning the repository: https://github.com/blacktop/symbolicator/
Then, run the following command against both kernelcaches:
ipsw kernel sym kernelcache.release.iPhone15,4 --json --signatures /symbolicator/kernel


Next, save the following script into Ghidra’s scripts directory: Symbolicate.java
Now create a Ghidra project, load the target KEXT, and symbolicate it via:
Script Manager > Symbolicate.java > Load the previously generated .json file

Repeat this process for both kernelcache projects. With both symbolicated, we can proceed to diffing.
Although tools like the BinExport Ghidra plugin and BinDiffHelper extension enable a full BinDiff analysis pipeline, and AI-based assembly reconstruction is an emerging option, this example takes a faster and more lightweight approach: exporting decompiled code from Ghidra and diffing the outputs using tools like Meld.
Use the following Ghidra script to dump all decompiled functions:
#@category Export
#@keybinding
#@menupath
#@toolbar
from ghidra.app.decompiler import DecompInterface
from ghidra.util.task import ConsoleTaskMonitor
import os
# Change to wherever you want the output written
output_file = ""
decomp = DecompInterface()
decomp.openProgram(currentProgram)
fm = currentProgram.getFunctionManager()
functions = fm.getFunctions(True)
with open(output_file, "w") as out:
out.write("// Decompiled functions from: {}\n\n".format(currentProgram.getName()))
for function in functions:
results = decomp.decompileFunction(function, 60, ConsoleTaskMonitor())
if results and results.decompileCompleted():
sig = function.getName()
body = results.getDecompiledFunction().getC()
out.write("// Function: {} at {}\n".format(sig, function.getEntryPoint()))
out.write(body)
out.write("\n\n")
else:
out.write("// Failed to decompile function: {} at {}\n\n".format(function.getName(), function.getEntryPoint()))
Now, to dump decompiled code directly via terminal, we can use Ghidra’s analyzeHeadless:
analyzeHeadless -process com.apple.driver.AppleDiskImages2 -scriptPath ~/ghidra_scripts -postScript DumpAllToSingleFile.py –noanalysis
After generating code dumps for both projects, we can start diffing them.
Identifying the Patch via Decompiled Diffing
Now we are ready for the diffing:
meld version1.c version2.c
After some inspection, meaningful changes appear starting around line ~8170:


Although symbolication was applied, many internal symbols remain unresolved. This specific function appears to implement pointer bounds checks and page table-like traversal logic in `AppleDiskImages2.kext`, but its exact name and role are not present in the available symbols.
Key differences include a newly introduced guard clause. In iOS 17.3.1:
if (lVar4 != 0) {
return lVar4;
}
In iOS 17.4:
if ((lVar4 != 0) && (lVar4 != -1)) {
return lVar4;
}
// ......
lVar5 = 0;
if (lVar4 != -1) {
lVar5 = lVar4;
}
This adds an explicit check against -1 (0xffffffffffffffff), preventing the return of a potentially poisoned or invalid pointer. This patch mitigates unvalidated pointer returns, addressing common race condition patterns in tightly packed pointer structures or allocation maps.
Another change we can see affects the loop’s termination logic:
if (uVar8 == *(uint *)(lVar5 + 0xc)) {
lVar5 = 0;
}
Instead of directly returning lVar4, the updated logic ensures:
– A null return on failure (clean fail)
– Only valid, non-`-1` results are propagated
This reinforces bounds checking and memory integrity validation.
Likely Vulnerable Conditions in iOS 17.3.1
At this point, the likely Vulnerable Conditions in 17.3.1 could be the following:
1. *(long *)(uVar3 + (uVar1 & 0xfffffff8)) may return -1:
– Not validated
– Returned directly to the caller
2. If backing data is modified concurrently:
– A stale -1 value can be returned
– Race with deallocation leads to UAF or invalid pointer exposure
3. User-controlled indexing (`param_1[1]`) drives the loop
This points to a potential race condition, allowing user-controlled conditions to bypass safety checks and return malicious or corrupted memory references. If this function is invoked via an IOUserClient::externalMethod path or equivalent, a malicious actor could:
– Race updates to the backing memory structure
– Force return of -1
– Trigger kernel memory corruption or unauthorized pointer access
These findings align closely with the CVE-2024-23265 advisory:
“Improved locking prevents unauthorized kernel memory writes or unexpected system termination.“
Returning -1 as a pointer could trigger both failure modes.
Tracing the Impact
Looking at the vulnerable function in iOS 17.3.1 inside Ghidra, we inspect the references to its usage:

One notable reference leads to __ZN20AppleDiskImageDevice15boostAnyRequestEh:
undefined8 __ZN20AppleDiskImageDevice15boostAnyRequestEh(long param_1, uint param_2)
This function, AppleDiskImageDevice::boostAnyRequest(uint8_t level):
– Directly calls the vulnerable function 9 times
– Iterates over param_1 + 0xf8 using param_1[1] as the loop index (likely user-influenced)
– Applies logic involving QoS flags and kernel-resident memory buffers
– Likely manipulating some sort of I/O queue or in-flight buffer list
– Has no bounds check for dereferenced values (prior to the fix)
Another reference hits:
void FUN_fffffff028a13b98(long param_1, long param_2)
This function: – Also calls the vulnerable function 3 times – Fills a structure at param_2 + 0x780
– Suggests gathering metadata or enumerating extents from a backing structure – Looks like a structured walk of disk image mappings, writing them into userland memory
Its core loop is:
while ((local_48 != local_60 || local_40 != local_58) && (*(uint *)(param_2 + 0x780) < 0x30))
Tracing Userland Reachability
To determine whether userland can reach boostAnyRequest() or FUN_fffffff028a13b98, we inspect the presence of externalMethod references or IOExternalMethodDispatch tables. These define how IOUserClient exposes method selectors to userland. If either function is referenced in these tables or in switch statements tied to selector indices, it confirms syscall reachability via IOConnectCallMethod.
Failing static references, we look for string evidence:

The string IOReturn AppleDiskImageDevice::boostAnyRequest(uint8_t) confirms that boostAnyRequest is a formally declared method with a visible symbol, not inlined or anonymous. While this doesn’t prove it’s exposed via IOExternalMethodDispatch, its naming and structure are consistent with methods that are dispatched through externalMethod in IOUserClient subclasses.
Cross-referencing boostAnyRequest, we encounter the following function:

This function calls boostAnyRequest(…) if FUN_fffffff028a0c98c(…) == 0.
It operates on an object field from param_1 + 0xf8, likely referencing the AppleDiskImages2UserClient or a related structure.
Then it calls DIDeviceRequestPool::GetRequest(…) which interestingly fits the naming seen when looking for externalMethod in the symbol tree: e.g. DIDeviceCreatorUserClient::externalMethod(…):

This tells us that the call to boostAnyRequest is not standalone. Instead, it’s:
– Likely a subroutine in a userclient or its helper class
– Called as part of a request path, likely from externalMethod of a DIDevice*UserClient subclass
– Implies that calling some selector will lead to GetRequest(…), which in turn may call boostAnyRequest(…) if a condition is met
There are also 4 xrefs to `__ZN20AppleDiskImageDevice10GetRequestEmhPFbPvES0_`:
– FUN_fffffff028a0f990
– AppleDiskImageDevice::doEjectMediaEv
– FUN_fffffff028a10444
– FUN_fffffff028a107e4
So, all call:
AppleDiskImageDevice::GetRequest(…)
→ which conditionally calls:
AppleDiskImageDevice::boostAnyRequest(…)
→ which contains the vulnerable call to:
FUN_fffffff028a156c8
These functions don’t appear in standard dispatch tables, meaning the KEXT likely uses a manual dispatch inside externalMethod(…), such as a switch on the selector or a custom function pointer array. This makes static analysis harder and forces runtime probing to find valid selectors.
Dispatch is likely done via a custom switch (`param_2`) or indirect table, with each selector value mapping to one of these functions.
Following that point, we attempted to identify the selector mapping by scanning the binary for scalar references to the vulnerable function addresses, but found no matches, confirming that the dispatch is dynamic and not table-driven.

Conclusion
By diffing the AppleDiskImages2 KEXT between iOS 17.3.1 and 17.4, we identified a kernel-level memory corruption vulnerability (CVE-2024-23265), caused by a missing check for the sentinel value -1 in a loop dereferencing kernel pointers.
The vulnerable function is called repeatedly in AppleDiskImageDevice::boostAnyRequest(uint8_t), likely exposed via a user client method. Apple patched this by adding locking and -1 guards, indicating a classic race condition where a pointer table or extent list may be modified during iteration, leading to an invalid dereference.
To exploit this, one would first identify the appropriate IOService via ioreg, then fuzz or brute-force selector values with IOConnectCallMethod to trigger the vulnerable code path (Reference), after that, supply crafted input to manipulate internal loop state or memory references, then use concurrency to induce a race condition in the backing structure, and finally observe kernel behavior for evidence of memory corruption, invalid pointer dereference, or system instability (Check 8ksec Kernel Panic Analysis).
About the Author
Sergio Lopez (s3rg0x)
Sergio Lopez (LinkedIn) is a Red Teamer and Security Researcher, with experience across AI security, kernel vulnerability analysis, shellcoding, C2 framework design, and evasive malware development.
He has presented at Black Hat and several other leading security conferences, and holds multiple industry certifications including OSEP, OSWE, GREM, CRTO, and eCPPT.
Sergio is passionate about advancing the security community and is always eager to contribute to research and knowledge sharing.