-
Notifications
You must be signed in to change notification settings - Fork 5
/
Copy path__init__.py
236 lines (198 loc) · 9.29 KB
/
__init__.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
#
# Description: Binary Ninja plugin to decompile all the codebase in Pseudo C
# and dump it into a given directory, though File -> Export would also work for some
# cases instead of using this plugin, depending on what you are trying to achieve.
#
# Author: Asher Davila (@asher_davila)
# https://github.com/AsherDLL/PCDump-bn
#
import calendar
import ntpath
import os
import platform
import re
import time
from binaryninja.binaryview import BinaryView
from binaryninja.enums import DisassemblyOption, FunctionAnalysisSkipOverride
from binaryninja.function import DisassemblySettings, Function
from binaryninja.interaction import get_directory_name_input
from binaryninja.lineardisassembly import LinearViewCursor, LinearViewObject
from binaryninja.log import log_alert, log_error, log_info, log_warn
from binaryninja.plugin import BackgroundTaskThread, PluginCommand
class PseudoCDump(BackgroundTaskThread):
"""PseudoCDump class definition.
Attributes:
bv: A Binary Ninja BinaryView instance which is a view on binary data,
and presents a queryable interface of a binary file.
msg: A string containing the message displayed when started.
destination_path: A string containing the path of the folder where
the Pseudo C code will be dumped.
Class constants:
FILE_SUFFIX: The suffix of the filenames where the content of the
functions will be written. In this case, is a constant string 'c'
(file extension .c).
MAX_PATH: Maximum path length (255).
"""
FILE_SUFFIX = 'c'
MAX_PATH = 255
def __init__(self, bv: BinaryView, msg: str, destination_path: str):
"""Inits PseudoCDump class"""
BackgroundTaskThread.__init__(self, msg, can_cancel=True)
self.bv = bv
self.destination_path = destination_path
def __get_function_name(self, function: Function) -> str:
"""This private method is used to normalize the name of the function
being dumped. It tries to use the symbol of the function if it exists
and if the length of the destination path plus the length of the
symbol doesn't exceed MAX_PATH. Otherwise, it uses the address at the
start of the function. Again, it checks that the length of the
destination path plus the length of the address(sub_<address>) doesn't
exceed MAX_PATH. If the still exceeds MAX_PATH, it raises an exception.
Args:
function: A Binary Ninja Function instance containing
the current function to be dumped.
Returns:
The string containing the normalized function name.
Raises:
File name too long for function <function>
Try using a different path.
"""
function_symbol = self.bv.get_symbol_at(function.start)
if hasattr(function_symbol,
'short_name') and (len(self.destination_path) + len(
function_symbol.short_name)) <= self.MAX_PATH:
return function_symbol.short_name
elif len(self.destination_path) + len(
'sub_%x' % (function.start)) <= self.MAX_PATH:
return 'sub_%x' % (function.start)
else:
if hasattr(function_symbol, 'short_name'):
raise ValueError('File name too long for function: '
f'{function_symbol.short_name!r}\n'
'Try using a different path')
else:
raise ValueError('File name too long for function: '
f'sub_{function.start:x}\n'
'Try using a different path')
def __create_directory(self) -> str:
"""This function creates a new directory with a name that is based on
the name of the file that is being processed and the current time, and
returns the path of the new directory.
"""
directory_name = ''.join(
(f'PseudoCDump_{ntpath.basename(self.bv.file.filename)}_',
str(calendar.timegm(time.gmtime()))))
new_directory = os.path.join(self.destination_path, directory_name)
os.mkdir(new_directory)
return new_directory
def run(self) -> None:
"""Method representing the thread's activity. It invokes the callable
object passed to the object's constructor as the target argument.
Additionally, writes the content of each function into a <function_name>.c
file in the provided destination folder.
"""
self.destination_path = self.__create_directory()
log_info(f'Number of functions to dump: {len(self.bv.functions)}')
count = 1
for function in self.bv.functions:
function_name = self.__get_function_name(function)
log_info(f'Dumping function {function_name}')
self.progress = "Dumping Pseudo C: %d/%d" % (
count, len(self.bv.functions))
force_analysis(self.bv, function)
pcode = get_pseudo_c(self.bv, function)
destination = os.path.join(
self.destination_path,
normalize_destination_file(function_name, self.FILE_SUFFIX))
with open(destination, 'wb') as file:
file.write(bytes(pcode, 'utf-8'))
count += 1
log_alert(f'Done \nFiles saved in {self.destination_path}')
def normalize_destination_file(destination_file: str,
filename_suffix: str) -> str:
"""Normalizes the file name depending on the platform being run.
It will replace reserved characters with an underscore '_'
Args:
destination_file: A string containing the file name.
filename_suffix: A string containing the file suffix
(file extension).
Return:
The string containing the normalized file name.
"""
if 'Windows' in platform.system():
normalized_destination_file = '.'.join(
(re.sub(r'[><:"/\\|\?\*]', '_',
destination_file), filename_suffix))
return normalized_destination_file
else:
normalized_destination_file = '.'.join(
(re.sub(r'/', '_', destination_file), filename_suffix))
return normalized_destination_file
def force_analysis(bv: BinaryView, function: Function) -> None:
"""Binary Ninja may have skipped the analysis of the function being dumped.
It forces the analysis of the function if Binary ninja skipped it.
Args:
bv: A Binary Ninja BinaryView instance which is a view on binary data,
and presents a queryable interface of a binary file.
function: A Binary Ninja Function instance containing
the current function to be dumped.
"""
if function is not None and function.analysis_skipped:
log_warn(
''
f'Analyzing the skipped function {bv.get_symbol_at(function.start)}'
)
function.analysis_skip_override = (
FunctionAnalysisSkipOverride.NeverSkipFunctionAnalysis)
bv.update_analysis_and_wait()
def get_pseudo_c(bv: BinaryView, function: Function) -> str:
"""Gets the Pseudo C of the function being dumped. It stores every
line of the function (header and body) into a list while the function
is being traversed. Finally, it returns the entire function Pseudo C
dump.
Args:
bv: A Binary Ninja BinaryView instance which is a view on binary data,
and presents a queryable interface of a binary file.
function: A Binary Ninja Function instance containing
the current function to be dumped.
Return:
lines_of_code: A single string containing the entire Pseudo C code of
the function.
"""
lines = []
settings = DisassemblySettings()
settings.set_option(DisassemblyOption.ShowAddress, False)
settings.set_option(DisassemblyOption.WaitForIL, True)
obj = LinearViewObject.language_representation(bv, settings)
cursor_end = LinearViewCursor(obj)
cursor_end.seek_to_address(function.highest_address)
body = bv.get_next_linear_disassembly_lines(cursor_end)
cursor_end.seek_to_address(function.highest_address)
header = bv.get_previous_linear_disassembly_lines(cursor_end)
for line in header:
lines.append(f'{str(line)}\n')
for line in body:
lines.append(f'{str(line)}\n')
lines_of_code = ''.join(lines)
return (lines_of_code)
def dump_pseudo_c(bv: BinaryView, function=None) -> None:
"""
Receives path and instantiates PseudoCDump, and calls PseudoCDump
to start the thread in the background.
Args:
bv: A Binary Ninja BinaryView instance which is a view on binary data,
and presents a queryable interface of a binary file.
function: None.
"""
destination_path = get_directory_name_input('Destination')
if destination_path == None:
log_error(''
'No directory was provided to save the decompiled Pseudo C')
return
dump = PseudoCDump(bv, 'Starting the Pseudo C Dump...', destination_path)
dump.start()
"""Register the plugin that will be called with an address argument.
"""
PluginCommand.register_for_address('Pseudo C Dump',
'Dumps Pseudo C for the whole code base',
dump_pseudo_c)