Adding recompilable C scripts - staging changes for next release

This commit is contained in:
evm
2023-11-12 19:00:10 -05:00
parent cab7f1774c
commit b1a348bc48
8 changed files with 365 additions and 76 deletions

View File

@@ -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

View File

@@ -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)

View File

@@ -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

View File

@@ -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

View File

@@ -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()

View File

@@ -1,23 +0,0 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<HTML>
<HEAD>
<META name="generator" content=
"HTML Tidy for Java (vers. 2009-12-01), see jtidy.sourceforge.net">
<META http-equiv="Content-Language" content="en-us">
<META http-equiv="Content-Type" content="text/html; charset=windows-1252">
<META name="GENERATOR" content="Microsoft FrontPage 4.0">
<META name="ProgId" content="FrontPage.Editor.Document">
<TITLE>Skeleton Help File for a Module</TITLE>
<LINK rel="stylesheet" type="text/css" href="../../shared/Frontpage.css">
</HEAD>
<BODY>
<H1><a name="HelpAnchor"></a>Skeleton Help File for a Module</H1>
<P>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.</P>
</BODY>
</HTML>

View File

@@ -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")) {

View File

@@ -97,10 +97,10 @@ class NamespacePanel extends JPanel {
nsList.sort(new Comparator<Namespace>() {
@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;
}
}