-
Notifications
You must be signed in to change notification settings - Fork 0
/
DumpAllVirtuals.java
251 lines (202 loc) · 8.99 KB
/
DumpAllVirtuals.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
// Dump virtual tables from the android version of GD and libcocos
// @author Mat, Calloc
// @category GD-Reverse-Engineering
/* To Anyone who wants to know why I modified this script for, I have a python script for building vtables in a header file
* so they can all get sent back to ghidra, this script might also be good for solving older versions of the game
* where we don't know or have no idea what robtop may have fucked around with.
*
* also renamed the category to GD-Reverse-Engineering so I can find my scripts I will add to github in the future including modified ones...
*/
import ghidra.app.script.GhidraScript;
// import ghidra.program.model.mem.*;
// import ghidra.program.model.lang.*;
// import ghidra.program.model.pcode.*;
// import ghidra.program.model.util.*;
// import ghidra.program.model.reloc.*;
import ghidra.program.model.data.*;
// import ghidra.program.model.block.*;
import ghidra.program.model.symbol.*;
// import ghidra.program.model.scalar.*;
import ghidra.program.model.listing.*;
// import ghidra.program.flatapi.FlatProgramAPI;
import ghidra.program.model.address.*;
// import ghidra.program.model.symbol.SymbolType;
import java.io.PrintWriter;
import java.util.ArrayList;
// import java.util.Arrays;
import java.util.HashMap;
public class DumpAllVirtuals extends GhidraScript {
int PTR_SIZE;
SymbolTable table;
Listing listing;
Symbol getChildOfName(Symbol parent, String name) {
for (var child : table.getChildren(parent)) {
if (child.getName().equals(name))
return child;
}
return null;
}
Data createPtrAt(Address addr) throws Exception {
Data data = listing.getDataAt(addr);
if (!data.isDefined())
data = listing.createData(addr, PointerDataType.dataType);
return data;
}
Address addrAtData(Data data) throws Exception {
return (Address)data.getValue();
}
Address removeThumbOffset(Address addr) {
// thumb addresses are stored as actual addr + 1
if (addr.getOffset() % 2 == 1) {
addr = addr.subtract(1);
}
return addr;
}
boolean isTypeinfo(Address addr) {
var com = listing.getComment(CodeUnit.PLATE_COMMENT, addr);
if (com == null) return false;
return com.contains("typeinfo");
// this.currentProgram.getSymbolTable().getPrimarySymbolAt(addr).getName().equals("typeinfo");
}
boolean isStartOfVtable(Address addr) throws Exception {
if (hasVtableComment(addr)) return true;
// (Mat) on itanium, vtable starts with 0 or a negative number,
// and then a pointer to type info.
// get the value of the pointer as an int, and see if its non positive
var offset = currentProgram.getMemory().getInt(addr);
var result = offset <= 0;
// the ptr after must be of a typeinfo
result = result && isTypeinfo(readPtrAt(addr.add(PTR_SIZE)));
return result;
}
Address readPtrAt(Address addr) throws Exception {
var unkData = listing.getDataAt(addr);
if (PTR_SIZE == 4) {
return toAddr(unkData.getInt(0));
} else {
return toAddr(unkData.getLong(0));
}
}
boolean hasVtableComment(Address addr) {
var com = listing.getComment(CodeUnit.PLATE_COMMENT, addr);
if (com == null) return false;
return com.contains("vtable");
}
HashMap<String, ArrayList<ArrayList<String>>> classes = new HashMap<>();
void processNamespace(Namespace cl) {
var name = cl.getName(true);
if (name.contains("switch")) return;
if (name.contains("llvm")) return;
if (name.contains("tinyxml2")) return;
if (name.contains("<")) return;
if (name.contains("__")) return;
if (name.contains("fmt")) return;
if (name.contains("std::")) return;
if (name.contains("pugi")) return;
// (Mat) i think theyre correct already
// EDIT: (Calloc) we need cocos2d:: for our android 32 bit vtables...
// We also don't know what robtop fucked around with on earlier versions of the game...
// if (name.contains("cocos2d::")) return;
// theres only one vtable on android,
// NOTE: (Calloc) There can be multiple if we have a delegate in the class (This is annoying I know...)
var vtable = getChildOfName(cl.getSymbol(), "vtable");
// and if there is none then we dont care
if (vtable == null) return;
// if (!name.equals("GJBaseGameLayer")) return;
println("Dumping " + name);
// println("DEBUG: (VTABLE): " + vtable.getName());
ArrayList<ArrayList<String>> bases = new ArrayList<>();
classes.put(name, bases);
var vtableAddr = vtable.getProgramLocation().getAddress();
try {
var curAddr = vtableAddr;
while (isStartOfVtable(curAddr) && !this.monitor.isCancelled()) {
ArrayList<String> virtuals = new ArrayList<>();
curAddr = curAddr.add(PTR_SIZE * 2);
while (!this.monitor.isCancelled()) {
if (isStartOfVtable(curAddr)) break;
// idk what this is for
// if (listing.getComment(CodeUnit.PLATE_COMMENT, curAddr) != null) break;
// ok, we're probably at the functions now!
var functionAddr = removeThumbOffset(readPtrAt(curAddr));
// some vtables have nullptrs in them, like GJBaseGameLayer
// since they are pure virtual or something
if (functionAddr.getUnsignedOffset() == 0) {
curAddr = curAddr.add(PTR_SIZE);
continue;
}
var function = listing.getFunctionAt(functionAddr);
if (function == null) break;
if (function.getName().contains("pure_virtual")) {
virtuals.add("pure_virtual_" + curAddr.toString() + "()");
} else {
var comment = listing.getComment(CodeUnit.PLATE_COMMENT, functionAddr);
var funcSig = comment.replaceAll("^(non-virtual thunk to )?(\\w+::)+(?=~?\\w+\\()", "");
virtuals.add(funcSig);
}
curAddr = curAddr.add(PTR_SIZE);
}
bases.add(virtuals);
// (Mat) we've reached another class's vtable! abort!!
if (hasVtableComment(curAddr) || hasVtableComment(curAddr.add(PTR_SIZE))) break;
// (Mat) risky but whatever
// if (readPtrAt(curAddr).getOffset() == 0) return;
}
} catch (Exception e) {}
}
public void run() throws Exception {
println("-------- STARTING -------");
PTR_SIZE = currentProgram.getDefaultPointerSize();
table = currentProgram.getSymbolTable();
listing = currentProgram.getListing();
table.getChildren(currentProgram.getGlobalNamespace().getSymbol()).forEach((sy) -> {
if (!sy.getSymbolType().equals(ghidra.program.model.symbol.SymbolType.CLASS) &&
!sy.getSymbolType().equals(ghidra.program.model.symbol.SymbolType.NAMESPACE)) return;
// var cl = (Namespace)sy;
// (Mat) ghidra is so stupid istg
var cl = table.getNamespace(sy.getName(), currentProgram.getGlobalNamespace());
processNamespace(cl);
});
// EDIT: (Calloc) Enable this...
if (true) {
var cocosNs = table.getNamespace("cocos2d", currentProgram.getGlobalNamespace());
table.getChildren(cocosNs.getSymbol()).forEach((sy) -> {
if (!sy.getSymbolType().equals(ghidra.program.model.symbol.SymbolType.CLASS) &&
!sy.getSymbolType().equals(ghidra.program.model.symbol.SymbolType.NAMESPACE)) return;
var cl = table.getNamespace(sy.getName(), cocosNs);
processNamespace(cl);
});
}
println("Generating json..");
var file = askFile("Save json output", "Save");
if (file == null || file.exists()) return;
var writer = new PrintWriter(file, "UTF-8");
try {
// writing json output manually..
writer.write("{");
boolean first1 = true;
for (var name : classes.keySet()) {
if (!first1) writer.write(",");
writer.write("\"" + name + "\":[");
boolean first2 = true;
for (var table : classes.get(name)) {
if (!first2) writer.write(",");
writer.write("[");
boolean first3 = true;
for (var func : table) {
if (!first3) writer.write(",");
writer.write("\"" + func + "\"");
first3 = false;
}
writer.write("]");
first2 = false;
}
writer.write("]");
first1 = false;
}
writer.write("}");
} finally {
writer.close();
}
}
}