Skip to content

Commit

Permalink
Add an analyzer to detect DTB signatures in memory.
Browse files Browse the repository at this point in the history
  • Loading branch information
antoniovazquezblanco committed Oct 2, 2024
1 parent 606b513 commit 3e3de2e
Show file tree
Hide file tree
Showing 4 changed files with 160 additions and 33 deletions.
127 changes: 127 additions & 0 deletions src/main/java/devicetreeblob/DeviceTreeBlobAnalyzer.java
Original file line number Diff line number Diff line change
@@ -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;
}
}
12 changes: 11 additions & 1 deletion src/main/java/devicetreeblob/DeviceTreeBlobPlugin.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);

Expand Down
48 changes: 16 additions & 32 deletions src/main/java/devicetreeblob/DtbLoadTask.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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();
Expand All @@ -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<DtbBlock> memBlocks = filterUnwantedBlocks(dtb.getBlocks());
List<DtbBlock> memBlocks = filterUnwantedBlocks(mDtbParser.getBlocks());

monitor.setMessage("Creating candidate blocks from DTB file...");
monitor.checkCancelled();
Expand Down Expand Up @@ -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);
Expand All @@ -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 {
Expand All @@ -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"
Expand All @@ -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"
Expand All @@ -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"
Expand All @@ -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"
Expand Down
6 changes: 6 additions & 0 deletions src/main/java/devicetreeblob/parser/DtbParser.java
Original file line number Diff line number Diff line change
Expand Up @@ -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<DtbBlock> mBlocks;

public DtbParser(byte[] bytes) throws ParseException {
mBlocks = new ArrayList<DtbBlock>();
parseBlocks(new Dtb(new ByteBufferKaitaiStream(bytes)));
}

public DtbParser(File file) throws IOException, ParseException {
mBlocks = new ArrayList<DtbBlock>();
parseBlocks(Dtb.fromFile(file.getAbsolutePath()));
Expand Down

0 comments on commit 3e3de2e

Please sign in to comment.