// Export function metadata, references, defined data, symbols, and unexplained // executable bytes for an auditable TDKPIN reconstruction ledger. // @category TDKPIN import java.io.BufferedWriter; import java.io.FileWriter; import java.io.PrintWriter; import ghidra.app.script.GhidraScript; import ghidra.program.model.address.Address; import ghidra.program.model.address.AddressRange; import ghidra.program.model.address.AddressSet; import ghidra.program.model.data.DataType; import ghidra.program.model.listing.Data; import ghidra.program.model.listing.Function; import ghidra.program.model.listing.FunctionIterator; import ghidra.program.model.listing.Instruction; import ghidra.program.model.listing.InstructionIterator; import ghidra.program.model.mem.MemoryBlock; import ghidra.program.model.symbol.Reference; import ghidra.program.model.symbol.ReferenceIterator; import ghidra.program.model.symbol.Symbol; import ghidra.program.model.symbol.SymbolIterator; public class ExportProgramEvidence extends GhidraScript { @Override public void run() throws Exception { String[] args = getScriptArgs(); if (args.length != 5) { throw new IllegalArgumentException( "usage: ExportProgramEvidence.java FUNCTIONS REFERENCES DATA SYMBOLS UNEXPLAINED"); } exportFunctions(args[0]); exportReferences(args[1]); exportDefinedData(args[2]); exportSymbols(args[3]); exportUnexplainedExecutableBytes(args[4]); } private void exportFunctions(String path) throws Exception { try (PrintWriter out = writer(path)) { out.println("address\tname\tnamespace\tblock\tbody_bytes\tentry_kind\tcalling_convention" + "\tstack_purge_bytes\treturn_type\tparameter_count\tvarargs\tthunk"); FunctionIterator functions = currentProgram.getFunctionManager().getFunctions(true); while (functions.hasNext() && !monitor.isCancelled()) { Function function = functions.next(); Address address = function.getEntryPoint(); MemoryBlock block = currentProgram.getMemory().getBlock(address); boolean imported = function.isExternal() || block == null || !block.isInitialized() || "EXTERNAL".equals(block.getName()); String kind = imported ? "external" : "internal"; DataType returnType = function.getReturnType(); out.printf("%s\t%s\t%s\t%s\t%d\t%s\t%s\t%d\t%s\t%d\t%s\t%s%n", address, clean(function.getName()), clean(function.getParentNamespace().getName()), block == null ? "" : clean(block.getName()), function.getBody().getNumAddresses(), kind, clean(function.getCallingConventionName()), function.getStackPurgeSize(), returnType == null ? "" : clean(returnType.getDisplayName()), function.getParameterCount(), function.hasVarArgs(), function.isThunk()); } } } private void exportReferences(String path) throws Exception { try (PrintWriter out = writer(path)) { out.println("from_address\tfrom_function\treference_type\toperand_index\tprimary" + "\tto_address\tto_block\tto_function\tto_symbol"); ReferenceIterator references = currentProgram.getReferenceManager() .getReferenceIterator(currentProgram.getMinAddress()); while (references.hasNext() && !monitor.isCancelled()) { Reference reference = references.next(); Address from = reference.getFromAddress(); Address to = reference.getToAddress(); Function fromFunction = currentProgram.getFunctionManager().getFunctionContaining(from); Function toFunction = currentProgram.getFunctionManager().getFunctionAt(to); MemoryBlock toBlock = currentProgram.getMemory().getBlock(to); Symbol toSymbol = currentProgram.getSymbolTable().getPrimarySymbol(to); out.printf("%s\t%s\t%s\t%d\t%s\t%s\t%s\t%s\t%s%n", from, fromFunction == null ? "" : clean(fromFunction.getName()), clean(reference.getReferenceType().toString()), reference.getOperandIndex(), reference.isPrimary(), to, toBlock == null ? "" : clean(toBlock.getName()), toFunction == null ? "" : clean(toFunction.getName()), toSymbol == null ? "-" : clean(toSymbol.getName())); } } } private void exportDefinedData(String path) throws Exception { try (PrintWriter out = writer(path)) { out.println("address\tblock\tlength\tdata_type\tlabel\tvalue"); var dataIterator = currentProgram.getListing().getDefinedData(true); while (dataIterator.hasNext() && !monitor.isCancelled()) { Data data = dataIterator.next(); MemoryBlock block = currentProgram.getMemory().getBlock(data.getAddress()); Symbol symbol = currentProgram.getSymbolTable().getPrimarySymbol(data.getAddress()); out.printf("%s\t%s\t%d\t%s\t%s\t%s%n", data.getAddress(), block == null ? "" : clean(block.getName()), data.getLength(), clean(data.getDataType().getDisplayName()), symbol == null ? "" : clean(symbol.getName()), data.getDefaultValueRepresentation().isEmpty() ? "-" : clean(data.getDefaultValueRepresentation())); } } } private void exportSymbols(String path) throws Exception { try (PrintWriter out = writer(path)) { out.println("address\tblock\tname\tsymbol_type\tsource\tprimary\tdynamic"); SymbolIterator symbols = currentProgram.getSymbolTable().getAllSymbols(true); while (symbols.hasNext() && !monitor.isCancelled()) { Symbol symbol = symbols.next(); Address address = symbol.getAddress(); MemoryBlock block = currentProgram.getMemory().getBlock(address); out.printf("%s\t%s\t%s\t%s\t%s\t%s\t%s%n", address, block == null ? "" : clean(block.getName()), clean(symbol.getName()), clean(symbol.getSymbolType().toString()), clean(symbol.getSource().toString()), symbol.isPrimary(), symbol.isDynamic()); } } } private void exportUnexplainedExecutableBytes(String path) throws Exception { AddressSet executable = new AddressSet(); AddressSet explained = new AddressSet(); for (MemoryBlock block : currentProgram.getMemory().getBlocks()) { if (block.isExecute()) { executable.add(block.getStart(), block.getEnd()); } } InstructionIterator instructions = currentProgram.getListing().getInstructions(executable, true); while (instructions.hasNext()) { Instruction instruction = instructions.next(); explained.add(instruction.getMinAddress(), instruction.getMaxAddress()); } var dataIterator = currentProgram.getListing().getDefinedData(executable, true); while (dataIterator.hasNext()) { Data data = dataIterator.next(); explained.add(data.getMinAddress(), data.getMaxAddress()); } AddressSet unexplained = executable.subtract(explained); try (PrintWriter out = writer(path)) { out.println("start\tend\tbytes\thex\tclassification\tevidence"); for (AddressRange range : unexplained) { int length = Math.toIntExact(range.getLength()); byte[] bytes = new byte[length]; currentProgram.getMemory().getBytes(range.getMinAddress(), bytes); String[] classification = classifyUnexplained(range); out.printf("%s\t%s\t%d\t%s\t%s\t%s%n", range.getMinAddress(), range.getMaxAddress(), length, hex(bytes), classification[0], classification[1]); } } } private String[] classifyUnexplained(AddressRange range) { String start = range.getMinAddress().toString(); String end = range.getMaxAddress().toString(); if ("1020:0666".equals(start) && start.equals(end)) { return new String[] { "overlapping-entry-opcode", "BA 33 D2 is mov dx,0xd233 at 0666; entry 0667 intentionally decodes 33 D2 as xor dx,dx" }; } if ("1020:0dea".equals(start) && start.equals(end)) { return new String[] { "alignment", "single NOP between the 0d97 and 0e93 Borland runtime routines" }; } return new String[] {"unclassified", ""}; } private PrintWriter writer(String path) throws Exception { return new PrintWriter(new BufferedWriter(new FileWriter(path))); } private String clean(String value) { if (value == null) { return ""; } return value.replace('\t', ' ').replace('\r', ' ').replace('\n', ' '); } private String hex(byte[] bytes) { StringBuilder result = new StringBuilder(bytes.length * 2); for (byte value : bytes) { result.append(String.format("%02x", value & 0xff)); } return result.toString(); } }