-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathfuncs.py
451 lines (371 loc) · 14.4 KB
/
funcs.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
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
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
from binaryninja import BinaryView
from binaryninja.function import Function
from binaryninja.enums import InstructionTextTokenType
def get_start(f: Function) -> int:
"""
Get the function start address.
:param f: The target function.
:return: The start address of the target function.
"""
return f.start
def get_end(f: Function) -> int:
"""
Get the address of the latest byte of a function.
:param f: The target function.
:return: The address of the latest byte of the target function.
"""
return f.address_ranges[-1].end - 1
def get_name(bv: BinaryView, addr: int) -> str:
"""
Get the function symbol name.
:param bv: The current binary view.
:param addr: The function address to use to locate the function.
:return: The function name.
"""
f = bv.get_function_at(addr)
if not f:
return ""
return f.symbol.short_name
def get_type_info(bv: BinaryView, addr: int) -> str:
"""
Get the function return type and its calling convention.
:param bv: The current binary view.
:param addr: The function address to use to locate the function.
:return: A string with the type info following the format
'<return_type> <calling_convention'
"""
f = bv.get_function_at(addr)
if not f:
return ""
calling_convention = f.calling_convention.name.lstrip("_")
return_type = f.return_type.get_string()
if calling_convention == "win64":
calling_convention = "fastcall"
return f"{return_type} {calling_convention}"
def get_args(bv: BinaryView, addr: int) -> str:
"""
Get the function arguments.
:param bv: The current binary view.
:param addr: The function address to use to locate the function.
:return: A string representation of the function parameters
following the format '(<type> arg1, <type> arg2, ...)'
"""
f = bv.get_function_at(addr)
if not f:
return ""
params_list = []
params = f.type.parameters
if params:
for param in params:
params_list.append(f"{param.type} {param.name}")
s = ", ".join(params_list)
return f"({s})"
return "(void)"
def get_basic_blocks_count(bv: BinaryView, addr: int) -> int:
"""
Get the number of basic blocks within a function.
:param bv: The current binary view.
:param addr: The function address to use to locate the function.
:return: The number of basic blocks of the target function.
"""
f = bv.get_function_at(addr)
if not f:
return 0
return len(f.basic_blocks)
def get_callers_info(f: Function) -> list:
"""
Get the name of the function callers and the address from
where the call is being performed.
:param f: The target function.
:return: A list with the caller names and the call locations.
"""
caller_refs = []
for caller in f.caller_sites:
info = {}
info["foreign_val"] = caller.function.name
info["from_addr"] = caller.address
caller_refs.append(info)
return caller_refs
def _is_inside_executable_segment(bv: BinaryView, addr: int) -> bool:
"""
Check if an address is inside an executable segment or not.
:param bv: The current binary view
:param addr: The address to be checked.
:return: If its executable or not.
"""
for segment in bv.segments:
if addr >= segment.start and addr <= segment.end:
return segment.executable
return False
def _parse_data_token(bv: BinaryView, token: InstructionTextTokenType) -> tuple:
"""
Parse a data token and determine what type of data we are
dealing with (e.g. string, function pointer, etc).
:param bv: The current binary view.
:param token: The data token itself.
:return: A tuple containing the token information.
"""
token_value = token.value
# Make sure its a valid address
if bv.is_valid_offset(token_value):
# Check if its a string
token_value_str = bv.get_string_at(token_value)
if token_value_str:
return token_value_str, "string"
if _is_inside_executable_segment(bv, token_value):
token_inst = bv.get_disassembly(token_value)
if token_inst:
token_inst = token_inst.replace(" ", " ")
inst = f"inst ptr -> {token_inst}"
return inst, "instruction"
else:
# Check if its pointing to some other data type
data = bv.get_data_var_at(token_value)
if data is not None:
try:
data_value = data.value
# If its not an int it might be a function name
if not isinstance(data.value, int):
if data.name:
return data.name, "function"
# Otherwise its probably a void pointer
elif data_value is None:
return "void*", "data"
else:
# If its an int check if its a function pointer
if bv.is_valid_offset(data_value):
for f in bv.functions:
if data_value == f.start:
func_name = f.symbol.short_name
if func_name:
return func_name, "function"
value = ""
# Might be just an int so check if its labeled or not
if data.name:
value = f"{data.name} = {hex(data_value)}"
else:
value = f"{data.type.get_string()} {hex(data_value)}"
return value, "data"
except:
pass
return None, None
def get_callee_info(bv: BinaryView, f: Function) -> list:
"""
Get the name, type and value of the callee references.
:param bv: The current binary view.
:param f: The target function.
:return: A list containing the callee refs information.
"""
callees_refs = []
for bb in f.basic_blocks:
disas = bb.get_disassembly_text()
for inst in disas:
info = {}
for token in inst.tokens:
# Check data tokens
if token.type == InstructionTextTokenType.DataSymbolToken:
value, type = _parse_data_token(bv, token)
if value and type:
foreign_val = ""
# Construct the data format based on
# the parsed type results
if type == "function":
foreign_val = value
if type == "string":
str_type = value.type
str_encoding = value._decodings[str_type]
foreign_val = f"[{hex(token.value)}]: {str_encoding} '{value}'"
if type == "data" or type == "instruction":
foreign_val = f"[{hex(token.value)}]: {value}"
info["foreign_val"] = foreign_val
info["from_addr"] = inst.address
callees_refs.append(info)
break
# Check imported functions
elif token.type == InstructionTextTokenType.ImportToken:
info["foreign_val"] = f"[{hex(token.value)}]: extrn {token.text}"
info["from_addr"] = inst.address
callees_refs.append(info)
break
# Check local functions
elif token.type == InstructionTextTokenType.CodeSymbolToken:
info["foreign_val"] = token.text
info["from_addr"] = inst.address
callees_refs.append(info)
break
return callees_refs
def get_indirect_transfers(f: Function) -> list:
"""
Get all the indirect transfers (e.g. call rax) in the current
function.
:param f: The target function.
:return: A list containing all the instruction addresses and
values.
"""
redirect_inst = ""
indirect_calls = []
for block in f.basic_blocks:
disas = block.get_disassembly_text()
for inst in disas:
for i in range(len(inst.tokens) - 2):
info = {}
if inst.tokens[i].text == "call":
redirect_inst = "call"
if inst.tokens[i].text == "jmp":
redirect_inst = "jmp"
if redirect_inst:
if inst.tokens[i+2].type == InstructionTextTokenType.RegisterToken:
info["foreign_val"] = f"{redirect_inst} {inst.tokens[i+2].text}"
info["from_addr"] = inst.address
indirect_calls.append(info)
redirect_inst = ""
break
redirect_inst = ""
return indirect_calls
class FunctionInfo:
"""
Represents the information of a single function.
"""
def __init__(self,
start,
end,
name,
ftype,
args,
basic_blocks_count,
callers_info,
callers_refs_count,
callee_info,
callee_refs_count,
indirect_transfers_info,
indirect_transfers_count
):
# Functions main info
self.start = start
self.end = end
self.name = name
self.type = ftype
self.args = args
self.basic_blocks_count = basic_blocks_count
# Callers refs
self.callers_info = callers_info
self.callers_refs_count = callers_refs_count
# Callee refs
self.callee_info = callee_info
self.callee_refs_count = callee_refs_count
# Indirect transfers
self.indirect_transfers_info = indirect_transfers_info
self.indirect_transfers_count = indirect_transfers_count
class FunctionsInfoMapper:
"""
Represents a mapper with all the functions info records.
"""
funcs_info_list = []
def __init__(self, bv: BinaryView):
self.bv = bv
self.init_funcs_info_list()
def init_funcs_info_list(self) -> None:
"""
Initialize all the functions informations.
:return None:
"""
# Reset list
FunctionsInfoMapper.funcs_info_list = []
for f in self.bv.functions:
# Functions main info
start = get_start(f)
end = get_end(f)
name = get_name(self.bv, f.start)
ftype = get_type_info(self.bv, f.start)
args = get_args(self.bv, f.start)
basic_blocks_count = get_basic_blocks_count(self.bv, f.start)
# Callers refs
callers_info = self._init_callers_info_list(f)
callers_refs_count = len(callers_info)
# Callee refs
callee_info = self._init_callee_info_list(self.bv, f)
callee_refs_count = len(callee_info)
# Indirect transfers
indirect_transfers_info = self._init_indirect_transfers_info_list(f)
indirect_transfers_count = len(indirect_transfers_info)
# Populate a single function with the proper info
func_info = FunctionInfo(
start,
end,
name,
ftype,
args,
basic_blocks_count,
callers_info,
callers_refs_count,
callee_info,
callee_refs_count,
indirect_transfers_info,
indirect_transfers_count
)
# Append the info to our global list
FunctionsInfoMapper.funcs_info_list.append(func_info)
@staticmethod
def _init_callers_info_list(f: Function) -> list:
"""
Set the 'refs from' info for a function.
:param f: The target function.
:return: A list containing all the callers info of a function.
"""
callers_info = get_callers_info(f)
if not callers_info:
return []
callers_info_list = []
for caller_info in callers_info:
try:
info = {}
info["foreign_val"] = caller_info["foreign_val"]
info["from_addr"] = hex(caller_info["from_addr"])
callers_info_list.append(info)
except KeyError:
pass
return callers_info_list
@staticmethod
def _init_callee_info_list(bv: BinaryView, f: Function) -> list:
"""
Set the 'refs to' info for a function.
:param bv: The current binary view.
:param f: The target function.
:return: A list containing all the callee info of a function.
"""
callees_info = get_callee_info(bv, f)
if not callees_info:
return []
callee_info_list = []
# Set the 'refs to' info for the function
for callee_info in callees_info:
try:
info = {}
info["foreign_val"] = callee_info["foreign_val"]
info["from_addr"] = hex(callee_info["from_addr"])
callee_info_list.append(info)
except KeyError:
pass
return callee_info_list
@staticmethod
def _init_indirect_transfers_info_list(f: Function) -> list:
"""
Set the indirect transfers info for the function.
:param f: The target function.
:return: A list containing all the indirect transfers info of
a function.
"""
indirect_transfers_info = get_indirect_transfers(f)
if not indirect_transfers_info:
return []
indirect_transfers_info_list = []
# Set the indirect transfers info for the function
for call_info in indirect_transfers_info:
try:
info = {}
info["foreign_val"] = call_info["foreign_val"]
info["from_addr"] = hex(call_info["from_addr"])
indirect_transfers_info_list.append(info)
except KeyError:
pass
return indirect_transfers_info_list