forked from 0xdea/ghidra-scripts
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathResolveMipsN32LinuxSyscallsScript.java
308 lines (277 loc) · 12.1 KB
/
ResolveMipsN32LinuxSyscallsScript.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
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
/*
* ResolveMipsN32LinuxSyscallsScript.java - MIPS N32 Linux
* Copyright (c) 2022 Marco Ivaldi <[email protected]>
*
* "The only intuitive interface is the nipple." -- Anonymous
*
* ResolveMipsN32LinuxSyscallsScript is a script based on the original
* ResolveX86orX64LinuxSyscallsScript distributed with Ghidra. It uses
* overriding references and the symbolic propogator (sic) to resolve
* system calls in MIPS N32 binaries, as described in the Advanced Ghidra
* Class documentation available at:
* https://github.com/NationalSecurityAgency/ghidra/blob/master/GhidraDocs/
*
* This script can assist in analyzing static binaries. It was tested with
* Ghidra v10.2.1 on an ELF 32-bit MSB executable, MIPS, N32 MIPS64 rel2
* version 1 (SYSV), statically linked, stripped Linux binary (Cavium
* Octeon III processor). Porting to other architectures should be trivial.
*
* NOTE.
* The file syscall_numbers/mips_n32_linux_syscall_numbers must be copied
* into /Ghidra/Features/Base/data/ in your Ghidra installation directory.
*/
//Uses overriding references and the symbolic propogator (sic) to resolve
//system calls
//@author NSA and Marco Ivaldi <[email protected]>
//@category Analysis
import java.io.*;
import java.util.*;
import java.util.Map.Entry;
import java.util.function.Predicate;
import generic.jar.ResourceFile;
import ghidra.app.cmd.function.ApplyFunctionDataTypesCmd;
import ghidra.app.cmd.memory.AddUninitializedMemoryBlockCmd;
import ghidra.app.plugin.core.analysis.AutoAnalysisManager;
import ghidra.app.plugin.core.analysis.ConstantPropagationContextEvaluator;
import ghidra.app.script.GhidraScript;
import ghidra.app.services.DataTypeManagerService;
import ghidra.app.util.opinion.ElfLoader;
import ghidra.framework.Application;
import ghidra.program.model.address.*;
import ghidra.program.model.data.DataTypeManager;
import ghidra.program.model.lang.Register;
import ghidra.program.model.lang.SpaceNames;
import ghidra.program.model.listing.*;
import ghidra.program.model.mem.MemoryAccessException;
import ghidra.program.model.pcode.PcodeOp;
import ghidra.program.model.symbol.*;
import ghidra.program.util.ContextEvaluator;
import ghidra.program.util.SymbolicPropogator;
import ghidra.program.util.SymbolicPropogator.Value;
import ghidra.util.Msg;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor;
/**
* This script will resolve system calls for MIPS N32 Linux binaries.
* It should be straightforward to modify this script for other cases.
*/
public class ResolveMipsN32LinuxSyscallsScript extends GhidraScript {
private static final String MIPS = "MIPS";
private static final String SYSCALL_SPACE_NAME = "syscall";
private static final int SYSCALL_SPACE_LENGTH = 0x10000;
//this is the name of the userop (aka CALLOTHER) in the pcode translation of the
//native "syscall" instruction
private static final String SYSCALL_MIPS_CALLOTHER = "syscall";
//a set of names of all syscalls that do not return
private static final Set<String> noreturnSyscalls = Set.of("exit", "exit_group");
//tests whether an instruction is making a system call
private Predicate<Instruction> tester = ResolveMipsN32LinuxSyscallsScript::checkMipsN32Instruction;
//register holding the syscall number
private String syscallRegister = "v0";
//TODO
//datatype archive containing signature of system calls
//private String datatypeArchiveName = "generic_clib_mips_n32";
//file containing map from syscall numbers to syscall names
//note that different architectures can have different system call numbers, even
//if they're both Linux...
private String syscallFileName = "mips_n32_linux_syscall_numbers";
//the type of overriding reference to apply
private RefType overrideType = RefType.CALLOTHER_OVERRIDE_CALL;
//the calling convention to use for system calls (must be defined in the appropriate .cspec file)
//see /Ghidra/Processors/MIPS/data/languages/mips64_32_n32.cspec
private String callingConvention = "__stdcall";
@Override
protected void run() throws Exception {
if (!(currentProgram.getExecutableFormat().equals(ElfLoader.ELF_NAME) &&
currentProgram.getLanguage().getProcessor().toString().equals(MIPS))) {
popup("This script is intended for MIPS Linux files");
return;
}
//get the space where the system calls live.
//If it doesn't exist, create it.
AddressSpace syscallSpace =
currentProgram.getAddressFactory().getAddressSpace(SYSCALL_SPACE_NAME);
if (syscallSpace == null) {
//don't muck with address spaces if you don't have exclusive access to the program.
if (!currentProgram.hasExclusiveAccess()) {
popup("Must have exclusive access to " + currentProgram.getName() +
" to run this script");
return;
}
Address startAddr = currentProgram.getAddressFactory().getAddressSpace(
SpaceNames.OTHER_SPACE_NAME).getAddress(0x0L);
AddUninitializedMemoryBlockCmd cmd = new AddUninitializedMemoryBlockCmd(
SYSCALL_SPACE_NAME, null, this.getClass().getName(), startAddr,
SYSCALL_SPACE_LENGTH, true, true, true, false, true);
if (!cmd.applyTo(currentProgram)) {
popup("Failed to create " + SYSCALL_SPACE_NAME);
return;
}
syscallSpace = currentProgram.getAddressFactory().getAddressSpace(SYSCALL_SPACE_NAME);
}
else {
printf("AddressSpace %s found, continuing...\n", SYSCALL_SPACE_NAME);
}
//get all of the functions that contain system calls
//note that this will not find system call instructions that are not in defined functions
Map<Function, Set<Address>> funcsToCalls = getSyscallsInFunctions(currentProgram, monitor);
if (funcsToCalls.isEmpty()) {
popup("No system calls found (within defined functions)");
return;
}
//get the system call number at each callsite of a system call.
//note that this is not guaranteed to succeed at a given system call call site -
//it might be hard (or impossible) to determine a specific constant
Map<Address, Long> addressesToSyscalls =
resolveConstants(funcsToCalls, currentProgram, monitor);
if (addressesToSyscalls.isEmpty()) {
popup("Couldn't resolve any syscall constants");
return;
}
//get the map from system call numbers to system call names
//you might have to create this yourself!
Map<Long, String> syscallNumbersToNames = getSyscallNumberMap();
//at each system call call site where a constant could be determined, create
//the system call (if not already created), then add the appropriate overriding reference
//use syscallNumbersToNames to name the created functions
//if there's not a name corresponding to the constant use a default
for (Entry<Address, Long> entry : addressesToSyscalls.entrySet()) {
Address callSite = entry.getKey();
Long offset = entry.getValue();
Address callTarget = syscallSpace.getAddress(offset);
Function callee = currentProgram.getFunctionManager().getFunctionAt(callTarget);
if (callee == null) {
String funcName = "syscall_" + String.format("%08X", offset);
if (syscallNumbersToNames.get(offset) != null) {
funcName = syscallNumbersToNames.get(offset);
}
callee = createFunction(callTarget, funcName);
callee.setCallingConvention(callingConvention);
//check if the function name is one of the non-returning syscalls
if (noreturnSyscalls.contains(funcName)) {
callee.setNoReturn(true);
}
}
Reference ref = currentProgram.getReferenceManager().addMemoryReference(callSite,
callTarget, overrideType, SourceType.USER_DEFINED, Reference.MNEMONIC);
//overriding references must be primary to be active
currentProgram.getReferenceManager().setPrimary(ref, true);
}
//TODO
//finally, open the appropriate data type archive and apply its function data types
//to the new system call space, so that the system calls have the correct signatures
/*
AutoAnalysisManager mgr = AutoAnalysisManager.getAnalysisManager(currentProgram);
DataTypeManagerService service = mgr.getDataTypeManagerService();
List<DataTypeManager> dataTypeManagers = new ArrayList<>();
dataTypeManagers.add(service.openDataTypeArchive(datatypeArchiveName));
dataTypeManagers.add(currentProgram.getDataTypeManager());
ApplyFunctionDataTypesCmd cmd = new ApplyFunctionDataTypesCmd(dataTypeManagers,
new AddressSet(syscallSpace.getMinAddress(), syscallSpace.getMaxAddress()),
SourceType.USER_DEFINED, false, false);
cmd.applyTo(currentProgram);
*/
}
//TODO: better error checking!
private Map<Long, String> getSyscallNumberMap() {
Map<Long, String> syscallMap = new HashMap<>();
ResourceFile rFile = Application.findDataFileInAnyModule(syscallFileName);
if (rFile == null) {
popup("Error opening syscall number file, using default names");
return syscallMap;
}
try (FileReader fReader = new FileReader(rFile.getFile(false));
BufferedReader bReader = new BufferedReader(fReader)) {
String line = null;
while ((line = bReader.readLine()) != null) {
//lines starting with # are comments
if (!line.startsWith("#")) {
String[] parts = line.trim().split(" ");
Long number = Long.parseLong(parts[0]);
syscallMap.put(number, parts[1]);
}
}
}
catch (IOException e) {
Msg.showError(this, null, "Error reading syscall map file", e.getMessage(), e);
}
return syscallMap;
}
/**
* Scans through all of the functions defined in {@code program} and returns
* a map which takes a function to the set of address in its body which contain
* system calls
* @param program program containing functions
* @param tMonitor monitor
* @return map function -> addresses in function containing syscalls
* @throws CancelledException if the user cancels
*/
private Map<Function, Set<Address>> getSyscallsInFunctions(Program program,
TaskMonitor tMonitor) throws CancelledException {
Map<Function, Set<Address>> funcsToCalls = new HashMap<>();
for (Function func : program.getFunctionManager().getFunctionsNoStubs(true)) {
tMonitor.checkCanceled();
for (Instruction inst : program.getListing().getInstructions(func.getBody(), true)) {
if (tester.test(inst)) {
Set<Address> callSites = funcsToCalls.get(func);
if (callSites == null) {
callSites = new HashSet<>();
funcsToCalls.put(func, callSites);
}
callSites.add(inst.getAddress());
}
}
}
return funcsToCalls;
}
/**
* Uses the symbolic propogator (sic) to attempt to determine the constant value in
* the syscall register at each system call instruction
*
* @param funcsToCalls map from functions containing syscalls to address in each function of
* the system call
* @param program containing the functions
* @return map from addresses of system calls to system call numbers
* @throws CancelledException if the user cancels
*/
private Map<Address, Long> resolveConstants(Map<Function, Set<Address>> funcsToCalls,
Program program, TaskMonitor tMonitor) throws CancelledException {
Map<Address, Long> addressesToSyscalls = new HashMap<>();
Register syscallReg = program.getLanguage().getRegister(syscallRegister);
for (Function func : funcsToCalls.keySet()) {
Address start = func.getEntryPoint();
ContextEvaluator eval = new ConstantPropagationContextEvaluator(true);
SymbolicPropogator symEval = new SymbolicPropogator(program);
symEval.flowConstants(start, func.getBody(), eval, true, tMonitor);
for (Address callSite : funcsToCalls.get(func)) {
Value val = symEval.getRegisterValue(callSite, syscallReg);
if (val == null) {
createBookmark(callSite, "System Call",
"Couldn't resolve value of " + syscallReg);
printf("Couldn't resolve value of " + syscallReg + " at " + callSite + "\n");
continue;
}
addressesToSyscalls.put(callSite, val.getValue());
}
}
return addressesToSyscalls;
}
/**
* Checks whether a MIPS N32 instruction is a system call
* @param inst instruction to check
* @return true precisely when the instruction is a system call
*/
private static boolean checkMipsN32Instruction(Instruction inst) {
boolean retVal = false;
for (PcodeOp op : inst.getPcode()) {
if (op.getOpcode() == PcodeOp.CALLOTHER) {
int index = (int) op.getInput(0).getOffset();
if (inst.getProgram().getLanguage().getUserDefinedOpName(index).equals(
SYSCALL_MIPS_CALLOTHER)) {
retVal = true;
}
}
}
return retVal;
}
}