-
Notifications
You must be signed in to change notification settings - Fork 4
/
Copy pathdatabase.py
291 lines (227 loc) · 8.67 KB
/
database.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
# =============================================================================
# >> IMPORTS
# =============================================================================
# Python
import cPickle as pickle
# IDA
try:
import idaapi
from idautils import Strings
from idautils import Functions
from idautils import XrefsTo
from idautils import FuncItems
from idautils import XrefsFrom
from idc import GetFunctionName
from idc import GetFunctionAttr
from idc import GetFuncOffset
from idc import FUNCATTR_START
CALL_JUMP_FLAGS = (
idaapi.fl_CF,
idaapi.fl_CN,
idaapi.fl_JF,
idaapi.fl_JN,
)
except ImportError:
print 'Script has been called outside of IDA.'
# =============================================================================
# >> CLASSES
# =============================================================================
class Database(object):
"""Create a pickle-able database of the analysed binary."""
def __init__(self):
"""Initialize the database."""
# {<function ea>: <Function object>, ...}
self.functions = {}
# {<string ea>: <str or None>, ...}
self.strings = {}
print 'Creating database...'
self._fill_strings()
self._fill_functions()
self._add_function_strings()
print 'Database has been created!'
def _fill_strings(self):
"""Fill the ``strings`` dict."""
strings = self.strings
for string in Strings():
try:
strings[string.ea] = str(string)
except TypeError:
# I forgot when this can happen...
continue
def _fill_functions(self):
"""Fill the ``functions`` dict."""
functions = self.functions
for ea in Functions():
if GetFunctionName(ea).startswith('_ZThn'):
continue
functions[ea] = Function(self, ea)
def _add_function_strings(self):
"""Add the strings to the functions that use them."""
for ea in self.strings.keys():
references = tuple(XrefsTo(ea))
del_count = 0
for xref in references:
func_ea = GetFunctionAttr(xref.frm, FUNCATTR_START)
# Is the reference not a function?
# Actually, we should compare func_ea == -1, but this seems to
# be buggy. The -1 is probably returned as an unsigned int,
# which results in 4294967295.
if func_ea == 4294967295:
del_count += 1
continue
self.get_function(func_ea).add_string(ea)
# No need to keep strings without a reference or without a
# reference to a function.
if del_count == len(references):
del self.strings[ea]
def get_function_by_symbol(self, symbol):
"""Retrieve a function by its symbol.
:param str symbol: Symbol of the function.
:rtype: Function
:raise ValueError: Raised when the symbol was not found.
"""
for function in self.functions.itervalues():
if function.symbol == symbol:
return function
raise ValueError('Symbol "{0}" not found.'.format(symbol))
def get_function(self, ea):
"""Retrieve a function by its ea value."""
return self.functions[ea]
def remove_string(self, ea):
"""Remove a string from the database."""
del self.strings[ea]
for function in self.functions.itervalues():
function.remove_string(ea)
def get_string(self, ea):
"""Retrieve a string by its ea value."""
return self.strings[ea]
def save(self, file_path):
"""Pickle the database and save it to the given path.
:param str file_path: Path to save the database at.
"""
print 'Saving database...'
with open(file_path, 'wb') as f:
pickle.dump(self, f)
print 'Database has been saved!'
@staticmethod
def load(file_path):
"""Load the database from the given file path.
:param str file_path: Path of the saved database.
"""
print 'Loading database...'
with open(file_path, 'rb') as f:
result = pickle.load(f)
print 'Database has been loaded!'
return result
def cleanup(self, other):
"""Compare this database with the given one and remove all platform
specific strings.
:param Database other: Database to compare to.
"""
print 'Cleaning up first database...'
self._cleanup(other)
print 'Cleaning up second database...'
other._cleanup(self)
print 'Databases have been cleaned up!'
def _cleanup(self, other):
self_strings = self.strings.values()
for ea, string in other.strings.items():
if string not in self_strings:
other.remove_string(ea)
class Function(object):
"""Represents a function."""
def __init__(self, database, ea):
"""Initialize the object.
:param Database database: Database that stores this function.
:param int ea: Start address of the function.
"""
#: Database that stores this function
self.database = database
#: Start address of this function
self.ea = ea
#: Symbol of this function
self.symbol = GetFunctionName(ea)
#: Demangled name of this function
self.demangled_name = GetFuncOffset(ea)
#: All strings that are used in this function
self.string_eas = set()
self._strings = None
#: All function addresses that call this function
self.xref_to_eas = set(self._get_xref_to_calls(ea))
self._xrefs_to = None
#: All function addresses that are called by this function
self.xref_from_eas = set(self._get_xref_from_calls(ea))
self._xrefs_from = None
#: Boolean that indicated if the function has been renamed
self.renamed = False
def add_string(self, ea):
"""Add a string to the function."""
self.string_eas.add(ea)
def remove_string(self, ea):
"""Remove a string from the function."""
self.string_eas.discard(ea)
def rename(self, linux_func):
"""Rename the function to its Linux equivalent.
:param Function linux_func: The Linux equivalent of this function.
"""
self.symbol = linux_func.symbol
self.demangled_name = linux_func.demangled_name
self.renamed = True
@property
def strings(self):
"""Return all strings contained by this function.
:rtype: set
"""
if self._strings is None:
database = self.database
self._strings = set(
database.get_string(ea) for ea in self.string_eas)
return self._strings
@property
def xrefs_to(self):
"""Return all functions that call this function.
:rtype: set
"""
if self._xrefs_to is None:
database = self.database
self._xrefs_to = set(
database.get_function(ea) for ea in self.xref_to_eas)
return self._xrefs_to
@property
def xrefs_from(self):
"""Return all functions that are called by this function.
:rtype: set
"""
if self._xrefs_from is None:
database = self.database
self._xrefs_from = set(
database.get_function(ea) for ea in self.xref_from_eas)
return self._xrefs_from
@staticmethod
def _get_xref_to_calls(ea):
"""Return a generator to iterate over all function addresses which
call the given function.
:param int ea: Start address of the function.
"""
for xref in XrefsTo(ea):
if xref.type not in CALL_JUMP_FLAGS:
continue
if GetFunctionName(xref.to).startswith('_ZThn'):
continue
yield xref.to
def _get_xref_from_calls(self, ea):
"""Return a generator to iterate over all function address that are
called in the given function address.
:param int ea: Start address of the function.
"""
# Code has been taken from here: https://github.com/darx0r/Reef
for item in FuncItems(ea):
for ref in XrefsFrom(item):
if ref.type not in CALL_JUMP_FLAGS:
continue
if ref.to in FuncItems(ea):
continue
# call loc_<label name> and other stuff we don't want
if ref.to not in self.database.functions:
continue
yield ref.to