package dragondance.eng; import java.awt.Color; import java.awt.Component; import java.awt.EventQueue; import java.io.BufferedReader; import java.io.File; import java.io.IOException; import java.io.InputStreamReader; import java.io.PrintWriter; import java.io.StringWriter; import java.lang.reflect.InvocationTargetException; import java.net.URL; import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; import javax.swing.SwingUtilities; import docking.widgets.OptionDialog; import docking.widgets.filechooser.GhidraFileChooser; import docking.widgets.filechooser.GhidraFileChooserMode; import dragondance.Globals; import dragondance.StringResources; import dragondance.exceptions.InvalidInstructionAddress; import dragondance.util.Util; import generic.concurrent.GThreadPool; import generic.jar.ResourceFile; import generic.json.JSONError; import generic.json.JSONParser; import generic.json.JSONToken; import ghidra.app.plugin.core.colorizer.ColorizingService; import ghidra.app.script.GhidraScript; import ghidra.app.script.GhidraScriptProvider; import ghidra.app.script.GhidraScriptUtil; import ghidra.app.script.GhidraState; import ghidra.app.services.ConsoleService; import ghidra.app.services.GoToService; import ghidra.framework.plugintool.PluginTool; import ghidra.program.flatapi.FlatProgramAPI; import ghidra.program.model.address.Address; import ghidra.program.model.address.AddressSet; import ghidra.program.model.listing.CodeUnit; import ghidra.program.model.listing.Instruction; import ghidra.program.model.mem.MemoryBlock; import ghidra.util.Msg; import ghidra.util.SystemUtilities; import ghidra.util.task.DummyCancellableTaskMonitor; import ghidra.util.task.TaskMonitor; public class DragonHelper { private static PluginTool tool = null; private static FlatProgramAPI fapi = null; private static GThreadPool tpool = null; public static void init(PluginTool pluginTool, FlatProgramAPI api) { DragonHelper.tool = pluginTool; DragonHelper.fapi = api; } public static int startTransaction(String name) { return fapi.getCurrentProgram().startTransaction(name); } public static void finishTransaction(int id, boolean commit) { fapi.getCurrentProgram().endTransaction(id, commit); } public static boolean runScript(String scriptName, String[] scriptArguments, GhidraState scriptState) throws Exception { //set dummy printwriter to satisfy ghidra scripting api StringWriter sw = new StringWriter(); PrintWriter writer = new PrintWriter(sw); TaskMonitor monitor = new DummyCancellableTaskMonitor(); Path p = Paths.get( System.getProperty("user.dir"), "Ghidra", "Extensions", "dragondance", "ghidra_scripts", scriptName ); //List<ResourceFile> dirs = GhidraScriptUtil.getScriptSourceDirectories(); ResourceFile scriptSource = new ResourceFile(p.toAbsolutePath().toString()); if (scriptSource.exists()) { GhidraScriptProvider gsp = GhidraScriptUtil.getProvider(scriptSource); if (gsp == null) { writer.close(); throw new IOException("Attempting to run subscript '" + scriptName + "': unable to run this script type."); } GhidraScript script = gsp.getScriptInstance(scriptSource, writer); script.setScriptArgs(scriptArguments); script.execute(scriptState, monitor, writer); } else { return false; } return true; } public static String getProgramName() { return fapi.getCurrentProgram().getDomainFile().getName(); } public static GThreadPool getTPool() { if (tpool == null) { tpool = GThreadPool.getPrivateThreadPool("DragonDance"); tpool.setMaxThreadCount(4); } return tpool; } public static void queuePoolWorkItem(Runnable r) { getTPool().submit(r); } public static boolean runOnSwingThread(Runnable r, boolean waitExecution) { if (waitExecution) { try { SwingUtilities.invokeAndWait(r); } catch (InvocationTargetException | InterruptedException e) { return false; } } else { SystemUtilities.runIfSwingOrPostSwingLater(r); } return true; } public static void showMessage(String message, Object...args) { if (isUiDispatchThread()) Msg.showInfo(DragonHelper.class, null, "Dragon Dance", String.format(message, args)); else showMessageOnSwingThread(message,args); } public static void showWarning(String message, Object...args) { if (isUiDispatchThread()) Msg.showWarn(DragonHelper.class, null, "Dragon Dance", String.format(message, args)); else showWarningOnSwingThread(message,args); } public static void showMessageOnSwingThread(String message, Object ...args) { try { SwingUtilities.invokeAndWait(() -> { showMessage(message, args); }); } catch (InvocationTargetException | InterruptedException e) { } } public static void showWarningOnSwingThread(String message, Object ...args) { try { SwingUtilities.invokeAndWait(() -> { showWarning(message, args); }); } catch (InvocationTargetException | InterruptedException e) { } } public static boolean showYesNoMessage(String title, String messageFormat, Object...args) { String message; message = String.format(messageFormat, args); AtomicBoolean yesno = new AtomicBoolean(); Runnable r = () -> { int choice = OptionDialog.showYesNoDialog(null, title, message); yesno.set(choice == OptionDialog.OPTION_ONE); }; SystemUtilities.runSwingNow(r); return yesno.get(); } public static void printConsole(String format, Object... args) { String message = String.format(format, args); if (tool == null) { return; } ConsoleService console = tool.getService(ConsoleService.class); if (console == null) { return; } console.print(message); } public static boolean isValidExecutableSectionAddress(long addr) { if (addr < getImageBase().getOffset()) return false; if (addr >= getImageEnd().getOffset()) return false; return isCodeSectionAddress(addr); } public static boolean goToAddress(long addr) { GoToService gotoService = tool.getService(GoToService.class); if (gotoService==null) return false; if (!isValidExecutableSectionAddress(addr)) { showWarning("%x is not valid offset.",addr); return false; } if (getInstructionNoThrow(getAddress(addr),true) == null) { return false; } return gotoService.goTo(getAddress(addr)); } public static Address getAddress(long addrValue) { return fapi.toAddr(addrValue); } public static String askFile(Component parent, String title, String okButtonText) { GhidraFileChooser gfc = new GhidraFileChooser(parent); if (!Globals.LastFileDialogPath.isEmpty()) { File def = new File(Globals.LastFileDialogPath); gfc.setSelectedFile(def); } gfc.setTitle(title); gfc.setApproveButtonText(okButtonText); gfc.setFileSelectionMode(GhidraFileChooserMode.FILES_ONLY); File file = gfc.getSelectedFile(); if (file == null) { return null; } if (!file.exists()) return null; Globals.LastFileDialogPath = Util.getDirectoryOfFile(file.getAbsolutePath()); if (Globals.LastFileDialogPath == null) Globals.LastFileDialogPath = System.getProperty("user.dir"); return file.getAbsolutePath(); } public static AddressSet makeAddressSet(long addr, int size) { Address bAddr, eAddr; bAddr = fapi.toAddr(addr); eAddr = bAddr.add(size); AddressSet addrSet = new AddressSet(); addrSet.add(bAddr, eAddr); return addrSet; } public static String getExecutableMD5Hash() { return fapi.getCurrentProgram().getExecutableMD5(); } public static Address getImageBase() { if (Globals.WithoutGhidra) return getAddress(0x10000000); return fapi.getCurrentProgram().getImageBase(); } public static Address getImageEnd() { return fapi.getCurrentProgram().getMaxAddress(); } public static InstructionContext getInstruction(long addr, boolean throwEx) throws InvalidInstructionAddress { return getInstruction(fapi.toAddr(addr),throwEx); } public static InstructionContext getInstruction(Address addr, boolean throwEx) throws InvalidInstructionAddress { return getInstruction(addr,throwEx,false); } private static InstructionContext getInstructionNoThrow(Address addr, boolean icall) { InstructionContext inst = null; try { inst = getInstruction(addr,false,icall); } catch (Exception e) {} return inst; } private static InstructionContext getInstruction(Address addr, boolean throwEx, boolean icall) throws InvalidInstructionAddress { InstructionContext ictx; if (addr == null) return null; long naddr=addr.getOffset(); Instruction inst = fapi.getInstructionAt(addr); CodeUnit cu; if (inst == null) { if (icall) { if (throwEx) throw new InvalidInstructionAddress(naddr); return null; } if (!isCodeSectionAddress(naddr)) { if (throwEx) throw new InvalidInstructionAddress(naddr); return null; } else if (isInDisassembledRange(naddr)) { //invalid instruction if (throwEx) throw new InvalidInstructionAddress(naddr); return null; } DragonHelper.goToAddress(naddr); boolean choice = DragonHelper.showYesNoMessage("Warning", StringResources.INVALID_CODE_ADDRESS_FIX_MESSAGE, naddr); if (choice) { if (!disassemble(naddr)) { if (throwEx) throw new InvalidInstructionAddress(naddr); return null; } return getInstruction(addr,throwEx,true); } return null; } cu = fapi.getCurrentProgram().getListing().getCodeUnitAt(addr); ictx = new InstructionContext(inst,cu); return ictx; } public static boolean isUiDispatchThread() { return EventQueue.isDispatchThread(); } public static boolean disassemble(long addr) { boolean result; AtomicBoolean ret = new AtomicBoolean(); int transId = startTransaction("DgDisasm"); if (EventQueue.isDispatchThread()) { result = fapi.disassemble(getAddress(addr)); } else { Runnable r = () -> { ret.set(fapi.disassemble(getAddress(addr))); }; runOnSwingThread(r, true); result = ret.get(); } finishTransaction(transId,result); return result; } public static List<MemoryBlock> getExecutableMemoryBlocks() { MemoryBlock[] blocks = fapi.getCurrentProgram().getMemory().getBlocks(); List<MemoryBlock> memList = new ArrayList<MemoryBlock>(); for (MemoryBlock block : blocks) { if (block.isExecute()) { memList.add(block); } } return memList; } public static boolean isCodeSectionAddress(long addr) { //.text, .init .fini __text boolean status=false; List<MemoryBlock> execBlocks = getExecutableMemoryBlocks(); for (MemoryBlock mb : execBlocks) { if (addr >= mb.getStart().getOffset() && addr < mb.getEnd().getOffset()) { status=true; break; } } execBlocks.clear(); return status; } public static boolean isInDisassembledRange(long addr) { int iMax=15; Address gaddr; InstructionContext inst; gaddr = getAddress(addr); inst = getInstructionNoThrow(gaddr,true); if (inst != null) return true; while (iMax-- > 0) { gaddr = gaddr.subtract(1); inst = getInstructionNoThrow(gaddr,true); if (inst != null) break; } if (inst == null) return false; gaddr = gaddr.add(inst.getSize()); inst = getInstructionNoThrow(gaddr,true); if (inst == null) return false; if (addr <= inst.getAddress()) { return true; } iMax = 15; while (iMax-- > 0) { gaddr = gaddr.add(inst.getSize()); inst = getInstructionNoThrow(gaddr,true); if (inst != null) { if (inst.getAddress() <= addr) { return true; } } else break; } return false; } public static boolean setInstructionBackgroundColor(long addr, Color color) { Address ba; ColorizingService colorService = tool.getService(ColorizingService.class); if (colorService == null) { return false; } ba = getAddress(addr); colorService.setBackgroundColor(ba, ba, color); return true; } public static boolean clearInstructionBackgroundColor(long addr) { Address ba; ColorizingService colorService = tool.getService(ColorizingService.class); if (colorService == null) { return false; } ba = getAddress(addr); colorService.clearBackgroundColor(ba, ba); return true; } public static String getStringFromURL(String url) { try { URL u = new URL(url); StringBuilder sb = new StringBuilder(); BufferedReader reader = new BufferedReader(new InputStreamReader(u.openStream())); String nextLine = ""; while ((nextLine = reader.readLine()) != null) { sb.append(nextLine + "\n"); } return sb.toString(); } catch (IOException e) { } return null; } public static Object parseJson(String jsonData) { char[] cbuf = new char[jsonData.length()]; Object obj = null; JSONParser parser = new JSONParser(); List<JSONToken> tokens = new ArrayList<JSONToken>(); jsonData.getChars(0, jsonData.length(), cbuf, 0); if (parser.parse(cbuf, tokens) != JSONError.JSMN_SUCCESS) { return null; } try { obj = parser.convert(cbuf, tokens); } catch (Exception ex) { tokens.clear(); return null; } tokens.clear(); return obj; } }