mirror of
https://github.com/0xdea/ghidra-scripts
synced 2026-06-08 10:09:44 +00:00
277 lines
11 KiB
Java
277 lines
11 KiB
Java
/*
|
|
* FOX_alpha.java - Fix Objective-C XREFs @Ghidra (AARCH64)
|
|
* Copyright (c) 2021 Marco Ivaldi <raptor@0xdeadbeef.info>
|
|
*
|
|
* "When it encounters a method call, the compiler generates a call to one of
|
|
* the functions objc_msgSend, objc_msgSend_stret, objc_msgSendSuper, or
|
|
* objc_msgSendSuper_stret. Messages sent to an object's superclass (using the
|
|
* super keyword) are sent using objc_msgSendSuper; other messages are sent
|
|
* using objc_msgSend. Methods that have data structures as return values are
|
|
* sent using objc_msgSendSuper_stret and objc_msgSend_stret."
|
|
* -- Apple Objective-C Documentation
|
|
*
|
|
* FOX_alpha is a simple Ghidra script to assist with reverse engineering of
|
|
* iOS apps. It locates all calls to *objc_msgSend* family functions, tries to
|
|
* infer the actual method that gets referenced, and updates cross-references
|
|
* accordingly. If the inferred *objc_msgSend* argument matches more than one
|
|
* method, it tries to determine the class of the called method. When this is
|
|
* not possible, it instead adds a plate comment to all potentially referenced
|
|
* methods that can be then checked manually, to avoid polluting the project
|
|
* with bogus XREFs.
|
|
*
|
|
* This is the first alpha version of FOX. See also the production version
|
|
* maintained by Federico Dotta (@apps3c):
|
|
* https://github.com/federicodotta/ghidra-scripts/tree/main/FOX
|
|
*
|
|
* Usage:
|
|
* - Auto analyze your target binary with the default analyzers (at least)
|
|
* - Copy the script into your ghidra_scripts directory
|
|
* - Open the Script Manager in Ghidra and run the script
|
|
* - You can also run it via the Tools > FOX menu or the shurtcut "X"
|
|
* - Navigate newly updated XREFs (and plate comments if applicable)
|
|
*
|
|
* Caveats:
|
|
* - This script works only with binaries compiled for the AARCH64 architecture
|
|
* - The list of *objc_msgSend* family functions may be incomplete (you can
|
|
* easily add your own, though)
|
|
* - If the arguments passed to *objc_msgSend* in the x1 and x0 registers are
|
|
* set in another code block with no fallthrough, the script might infer the
|
|
* wrong values (could be fixed by looking at decompiled code instead?)
|
|
* - If a method in an external library has the same name of a method in the
|
|
* binary, and this name is unique in the binary, a wrong XREF to the
|
|
* internal method might be added when the call is actually made to the
|
|
* external method (this could be avoided by mapping all external methods via
|
|
* dynamic analysis techniques).
|
|
* - Large binaries require a long processing time: on my MacBook Air,
|
|
* processing speed is about 10 calls to *objc_msgSend* per second (could
|
|
* headless execution improve performance? what about multi-threading?)
|
|
*
|
|
* Inspired by Federico Dotta (@apps3c). Tested with Ghidra v9.2.1.
|
|
*/
|
|
|
|
// This script locates all calls to *objc_msgSend* family functions, tries to
|
|
// infer the actual method that gets referenced, and updates cross-references
|
|
// accordingly. It works only with AARCH64 binaries. Some other caveats apply
|
|
// (see comments to source code).
|
|
// @author Marco Ivaldi <raptor@0xdeadbeef.info>
|
|
// @category iOS
|
|
// @keybinding X
|
|
// @menupath Tools.FOX
|
|
// @toolbar
|
|
|
|
import java.util.List;
|
|
import java.util.ArrayList;
|
|
|
|
import ghidra.app.script.GhidraScript;
|
|
import ghidra.program.model.symbol.*;
|
|
import ghidra.program.model.listing.*;
|
|
import ghidra.program.model.lang.*;
|
|
import ghidra.program.model.address.*;
|
|
import ghidra.app.util.DisplayableEol;
|
|
|
|
public class FOX_alpha extends GhidraScript
|
|
{
|
|
@Override
|
|
public void run() throws Exception
|
|
{
|
|
// *objc_msgSend* family functions
|
|
// CAVEAT: this list may be incomplete
|
|
List<String> msgSendFuncs = new ArrayList<>(List.of(
|
|
"_objc_msgSend", "_objc_msgSend_stret",
|
|
"_objc_msgSendSuper", "_objc_msgSendSuper_stret",
|
|
"_objc_msgSendSuper2", "_objc_msgSendSuper2_stret"
|
|
));
|
|
|
|
// function list
|
|
List<Function> functions = new ArrayList<>();
|
|
|
|
printf("\nFOX_alpha.java - Fix Objective-C XREFs @Ghidra (AARCH64)\n");
|
|
printf("Copyright (c) 2021 Marco Ivaldi <raptor@0xdeadbeef.info>\n\n");
|
|
printf("Attempting to fix Objective-C XREFs...\n\n");
|
|
|
|
// fix XREFs at each call to *objc_msgSend* family functions
|
|
msgSendFuncs.forEach(m -> getFunctions(m, functions));
|
|
functions.forEach(f -> fixXrefs(f));
|
|
}
|
|
|
|
// collect Function objects associated with the specified name
|
|
// @param name function name
|
|
// @param funcs list to add collected Function objects to
|
|
public void getFunctions(String name, List<Function> funcs)
|
|
{
|
|
SymbolTable st = currentProgram.getSymbolTable();
|
|
SymbolIterator si = st.getSymbolIterator();
|
|
|
|
while (si.hasNext()) {
|
|
Symbol s = si.next();
|
|
if ((s.getSymbolType() == SymbolType.FUNCTION) && (!s.isExternal()) && (s.getName().equals(name))) {
|
|
funcs.add(getFunctionAt(s.getAddress()));
|
|
}
|
|
}
|
|
}
|
|
|
|
// process XREFs to *objc_msgSend* family functions and attempt to fix them
|
|
// @param dstFunc destination function
|
|
public void fixXrefs(Function dstFunc)
|
|
{
|
|
// get XREFs
|
|
// the getReferencesTo() method of the FlatProgramAPI is limited to 4096 records
|
|
// for this reason this script uses ReferenceManager::getReferencesTo() instead
|
|
Address dstAddr = dstFunc.getEntryPoint();
|
|
ReferenceManager refman = currentProgram.getReferenceManager();
|
|
ReferenceIterator ri = refman.getReferencesTo(dstAddr);
|
|
Listing listing = currentProgram.getListing(); // speed hack
|
|
int i = 0;
|
|
|
|
// process XREFs
|
|
// CAVEAT: large binaries require a long processing time
|
|
while (ri.hasNext()) {
|
|
Reference ref = ri.next();
|
|
i++;
|
|
|
|
if (ref.getReferenceType().isCall()) {
|
|
Address callAddr = ref.getFromAddress();
|
|
Function srcFunc = getFunctionContaining(callAddr);
|
|
|
|
if ((srcFunc != null) && (!srcFunc.isThunk())) {
|
|
// print *objc_msgSend* call address and caller function name
|
|
String srcName = srcFunc.getName();
|
|
printf("0x%s in %s (%d)\n", callAddr.toString(), srcName, i);
|
|
|
|
// infer method name and collect corresponding Function objects
|
|
String methodName = getMethodName(listing, callAddr);
|
|
List<Function> methods = new ArrayList<>();
|
|
getFunctions(methodName, methods);
|
|
|
|
// add XREF (or plate comment)
|
|
addXref(listing, methods, callAddr, srcName);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// add XREF (or plate comment)
|
|
// @param list program listing
|
|
// @param methods list of matching methods
|
|
// @param addr *objc_msgSend* call address
|
|
// @param name *objc_msgSend* caller function name
|
|
public void addXref(Listing list, List<Function> methods, Address addr, String name)
|
|
{
|
|
// if the argument matches only one method, add an XREF
|
|
if (methods.size() <= 1) {
|
|
methods.forEach(m -> addInstructionXref(addr, m.getEntryPoint(), -1, FlowType.UNCONDITIONAL_CALL));
|
|
return;
|
|
}
|
|
|
|
// if the argument matches more than one method, try to infer class name and add a corresponding XREF
|
|
String className = getClassName(list, addr);
|
|
if ((className != null) && (className.length() > 0)) {
|
|
methods.forEach(m -> addXrefWithClass(m, addr, className));
|
|
}
|
|
|
|
// if no reference was added, add a plate comment to all matching methods instead
|
|
if (getReferencesFrom(addr).length < 2) {
|
|
methods.forEach(m -> addComment(m, addr, name));
|
|
}
|
|
}
|
|
|
|
// search backwards for the method argument passed to *objc_msgSend* in the x1 register
|
|
// CAVEAT: if the register is set in another code block with no fallthrough, the script might infer the wrong value
|
|
// @param list program listing
|
|
// @param addr *objc_msgSend* call address
|
|
// @return name of the method argument to *objc_msgSend*
|
|
public String getMethodName(Listing list, Address addr)
|
|
{
|
|
InstructionIterator ii = list.getInstructions(addr, false);
|
|
|
|
while (ii.hasNext()) {
|
|
Instruction instr = ii.next();
|
|
Register reg = instr.getRegister(0);
|
|
|
|
if ((reg != null) && (reg.getName().equals("x1"))) {
|
|
|
|
// it looks like the method name is always returned this way,
|
|
// even if there is more than one line in the autocomment...
|
|
DisplayableEol eol = new DisplayableEol(instr, true, true, true, true, 1, true);
|
|
String[] comment = eol.getAutomaticComment();
|
|
|
|
// extract the real method name from autocomment, handling empty comments
|
|
if ((comment != null) && (comment.length > 0)) {
|
|
String realName = comment[0].replaceAll("\"", "").replace("= ", "");
|
|
printf("\t%s | %s\n", instr, realName);
|
|
return realName;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
// search backwards for the class argument passed to *objc_msgSend* in the x0 register
|
|
// CAVEAT: if the register is set in another code block with no fallthrough, the script might infer the wrong value
|
|
// @param list program listing
|
|
// @param addr *objc_msgSend* call address
|
|
// @return name of the class argument to *objc_msgSend*
|
|
public String getClassName(Listing list, Address addr)
|
|
{
|
|
CodeUnitFormatOptions opt = new CodeUnitFormatOptions(
|
|
CodeUnitFormatOptions.ShowBlockName.NEVER,
|
|
CodeUnitFormatOptions.ShowNamespace.NEVER,
|
|
"", false, false, false, true, false, false, false);
|
|
CodeUnitFormat cuf = new CodeUnitFormat(opt);
|
|
|
|
InstructionIterator ii = list.getInstructions(addr, false);
|
|
|
|
while (ii.hasNext()) {
|
|
Instruction instr = ii.next();
|
|
Register reg = instr.getRegister(0);
|
|
|
|
// extract the real class name from operand representation, if present
|
|
if ((reg != null) && (reg.getName().equals("x0"))) {
|
|
String op = cuf.getOperandRepresentationString(instr, 0);
|
|
String realName = op.replace("x0=>", "");
|
|
realName = realName.equals("x0") ? null : realName;
|
|
printf("\t%s | %s\n", instr, realName);
|
|
return realName;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
// add an XREF to a method if it belongs to the specified class
|
|
// @param method method to which the XREF is to be added
|
|
// @param addr *objc_msgSend* call address
|
|
// @param name name of class to which the method must belong
|
|
public void addXrefWithClass(Function method, Address addr, String name)
|
|
{
|
|
if (method.getParentNamespace().getName().equals(name)) {
|
|
addInstructionXref(addr, method.getEntryPoint(), -1, FlowType.UNCONDITIONAL_CALL);
|
|
}
|
|
}
|
|
|
|
// add a plate comment to a method to indicate a potential XREF
|
|
// @param method method to which the plate comment is to be added
|
|
// @param addr *objc_msgSend* call address
|
|
// @param name name of the caller function
|
|
public void addComment(Function method, Address addr, String name)
|
|
{
|
|
String tag = "Potential XREF: " + addr.toString() + " " + name;
|
|
|
|
// set new plate comment
|
|
String cur = method.getComment();
|
|
if (cur == null) {
|
|
method.setComment(tag);
|
|
return;
|
|
}
|
|
|
|
// append tag to plate comment if not present already
|
|
String[] comments = method.getCommentAsArray();
|
|
for (int i = 0; i < comments.length; i++) {
|
|
if (comments[i].startsWith(tag)) {
|
|
return;
|
|
}
|
|
}
|
|
method.setComment(cur + "\n" + tag);
|
|
}
|
|
}
|