diff --git a/codecut-gui/ghidra_scripts/decomprange.py b/codecut-gui/ghidra_scripts/decomprange.py new file mode 100644 index 0000000..5851964 --- /dev/null +++ b/codecut-gui/ghidra_scripts/decomprange.py @@ -0,0 +1,63 @@ +from ghidra.app.decompiler import DecompInterface +from ghidra.util.task import ConsoleTaskMonitor + + +def decompile_user_functions_in_range( + current_program, + start_address_str, + end_address_str, + standard_library_namespaces=None, + ): + if standard_library_namespaces is None: + standard_library_namespaces = ['libc', 'libm', 'libpthread', + 'libdl', 'std'] + + def is_user_written_function(function): + namespace = function.getParentNamespace().getName() + return namespace not in standard_library_namespaces + + #doesn't quite work right if you have overlapping functions + #we should rewrite to use FunctionManager/getFunctionsAt functionality + def getFunctions(start_address, end_address): + current_addr = start_address + while current_addr < end_address: + function = \ + current_program.getFunctionManager().getFunctionContaining(current_addr) + if function is not None: + yield function + current_addr = function.getBody().getMaxAddress().add(1) + else: + current_addr = current_addr.add(1) + + start_address = \ + current_program.getAddressFactory().getAddress(start_address_str) + end_address = \ + current_program.getAddressFactory().getAddress(end_address_str) + + if start_address is None or end_address is None: + print 'Invalid address range specified.' + return + + if start_address >= end_address: + print 'Invalid address range: start address should be less than end address.' + return + + decompiler = DecompInterface() + decompiler.openProgram(current_program) + monitor = ConsoleTaskMonitor() + + functions = list(getFunctions(start_address, end_address)) + user_written_functions = list(filter(is_user_written_function, + functions)) + + decompiled_functions = {} + for function in user_written_functions: + decomp_result = decompiler.decompileFunction(function, 0, + monitor) + if decomp_result is not None: + decompiled_functions[function.getName()] = \ + {'address': function.getEntryPoint(), + 'code': decomp_result.getDecompiledFunction().getC()} + + return decompiled_functions + diff --git a/codecut-gui/ghidra_scripts/funcsig.py b/codecut-gui/ghidra_scripts/funcsig.py new file mode 100644 index 0000000..830f12a --- /dev/null +++ b/codecut-gui/ghidra_scripts/funcsig.py @@ -0,0 +1,69 @@ +from ghidra.program.model.listing import Function +from ghidra.program.model.symbol import SourceType + + +def get_referenced_function_signatures(program, function_address, + monitor): + if function_address is None: + popup('No address entered') + return + + function = \ + program.getFunctionManager().getFunctionAt(function_address) + if function is None: + popup('No function found at the given address') + return + + function_signatures = \ + get_referenced_function_signatures_base(function, monitor) + + sigs = '\n/* Refereneced Signatures for: ' + function.getName() \ + + ' */\n' + for signature in function_signatures: + sigs += signature + + return sigs + + +def get_referenced_function_signatures_base(function, monitor): + if function is None: + raise ValueError('function is none') + + funcRefs = getFunctionReferences(function, monitor) + + signatures = [] + for refFunc in funcRefs: + if isUserDefinedFunction(refFunc): + signatures.append(getFunctionSignature(refFunc) + ';\n') + + return signatures + + +def getFunctionReferences(function, monitor): + refs = set() + instructions = \ + function.getProgram().getListing().getInstructions(function.getBody(), + True) + for instr in instructions: + flowType = instr.getFlowType() + if flowType.isCall(): + target = instr.getOperandReferences(0)[0].getToAddress() + func = \ + function.getProgram().getFunctionManager().getFunctionAt(target) + if func is not None: + refs.add(func) + return refs + + +def getFunctionSignature(function): + sig = function.getPrototypeString(False, False) + return sig + + +def isUserDefinedFunction(function): + symbol = function.getSymbol() + namespace = symbol.getParentNamespace().getName() + standard_libraries = ['libc', 'libm', 'libpthread', 'libdl', 'std'] + return not any(namespace.startswith(lib) for lib in + standard_libraries) + diff --git a/codecut-gui/ghidra_scripts/generate_c.py b/codecut-gui/ghidra_scripts/generate_c.py new file mode 100644 index 0000000..f596241 --- /dev/null +++ b/codecut-gui/ghidra_scripts/generate_c.py @@ -0,0 +1,64 @@ +from globalvars import get_global_variables +from funcsig import get_referenced_function_signatures +from decomprange import decompile_user_functions_in_range +import re + + +def fixCasting(code_str): + pattern = re.compile(r"\b([a-zA-Z0-9._]+)\._(\d{0,4})_(\d{0,4})_") + matches = pattern.finditer(code_str) + + for match in matches: + name = match.group(1) + offset = match.group(2) + size = match.group(3) + + if size == '4': + fixedref = '((int*)' + name + ')[' + offset + ']' + code_str = code_str.replace(match.group(), fixedref) + + if size == '1': + fixedref = '*(uint8_t *)&' + name + code_str = code_str.replace(match.group(), fixedref) + + return code_str + + +def generate_recompilable_c_code( + start_addr, + end_addr, + currentProgram, + monitor, + ): + header_prefix = \ + '''/* Ghidra type resolution */ +#include "ghidra.h" + +''' + + referenced_funcs = '' + in_range_c_code = \ + ''' + +/* Decompiled functions within address range: %s - %s */''' \ + % (start_addr, end_addr) + + global_vars = get_global_variables(currentProgram, start_addr, end_addr) + print('Global vars:') + print(global_vars) + + decompiled_functions = \ + decompile_user_functions_in_range(currentProgram, start_addr, + end_addr) + + for (function_name, function_data) in decompiled_functions.items(): + referenced_funcs += \ + get_referenced_function_signatures(currentProgram, + function_data['address'], monitor) + in_range_c_code += function_data['code'] + + c_code = header_prefix + global_vars + referenced_funcs \ + + in_range_c_code + c_code = fixCasting(c_code) + return c_code + diff --git a/codecut-gui/ghidra_scripts/globalvars.py b/codecut-gui/ghidra_scripts/globalvars.py new file mode 100644 index 0000000..0895980 --- /dev/null +++ b/codecut-gui/ghidra_scripts/globalvars.py @@ -0,0 +1,99 @@ +from ghidra.program.model.symbol import SourceType +from ghidra.program.model.symbol import SymbolType +from ghidra.program.model.address import AddressSet +#from ghidra.program.flatapi.FlatProgramAPI import getFunctionAt, getDataAt +from ghidra.program.model.data import Array, FloatDataType, \ + DoubleDataType + + +def get_global_variables(program, start_addr, end_addr): + global_vars = [] + symbol_table = program.getSymbolTable() + start_address = \ + program.getAddressFactory().getAddress(start_addr) + end_address = \ + program.getAddressFactory().getAddress(end_addr) + addrset = AddressSet(start_address,end_address) + #set.addRange(start_addr, end_addr) + print(start_address, end_address) + print(addrset) + + #for symbol in symbol_table.getAllSymbols(False): + for symbol in symbol_table.getSymbols(addrset,SymbolType.LABEL,True): + print(symbol) + if (symbol.getSymbolType() == SymbolType.LABEL): + if (symbol.isGlobal()): + if (not program.getListing().getFunctionAt(symbol.getAddress())): + if (program.getListing().getDataAt(symbol.getAddress())): + global_vars.append(symbol) + + ''' + def is_user_defined(var): + var_name = var.getName() + var_addr = var.getAddress() + + if var_name.startswith('__') or var_name.startswith('_'): + return False + + if var_name.startswith('imp_') or var_name.startswith('thunk_'): + return False + + if var_name.startswith('fde_') or var_name.startswith('cie_'): + return False + + if var_name.startswith('completed.0') \ + or var_name.startswith('data_start'): + return False + + if var_addr.toString().startswith('EXTERNAL:'): + return False + section_name = program.getMemory().getBlock(var_addr).getName() + #if section_name not in ['.data', '.bss']: + # return False + + return True + ''' + + + #global_vars = list(filter(is_user_defined, global_vars)) + #global_vars = list(filter(is_global_var, global_vars)) + + print("G vars first pass:") + print(global_vars) + + output = '/* Global Variables */\n' + for var in global_vars: + var_addr = var.getAddress() + var_name = var.getName() + + data = program.getListing().getDataAt(var_addr) + if data is None: + continue + + dt = data.getDataType() + dt_name = dt.getDisplayName() + value = data.getValue() + + pointer_count = 0 + while dt_name.endswith('*'): + pointer_count += 1 + dt_name = dt_name[:-1].strip() + + if isinstance(dt, Array): + value = '{' + ', '.join(str(value[i]) for i in + range(len(value))) + '}' + elif isinstance(dt, FloatDataType) or isinstance(dt, + DoubleDataType): + value = '{:.6f}'.format(value) + elif value is not None: + value = str(value) + else: + value = '' + + output += '{}{} {}{}{};\n'.format(dt_name, (' ' + if not dt_name.endswith('*') else ''), var_name, '*' + * pointer_count, (' = {}'.format(value) if value else '' + )) + + return output + diff --git a/codecut-gui/ghidra_scripts/range.py b/codecut-gui/ghidra_scripts/range.py index f6a80af..e557367 100644 --- a/codecut-gui/ghidra_scripts/range.py +++ b/codecut-gui/ghidra_scripts/range.py @@ -1,44 +1,36 @@ -#@category AMP +#@category AMP-Improved +from generate_c import generate_recompilable_c_code +import os +from ghidra.util.task import TaskMonitor -from std import * -# ------------------------------------------------------------- -# Decompile all functions within a range of specified addresses -# ------------------------------------------------------------- -# Definitions for decompiler to function -program = getCurrentProgram() -ifc = DecompInterface() -ifc.setOptions(DecompileOptions()) -ifc.openProgram(program) +def write_c_code_to_file(c_code, output_file_path): + with open(output_file_path, 'w') as f: + f.write(c_code) -inRangeFunc = [] -externFunc = [] - -# Take user input for address range -args = getScriptArgs() -start_addr = args[0] -end_addr = args[1] -file = args[2] -path = file.rsplit("/", 1)[0] + "/" +if __name__ == '__main__': -start = int(start_addr, 16) -end = int(end_addr, 16) + args = getScriptArgs() + start_addr = args[0] + end_addr = args[1] -def main(): - # Get functions in range of specified addr - # store functions in range -> inRangeFunc - # store called functions from in range functions -> externFunc - getFunctionsInRange(start, end, externFunc, inRangeFunc) - - # Decompile in range functions and write to file - writeToFile(file, externFunc, inRangeFunc) - - # Produce "ghidra.h" file - writeHeader(path) + file_name = args[2] + output_dir = file_name.rsplit("/", 1)[0] + "/" - # Fix ghidra decompiler access notation - fixCasting(file) + println("Recomp C Range Entry: %s - %s" % (start_addr, end_addr)) + c_code = generate_recompilable_c_code(start_addr, end_addr, + currentProgram, monitor) + + #file_name = currentProgram.getName() + + #output_dir = askDirectory('Output Directory', + # 'Save C code output' + # ).getPath() + + #output_file_path = os.path.join(output_dir, file_name) + + write_c_code_to_file(c_code, file_name) + + println('C code has been saved to %s' % file_name) -if __name__ == "__main__": - main() diff --git a/codecut-gui/src/main/help/help/topics/deepcutgui/help.html b/codecut-gui/src/main/help/help/topics/deepcutgui/help.html deleted file mode 100644 index 8f858d2..0000000 --- a/codecut-gui/src/main/help/help/topics/deepcutgui/help.html +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - - - - - - Skeleton Help File for a Module - - - - -

Skeleton Help File for a Module

- -

This is a simple skeleton help topic. For a better description of what should and should not - go in here, see the "sample" Ghidra extension in the Extensions/Ghidra directory, or see your - favorite help topic. In general, language modules do not have their own help topics.

- - diff --git a/codecut-gui/src/main/java/codecutguiv2/CodeCutGUIPlugin.java b/codecut-gui/src/main/java/codecutguiv2/CodeCutGUIPlugin.java index 36af877..f60273f 100644 --- a/codecut-gui/src/main/java/codecutguiv2/CodeCutGUIPlugin.java +++ b/codecut-gui/src/main/java/codecutguiv2/CodeCutGUIPlugin.java @@ -41,6 +41,7 @@ import java.awt.event.KeyEvent; import java.io.*; import javax.swing.ImageIcon; +import javax.swing.Icon; import docking.ActionContext; import docking.action.*; @@ -526,7 +527,7 @@ public class CodeCutGUIPlugin extends Plugin implements DomainObjectListener { return symProvider.getCurrentSymbol() != null; } }; - ImageIcon icon = ResourceManager.loadImage("images/table_go.png"); + Icon icon = ResourceManager.loadImage("images/table_go.png"); openRefsAction.setPopupMenuData( new MenuData(new String[] { "Symbol References" }, icon, popupGroup)); openRefsAction.setToolBarData(new ToolBarData(icon)); @@ -752,7 +753,7 @@ public class CodeCutGUIPlugin extends Plugin implements DomainObjectListener { } private void createExportActions() { - +//Need Decompiler extensions /* DockingAction exportDwarf = new DockingAction("export_dwarf", this.getName()) { @@ -794,12 +795,35 @@ public class CodeCutGUIPlugin extends Plugin implements DomainObjectListener { }; - MenuData menuData = new MenuData(new String[] {"Export", "Export to C" }, null, "Export"); + MenuData menuData = new MenuData(new String[] {"Export", "Select range to export to recomp. C (EXPERIMENTAL)" }, null, "Export"); menuData.setMenuSubGroup("1"); exportC.setPopupMenuData(menuData); //exportC.setAddToAllWindows(true); tool.addLocalAction(symProvider, exportC); + DockingAction exportCModule = + new DockingAction("export_c_mod", this.getName()) { + @Override + public boolean isEnabledForContext(ActionContext context) { + return symProvider.getCurrentSymbol() != null; + } + @Override + public void actionPerformed(ActionContext context) { + AddressRange theRange = CodecutUtils.getNamespaceRange(getProgram(), getSymbolProvider().getCurrentSymbol()); + exportC(theRange.getMinAddress().toString(), theRange.getMaxAddress().toString()); + } + + }; + + menuData = new MenuData(new String[] {"Export", "Export module to recomp. C (EXPERIMENTAL)" }, null, "Export"); + menuData.setMenuSubGroup("1"); + exportCModule.setPopupMenuData(menuData); + //exportC.setAddToAllWindows(true); + tool.addLocalAction(symProvider, exportCModule); + +//Need to get obj file output script to work without the parent ELF +//Could check and only enable this if the loaded file was an ARM ELF +/* DockingAction exportElf = new DockingAction("export_elf", this.getName()) { @Override @@ -808,7 +832,7 @@ public class CodeCutGUIPlugin extends Plugin implements DomainObjectListener { } @Override public void actionPerformed(ActionContext programContext) { - Namespace ns = symProvider.getCurrentSymbol().getParentNamespace(); // for o output + Namespace ns = symProvider.getCurrentSymbol().getParentNamespace(); // for .o output Msg.info(new Object(), ns.getName()); GhidraState gstate = new GhidraState(tool, tool.getProject(), currentProgram, null, null, null); OFileExporter outputELF = new OFileExporter(gstate, ns.getName()); @@ -822,12 +846,13 @@ public class CodeCutGUIPlugin extends Plugin implements DomainObjectListener { }; - menuData = new MenuData(new String[] {"Export", "Export to ELF" }, null, "Export"); + menuData = new MenuData(new String[] {"Export", "Export to ELF (EXPERIMENTAL)" }, null, "Export"); menuData.setMenuSubGroup("1"); exportElf.setPopupMenuData(menuData); //exportElf.setAddToAllWindows(true); tool.addLocalAction(symProvider, exportElf); - +*/ +//Need Decompiler Extensions /* DockingAction updateMapping = new DockingAction("Update Decompiler Mapping", getName()) { @Override @@ -1033,7 +1058,7 @@ public class CodeCutGUIPlugin extends Plugin implements DomainObjectListener { stringInstance.getComponentPath(), null, 0, 0, 0); accumulator.add(pl); - monitor.checkCanceled(); + //monitor.checkCanceled(); monitor.incrementProgress(1); } @@ -1181,7 +1206,7 @@ public class CodeCutGUIPlugin extends Plugin implements DomainObjectListener { private File loadMapFile() { GhidraFileChooser fileChooser = new GhidraFileChooser(this.symProvider.getComponent()); - String dir = Preferences.getProperty(Preferences.LAST_IMPORT_DIRECTORY); + String dir = Preferences.getProperty(Preferences.LAST_TOOL_IMPORT_DIRECTORY ); if (dir != null) { File file = new File(dir); fileChooser.setCurrentDirectory(file); @@ -1194,7 +1219,7 @@ public class CodeCutGUIPlugin extends Plugin implements DomainObjectListener { if (file != null) { File parent = file.getParentFile(); if (parent != null) { - Preferences.setProperty(Preferences.LAST_IMPORT_DIRECTORY, parent.getAbsolutePath()); + Preferences.setProperty(Preferences.LAST_TOOL_IMPORT_DIRECTORY, parent.getAbsolutePath()); } if (!file.getName().endsWith(".map")) { Msg.showInfo(this, this.symProvider.getComponent(), @@ -1209,7 +1234,7 @@ public class CodeCutGUIPlugin extends Plugin implements DomainObjectListener { protected File getMapFile() { GhidraFileChooser fileChooser = new GhidraFileChooser(this.symProvider.getComponent()); - String dir = Preferences.getProperty(Preferences.LAST_EXPORT_DIRECTORY); + String dir = Preferences.getProperty(Preferences.LAST_TOOL_EXPORT_DIRECTORY); if (dir != null) { File file = new File(dir); fileChooser.setCurrentDirectory(file); @@ -1222,7 +1247,7 @@ public class CodeCutGUIPlugin extends Plugin implements DomainObjectListener { if (file != null) { File parent = file.getParentFile(); if (parent != null) { - Preferences.setProperty(Preferences.LAST_EXPORT_DIRECTORY, parent.getAbsolutePath()); + Preferences.setProperty(Preferences.LAST_TOOL_EXPORT_DIRECTORY, parent.getAbsolutePath()); } String name = file.getName(); if (!file.getName().endsWith(".map")) { @@ -1282,7 +1307,7 @@ public class CodeCutGUIPlugin extends Plugin implements DomainObjectListener { protected File getCFile() { GhidraFileChooser fileChooser = new GhidraFileChooser(this.symProvider.getComponent()); - String dir = Preferences.getProperty(Preferences.LAST_EXPORT_DIRECTORY); + String dir = Preferences.getProperty(Preferences.LAST_TOOL_EXPORT_DIRECTORY); if (dir != null) { File file = new File(dir); fileChooser.setCurrentDirectory(file); @@ -1295,7 +1320,7 @@ public class CodeCutGUIPlugin extends Plugin implements DomainObjectListener { if (file != null) { File parent = file.getParentFile(); if (parent != null) { - Preferences.setProperty(Preferences.LAST_EXPORT_DIRECTORY, parent.getAbsolutePath()); + Preferences.setProperty(Preferences.LAST_TOOL_EXPORT_DIRECTORY, parent.getAbsolutePath()); } String name = file.getName(); if (!file.getName().endsWith(".c")) { diff --git a/codecut-gui/src/main/java/codecutguiv2/NamespacePanel.java b/codecut-gui/src/main/java/codecutguiv2/NamespacePanel.java index 0aeeb4f..f8c645e 100644 --- a/codecut-gui/src/main/java/codecutguiv2/NamespacePanel.java +++ b/codecut-gui/src/main/java/codecutguiv2/NamespacePanel.java @@ -97,10 +97,10 @@ class NamespacePanel extends JPanel { nsList.sort(new Comparator() { @Override public int compare(Namespace ns1, Namespace ns2) { - Address ns1MaxAddress = ns1.getBody().getMaxAddress(); - Address ns2MaxAddress = ns2.getBody().getMaxAddress(); - if (ns1MaxAddress != null && ns2MaxAddress != null) { - if (ns1MaxAddress.getAddressableWordOffset() < ns2MaxAddress.getAddressableWordOffset()) { + Address ns1MinAddress = ns1.getBody().getMinAddress(); + Address ns2MinAddress = ns2.getBody().getMinAddress(); + if (ns1MinAddress != null && ns2MinAddress != null) { + if (ns1MinAddress.getAddressableWordOffset() < ns2MinAddress.getAddressableWordOffset()) { return -1; } }