From 3e3de2e3e3d702f110a429b67e660e7269d7f36c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antonio=20V=C3=A1zquez=20Blanco?= Date: Wed, 2 Oct 2024 09:45:33 +0200 Subject: [PATCH] Add an analyzer to detect DTB signatures in memory. --- .../DeviceTreeBlobAnalyzer.java | 127 ++++++++++++++++++ .../devicetreeblob/DeviceTreeBlobPlugin.java | 12 +- src/main/java/devicetreeblob/DtbLoadTask.java | 48 +++---- .../java/devicetreeblob/parser/DtbParser.java | 6 + 4 files changed, 160 insertions(+), 33 deletions(-) create mode 100644 src/main/java/devicetreeblob/DeviceTreeBlobAnalyzer.java diff --git a/src/main/java/devicetreeblob/DeviceTreeBlobAnalyzer.java b/src/main/java/devicetreeblob/DeviceTreeBlobAnalyzer.java new file mode 100644 index 0000000..f1c7b44 --- /dev/null +++ b/src/main/java/devicetreeblob/DeviceTreeBlobAnalyzer.java @@ -0,0 +1,127 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package devicetreeblob; + +import java.text.ParseException; + +import javax.swing.SwingConstants; + +import devicetreeblob.parser.Dtb; +import devicetreeblob.parser.DtbParser; +import ghidra.app.services.AbstractAnalyzer; +import ghidra.app.services.AnalyzerType; +import ghidra.app.util.importer.MessageLog; +import ghidra.program.model.address.Address; +import ghidra.program.model.address.AddressSetView; +import ghidra.program.model.listing.Program; +import ghidra.program.model.mem.MemoryAccessException; +import ghidra.util.Msg; +import ghidra.util.exception.CancelledException; +import ghidra.util.task.TaskBuilder; +import ghidra.util.task.TaskLauncher; +import ghidra.util.task.TaskMonitor; +import io.kaitai.struct.ByteBufferKaitaiStream; +import io.kaitai.struct.KaitaiStream.ValidationGreaterThanError; + +public class DeviceTreeBlobAnalyzer extends AbstractAnalyzer { + private static byte[] DTB_SIGNATURE = new byte[] { -48, 13, -2, -19 }; + + public DeviceTreeBlobAnalyzer() { + super("Device Tree Blob Analyzer", "Find Device Tree signatures and import memory map information from them.", + AnalyzerType.BYTE_ANALYZER); + } + + @Override + public boolean getDefaultEnablement(Program program) { + return true; + } + + @Override + public boolean canAnalyze(Program program) { + return true; + } + + @Override + public boolean added(Program program, AddressSetView set, TaskMonitor monitor, MessageLog log) + throws CancelledException { + monitor.setMessage("Looking for DTB signatures..."); + Address search_from = set.getMinAddress(); + while (search_from != null) { + monitor.checkCancelled(); + Address found_addr = program.getMemory().findBytes(search_from, DTB_SIGNATURE, null, true, monitor); + if (found_addr != null) { + monitor.setMessage(String.format("Checking DTB signatures in %s...", found_addr.toString())); + try { + parseDtb(program, found_addr); + } catch (MemoryAccessException | ParseException e) { + Msg.error(getClass(), "Could not parse DTB file!", e); + } + search_from = found_addr.next(); + } else { + search_from = null; + } + } + return true; + } + + private void parseDtb(Program program, Address addr) throws MemoryAccessException, ParseException { + int len = parseDtbLength(program, addr); + + byte[] bytes = new byte[len]; + int read = program.getMemory().getBytes(addr, bytes); + if (read < len) + throw new ParseException("Not enough bytes to parse DTB.", read); + + DtbParser parser = new DtbParser(bytes); + + DtbLoadTask loadTask = new DtbLoadTask(program, parser); + TaskBuilder.withTask(loadTask).setStatusTextAlignment(SwingConstants.LEADING).setLaunchDelay(0); + + new TaskLauncher(loadTask); + + // TODO Create a DTB byte array in memory + } + + private int parseDtbLength(Program program, Address addr) throws MemoryAccessException, ParseException { + byte[] header = new byte[40]; + int read = program.getMemory().getBytes(addr, header); + if (read < 40) + throw new ParseException("Not enough bytes to parse DTB header.", read); + Dtb dtb = null; + try { + dtb = new Dtb(new ByteBufferKaitaiStream(header)); + } catch (ValidationGreaterThanError e) { + throw new ParseException("Wrong DTB format.", 0); + } + long len = dtb.totalSize(); + if (len < 40) + // Full DTB cannot be shorter than its header... + throw new ParseException("Wrong DTB length signature.", 0); + if (dtb.ofsStructureBlock() < 40) + throw new ParseException("Offset of structure block cannot be lower than header size.", 0); + if ((dtb.ofsStructureBlock() & 0x3) != 0) + throw new ParseException("Offset of structure must be aligned to a 4 byte boundary.", 0); + if (dtb.lenStructureBlock() <= 0) + throw new ParseException("Len of structure block cannot be zero or negative.", 0); + if (dtb.ofsStringsBlock() < 40) + throw new ParseException("Offset of strings block cannot be lower than header size.", 0); + if (dtb.lenStringsBlock() <= 0) + throw new ParseException("Len of strings block cannot be zero or negative.", 0); + if (dtb.version() > 30) + throw new ParseException("Invalid FDT version.", 0); + return (int) len; + } +} diff --git a/src/main/java/devicetreeblob/DeviceTreeBlobPlugin.java b/src/main/java/devicetreeblob/DeviceTreeBlobPlugin.java index 4a8da3c..bc6660a 100644 --- a/src/main/java/devicetreeblob/DeviceTreeBlobPlugin.java +++ b/src/main/java/devicetreeblob/DeviceTreeBlobPlugin.java @@ -16,9 +16,12 @@ package devicetreeblob; import java.io.File; +import java.io.IOException; +import java.text.ParseException; import javax.swing.SwingConstants; +import devicetreeblob.parser.DtbParser; import docking.action.builder.ActionBuilder; import docking.tool.ToolConstants; import ghidra.app.CorePluginPackage; @@ -73,7 +76,14 @@ private void loadDtb(ProgramActionContext pac) { return; } - DtbLoadTask loadTask = new DtbLoadTask(program, file); + DtbParser parser; + try { + parser = new DtbParser(file); + } catch (IOException | ParseException e) { + Msg.showError(getClass(), null, "Load PDB", "Unable to load PDB file while analysis is running.", e); + return; + } + DtbLoadTask loadTask = new DtbLoadTask(program, parser); TaskBuilder.withTask(loadTask).setStatusTextAlignment(SwingConstants.LEADING).setLaunchDelay(0); new TaskLauncher(loadTask); diff --git a/src/main/java/devicetreeblob/DtbLoadTask.java b/src/main/java/devicetreeblob/DtbLoadTask.java index ab62929..3fd5397 100644 --- a/src/main/java/devicetreeblob/DtbLoadTask.java +++ b/src/main/java/devicetreeblob/DtbLoadTask.java @@ -15,9 +15,6 @@ */ package devicetreeblob; -import java.io.File; -import java.io.IOException; -import java.text.ParseException; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -52,15 +49,15 @@ import ghidra.util.task.TaskMonitor; public class DtbLoadTask extends Task { - private File mFile; + private DtbParser mDtbParser; private Program mProgram; private Memory mMemory; private SymbolTable mSymTable; private AddressSpace mAddrSpace; - public DtbLoadTask(Program program, File file) { + public DtbLoadTask(Program program, DtbParser dtbparser) { super("Load DTB", true, false, true, true); - mFile = file; + mDtbParser = dtbparser; mProgram = program; mMemory = program.getMemory(); mSymTable = program.getSymbolTable(); @@ -69,20 +66,12 @@ public DtbLoadTask(Program program, File file) { @Override public void run(TaskMonitor monitor) throws CancelledException { - monitor.setMessage("Loading " + mFile.getPath() + "..."); + monitor.setMessage("Loading DTB..."); monitor.checkCancelled(); - DtbParser dtb; - try { - dtb = new DtbParser(mFile); - } catch (IOException | ParseException e) { - Msg.error(getClass(), "Could not parse DTB file!", e); - return; - } - monitor.setMessage("Filtering unwanted DTB blocks..."); monitor.checkCancelled(); - List memBlocks = filterUnwantedBlocks(dtb.getBlocks()); + List memBlocks = filterUnwantedBlocks(mDtbParser.getBlocks()); monitor.setMessage("Creating candidate blocks from DTB file..."); monitor.checkCancelled(); @@ -168,8 +157,8 @@ private void createMemoryBlock(BlockInfo blockInfo) { int transactionId = mProgram.startTransaction("DTB memory block creation"); boolean ok = false; try { - MemoryBlock memBlock = mMemory.createUninitializedBlock(blockInfo.name, addr, blockInfo.block.getSize().longValue(), - false); + MemoryBlock memBlock = mMemory.createUninitializedBlock(blockInfo.name, addr, + blockInfo.block.getSize().longValue(), false); memBlock.setRead(blockInfo.isReadable); memBlock.setWrite(blockInfo.isWritable); memBlock.setExecute(blockInfo.isExecutable); @@ -193,12 +182,11 @@ private void createMemoryBlock(BlockInfo blockInfo) { } private void updateMatchingMemoryBlock(MemoryBlock collidingMemoryBlock, BlockInfo blockInfo) { - if (!collidingMemoryBlock.getName().equals(blockInfo.name) - && OptionDialog.showYesNoDialog(null, "Load DTB", - "An existing memory block with name \"" + collidingMemoryBlock.getName() - + "\" is in the same region as the \"" + blockInfo.name - + "\" peripheral. Do you want to rename it to \"" + blockInfo.name - + "\"?") == OptionDialog.OPTION_ONE) { + if (!collidingMemoryBlock.getName().equals(blockInfo.name) && OptionDialog.showYesNoDialog(null, "Load DTB", + "An existing memory block with name \"" + collidingMemoryBlock.getName() + + "\" is in the same region as the \"" + blockInfo.name + + "\" peripheral. Do you want to rename it to \"" + blockInfo.name + + "\"?") == OptionDialog.OPTION_ONE) { int transactionId = mProgram.startTransaction("DTB memory block rename"); boolean ok = false; try { @@ -210,8 +198,7 @@ private void updateMatchingMemoryBlock(MemoryBlock collidingMemoryBlock, BlockIn } mProgram.endTransaction(transactionId, ok); } - if (collidingMemoryBlock.isRead() != blockInfo.isReadable && OptionDialog.showYesNoDialog(null, - "Load DTB", + if (collidingMemoryBlock.isRead() != blockInfo.isReadable && OptionDialog.showYesNoDialog(null, "Load DTB", "Memory block \"" + collidingMemoryBlock.getName() + "\" is marked as" + ((!collidingMemoryBlock.isRead()) ? " non" : "") + " readable. The DTB file suggests it should be" @@ -230,8 +217,7 @@ private void updateMatchingMemoryBlock(MemoryBlock collidingMemoryBlock, BlockIn mProgram.endTransaction(transactionId, ok); } - if (collidingMemoryBlock.isWrite() != blockInfo.isWritable && OptionDialog.showYesNoDialog(null, - "Load DTB", + if (collidingMemoryBlock.isWrite() != blockInfo.isWritable && OptionDialog.showYesNoDialog(null, "Load DTB", "Memory block \"" + collidingMemoryBlock.getName() + "\" is marked as" + ((!collidingMemoryBlock.isWrite()) ? " non" : "") + " writable. The DTB file suggests it should be" @@ -250,8 +236,7 @@ private void updateMatchingMemoryBlock(MemoryBlock collidingMemoryBlock, BlockIn mProgram.endTransaction(transactionId, ok); } - if (collidingMemoryBlock.isExecute() != blockInfo.isExecutable && OptionDialog.showYesNoDialog(null, - "Load DTB", + if (collidingMemoryBlock.isExecute() != blockInfo.isExecutable && OptionDialog.showYesNoDialog(null, "Load DTB", "Memory block \"" + collidingMemoryBlock.getName() + "\" is marked as" + ((!collidingMemoryBlock.isExecute()) ? " non" : "") + " executable. The DTB file suggests it should be" @@ -271,8 +256,7 @@ private void updateMatchingMemoryBlock(MemoryBlock collidingMemoryBlock, BlockIn mProgram.endTransaction(transactionId, ok); } - if (collidingMemoryBlock.isVolatile() != blockInfo.isVolatile && OptionDialog.showYesNoDialog(null, - "Load DTB", + if (collidingMemoryBlock.isVolatile() != blockInfo.isVolatile && OptionDialog.showYesNoDialog(null, "Load DTB", "Memory block \"" + collidingMemoryBlock.getName() + "\" is marked as" + ((!collidingMemoryBlock.isVolatile()) ? " non" : "") + " volatile. The DTB file suggests it should be" diff --git a/src/main/java/devicetreeblob/parser/DtbParser.java b/src/main/java/devicetreeblob/parser/DtbParser.java index 3780d11..dad1ebb 100644 --- a/src/main/java/devicetreeblob/parser/DtbParser.java +++ b/src/main/java/devicetreeblob/parser/DtbParser.java @@ -25,11 +25,17 @@ import devicetreeblob.parser.Dtb.FdtBlock; import devicetreeblob.parser.Dtb.FdtNode; import devicetreeblob.parser.Dtb.FdtProp; +import io.kaitai.struct.ByteBufferKaitaiStream; public class DtbParser { private ArrayList mBlocks; + public DtbParser(byte[] bytes) throws ParseException { + mBlocks = new ArrayList(); + parseBlocks(new Dtb(new ByteBufferKaitaiStream(bytes))); + } + public DtbParser(File file) throws IOException, ParseException { mBlocks = new ArrayList(); parseBlocks(Dtb.fromFile(file.getAbsolutePath()));