Created
June 16, 2022 16:06
-
-
Save DRKV333/4e8e88c91666b02e2be959e53af09d2a to your computer and use it in GitHub Desktop.
A Ghidra script for renaming functions based on strings found in log4cxx ""LocationInfo" constructor calls.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import java.io.PrintWriter; | |
import java.io.StringWriter; | |
import java.util.ArrayList; | |
import java.util.Iterator; | |
import java.util.List; | |
import java.util.regex.Matcher; | |
import java.util.regex.Pattern; | |
import ghidra.app.decompiler.DecompInterface; | |
import ghidra.app.decompiler.DecompileOptions; | |
import ghidra.app.decompiler.DecompileResults; | |
import ghidra.app.script.GhidraScript; | |
import ghidra.framework.options.ToolOptions; | |
import ghidra.framework.plugintool.util.OptionsService; | |
import ghidra.program.model.address.Address; | |
import ghidra.program.model.listing.Data; | |
import ghidra.program.model.listing.Function; | |
import ghidra.program.model.pcode.PcodeOp; | |
import ghidra.program.model.pcode.PcodeOpAST; | |
import ghidra.program.model.pcode.Varnode; | |
import ghidra.program.model.symbol.Namespace; | |
import ghidra.program.model.symbol.RefType; | |
import ghidra.program.model.symbol.Reference; | |
import ghidra.program.model.symbol.ReferenceIterator; | |
import ghidra.program.model.symbol.SourceType; | |
import ghidra.program.model.symbol.Symbol; | |
import ghidra.program.model.symbol.SymbolIterator; | |
import ghidra.program.model.symbol.SymbolTable; | |
import ghidra.program.model.symbol.SymbolType; | |
public class Log4cxxRenamer extends GhidraScript { | |
private Function locationInfo; | |
private DecompInterface decomp; | |
private Pattern sigPattern = Pattern.compile("(__\\w+) (?:\\w+::)*?(\\w+)(?:::(\\w+))?[^\\w\\s:]"); | |
private SymbolTable symbolTable; | |
private static boolean isFunction(Symbol s) { | |
return s.getSymbolType() == SymbolType.FUNCTION; | |
} | |
private static boolean isCall(Reference r) { | |
final RefType type = r.getReferenceType(); | |
if (type.isCall()) { | |
return !(type.isComputed() || type.isIndirect()); | |
} | |
return false; | |
} | |
private Address convertAddressToRamSpace(Address address) { | |
String addressString = address.toString(false); | |
return currentProgram.getAddressFactory().getAddress(addressString); | |
} | |
private List<Namespace> getAllMatchingNamespaces(String name) | |
{ | |
return getAllMatchingNamespaces(name, currentProgram.getGlobalNamespace(), new ArrayList<>()); | |
} | |
private List<Namespace> getAllMatchingNamespaces(String name, Namespace namespace, List<Namespace> res) | |
{ | |
if (!(namespace instanceof Function) && namespace.getName(false).equals(name)) | |
res.add(namespace); | |
return getAllMatchingNamespaces(name, symbolTable.getSymbols(namespace), res); | |
} | |
private List<Namespace> getAllMatchingNamespaces(String name, SymbolIterator iter, List<Namespace> res) | |
{ | |
while(iter.hasNext()) | |
{ | |
Object namespaceMaybe = iter.next().getObject(); | |
if (!(namespaceMaybe instanceof Namespace)) | |
continue; | |
Namespace namespace = (Namespace)namespaceMaybe; | |
getAllMatchingNamespaces(name, namespace, res); | |
} | |
return res; | |
} | |
private String getOrCreateStringAt(Address address) throws Exception | |
{ | |
Data data = getDataAt(address); | |
if (data == null) | |
{ | |
Data overlapping = getDataContaining(address); | |
if (overlapping != null) | |
{ | |
println("Retracted overlapping data at: " + overlapping.getAddress()); | |
removeData(overlapping); | |
} | |
println("Created data string at: " + address); | |
data = createAsciiString(address); | |
} | |
return (String)data.getValue(); | |
} | |
private String traceToString(Throwable f) | |
{ | |
StringWriter sw = new StringWriter(); | |
PrintWriter pw = new PrintWriter(sw); | |
f.printStackTrace(pw); | |
return sw.toString(); // stack trace as a string | |
} | |
@Override | |
public void run() throws Exception | |
{ | |
symbolTable = currentProgram.getSymbolTable(); | |
decomp = new DecompInterface(); | |
DecompileOptions options; | |
options = new DecompileOptions(); | |
OptionsService service = state.getTool().getService(OptionsService.class); | |
if (service != null) | |
{ | |
ToolOptions opt = service.getOptions("Decompiler"); | |
options.grabFromToolAndProgram(null, opt, currentProgram); | |
} | |
decomp.setOptions(options); | |
decomp.toggleSyntaxTree(true); | |
decomp.toggleCCode(false); | |
decomp.openProgram(currentProgram); | |
Symbol locationInfoSymbol = symbolTable.getExternalSymbol("LocationInfo"); | |
locationInfo = (Function)locationInfoSymbol.getObject(); | |
//doThisAddress(currentAddress); | |
monitor.initialize(currentProgram.getReferenceManager().getReferenceCountTo(locationInfo.getEntryPoint())); | |
monitor.setProgress(0); | |
monitor.setCancelEnabled(true); | |
//getAllMatchingNamespaces("PacketDispatcher").forEach((Namespace n) -> println(n.getName(true))); | |
ReferenceIterator refs = currentProgram.getReferenceManager().getReferencesTo(locationInfo.getEntryPoint()); | |
while (refs.hasNext()) | |
{ | |
if (monitor.isCancelled()) | |
break; | |
try | |
{ | |
Reference ref = refs.next(); | |
if (!ref.getReferenceType().isCall()) | |
continue; | |
doThisAddress(ref.getFromAddress()); | |
} | |
catch (Exception e) | |
{ | |
println("FATAL ERROR!!!"); | |
println(traceToString(e)); | |
} | |
monitor.incrementProgress(1); | |
} | |
decomp.dispose(); | |
} | |
private Function prevFunc = null; | |
private void doThisAddress(Address address) throws Exception | |
{ | |
println(address.toString()); | |
Function func = getFunctionContaining(address); | |
if (func == null) | |
{ | |
println("Not a function"); | |
return; | |
} | |
if (prevFunc != null && prevFunc.getEntryPoint().equals(func.getEntryPoint())) | |
{ | |
println("skipping duplicate."); | |
return; | |
} | |
prevFunc = func; | |
//String currentName = func.getName(); | |
//if (!currentName.contains("FUN_")) | |
//{ | |
// println("Already has name: " + currentName); | |
// return; | |
//} | |
DecompileResults res = decomp.decompileFunction(func, 3600, monitor); | |
if (res.getHighFunction() == null) | |
{ | |
println("Failed to decompile!!!"); | |
return; | |
} | |
Iterator<PcodeOpAST> pcodesAtCall = res.getHighFunction().getPcodeOps(address); | |
boolean hasCall = false; | |
while (pcodesAtCall.hasNext()) | |
{ | |
PcodeOpAST pcode = pcodesAtCall.next(); | |
if (pcode.getOpcode() == PcodeOp.CALL) | |
{ | |
Varnode locArg = pcode.getInput(3); | |
if (locArg.getDef().getOpcode() == PcodeOp.COPY) | |
locArg = locArg.getDef().getInput(0); | |
String signature = getOrCreateStringAt(convertAddressToRamSpace(locArg.getAddress())); | |
Matcher sigMatch = sigPattern.matcher(signature); | |
if (sigMatch.find()) | |
{ | |
String callConv = sigMatch.group(1); | |
String className = sigMatch.group(2); | |
String funcName = sigMatch.group(3); | |
if (funcName == null) | |
{ | |
funcName = className; | |
className = null; | |
} | |
func.setCallingConvention(callConv); | |
func.setName(funcName, SourceType.USER_DEFINED); | |
if (className != null) | |
{ | |
List<Namespace> possibleClasses = getAllMatchingNamespaces(className); | |
if (possibleClasses.size() > 1) | |
{ | |
println("Multiple possible classes"); | |
} | |
else if (possibleClasses.isEmpty()) | |
{ | |
func.setParentNamespace(symbolTable.createClass(currentProgram.getGlobalNamespace(), className, SourceType.USER_DEFINED)); | |
println("Created class: " + className); | |
} | |
else | |
{ | |
func.setParentNamespace(possibleClasses.get(0)); | |
} | |
} | |
println("Renamed to " + callConv + " " + funcName + " " + (className == null ? "(No a method)" : className)); | |
} | |
else | |
{ | |
println("Sig not matches"); | |
} | |
hasCall = true; | |
break; | |
} | |
} | |
if (!hasCall) | |
println("No CALL found"); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment