-
Notifications
You must be signed in to change notification settings - Fork 29
/
FOX_alpha.java
276 lines (245 loc) · 10.6 KB
/
FOX_alpha.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
/*
* FOX_alpha.java - Fix Objective-C XREFs @Ghidra (AARCH64)
* Copyright (c) 2021 Marco Ivaldi <[email protected]>
*
* "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 <[email protected]>
// @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 <[email protected]>\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);
}
}