-
Notifications
You must be signed in to change notification settings - Fork 1
/
FIOFinder.py
executable file
·250 lines (202 loc) · 8.03 KB
/
FIOFinder.py
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
from ghidra.app.decompiler import DecompileOptions
from ghidra.app.decompiler import DecompInterface
from ghidra.util.task import ConsoleTaskMonitor
from ghidra.program.model.symbol.SourceType import *
def stringToAddress(addr):
"""
Create Ghidra address object from string containing address
:param addr: String with address representation
:return: Ghidra address object
"""
return currentProgram.getAddressFactory().getDefaultAddressSpace().getAddress(addr)
def convToBytes(addr, len=4):
"""
Ghidra getBytes, but returns actual unsigned bytes instead of signed representations
:param addr: Valid adress object of in-memory string
:param len: Buffer len to read.
:return: Map with read bytes
"""
return map(lambda b: b & 0xff, getBytes(addr,len))
def getPtrFromMemory(addr):
"""
Decode 32 bit LE value from a memory space into a string representation
TODO: Shall this be left as a string? Makes it easy to use stringToAddress
for decoding pointers, and avoiding nonsense with lack of unsinged
values in Python
:param addr: Valid adress object of in-memory string
:return: String representation of a value, encoded as base 16
"""
data = convToBytes(addr,4)
val = data[0] + (data[1] << 8) + (data[2] << 16) + (data[3] << 24)
return hex(val).rstrip("L")
def getStringFromMemory(addr, len=0x64):
"""
Naive cstring string decoder
:param addr: Valid adress object of in-memory string
:param len: Maximum buffer len to decode.
:return: Decoded string
"""
msg = bytearray(convToBytes(addr, len))
result = ""
try:
return msg.decode().split('\x00')[0]
except UnicodeDecodeError as e:
return msg.decode(errors="ignore").split('\x00')[0]
def getCallOps(addr):
"""
Get operands of a function call
Based on https://github.com/HackOvert/GhidraSnippets
:param addr: Valid adress object with function call
:return: Inputs of operand at :address:
"""
options = DecompileOptions()
monitor = ConsoleTaskMonitor()
ifc = DecompInterface()
ifc.setOptions(options)
ifc.openProgram(currentProgram)
func = getFunctionContaining(addr)
res = ifc.decompileFunction(func, 60, monitor)
high_func = res.getHighFunction()
pcodeops = high_func.getPcodeOps(addr)
op = pcodeops.next()
return op.getInputs()
def uniqueToString(op):
"""
Decode OperandInput from getUniqueValue into a string
:param op: Operand to decode value from
:return: String with decoded value
"""
return hex(op.getOffset()).rstrip("L")
def getUniqueValue(node):
"""
Attempt to decode a value from PcodeOps
For constant values returns a value.
For unique values attempts to follow the chain.
see https://github.com/NationalSecurityAgency/ghidra/discussions/3711
TODO: Return computed value instead of operand input objects.
This will become handy when properly working with PTRSUB and others.
:param node: Single PcodeOps node to decode:
:return: Operand input
"""
if node.isUnique():
tmp = node.getDef()
inp = tmp.getInputs()
if len(inp) == 1:
if tmp.getMnemonic() == "CAST":
return getUniqueValue(inp[0])
else:
return inp[0]
elif len(inp) == 2 and tmp.getMnemonic() == "PTRSUB":
if inp[0].isConstant() and inp[1].isConstant():
return inp[1]
elif node.isConstant:
return node.getHigh().getScalar()
else:
return None
def decodeCallArgs(addr, argNo):
"""
Decodes arguments of CreateStateObject function call
TODO: Return arguments instead of printing them
:param addr: Valid adress object with function call
"""
name = "N/A"
try:
ops = getCallOps(addr)
data = getUniqueValue(ops[argNo])
print("Data: {}".format(data))
except Exception as e:
print("Exception: {}".format(e))
return data
def runFinder(refs, mnemonic, argNo):
results = {}
for ref in refs:
addr = ref.getFromAddress()
ins = getInstructionAt(addr)
# TODO: This is a very naive method, using disassembler to get "CALL" shall be better
# TODO: Attempt to find thunks, and process them too?
if ins.getMnemonicString().startswith(mnemonic):
print ("branch at {}, {}".format(addr.toString(), ins.toString()))
name = decodeCallArgs(addr, argNo)
if not name in results.keys():
results[name] = []
results[name].append(addr)
else:
print ("not a branch at {}, {}".format(addr.toString(), ins.toString()))
return results
def nameFunctions(calls, prefix = "", stub_names = None):
stubs = {}
for name, ptrs in calls.items():
sources = []
for p in ptrs:
fn = getFunctionContaining(p)
sources.append(fn)
sources = list(set(sources)) #keep unique values
if len(sources) > 1:
print("{}: Multiple functions found, skipping! {}".format(name, sources))
else:
if not prefix or name.startswith(prefix):
newName = name
else:
newName = "{]{}".format(prefix, name)
func = sources[0]
pFunc = sources[0].getEntryPoint()
print("Rename {} as {}".format(sources[0], newName))
func.setName(newName, USER_DEFINED)
if stub_names and name in stub_names:
ml_name = stub_names[name]
print(" + Create a label with ML name {}".format(ml_name))
createLabel(pFunc, ml_name, False)
stubs[stub_names[name]] = (pFunc, name)
return stubs
def printStubs(stubs):
print("Insert into stubs.S: ")
for name, data in stubs.items():
if name[0] != "_":
name = " " + name
addr = data[0]
comment = data[1]
tmp = "THUMB_FN(0x{}, {})".format(addr, name).ljust(50)
print("{}// {}".format(tmp, comment))
FIO_ML1 = {
"Open" : "_FIO_OpenFile",
"Create" : "_FIO_CreateFile",
"Remove" : "_FIO_RemoveFile",
"Read" : "_FIO_ReadFile",
"Search" : "FIO_SeekSkipFile",
"Write" : "_FIO_WriteFile",
"Close" : "FIO_CloseFile",
"AcqSize" : "_FIO_GetFileSize64",
"Rename" : "_FIO_RenameFile",
"CreateDir" : "_FIO_CreateDirectory",
"Flush" : "FIO_Flush",
"FirstEnt" : "_FIO_FindFirstEx",
"NextEnt" : "FIO_FindNextEx",
"CloseEnt" : "FIO_FindClose"
}
FIO_ML2 = {
"FIO_OpenFile" : "_FIO_OpenFile",
"FIO_CreateFile" : "_FIO_CreateFile",
"FIO_RemoveFile" : "_FIO_RemoveFile",
"FIO_ReadFile" : "_FIO_ReadFile",
"FIO_SeekSkipFile" : "FIO_SeekSkipFile",
"FIO_WriteFile" : "_FIO_WriteFile",
"FIO_CloseFile" : "FIO_CloseFile",
"FIO_GetFileSize64" : "_FIO_GetFileSize64",
"FIO_RenameFile" : "_FIO_RenameFile",
"FIO_CreateDirectory" : "_FIO_CreateDirectory",
"FIO_Flush" : "FIO_Flush",
"FIO_FindFirstEx" : "_FIO_FindFirstEx",
"FIO_FindNextEx" : "FIO_FindNextEx",
"FIO_FindClose" : "FIO_FindClose"
}
# Get all *known* xrefs to FIO_logger()
refs = getReferencesTo(toAddr("FIO_logger"))
# Note: This will fail if function is not yet defined
# On fail, just go to the address and create a missing function.
# Repeat until it won't fail.
calls = runFinder(refs, "bl", 1)
stubs = nameFunctions(calls, "FIO_", FIO_ML2)
#refs = getReferencesTo(toAddr("mzrm_functable_logger"))
#calls = runFinder(refs, "callx8", 3)
#stubs = nameFunctions(calls)
#printStubs(stubs)