Describe the bug
Ghidra 9.1.2 fails to import the boot kext cache from macOS 11 beta 3, because its Mach-O file contains a LC_BUILD_VERSION header with ntools set to 0.
To Reproduce
Steps to reproduce the behavior:
- Try to import /System/Library/KernelCollections/BootKernelExtensions.kc from macOS 11 beta 3
Expected behavior
The kernel cache is imported.
Actual behaviour
This error is shown:
Error importing file:
java.io.IOException
at ghidra.app.util.opinion.MachoLoader.load(MachoLoader.java:98)
at ghidra.app.util.opinion.AbstractLibrarySupportLoader.doLoad(AbstractLibrarySupportLoader.java:346)
at ghidra.app.util.opinion.AbstractLibrarySupportLoader.loadProgram(AbstractLibrarySupportLoader.java:83)
at ghidra.app.util.opinion.AbstractProgramLoader.load(AbstractProgramLoader.java:112)
at ghidra.plugin.importer.ImporterUtilities.importSingleFile(ImporterUtilities.java:401)
at ghidra.plugin.importer.ImporterDialog.lambda$okCallback$7(ImporterDialog.java:351)
at ghidra.util.task.TaskLauncher$1.run(TaskLauncher.java:90)
at ghidra.util.task.Task.monitoredRun(Task.java:126)
at ghidra.util.task.TaskRunner.lambda$startTaskThread$1(TaskRunner.java:94)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
at java.base/java.lang.Thread.run(Thread.java:834)
---------------------------------------------------
Build Date: 2020-Feb-12 1149 EST
Ghidra Version: 9.1.2
Java Home: /Library/Java/JavaVirtualMachines/amazon-corretto-11.jdk/Contents/Home
JVM Version: Amazon.com Inc. 11.0.4
OS: Mac OS X 10.15.6 x86_64
After looking for the root cause of this, it appears that this is caused by BuildVersionCommand.toDataType() passing the ntools field from the load command directly to ArrayDataType, which throws if the number of items is 0.
Unlike other MachO executables, the kext collection sets ntool to 0:
Load command 1
cmd LC_BUILD_VERSION
cmdsize 24
platform 0
minos 0.0
sdk n/a
ntools 0
This appears to still be a valid MachO, and indeed Ghidra does handle .o files with ntools=0 just fine; it only fails to handle this case for executables such as the kext collection.
Environment (please complete the following information):
- OS: macOS 10.15.6
- Java Version: 11.0.4
- Ghidra Version: 9.1.2
Appendix: tracking down the root cause
The top level IOException wraps a NullPointerException:
java.lang.NullPointerException
at ghidra.program.model.data.DataUtilities.createData(DataUtilities.java:138)
at ghidra.app.util.opinion.MachoProgramBuilder.markupHeaders(MachoProgramBuilder.java:751)
at ghidra.app.util.opinion.MachoProgramBuilder.build(MachoProgramBuilder.java:133)
at ghidra.app.util.opinion.MachoProgramBuilder.buildProgram(MachoProgramBuilder.java:102)
at ghidra.app.util.opinion.MachoLoader.load(MachoLoader.java:94)
at ghidra.app.util.opinion.AbstractLibrarySupportLoader.doLoad(AbstractLibrarySupportLoader.java:346)
at ghidra.app.util.opinion.AbstractLibrarySupportLoader.loadProgram(AbstractLibrarySupportLoader.java:83)
at ghidra.app.util.opinion.AbstractProgramLoader.load(AbstractProgramLoader.java:112)
at ghidra.plugin.importer.ImporterUtilities.importSingleFile(ImporterUtilities.java:401)
at ghidra.plugin.importer.ImporterDialog.lambda$okCallback$7(ImporterDialog.java:351)
at ghidra.util.task.TaskLauncher$1.run(TaskLauncher.java:90)
at ghidra.util.task.Task.monitoredRun(Task.java:126)
at ghidra.util.task.TaskRunner.lambda$startTaskThread$1(TaskRunner.java:94)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
at java.base/java.lang.Thread.run(Thread.java:834)
This shows that the loadCommandDataType is set to null:
|
newDataType = newDataType.clone(program.getDataTypeManager()); |
|
DataUtilities.createData(program, loadCommandAddr, loadCommandDataType, -1, false, |
>
Breakpoint hit: "thread=Task - Import File", ghidra.app.util.opinion.MachoProgramBuilder.markupHeaders(), line=751 bci=104
Task - Import File[1] locals
Method arguments:
header = instance of ghidra.app.util.bin.format.macho.MachHeader$$EnhancerByCGLIB$$f0aa4076(id=9950)
headerAddr = instance of ghidra.program.model.address.GenericAddress(id=9951)
Local variables:
loadCommand = instance of ghidra.app.util.bin.format.macho.commands.BuildVersionCommand$$EnhancerByCGLIB$$b005e22d(id=9955)
loadCommandAddr = instance of ghidra.program.model.address.GenericAddress(id=9956)
loadCommandDataType = null
Task - Import File[1] print loadCommand.toDataType()
loadCommand.toDataType() = null
loadCommand.toDataType() is returning null because the ContinuesInterceptor caught an IllegalArgumentException:
Step completed: "thread=Task - Import File", generic.continues.ContinuesInterceptor.intercept(), line=42 bci=38
Task - Import File[1] locals
Method arguments:
obj = instance of ghidra.app.util.bin.format.macho.commands.BuildVersionCommand$$EnhancerByCGLIB$$b005e22d(id=9955)
method = instance of java.lang.reflect.Method(id=9976)
args = instance of java.lang.Object[0] (id=9974)
proxy = instance of net.sf.cglib.proxy.MethodProxy(id=9977)
Local variables:
retValFromSuper = null
e = instance of java.lang.IllegalArgumentException(id=9978)
Task - Import File[1] print e
Internal exception during operation:
Unexpected JDWP Error: 502
The original IllegalArgumentException is:
Exception occurred: java.lang.IllegalArgumentException (to be caught at: ghidra.app.util.bin.format.macho.commands.BuildVersionCommand$$EnhancerByCGLIB$$b005e22d$$FastClassByCGLIB$$2ec3d351.invoke(), line=-1 bci=774)"thread=Task - Import File", ghidra.program.model.data.ArrayDataType.<init>(), line=64 bci=85
|
throw new IllegalArgumentException( |
shows that ArrayDataType throws the IllegalArgumentException if the number of items is <= 0
|
struct.add(new ArrayDataType(buildToolVersionDataType, ntools, |
shows that ArrayDataType would be called based on
ntools from the header
Describe the bug
Ghidra 9.1.2 fails to import the boot kext cache from macOS 11 beta 3, because its Mach-O file contains a LC_BUILD_VERSION header with ntools set to 0.
To Reproduce
Steps to reproduce the behavior:
Expected behavior
The kernel cache is imported.
Actual behaviour
This error is shown:
After looking for the root cause of this, it appears that this is caused by BuildVersionCommand.toDataType() passing the
ntoolsfield from the load command directly to ArrayDataType, which throws if the number of items is 0.Unlike other MachO executables, the kext collection sets ntool to 0:
This appears to still be a valid MachO, and indeed Ghidra does handle
.ofiles with ntools=0 just fine; it only fails to handle this case for executables such as the kext collection.Environment (please complete the following information):
Appendix: tracking down the root cause
The top level IOException wraps a NullPointerException:
This shows that the loadCommandDataType is set to null:
ghidra/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/DataUtilities.java
Line 138 in 687ce7f
ghidra/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/MachoProgramBuilder.java
Line 751 in 687ce7f
loadCommand.toDataType()is returning null because the ContinuesInterceptor caught an IllegalArgumentException:The original IllegalArgumentException is:
ghidra/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/ArrayDataType.java
Line 64 in 687ce7f
shows that ArrayDataType throws the IllegalArgumentException if the number of items is <= 0
ghidra/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/macho/commands/BuildVersionCommand.java
Line 83 in 49c2010
ntoolsfrom the header