declib 3.8.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- declib/__init__.py +9 -0
- declib/__main__.py +190 -0
- declib/api/__init__.py +13 -0
- declib/api/artifact_dict.py +153 -0
- declib/api/artifact_lifter.py +161 -0
- declib/api/decompiler_client.py +1219 -0
- declib/api/decompiler_interface.py +1261 -0
- declib/api/decompiler_server.py +782 -0
- declib/api/server_registry.py +171 -0
- declib/api/type_definition_parser.py +201 -0
- declib/api/type_parser.py +409 -0
- declib/api/utils.py +31 -0
- declib/artifacts/__init__.py +93 -0
- declib/artifacts/artifact.py +311 -0
- declib/artifacts/comment.py +49 -0
- declib/artifacts/context.py +61 -0
- declib/artifacts/decompilation.py +35 -0
- declib/artifacts/enum.py +53 -0
- declib/artifacts/formatting.py +27 -0
- declib/artifacts/func.py +433 -0
- declib/artifacts/global_variable.py +31 -0
- declib/artifacts/patch.py +49 -0
- declib/artifacts/segment.py +37 -0
- declib/artifacts/stack_variable.py +50 -0
- declib/artifacts/struct.py +184 -0
- declib/artifacts/typedef.py +59 -0
- declib/cli/__init__.py +3 -0
- declib/cli/decompiler_cli.py +1487 -0
- declib/configuration.py +184 -0
- declib/decompiler_stubs/__init__.py +0 -0
- declib/decompiler_stubs/angr_declib/__init__.py +4 -0
- declib/decompiler_stubs/binja_declib/__init__.py +4 -0
- declib/decompiler_stubs/ida_declib.py +8 -0
- declib/decompilers/__init__.py +8 -0
- declib/decompilers/angr/__init__.py +11 -0
- declib/decompilers/angr/artifact_lifter.py +46 -0
- declib/decompilers/angr/compat.py +262 -0
- declib/decompilers/angr/interface.py +949 -0
- declib/decompilers/binja/__init__.py +0 -0
- declib/decompilers/binja/artifact_lifter.py +32 -0
- declib/decompilers/binja/hooks.py +201 -0
- declib/decompilers/binja/interface.py +795 -0
- declib/decompilers/ghidra/__init__.py +0 -0
- declib/decompilers/ghidra/artifact_lifter.py +60 -0
- declib/decompilers/ghidra/compat/__init__.py +0 -0
- declib/decompilers/ghidra/compat/headless.py +156 -0
- declib/decompilers/ghidra/compat/imports.py +78 -0
- declib/decompilers/ghidra/compat/state.py +54 -0
- declib/decompilers/ghidra/compat/transaction.py +30 -0
- declib/decompilers/ghidra/hooks.py +242 -0
- declib/decompilers/ghidra/interface.py +1433 -0
- declib/decompilers/ida/__init__.py +0 -0
- declib/decompilers/ida/artifact_lifter.py +51 -0
- declib/decompilers/ida/compat.py +2054 -0
- declib/decompilers/ida/hooks.py +700 -0
- declib/decompilers/ida/ida_ui.py +80 -0
- declib/decompilers/ida/interface.py +659 -0
- declib/logger.py +101 -0
- declib/plugin_installer.py +259 -0
- declib/skills/__init__.py +24 -0
- declib/skills/decompiler/SKILL.md +316 -0
- declib/ui/__init__.py +33 -0
- declib/ui/qt_objects.py +146 -0
- declib/ui/utils.py +115 -0
- declib/ui/version.py +14 -0
- declib-3.8.0.dist-info/METADATA +138 -0
- declib-3.8.0.dist-info/RECORD +71 -0
- declib-3.8.0.dist-info/WHEEL +5 -0
- declib-3.8.0.dist-info/entry_points.txt +3 -0
- declib-3.8.0.dist-info/licenses/LICENSE +24 -0
- declib-3.8.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,1261 @@
|
|
|
1
|
+
import inspect
|
|
2
|
+
import logging
|
|
3
|
+
import re
|
|
4
|
+
import threading
|
|
5
|
+
from collections import defaultdict
|
|
6
|
+
from functools import wraps
|
|
7
|
+
from typing import Dict, Optional, Tuple, List, Callable, Type, Union
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
import os
|
|
10
|
+
|
|
11
|
+
import networkx as nx
|
|
12
|
+
|
|
13
|
+
import declib
|
|
14
|
+
from declib.api.artifact_lifter import ArtifactLifter
|
|
15
|
+
from declib.api.artifact_dict import ArtifactDict
|
|
16
|
+
from declib.api.type_parser import CTypeParser, CType
|
|
17
|
+
from declib.configuration import DecLibConfig
|
|
18
|
+
from declib.artifacts import (
|
|
19
|
+
Artifact,
|
|
20
|
+
Function, FunctionHeader, StackVariable,
|
|
21
|
+
Comment, GlobalVariable, Patch, Segment,
|
|
22
|
+
Enum, Struct, FunctionArgument, Context, Decompilation, Typedef
|
|
23
|
+
)
|
|
24
|
+
from declib.decompilers import SUPPORTED_DECOMPILERS, ANGR_DECOMPILER, \
|
|
25
|
+
BINJA_DECOMPILER, IDA_DECOMPILER, GHIDRA_DECOMPILER
|
|
26
|
+
|
|
27
|
+
_l = logging.getLogger(name=__name__)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def requires_decompilation(f):
|
|
31
|
+
@wraps(f)
|
|
32
|
+
def _requires_decompilation(self, *args, **kwargs):
|
|
33
|
+
if self._decompiler_available:
|
|
34
|
+
for arg in args:
|
|
35
|
+
if isinstance(arg, Function) and arg.dec_obj is None:
|
|
36
|
+
arg.dec_obj = self.get_decompilation_object(arg)
|
|
37
|
+
|
|
38
|
+
return f(self, *args, **kwargs)
|
|
39
|
+
return _requires_decompilation
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class DecompilerInterface:
|
|
43
|
+
def __init__(
|
|
44
|
+
self,
|
|
45
|
+
# these flags should mostly be unchanged when passed through subclasses
|
|
46
|
+
name: str = "generic",
|
|
47
|
+
qt_version: str = "PySide6",
|
|
48
|
+
default_func_prefix: str = "sub_",
|
|
49
|
+
artifact_lifter: Optional[ArtifactLifter] = None,
|
|
50
|
+
error_on_artifact_duplicates: bool = False,
|
|
51
|
+
decompiler_available: bool = True,
|
|
52
|
+
supports_undo: bool = False,
|
|
53
|
+
supports_type_scopes: bool = False,
|
|
54
|
+
# these flags can be changed by subclassed decis
|
|
55
|
+
headless: bool = False,
|
|
56
|
+
binary_path: Optional[Union[Path, str]] = None,
|
|
57
|
+
init_plugin: bool = False,
|
|
58
|
+
plugin_name: str = f"generic_declib_plugin",
|
|
59
|
+
config: Optional[DecLibConfig] = None,
|
|
60
|
+
# [category/name] = (action_string, callback_func)
|
|
61
|
+
gui_ctx_menu_actions: Optional[dict] = None,
|
|
62
|
+
gui_init_args: Optional[Tuple] = None,
|
|
63
|
+
gui_init_kwargs: Optional[Dict] = None,
|
|
64
|
+
# [artifact_class] = list(callback_func)
|
|
65
|
+
artifact_change_callbacks: Optional[Dict[Type[Artifact], List[Callable]]] = None,
|
|
66
|
+
undo_event_callbacks: Optional[List[Callable]] = None,
|
|
67
|
+
decompiler_opened_callbacks: Optional[List[Callable]] = None,
|
|
68
|
+
decompiler_closed_callbacks: Optional[List[Callable]] = None,
|
|
69
|
+
thread_artifact_callbacks: bool = True,
|
|
70
|
+
force_click_recording: bool = False,
|
|
71
|
+
track_mouse_moves: bool = False,
|
|
72
|
+
**kwargs,
|
|
73
|
+
):
|
|
74
|
+
self.name = name
|
|
75
|
+
self.art_lifter = artifact_lifter
|
|
76
|
+
self.type_parser = CTypeParser()
|
|
77
|
+
self.supports_undo = supports_undo
|
|
78
|
+
self.supports_type_scopes = supports_type_scopes
|
|
79
|
+
self.qt_version = qt_version
|
|
80
|
+
self.default_func_prefix = default_func_prefix
|
|
81
|
+
self._error_on_artifact_duplicates = error_on_artifact_duplicates
|
|
82
|
+
|
|
83
|
+
self.headless = headless
|
|
84
|
+
self._binary_path = Path(binary_path) if binary_path else None
|
|
85
|
+
self._init_plugin = init_plugin
|
|
86
|
+
self._unparsed_gui_ctx_actions: dict[str, tuple[str, Callable]] = gui_ctx_menu_actions or {}
|
|
87
|
+
# (category, name, action_string, callback_func)
|
|
88
|
+
self._gui_ctx_menu_actions = []
|
|
89
|
+
self._plugin_name = plugin_name
|
|
90
|
+
self.gui_plugin = None
|
|
91
|
+
self.artifact_watchers_started = False
|
|
92
|
+
self.force_click_recording = force_click_recording
|
|
93
|
+
self.track_mouse_moves = track_mouse_moves
|
|
94
|
+
|
|
95
|
+
# locks
|
|
96
|
+
self.artifact_write_lock = threading.Lock()
|
|
97
|
+
|
|
98
|
+
# callback functions, keyed by Artifact class
|
|
99
|
+
self.artifact_change_callbacks = artifact_change_callbacks or defaultdict(list)
|
|
100
|
+
self.undo_event_callbacks = undo_event_callbacks or []
|
|
101
|
+
self.decompiler_opened_callbacks = decompiler_opened_callbacks or []
|
|
102
|
+
self.decompiler_closed_callbacks = decompiler_closed_callbacks or []
|
|
103
|
+
self._thread_artifact_callbacks = thread_artifact_callbacks
|
|
104
|
+
|
|
105
|
+
# artifact dict aliases:
|
|
106
|
+
# these are the public API for artifacts that are used by the decompiler interface
|
|
107
|
+
self.functions = ArtifactDict(Function, self, error_on_duplicate=error_on_artifact_duplicates)
|
|
108
|
+
self.comments = ArtifactDict(Comment, self, error_on_duplicate=error_on_artifact_duplicates)
|
|
109
|
+
self.patches = ArtifactDict(Patch, self, error_on_duplicate=error_on_artifact_duplicates)
|
|
110
|
+
self.global_vars = ArtifactDict(GlobalVariable, self, error_on_duplicate=error_on_artifact_duplicates)
|
|
111
|
+
self.segments = ArtifactDict(Segment, self, error_on_duplicate=error_on_artifact_duplicates)
|
|
112
|
+
self.structs = ArtifactDict(Struct, self, error_on_duplicate=error_on_artifact_duplicates, scopable=True)
|
|
113
|
+
self.enums = ArtifactDict(Enum, self, error_on_duplicate=error_on_artifact_duplicates, scopable=True)
|
|
114
|
+
self.typedefs = ArtifactDict(Typedef, self, error_on_duplicate=error_on_artifact_duplicates, scopable=True)
|
|
115
|
+
|
|
116
|
+
self._decompiler_available = decompiler_available
|
|
117
|
+
# override the file-saved config when one is passed in manually, otherwise
|
|
118
|
+
# either load it from the filesystem or create a new one and place it there
|
|
119
|
+
self.config = config if config is not None else DecLibConfig.update_or_make()
|
|
120
|
+
|
|
121
|
+
if not self.headless:
|
|
122
|
+
args = gui_init_args or []
|
|
123
|
+
kwargs = gui_init_kwargs or {}
|
|
124
|
+
self._init_gui_components(*args, **kwargs)
|
|
125
|
+
else:
|
|
126
|
+
self._init_headless_components()
|
|
127
|
+
|
|
128
|
+
self.debug(f"Using configuration file: {self.config.save_location}")
|
|
129
|
+
self.config.save()
|
|
130
|
+
|
|
131
|
+
def _init_headless_components(self, *args, **kwargs):
|
|
132
|
+
if not self._binary_path.exists():
|
|
133
|
+
raise FileNotFoundError("You must provide a valid target binary path when using headless mode.")
|
|
134
|
+
|
|
135
|
+
def _deinit_headless_components(self):
|
|
136
|
+
pass
|
|
137
|
+
|
|
138
|
+
def _init_gui_components(self, *args, **kwargs):
|
|
139
|
+
from declib.ui.version import set_ui_version
|
|
140
|
+
set_ui_version(self.qt_version)
|
|
141
|
+
|
|
142
|
+
# register a real plugin in the GUI
|
|
143
|
+
if self._init_plugin:
|
|
144
|
+
self.gui_plugin = self._init_gui_plugin(*args, **kwargs)
|
|
145
|
+
|
|
146
|
+
# parse & register all context menu actions
|
|
147
|
+
self.gui_register_ctx_menu_many(self._unparsed_gui_ctx_actions)
|
|
148
|
+
|
|
149
|
+
def _init_gui_plugin(self, *args, **kwargs):
|
|
150
|
+
return None
|
|
151
|
+
|
|
152
|
+
def shutdown(self):
|
|
153
|
+
if self.artifact_watchers_started:
|
|
154
|
+
self.stop_artifact_watchers()
|
|
155
|
+
if self.headless:
|
|
156
|
+
self._deinit_headless_components()
|
|
157
|
+
|
|
158
|
+
#
|
|
159
|
+
# Public API:
|
|
160
|
+
# These functions are the main API for interacting with the decompiler. In general, every function that takes
|
|
161
|
+
# an Artifact (including addresses) should be in the lifted form. Additionally, every function that returns an
|
|
162
|
+
# Artifact should be in the lifted form. This is to ensure that the decompiler interface is always in sync with
|
|
163
|
+
# the lifter. For getting and setting artifacts, the ArtifactDicts defined in the init should be used.
|
|
164
|
+
#
|
|
165
|
+
|
|
166
|
+
#
|
|
167
|
+
# GUI API
|
|
168
|
+
#
|
|
169
|
+
|
|
170
|
+
def gui_active_context(self) -> Optional[declib.artifacts.Context]:
|
|
171
|
+
"""
|
|
172
|
+
Returns the active location that the user is currently _clicked_ on in the decompiler.
|
|
173
|
+
This is returned as a Context object, which can address and screen naming information dependent
|
|
174
|
+
on the decompilers exposed data.
|
|
175
|
+
"""
|
|
176
|
+
raise NotImplementedError
|
|
177
|
+
|
|
178
|
+
def gui_goto(self, func_addr) -> None:
|
|
179
|
+
"""
|
|
180
|
+
Relocates decompiler display to provided address
|
|
181
|
+
|
|
182
|
+
@param func_addr:
|
|
183
|
+
@return:
|
|
184
|
+
"""
|
|
185
|
+
raise NotImplementedError
|
|
186
|
+
|
|
187
|
+
def gui_show_type(self, type_name: str) -> None:
|
|
188
|
+
"""
|
|
189
|
+
Relocates decompiler display to type definition
|
|
190
|
+
|
|
191
|
+
Does nothing if not implemented in a subclass
|
|
192
|
+
"""
|
|
193
|
+
pass
|
|
194
|
+
|
|
195
|
+
def gui_register_ctx_menu(self, name, action_string, callback_func, category=None, shortcut=None) -> bool:
|
|
196
|
+
"""
|
|
197
|
+
Register a context menu / plugin action.
|
|
198
|
+
|
|
199
|
+
:param name: unique identifier for the action
|
|
200
|
+
:param action_string: human-readable label shown in the menu
|
|
201
|
+
:param callback_func: function to invoke when the action fires
|
|
202
|
+
:param category: optional menu category / sub-path
|
|
203
|
+
:param shortcut: optional keyboard shortcut in Qt format (e.g. "Ctrl+Shift+D").
|
|
204
|
+
Implementations translate this to their native format. When the native
|
|
205
|
+
decompiler cannot bind a shortcut programmatically, this is a no-op.
|
|
206
|
+
"""
|
|
207
|
+
raise NotImplementedError
|
|
208
|
+
|
|
209
|
+
def gui_ask_for_string(self, question, title="Plugin Question", default="") -> str:
|
|
210
|
+
"""
|
|
211
|
+
Opens a GUI dialog box that asks the user for a string. If not overriden by the decompiler interface,
|
|
212
|
+
this will default to a Qt dialog box that is based on the decompilers Qt version.
|
|
213
|
+
"""
|
|
214
|
+
from declib.ui.utils import gui_ask_for_string
|
|
215
|
+
return gui_ask_for_string(question, title=title, default=default)
|
|
216
|
+
|
|
217
|
+
def gui_ask_for_choice(self, question: str, choices: list, title="Plugin Question") -> str:
|
|
218
|
+
"""
|
|
219
|
+
Opens a GUI dialog box that asks the user for a choice. If not overriden by the decompiler interface,
|
|
220
|
+
this will default to a Qt dialog box that is based on the decompilers Qt version.
|
|
221
|
+
"""
|
|
222
|
+
from declib.ui.utils import gui_ask_for_choice
|
|
223
|
+
return gui_ask_for_choice(question, choices, title=title)
|
|
224
|
+
|
|
225
|
+
def gui_popup_text(self, text: str, title: str = "Plugin Message") -> bool:
|
|
226
|
+
"""
|
|
227
|
+
Opens a GUI dialog box that displays a message. If not overriden by the decompiler interface,
|
|
228
|
+
this will default to a Qt dialog box that is based on the decompilers Qt version.
|
|
229
|
+
"""
|
|
230
|
+
from declib.ui.utils import gui_popup_text
|
|
231
|
+
return gui_popup_text(text, title=title)
|
|
232
|
+
|
|
233
|
+
def gui_run_on_main_thread(self, func: Callable, *args, **kwargs):
|
|
234
|
+
"""
|
|
235
|
+
Runs the provided function on the main thread of the GUI. This is useful for updating the GUI from a
|
|
236
|
+
background thread. Only in Ghidra is this useful.
|
|
237
|
+
"""
|
|
238
|
+
return func(*args, **kwargs)
|
|
239
|
+
|
|
240
|
+
def gui_attach_qt_window(self, qt_window: type["QWidgt"], title: str, target_window=None, position=None, *args, **kwargs) -> bool:
|
|
241
|
+
"""
|
|
242
|
+
Attaches a Qt window to the decompiler interface. This is useful for embedding custom Qt windows
|
|
243
|
+
into the decompiler interface.
|
|
244
|
+
"""
|
|
245
|
+
raise NotImplementedError
|
|
246
|
+
|
|
247
|
+
@staticmethod
|
|
248
|
+
def _parse_ctx_menu_actions(actions: dict[str, tuple[str, Callable]]) -> List[Tuple[str, str, str, Callable]]:
|
|
249
|
+
gui_ctx_menu_actions = []
|
|
250
|
+
for combined_name, items in actions.items():
|
|
251
|
+
slashes = list(re.finditer("/", combined_name))
|
|
252
|
+
if not slashes:
|
|
253
|
+
category = ""
|
|
254
|
+
name = combined_name
|
|
255
|
+
else:
|
|
256
|
+
last_slash = slashes[-1]
|
|
257
|
+
category = combined_name[:last_slash.start()]
|
|
258
|
+
name = combined_name[last_slash.start()+1:]
|
|
259
|
+
|
|
260
|
+
gui_ctx_menu_actions.append((category, name,) + items)
|
|
261
|
+
|
|
262
|
+
return gui_ctx_menu_actions
|
|
263
|
+
|
|
264
|
+
def gui_register_ctx_menu_many(self, actions: dict[str, tuple[str, Callable]]):
|
|
265
|
+
parsed_actions = self._parse_ctx_menu_actions(actions)
|
|
266
|
+
for action in parsed_actions:
|
|
267
|
+
category, name, action_string, callback_func = action[:4]
|
|
268
|
+
shortcut = action[4] if len(action) > 4 else None
|
|
269
|
+
self.gui_register_ctx_menu(
|
|
270
|
+
name, action_string, callback_func, category=category, shortcut=shortcut
|
|
271
|
+
)
|
|
272
|
+
|
|
273
|
+
#
|
|
274
|
+
# Override Mandatory API
|
|
275
|
+
#
|
|
276
|
+
|
|
277
|
+
def start_artifact_watchers(self):
|
|
278
|
+
"""
|
|
279
|
+
Starts the artifact watchers for the decompiler. This is a special function that is called
|
|
280
|
+
by the decompiler interface when the decompiler is ready to start watching for changes in the
|
|
281
|
+
decompiler. This is useful for plugins that want to watch for changes in the decompiler and
|
|
282
|
+
react to them.
|
|
283
|
+
|
|
284
|
+
@return:
|
|
285
|
+
"""
|
|
286
|
+
self.debug("Starting BinSync artifact watchers...")
|
|
287
|
+
self.artifact_watchers_started = True
|
|
288
|
+
|
|
289
|
+
def stop_artifact_watchers(self):
|
|
290
|
+
"""
|
|
291
|
+
Stops the artifact watchers for the decompiler. This is a special function that is called
|
|
292
|
+
by the decompiler interface when the decompiler is ready to stop watching for changes in the
|
|
293
|
+
decompiler. This is useful for plugins that want to watch for changes in the decompiler and
|
|
294
|
+
react to them.
|
|
295
|
+
"""
|
|
296
|
+
self.debug("Stopping BinSync artifact watchers...")
|
|
297
|
+
self.artifact_watchers_started = False
|
|
298
|
+
|
|
299
|
+
@property
|
|
300
|
+
def binary_base_addr(self) -> int:
|
|
301
|
+
"""
|
|
302
|
+
Returns the base address of the binary in the decompiler. This is useful for calculating offsets
|
|
303
|
+
in the binary. Also mandatory for using the lifting and lowering API.
|
|
304
|
+
"""
|
|
305
|
+
raise NotImplementedError
|
|
306
|
+
|
|
307
|
+
@property
|
|
308
|
+
def binary_hash(self) -> str:
|
|
309
|
+
"""
|
|
310
|
+
Returns a hex string of the currently loaded binary in the decompiler. For most cases,
|
|
311
|
+
this will simply be a md5hash of the binary.
|
|
312
|
+
|
|
313
|
+
@rtype: hex string
|
|
314
|
+
"""
|
|
315
|
+
raise NotImplementedError
|
|
316
|
+
|
|
317
|
+
@property
|
|
318
|
+
def binary_path(self) -> Optional[str]:
|
|
319
|
+
"""
|
|
320
|
+
Returns a string that is the path of the currently loaded binary. If there is no binary loaded
|
|
321
|
+
then None should be returned.
|
|
322
|
+
|
|
323
|
+
@rtype: path-like string (/path/to/binary)
|
|
324
|
+
"""
|
|
325
|
+
return self._binary_path
|
|
326
|
+
|
|
327
|
+
def fast_get_function(self, func_addr) -> Optional[Function]:
|
|
328
|
+
"""
|
|
329
|
+
Attempts to get a light version of the Function at func_addr.
|
|
330
|
+
This function implements special logic to be faster than grabbing all light-functions, or grabbing
|
|
331
|
+
a decompiled function. Use this API in the case where you may need to get a single functions info
|
|
332
|
+
many times in a loop.
|
|
333
|
+
|
|
334
|
+
@param func_addr:
|
|
335
|
+
@return:
|
|
336
|
+
"""
|
|
337
|
+
raise NotImplementedError
|
|
338
|
+
|
|
339
|
+
def get_func_size(self, func_addr) -> int:
|
|
340
|
+
"""
|
|
341
|
+
Returns the size of a function
|
|
342
|
+
|
|
343
|
+
@param func_addr:
|
|
344
|
+
@return:
|
|
345
|
+
"""
|
|
346
|
+
raise NotImplementedError
|
|
347
|
+
|
|
348
|
+
@property
|
|
349
|
+
def decompiler_available(self) -> bool:
|
|
350
|
+
"""
|
|
351
|
+
@return: True if decompiler is available for decompilation, False if otherwise
|
|
352
|
+
"""
|
|
353
|
+
return True
|
|
354
|
+
|
|
355
|
+
def decompile(self, addr: int, map_lines=False, **kwargs) -> Optional[Decompilation]:
|
|
356
|
+
lowered_addr = self.art_lifter.lower_addr(addr)
|
|
357
|
+
if not self.decompiler_available:
|
|
358
|
+
_l.error("Decompiler is not available.")
|
|
359
|
+
return None
|
|
360
|
+
|
|
361
|
+
sorted_funcs = sorted(self._functions().items(), key=lambda x: x[0])
|
|
362
|
+
func_by_addr = {_addr: func for _addr, func in sorted_funcs}
|
|
363
|
+
func = None
|
|
364
|
+
if lowered_addr in func_by_addr:
|
|
365
|
+
func = func_by_addr[lowered_addr]
|
|
366
|
+
else:
|
|
367
|
+
_l.debug("Address is not a function start, searching for function...")
|
|
368
|
+
for func_addr, _func in sorted_funcs:
|
|
369
|
+
if _func.addr <= lowered_addr < (_func.addr + _func.size):
|
|
370
|
+
func = _func
|
|
371
|
+
break
|
|
372
|
+
|
|
373
|
+
if func is None:
|
|
374
|
+
self.warning(f"Failed to find function for address {hex(lowered_addr)}")
|
|
375
|
+
return None
|
|
376
|
+
|
|
377
|
+
try:
|
|
378
|
+
decompilation = self._decompile(func, map_lines=map_lines, **kwargs)
|
|
379
|
+
except Exception as e:
|
|
380
|
+
self.warning(f"Failed to decompile function at {hex(lowered_addr)}: {e}")
|
|
381
|
+
decompilation = None
|
|
382
|
+
|
|
383
|
+
if decompilation is not None:
|
|
384
|
+
decompilation = self.art_lifter.lift(decompilation)
|
|
385
|
+
|
|
386
|
+
return decompilation
|
|
387
|
+
|
|
388
|
+
def xrefs_to(self, artifact: Artifact, decompile=False, only_code=False) -> List[Artifact]:
|
|
389
|
+
"""
|
|
390
|
+
Returns a list of artifacts that reference the provided artifact.
|
|
391
|
+
@param artifact: Artifact to find references to
|
|
392
|
+
@param decompile: If True, decompile the function before searching for xrefs
|
|
393
|
+
@return: List of artifacts that reference the provided artifact
|
|
394
|
+
"""
|
|
395
|
+
if not isinstance(artifact, Function):
|
|
396
|
+
raise ValueError("Only functions are supported for xrefs_to")
|
|
397
|
+
|
|
398
|
+
return []
|
|
399
|
+
|
|
400
|
+
def xrefs_to_addr(self, addr: int, only_code: bool = False) -> List[Artifact]:
|
|
401
|
+
"""Return artifacts that reference ``addr``.
|
|
402
|
+
|
|
403
|
+
Unlike :meth:`xrefs_to`, which assumes a Function target and therefore
|
|
404
|
+
only fires on function entry points, this is a raw "who references
|
|
405
|
+
this address?" query. It's what you want after ``list_strings`` finds
|
|
406
|
+
a candidate string and you need to know which functions read it.
|
|
407
|
+
|
|
408
|
+
The default implementation turns the address into a stub Function and
|
|
409
|
+
delegates to :meth:`xrefs_to`; subclasses should override this with a
|
|
410
|
+
real data-xref query when their backend exposes one.
|
|
411
|
+
|
|
412
|
+
@param addr: Address (lifted) to find references to.
|
|
413
|
+
@param only_code: Restrict to code references if the backend supports it.
|
|
414
|
+
@return: List of referencing artifacts (typically Function stubs).
|
|
415
|
+
"""
|
|
416
|
+
return self.xrefs_to(Function(addr, 0), only_code=only_code)
|
|
417
|
+
|
|
418
|
+
def xrefs_from(self, func_addr: int) -> List[Function]:
|
|
419
|
+
"""Return the functions that ``func_addr`` calls (its direct callees).
|
|
420
|
+
|
|
421
|
+
The default implementation falls back to get_callgraph + out_edges,
|
|
422
|
+
which is expensive because it computes xrefs for every function in
|
|
423
|
+
the binary. Subclasses should override with a direct per-function
|
|
424
|
+
callee query when their backend exposes one.
|
|
425
|
+
"""
|
|
426
|
+
try:
|
|
427
|
+
cg = self.get_callgraph(only_names=False)
|
|
428
|
+
except Exception as exc:
|
|
429
|
+
_l.debug("get_callgraph failed: %s", exc)
|
|
430
|
+
return []
|
|
431
|
+
callees: List[Function] = []
|
|
432
|
+
seen = set()
|
|
433
|
+
for caller, callee in cg.out_edges(nbunch=None):
|
|
434
|
+
if getattr(caller, "addr", None) != func_addr:
|
|
435
|
+
continue
|
|
436
|
+
callee_addr = getattr(callee, "addr", None)
|
|
437
|
+
if callee_addr in seen:
|
|
438
|
+
continue
|
|
439
|
+
seen.add(callee_addr)
|
|
440
|
+
callees.append(callee)
|
|
441
|
+
return callees
|
|
442
|
+
|
|
443
|
+
def get_callers(self, target) -> List[Function]:
|
|
444
|
+
"""
|
|
445
|
+
Returns a list of Functions that call/reference the provided target.
|
|
446
|
+
|
|
447
|
+
@param target: A Function, address (int), or symbol name (str).
|
|
448
|
+
@return: List of Function objects whose bodies reference `target`. Each result is a (light)
|
|
449
|
+
Function; only its addr (and name when resolvable) are guaranteed to be populated.
|
|
450
|
+
"""
|
|
451
|
+
func: Optional[Function] = None
|
|
452
|
+
if isinstance(target, Function):
|
|
453
|
+
func = target
|
|
454
|
+
elif isinstance(target, int):
|
|
455
|
+
func = self.fast_get_function(target)
|
|
456
|
+
if func is None:
|
|
457
|
+
func = Function(target, 0)
|
|
458
|
+
elif isinstance(target, str):
|
|
459
|
+
for addr, light_func in self.functions.items():
|
|
460
|
+
if light_func.name == target:
|
|
461
|
+
func = self.fast_get_function(addr) or Function(addr, 0)
|
|
462
|
+
break
|
|
463
|
+
if func is None:
|
|
464
|
+
raise ValueError(f"Unable to locate function named {target!r}")
|
|
465
|
+
else:
|
|
466
|
+
raise ValueError(f"Unsupported target type for get_callers: {type(target)}")
|
|
467
|
+
|
|
468
|
+
callers: List[Function] = []
|
|
469
|
+
seen = set()
|
|
470
|
+
for xref in self.xrefs_to(func):
|
|
471
|
+
if not isinstance(xref, Function):
|
|
472
|
+
continue
|
|
473
|
+
if xref.addr in seen:
|
|
474
|
+
continue
|
|
475
|
+
seen.add(xref.addr)
|
|
476
|
+
if not xref.name:
|
|
477
|
+
resolved = self.fast_get_function(xref.addr)
|
|
478
|
+
if resolved is not None:
|
|
479
|
+
xref = resolved
|
|
480
|
+
callers.append(xref)
|
|
481
|
+
|
|
482
|
+
return callers
|
|
483
|
+
|
|
484
|
+
def list_strings(self, filter: Optional[str] = None) -> List[Tuple[int, str]]:
|
|
485
|
+
"""
|
|
486
|
+
Returns a list of (addr, string) tuples for strings found in the binary.
|
|
487
|
+
|
|
488
|
+
This reports **only what the decompiler's own string detector
|
|
489
|
+
surfaced** — it is deliberately not a substitute for a full-file
|
|
490
|
+
scan. Backend fidelity varies (angr in particular misses most of
|
|
491
|
+
``.rodata``); callers that need an exhaustive list should fall
|
|
492
|
+
back to external tools such as ``strings(1)``, ``rabin2 -z``, or
|
|
493
|
+
``readelf -p`` and then use the resulting addresses with the
|
|
494
|
+
other APIs (``decompile``, ``xrefs_to_addr``, etc.).
|
|
495
|
+
|
|
496
|
+
Subclasses are expected to override this with native, fast string
|
|
497
|
+
discovery. The base implementation returns an empty list.
|
|
498
|
+
|
|
499
|
+
@param filter: Optional regex string; only strings that match will be returned.
|
|
500
|
+
@return: List of (address, string) tuples.
|
|
501
|
+
"""
|
|
502
|
+
return []
|
|
503
|
+
|
|
504
|
+
def disassemble(self, addr: int, **kwargs) -> Optional[str]:
|
|
505
|
+
"""
|
|
506
|
+
Returns the disassembly of a function as a single string.
|
|
507
|
+
|
|
508
|
+
Subclasses should override this to emit decompiler-native disassembly. The default
|
|
509
|
+
implementation returns None.
|
|
510
|
+
|
|
511
|
+
@param addr: Address of the function (or any address inside the function).
|
|
512
|
+
@return: The disassembly string, or None if unavailable.
|
|
513
|
+
"""
|
|
514
|
+
return None
|
|
515
|
+
|
|
516
|
+
def read_memory(self, addr: int, size: int) -> Optional[bytes]:
|
|
517
|
+
"""Read ``size`` bytes from the loaded program at ``addr``.
|
|
518
|
+
|
|
519
|
+
Returns the raw bytes the backend has for the requested span. ``None``
|
|
520
|
+
means "I couldn't satisfy the read at all" — out-of-range, uninitialized,
|
|
521
|
+
or the backend can't reach that memory. A short read (fewer bytes than
|
|
522
|
+
requested) is still valid and returned as-is; callers should check
|
|
523
|
+
``len(result)`` if they need an exact count.
|
|
524
|
+
|
|
525
|
+
@param addr: Lifted address to start reading from.
|
|
526
|
+
@param size: Number of bytes to read. Must be > 0.
|
|
527
|
+
@return: Bytes read, or ``None`` if the backend can't read this region.
|
|
528
|
+
"""
|
|
529
|
+
raise NotImplementedError
|
|
530
|
+
|
|
531
|
+
def get_callgraph(self, only_names=False) -> nx.DiGraph:
|
|
532
|
+
"""
|
|
533
|
+
Returns the callgraph of the binary. This is a dict of function addresses to a list of function addresses
|
|
534
|
+
that the function calls.
|
|
535
|
+
"""
|
|
536
|
+
callgraph = nx.DiGraph()
|
|
537
|
+
for func in self.functions.values():
|
|
538
|
+
callers = self.xrefs_to(func)
|
|
539
|
+
for caller in callers:
|
|
540
|
+
if isinstance(caller, Function):
|
|
541
|
+
if only_names:
|
|
542
|
+
callgraph.add_edge(caller.name, func.name)
|
|
543
|
+
else:
|
|
544
|
+
callgraph.add_edge(caller, func)
|
|
545
|
+
|
|
546
|
+
return callgraph
|
|
547
|
+
|
|
548
|
+
def get_dependencies(self, artifact: Artifact, decompile=True, max_resolves=50, **kwargs) -> List[Artifact]:
|
|
549
|
+
if not isinstance(artifact, Function):
|
|
550
|
+
raise ValueError("Only functions are supported for get_dependencies")
|
|
551
|
+
|
|
552
|
+
# collect all xrefs to the function (for global variables)
|
|
553
|
+
if decompile:
|
|
554
|
+
# the function was never decompiled
|
|
555
|
+
if artifact.dec_obj is None:
|
|
556
|
+
# TODO: this needs to be fixed so that it still works without redecompiling. What if we want
|
|
557
|
+
# to do analysis on a function that is not set yet.
|
|
558
|
+
artifact = self.functions[artifact.addr]
|
|
559
|
+
|
|
560
|
+
art_users = self.xrefs_to(artifact, decompile=decompile)
|
|
561
|
+
gvars = [art for art in art_users if isinstance(art, GlobalVariable)]
|
|
562
|
+
|
|
563
|
+
# collect all structs/enums used in the function types
|
|
564
|
+
imported_types = set()
|
|
565
|
+
imported_types.add(self.get_defined_type(artifact.header.type))
|
|
566
|
+
for arg in artifact.header.args.values():
|
|
567
|
+
imported_types.add(self.get_defined_type(arg.type))
|
|
568
|
+
for svar in artifact.stack_vars.values():
|
|
569
|
+
imported_types.add(self.get_defined_type(svar.type))
|
|
570
|
+
|
|
571
|
+
# start resolving dependencies in structs
|
|
572
|
+
for _ in range(max_resolves):
|
|
573
|
+
new_imports = False
|
|
574
|
+
for imported_type in list(imported_types):
|
|
575
|
+
if isinstance(imported_type, Struct):
|
|
576
|
+
for member in imported_type.members.values():
|
|
577
|
+
new_type = self.get_defined_type(member.type)
|
|
578
|
+
if new_type is not None and new_type not in imported_types:
|
|
579
|
+
imported_types.add(new_type)
|
|
580
|
+
new_imports = True
|
|
581
|
+
break
|
|
582
|
+
|
|
583
|
+
if new_imports:
|
|
584
|
+
break
|
|
585
|
+
|
|
586
|
+
if isinstance(imported_type, Typedef):
|
|
587
|
+
new_type = self.get_defined_type(imported_type.type)
|
|
588
|
+
if new_type is not None and new_type not in imported_types:
|
|
589
|
+
imported_types.add(new_type)
|
|
590
|
+
new_imports = True
|
|
591
|
+
|
|
592
|
+
if not new_imports:
|
|
593
|
+
break
|
|
594
|
+
else:
|
|
595
|
+
self.warning("Max dependency resolves reached, returning partial results")
|
|
596
|
+
|
|
597
|
+
all_deps = [art for art in list(imported_types) + gvars if art is not None]
|
|
598
|
+
return all_deps
|
|
599
|
+
|
|
600
|
+
def get_func_containing(self, addr: int) -> Optional[Function]:
|
|
601
|
+
raise NotImplementedError
|
|
602
|
+
|
|
603
|
+
def _decompile(self, function: Function, map_lines=False, **kwargs) -> Optional[Decompilation]:
|
|
604
|
+
raise NotImplementedError
|
|
605
|
+
|
|
606
|
+
def get_decompilation_object(self, function: Function, **kwargs) -> Optional[object]:
|
|
607
|
+
raise NotImplementedError
|
|
608
|
+
|
|
609
|
+
def should_watch_artifacts(self) -> bool:
|
|
610
|
+
return True
|
|
611
|
+
|
|
612
|
+
#
|
|
613
|
+
# Override Optional API:
|
|
614
|
+
# These are API that provide extra introspection for plugins that may rely on DecLib Interface
|
|
615
|
+
#
|
|
616
|
+
|
|
617
|
+
@property
|
|
618
|
+
def binary_arch(self) -> str:
|
|
619
|
+
"""
|
|
620
|
+
Returns a string of the currently loaded binary's architecture.
|
|
621
|
+
"""
|
|
622
|
+
raise NotImplementedError
|
|
623
|
+
|
|
624
|
+
@property
|
|
625
|
+
def default_pointer_size(self) -> int:
|
|
626
|
+
"""
|
|
627
|
+
Returns the default pointer size of the binary. This is useful for calculating offsets
|
|
628
|
+
in the binary.
|
|
629
|
+
"""
|
|
630
|
+
raise NotImplementedError
|
|
631
|
+
|
|
632
|
+
def undo(self):
|
|
633
|
+
"""
|
|
634
|
+
Undoes the last change made to the decompiler.
|
|
635
|
+
"""
|
|
636
|
+
raise NotImplementedError
|
|
637
|
+
|
|
638
|
+
def local_variable_names(self, func: Function) -> List[str]:
|
|
639
|
+
"""
|
|
640
|
+
Returns a list of local variable names for a function. Note, these also include register variables
|
|
641
|
+
that are normally not liftable in DecLib.
|
|
642
|
+
@param func: Function to get local variable names for
|
|
643
|
+
@return: List of local variable names
|
|
644
|
+
"""
|
|
645
|
+
return []
|
|
646
|
+
|
|
647
|
+
def rename_local_variables_by_names(self, func: Function, name_map: Dict[str, str], **kwargs) -> bool:
|
|
648
|
+
"""
|
|
649
|
+
Renames local variables in a function by a name map. Note, these also include register variables
|
|
650
|
+
that are normally not liftable in DecLib.
|
|
651
|
+
@param func: Function to rename local variables in
|
|
652
|
+
@param name_map: Dictionary of old name to new name
|
|
653
|
+
@return: True if any local variables were renamed, False if otherwise
|
|
654
|
+
"""
|
|
655
|
+
return False
|
|
656
|
+
|
|
657
|
+
#
|
|
658
|
+
# Private Artifact API:
|
|
659
|
+
# Unlike the public API, every function in this section should take and return artifacts in their native (lowered)
|
|
660
|
+
# form.
|
|
661
|
+
#
|
|
662
|
+
|
|
663
|
+
# functions
|
|
664
|
+
def _set_function(self, func: Function, **kwargs) -> bool:
|
|
665
|
+
update = False
|
|
666
|
+
header = func.header
|
|
667
|
+
if header is not None:
|
|
668
|
+
update |= self._set_function_header(header, **kwargs)
|
|
669
|
+
|
|
670
|
+
if func.stack_vars:
|
|
671
|
+
update |= self._set_stack_variables(list(func.stack_vars.values()), **kwargs)
|
|
672
|
+
|
|
673
|
+
return update
|
|
674
|
+
|
|
675
|
+
def _get_function(self, addr, **kwargs) -> Optional[Function]:
|
|
676
|
+
return None
|
|
677
|
+
|
|
678
|
+
def _del_function(self, addr, **kwargs) -> bool:
|
|
679
|
+
return False
|
|
680
|
+
|
|
681
|
+
def _functions(self) -> Dict[int, Function]:
|
|
682
|
+
"""
|
|
683
|
+
Returns a dict of declib.Functions that contain the addr, name, and size of each function in the decompiler.
|
|
684
|
+
Note: this does not contain the live artifacts of the Artifact, only the minimum knowledge to that the Artifact
|
|
685
|
+
exists. To get live artifacts, use the singleton function of the same name.
|
|
686
|
+
|
|
687
|
+
@return:
|
|
688
|
+
"""
|
|
689
|
+
return {}
|
|
690
|
+
|
|
691
|
+
# stack vars
|
|
692
|
+
def _set_stack_variables(self, svars: List[StackVariable], **kwargs) -> bool:
|
|
693
|
+
update = False
|
|
694
|
+
for svar in svars:
|
|
695
|
+
update |= self._set_stack_variable(svar, **kwargs)
|
|
696
|
+
|
|
697
|
+
return update
|
|
698
|
+
|
|
699
|
+
def _set_stack_variable(self, svar: StackVariable, **kwargs) -> bool:
|
|
700
|
+
return False
|
|
701
|
+
|
|
702
|
+
def _get_stack_variable(self, addr: int, offset: int, **kwargs) -> Optional[StackVariable]:
|
|
703
|
+
func = self._get_function(addr, **kwargs)
|
|
704
|
+
if func is None:
|
|
705
|
+
return None
|
|
706
|
+
|
|
707
|
+
return func.stack_vars.get(offset, None)
|
|
708
|
+
|
|
709
|
+
def _del_stack_variable(self, addr: int, offset: int, **kwargs) -> bool:
|
|
710
|
+
return False
|
|
711
|
+
|
|
712
|
+
def _stack_variables(self, **kwargs) -> Dict[int,Dict[int, StackVariable]]:
|
|
713
|
+
stack_vars = defaultdict(dict)
|
|
714
|
+
for addr in self._functions():
|
|
715
|
+
func = self._get_function(addr, **kwargs)
|
|
716
|
+
for svar in func.stack_vars.values():
|
|
717
|
+
stack_vars[addr][svar.offset] = svar
|
|
718
|
+
|
|
719
|
+
return dict(stack_vars)
|
|
720
|
+
|
|
721
|
+
# global variables
|
|
722
|
+
def _set_global_variable(self, gvar: GlobalVariable, **kwargs) -> bool:
|
|
723
|
+
return False
|
|
724
|
+
|
|
725
|
+
def _get_global_var(self, addr) -> Optional[GlobalVariable]:
|
|
726
|
+
return None
|
|
727
|
+
|
|
728
|
+
def _del_global_var(self, addr) -> bool:
|
|
729
|
+
return False
|
|
730
|
+
|
|
731
|
+
def _global_vars(self, **kwargs) -> Dict[int, GlobalVariable]:
|
|
732
|
+
"""
|
|
733
|
+
Returns a dict of declib.GlobalVariable that contain the addr and size of each global var.
|
|
734
|
+
Note: this does not contain the live artifacts of the Artifact, only the minimum knowledge to that the Artifact
|
|
735
|
+
exists. To get live artifacts, use the singleton function of the same name.
|
|
736
|
+
|
|
737
|
+
@return:
|
|
738
|
+
"""
|
|
739
|
+
return {}
|
|
740
|
+
|
|
741
|
+
# structs
|
|
742
|
+
def _set_struct(self, struct: Struct, header=True, members=True, **kwargs) -> bool:
|
|
743
|
+
return False
|
|
744
|
+
|
|
745
|
+
def _get_struct(self, name) -> Optional[Struct]:
|
|
746
|
+
return None
|
|
747
|
+
|
|
748
|
+
def _del_struct(self, name) -> bool:
|
|
749
|
+
return False
|
|
750
|
+
|
|
751
|
+
def _structs(self) -> Dict[str, Struct]:
|
|
752
|
+
"""
|
|
753
|
+
Returns a dict of declib.Structs that contain the name and size of each struct in the decompiler.
|
|
754
|
+
Note: this does not contain the live artifacts of the Artifact, only the minimum knowledge to that the Artifact
|
|
755
|
+
exists. To get live artifacts, use the singleton function of the same name.
|
|
756
|
+
|
|
757
|
+
@return:
|
|
758
|
+
"""
|
|
759
|
+
return {}
|
|
760
|
+
|
|
761
|
+
# enums
|
|
762
|
+
def _set_enum(self, enum: Enum, **kwargs) -> bool:
|
|
763
|
+
return False
|
|
764
|
+
|
|
765
|
+
def _get_enum(self, name) -> Optional[Enum]:
|
|
766
|
+
return None
|
|
767
|
+
|
|
768
|
+
def _del_enum(self, name) -> bool:
|
|
769
|
+
return False
|
|
770
|
+
|
|
771
|
+
def _enums(self) -> Dict[str, Enum]:
|
|
772
|
+
"""
|
|
773
|
+
Returns a dict of declib.Enum that contain the name of the enums in the decompiler.
|
|
774
|
+
Note: this does not contain the live artifacts of the Artifact, only the minimum knowledge to that the Artifact
|
|
775
|
+
exists. To get live artifacts, use the singleton function of the same name.
|
|
776
|
+
|
|
777
|
+
@return:
|
|
778
|
+
"""
|
|
779
|
+
return {}
|
|
780
|
+
|
|
781
|
+
# typedefs
|
|
782
|
+
def _set_typedef(self, typedef: Typedef, **kwargs) -> bool:
|
|
783
|
+
return False
|
|
784
|
+
|
|
785
|
+
def _get_typedef(self, name) -> Optional[Typedef]:
|
|
786
|
+
return None
|
|
787
|
+
|
|
788
|
+
def _del_typedef(self, name) -> bool:
|
|
789
|
+
return False
|
|
790
|
+
|
|
791
|
+
def _typedefs(self) -> Dict[str, Typedef]:
|
|
792
|
+
"""
|
|
793
|
+
Returns a dict of declib.Typedef that contain the name of the typedefs in the decompiler.
|
|
794
|
+
Note: this does not contain the live artifacts of the Artifact, only the minimum knowledge to that the Artifact
|
|
795
|
+
exists. To get live artifacts, use the singleton function of the same name.
|
|
796
|
+
|
|
797
|
+
@return:
|
|
798
|
+
"""
|
|
799
|
+
return {}
|
|
800
|
+
|
|
801
|
+
# patches
|
|
802
|
+
def _set_patch(self, patch: Patch, **kwargs) -> bool:
|
|
803
|
+
return False
|
|
804
|
+
|
|
805
|
+
def _get_patch(self, addr) -> Optional[Patch]:
|
|
806
|
+
return None
|
|
807
|
+
|
|
808
|
+
def _del_patch(self, addr) -> bool:
|
|
809
|
+
return False
|
|
810
|
+
|
|
811
|
+
def _patches(self) -> Dict[int, Patch]:
|
|
812
|
+
"""
|
|
813
|
+
Returns a dict of declib.Patch that contain the addr of each Patch and the bytes.
|
|
814
|
+
Note: this does not contain the live artifacts of the Artifact, only the minimum knowledge to that the Artifact
|
|
815
|
+
exists. To get live artifacts, use the singleton function of the same name.
|
|
816
|
+
|
|
817
|
+
@return:
|
|
818
|
+
"""
|
|
819
|
+
return {}
|
|
820
|
+
|
|
821
|
+
# comments
|
|
822
|
+
def _set_comment(self, comment: Comment, **kwargs) -> bool:
|
|
823
|
+
return False
|
|
824
|
+
|
|
825
|
+
def _get_comment(self, addr) -> Optional[Comment]:
|
|
826
|
+
return None
|
|
827
|
+
|
|
828
|
+
def _del_comment(self, addr) -> bool:
|
|
829
|
+
return False
|
|
830
|
+
|
|
831
|
+
def _comments(self) -> Dict[int, Comment]:
|
|
832
|
+
return {}
|
|
833
|
+
|
|
834
|
+
# segments
|
|
835
|
+
def _set_segment(self, segment: Segment, **kwargs) -> bool:
|
|
836
|
+
return False
|
|
837
|
+
|
|
838
|
+
def _get_segment(self, name) -> Optional[Segment]:
|
|
839
|
+
return None
|
|
840
|
+
|
|
841
|
+
def _del_segment(self, name) -> bool:
|
|
842
|
+
return False
|
|
843
|
+
|
|
844
|
+
def _segments(self) -> Dict[str, Segment]:
|
|
845
|
+
"""
|
|
846
|
+
Returns a dict of declib.Segment that contain the name, start_addr, end_addr, and permissions of each segment.
|
|
847
|
+
Note: this does not contain the live artifacts of the Artifact, only the minimum knowledge to that the Artifact
|
|
848
|
+
exists. To get live artifacts, use the singleton function of the same name.
|
|
849
|
+
|
|
850
|
+
@return:
|
|
851
|
+
"""
|
|
852
|
+
return {}
|
|
853
|
+
|
|
854
|
+
# others...
|
|
855
|
+
def _set_function_header(self, fheader: FunctionHeader, **kwargs) -> bool:
|
|
856
|
+
return False
|
|
857
|
+
|
|
858
|
+
#
|
|
859
|
+
# Change Callback API:
|
|
860
|
+
# Every callback in this group assumes the input will be decompiler-specific (lowered) and will
|
|
861
|
+
# lift it ONCE inside this function. Each one will return the lifted form, for easier overriding.
|
|
862
|
+
#
|
|
863
|
+
|
|
864
|
+
def decompiler_opened_event(self, **kwargs):
|
|
865
|
+
"""
|
|
866
|
+
This function is called when the decompiler platform this interface is running on is opened for the first time.
|
|
867
|
+
In the presence of a decompiler with multiple tabs, this function will still only be called once.
|
|
868
|
+
"""
|
|
869
|
+
for callback_func in self.decompiler_opened_callbacks:
|
|
870
|
+
if self._thread_artifact_callbacks:
|
|
871
|
+
threading.Thread(target=callback_func, kwargs=kwargs, daemon=True).start()
|
|
872
|
+
else:
|
|
873
|
+
callback_func(**kwargs)
|
|
874
|
+
|
|
875
|
+
def decompiler_closed_event(self, **kwargs):
|
|
876
|
+
"""
|
|
877
|
+
This function is called when the decompiler platform this interface is running on is closing/closed.
|
|
878
|
+
In the presence of a decompiler with multiple tabs, this function will still only be called once.
|
|
879
|
+
"""
|
|
880
|
+
for callback_func in self.decompiler_closed_callbacks:
|
|
881
|
+
if self._thread_artifact_callbacks:
|
|
882
|
+
threading.Thread(target=callback_func, kwargs=kwargs, daemon=True).start()
|
|
883
|
+
else:
|
|
884
|
+
callback_func(**kwargs)
|
|
885
|
+
|
|
886
|
+
def gui_undo_event(self, **kwargs):
|
|
887
|
+
for callback_func in self.undo_event_callbacks:
|
|
888
|
+
if self._thread_artifact_callbacks:
|
|
889
|
+
threading.Thread(target=callback_func, kwargs=kwargs, daemon=True).start()
|
|
890
|
+
else:
|
|
891
|
+
callback_func(**kwargs)
|
|
892
|
+
|
|
893
|
+
def gui_context_changed(self, ctx: Context, **kwargs) -> declib.artifacts.Context:
|
|
894
|
+
# XXX: should this be lifted?
|
|
895
|
+
for callback_func in self.artifact_change_callbacks[Context]:
|
|
896
|
+
args = (ctx,)
|
|
897
|
+
if self._thread_artifact_callbacks:
|
|
898
|
+
threading.Thread(target=callback_func, args=args, kwargs=kwargs, daemon=True).start()
|
|
899
|
+
else:
|
|
900
|
+
callback_func(*args, **kwargs)
|
|
901
|
+
|
|
902
|
+
return ctx
|
|
903
|
+
|
|
904
|
+
def segment_changed(self, segment: Segment, **kwargs) -> Segment:
|
|
905
|
+
lifted_segment = self.art_lifter.lift(segment)
|
|
906
|
+
for callback_func in self.artifact_change_callbacks[Segment]:
|
|
907
|
+
args = (lifted_segment,)
|
|
908
|
+
if self._thread_artifact_callbacks:
|
|
909
|
+
threading.Thread(target=callback_func, args=args, kwargs=kwargs, daemon=True).start()
|
|
910
|
+
else:
|
|
911
|
+
callback_func(*args, **kwargs)
|
|
912
|
+
|
|
913
|
+
return lifted_segment
|
|
914
|
+
|
|
915
|
+
def function_header_changed(self, fheader: FunctionHeader, **kwargs) -> FunctionHeader:
|
|
916
|
+
lifted_fheader = self.art_lifter.lift(fheader)
|
|
917
|
+
for callback_func in self.artifact_change_callbacks[FunctionHeader]:
|
|
918
|
+
args = (lifted_fheader,)
|
|
919
|
+
if self._thread_artifact_callbacks:
|
|
920
|
+
threading.Thread(target=callback_func, args=args, kwargs=kwargs, daemon=True).start()
|
|
921
|
+
else:
|
|
922
|
+
callback_func(*args, **kwargs)
|
|
923
|
+
|
|
924
|
+
return lifted_fheader
|
|
925
|
+
|
|
926
|
+
def stack_variable_changed(self, svar: StackVariable, **kwargs) -> StackVariable:
|
|
927
|
+
lifted_svar = self.art_lifter.lift(svar)
|
|
928
|
+
for callback_func in self.artifact_change_callbacks[StackVariable]:
|
|
929
|
+
args = (lifted_svar,)
|
|
930
|
+
if self._thread_artifact_callbacks:
|
|
931
|
+
threading.Thread(target=callback_func, args=args, kwargs=kwargs, daemon=True).start()
|
|
932
|
+
else:
|
|
933
|
+
callback_func(*args, **kwargs)
|
|
934
|
+
|
|
935
|
+
return lifted_svar
|
|
936
|
+
|
|
937
|
+
def comment_changed(self, comment: Comment, deleted=False, **kwargs) -> Comment:
|
|
938
|
+
kwargs["deleted"] = deleted
|
|
939
|
+
lifted_cmt = self.art_lifter.lift(comment)
|
|
940
|
+
for callback_func in self.artifact_change_callbacks[Comment]:
|
|
941
|
+
args = (lifted_cmt,)
|
|
942
|
+
if self._thread_artifact_callbacks:
|
|
943
|
+
threading.Thread(target=callback_func, args=args, kwargs=kwargs, daemon=True).start()
|
|
944
|
+
else:
|
|
945
|
+
callback_func(*args, **kwargs)
|
|
946
|
+
|
|
947
|
+
return lifted_cmt
|
|
948
|
+
|
|
949
|
+
def struct_changed(self, struct: Struct, deleted=False, **kwargs) -> Struct:
|
|
950
|
+
kwargs["deleted"] = deleted
|
|
951
|
+
lifted_struct = self.art_lifter.lift(struct)
|
|
952
|
+
for callback_func in self.artifact_change_callbacks[Struct]:
|
|
953
|
+
args = (lifted_struct,)
|
|
954
|
+
if self._thread_artifact_callbacks:
|
|
955
|
+
threading.Thread(target=callback_func, args=args, kwargs=kwargs, daemon=True).start()
|
|
956
|
+
else:
|
|
957
|
+
callback_func(*args, **kwargs)
|
|
958
|
+
|
|
959
|
+
return lifted_struct
|
|
960
|
+
|
|
961
|
+
def decompilation_changed(self, decompilation: Decompilation, **kwargs) -> Decompilation:
|
|
962
|
+
lifted_dcmp = self.art_lifter.lift(decompilation)
|
|
963
|
+
for callback_func in self.artifact_change_callbacks[Decompilation]:
|
|
964
|
+
args = (lifted_dcmp,)
|
|
965
|
+
if self._thread_artifact_callbacks:
|
|
966
|
+
threading.Thread(target=callback_func, args=args, kwargs=kwargs, daemon=True).start()
|
|
967
|
+
else:
|
|
968
|
+
callback_func(*args, **kwargs)
|
|
969
|
+
|
|
970
|
+
return lifted_dcmp
|
|
971
|
+
|
|
972
|
+
def enum_changed(self, enum: Enum, deleted=False, **kwargs) -> Enum:
|
|
973
|
+
kwargs["deleted"] = deleted
|
|
974
|
+
lifted_enum = self.art_lifter.lift(enum)
|
|
975
|
+
for callback_func in self.artifact_change_callbacks[Enum]:
|
|
976
|
+
args = (lifted_enum,)
|
|
977
|
+
if self._thread_artifact_callbacks:
|
|
978
|
+
threading.Thread(target=callback_func, args=args, kwargs=kwargs, daemon=True).start()
|
|
979
|
+
else:
|
|
980
|
+
callback_func(*args, **kwargs)
|
|
981
|
+
|
|
982
|
+
return lifted_enum
|
|
983
|
+
|
|
984
|
+
def typedef_changed(self, typedef: Typedef, deleted=False, **kwargs) -> Typedef:
|
|
985
|
+
kwargs["deleted"] = deleted
|
|
986
|
+
lifted_typedef = self.art_lifter.lift(typedef)
|
|
987
|
+
for callback_func in self.artifact_change_callbacks[Typedef]:
|
|
988
|
+
args = (lifted_typedef,)
|
|
989
|
+
if self._thread_artifact_callbacks:
|
|
990
|
+
threading.Thread(target=callback_func, args=args, kwargs=kwargs, daemon=True).start()
|
|
991
|
+
else:
|
|
992
|
+
callback_func(*args, **kwargs)
|
|
993
|
+
|
|
994
|
+
return lifted_typedef
|
|
995
|
+
|
|
996
|
+
def global_variable_changed(self, gvar: GlobalVariable, **kwargs) -> GlobalVariable:
|
|
997
|
+
lifted_gvar = self.art_lifter.lift(gvar)
|
|
998
|
+
for callback_func in self.artifact_change_callbacks[GlobalVariable]:
|
|
999
|
+
args = (lifted_gvar,)
|
|
1000
|
+
if self._thread_artifact_callbacks:
|
|
1001
|
+
threading.Thread(target=callback_func, args=args, kwargs=kwargs, daemon=True).start()
|
|
1002
|
+
else:
|
|
1003
|
+
callback_func(*args, **kwargs)
|
|
1004
|
+
|
|
1005
|
+
return lifted_gvar
|
|
1006
|
+
|
|
1007
|
+
#
|
|
1008
|
+
# Special Loggers and Printers
|
|
1009
|
+
#
|
|
1010
|
+
|
|
1011
|
+
def print(self, msg: str, **kwargs):
|
|
1012
|
+
print(msg)
|
|
1013
|
+
|
|
1014
|
+
def info(self, msg: str, **kwargs):
|
|
1015
|
+
_l.info(msg)
|
|
1016
|
+
|
|
1017
|
+
def debug(self, msg: str, **kwargs):
|
|
1018
|
+
_l.debug(msg)
|
|
1019
|
+
|
|
1020
|
+
def warning(self, msg: str, **kwargs):
|
|
1021
|
+
_l.warning(msg)
|
|
1022
|
+
|
|
1023
|
+
def error(self, msg: str, **kwargs):
|
|
1024
|
+
_l.error(msg)
|
|
1025
|
+
|
|
1026
|
+
#
|
|
1027
|
+
# Utils
|
|
1028
|
+
#
|
|
1029
|
+
|
|
1030
|
+
def set_artifact(self, artifact: Artifact, lower=True, **kwargs) -> bool:
|
|
1031
|
+
"""
|
|
1032
|
+
Sets a declib Artifact into the decompilers local database. This operations allows you to change
|
|
1033
|
+
what the native decompiler sees with declib Artifacts. This is different from opertions on a declib State,
|
|
1034
|
+
since this is native to the decompiler
|
|
1035
|
+
|
|
1036
|
+
>>> func = Function(0xdeadbeef, 0x800)
|
|
1037
|
+
>>> func.name = "main"
|
|
1038
|
+
>>> deci.set_artifact(func)
|
|
1039
|
+
|
|
1040
|
+
@param artifact:
|
|
1041
|
+
@param lower: Wether to convert the Artifacts types and offset into the local decompilers format
|
|
1042
|
+
@return: True if the Artifact was succesfuly set into the decompiler
|
|
1043
|
+
"""
|
|
1044
|
+
set_map = {
|
|
1045
|
+
Function: self._set_function,
|
|
1046
|
+
FunctionHeader: self._set_function_header,
|
|
1047
|
+
StackVariable: self._set_stack_variable,
|
|
1048
|
+
Comment: self._set_comment,
|
|
1049
|
+
GlobalVariable: self._set_global_variable,
|
|
1050
|
+
Struct: self._set_struct,
|
|
1051
|
+
Enum: self._set_enum,
|
|
1052
|
+
Patch: self._set_patch,
|
|
1053
|
+
Segment: self._set_segment,
|
|
1054
|
+
Artifact: None,
|
|
1055
|
+
}
|
|
1056
|
+
|
|
1057
|
+
if lower:
|
|
1058
|
+
artifact = self.art_lifter.lower(artifact)
|
|
1059
|
+
|
|
1060
|
+
setter = set_map.get(type(artifact), None)
|
|
1061
|
+
if setter is None:
|
|
1062
|
+
_l.critical("Unsupported object is attempting to be set, please check your object: %s", artifact)
|
|
1063
|
+
return False
|
|
1064
|
+
|
|
1065
|
+
return setter(artifact, **kwargs)
|
|
1066
|
+
|
|
1067
|
+
@staticmethod
|
|
1068
|
+
def get_identifiers(artifact: Artifact) -> Tuple:
|
|
1069
|
+
if isinstance(artifact, (Function, FunctionHeader, GlobalVariable, Patch, Comment)):
|
|
1070
|
+
return (artifact.addr,)
|
|
1071
|
+
elif isinstance(artifact, StackVariable):
|
|
1072
|
+
return artifact.addr, artifact.offset
|
|
1073
|
+
elif isinstance(artifact, FunctionArgument):
|
|
1074
|
+
# TODO: add addr to function arguments
|
|
1075
|
+
return (artifact.offset,)
|
|
1076
|
+
elif isinstance(artifact, (Struct, Enum, Typedef, Segment)):
|
|
1077
|
+
return (artifact.name,)
|
|
1078
|
+
else:
|
|
1079
|
+
raise ValueError(f"Unsupported artifact type: {type(artifact)}")
|
|
1080
|
+
|
|
1081
|
+
def get_defined_type(self, type_str) -> Optional[Artifact]:
|
|
1082
|
+
if not type_str:
|
|
1083
|
+
return None
|
|
1084
|
+
|
|
1085
|
+
normalized_type, scope = self.art_lifter.parse_scoped_type(type_str)
|
|
1086
|
+
type_: CType = self.type_parser.parse_type(normalized_type)
|
|
1087
|
+
if not type_:
|
|
1088
|
+
# it was not parseable
|
|
1089
|
+
return None
|
|
1090
|
+
|
|
1091
|
+
# type is a primitive that returns no base type
|
|
1092
|
+
base_type = type_.base_type
|
|
1093
|
+
if base_type is None:
|
|
1094
|
+
return None
|
|
1095
|
+
|
|
1096
|
+
# if we trigger here, it means it's not a user-defined type
|
|
1097
|
+
if not base_type.is_unknown:
|
|
1098
|
+
return None
|
|
1099
|
+
|
|
1100
|
+
base_type_str = base_type.type
|
|
1101
|
+
lifted_scoped_type = self.art_lifter.scoped_type_to_str(base_type_str, scope)
|
|
1102
|
+
if lifted_scoped_type in self.structs:
|
|
1103
|
+
return self.structs[lifted_scoped_type]
|
|
1104
|
+
elif lifted_scoped_type in self.enums:
|
|
1105
|
+
return self.enums[lifted_scoped_type]
|
|
1106
|
+
elif lifted_scoped_type in self.typedefs:
|
|
1107
|
+
return self.typedefs[lifted_scoped_type]
|
|
1108
|
+
else:
|
|
1109
|
+
return None
|
|
1110
|
+
|
|
1111
|
+
@staticmethod
|
|
1112
|
+
def _find_global_in_call_frames(global_name, max_frames=10):
|
|
1113
|
+
curr_frame = inspect.currentframe()
|
|
1114
|
+
outer_frames = inspect.getouterframes(curr_frame, max_frames)
|
|
1115
|
+
for frame in outer_frames:
|
|
1116
|
+
global_data = frame.frame.f_globals.get(global_name, None)
|
|
1117
|
+
if global_data is not None:
|
|
1118
|
+
return global_data
|
|
1119
|
+
else:
|
|
1120
|
+
return None
|
|
1121
|
+
|
|
1122
|
+
@staticmethod
|
|
1123
|
+
def find_current_decompiler(force: str = None) -> Optional[str]:
|
|
1124
|
+
"""
|
|
1125
|
+
Finds the name of the current decompiler that this function is running inside of. Note, this function
|
|
1126
|
+
does not create an interface, but instead finds the name of the decompiler that is currently running.
|
|
1127
|
+
"""
|
|
1128
|
+
available = set()
|
|
1129
|
+
|
|
1130
|
+
# Binary Ninja
|
|
1131
|
+
# this check needs to be done last since there is no way to traverse the stack frame to find the correct
|
|
1132
|
+
# BV at this point in time.
|
|
1133
|
+
try:
|
|
1134
|
+
import binaryninja
|
|
1135
|
+
has_bn_ui = False
|
|
1136
|
+
try:
|
|
1137
|
+
import binaryninjaui
|
|
1138
|
+
has_bn_ui = True
|
|
1139
|
+
except Exception:
|
|
1140
|
+
pass
|
|
1141
|
+
|
|
1142
|
+
if has_bn_ui:
|
|
1143
|
+
return BINJA_DECOMPILER
|
|
1144
|
+
available.add(BINJA_DECOMPILER)
|
|
1145
|
+
# error can be thrown for an invalid license
|
|
1146
|
+
except Exception as e:
|
|
1147
|
+
if "License is not valid" in str(e):
|
|
1148
|
+
_l.warning("Binary Ninja license is invalid, skipping...")
|
|
1149
|
+
|
|
1150
|
+
# Ghidra
|
|
1151
|
+
this_obj = DecompilerInterface._find_global_in_call_frames("__this__")
|
|
1152
|
+
if (this_obj is not None) and (hasattr(this_obj, "currentProgram")):
|
|
1153
|
+
available.add(GHIDRA_DECOMPILER)
|
|
1154
|
+
if not force:
|
|
1155
|
+
return GHIDRA_DECOMPILER
|
|
1156
|
+
|
|
1157
|
+
# angr-management
|
|
1158
|
+
try:
|
|
1159
|
+
import angr
|
|
1160
|
+
available.add(ANGR_DECOMPILER)
|
|
1161
|
+
import angrmanagement
|
|
1162
|
+
if DecompilerInterface._find_global_in_call_frames('workspace') is not None:
|
|
1163
|
+
return ANGR_DECOMPILER
|
|
1164
|
+
except Exception:
|
|
1165
|
+
pass
|
|
1166
|
+
|
|
1167
|
+
# IDA Pro
|
|
1168
|
+
try:
|
|
1169
|
+
import idaapi
|
|
1170
|
+
available.add(IDA_DECOMPILER)
|
|
1171
|
+
if not force:
|
|
1172
|
+
return IDA_DECOMPILER
|
|
1173
|
+
except Exception:
|
|
1174
|
+
pass
|
|
1175
|
+
|
|
1176
|
+
try:
|
|
1177
|
+
# for IDA 9 Beta
|
|
1178
|
+
import ida
|
|
1179
|
+
available.add(IDA_DECOMPILER)
|
|
1180
|
+
except ImportError:
|
|
1181
|
+
pass
|
|
1182
|
+
try:
|
|
1183
|
+
# for IDA 9+
|
|
1184
|
+
import idapro
|
|
1185
|
+
available.add(IDA_DECOMPILER)
|
|
1186
|
+
except Exception:
|
|
1187
|
+
pass
|
|
1188
|
+
|
|
1189
|
+
if not available:
|
|
1190
|
+
_l.critical("DecLib was unable to find the current decompiler you are running in or any headless instances!")
|
|
1191
|
+
return None
|
|
1192
|
+
|
|
1193
|
+
if force is not None and force not in available:
|
|
1194
|
+
_l.critical("DecLib was unable to force the decompiler you requested... please check your environment.")
|
|
1195
|
+
return None
|
|
1196
|
+
|
|
1197
|
+
if force is None:
|
|
1198
|
+
return available.pop()
|
|
1199
|
+
|
|
1200
|
+
if force in available:
|
|
1201
|
+
return force
|
|
1202
|
+
|
|
1203
|
+
return None
|
|
1204
|
+
|
|
1205
|
+
@staticmethod
|
|
1206
|
+
def discover(
|
|
1207
|
+
force_decompiler: str = None,
|
|
1208
|
+
interface_overrides: Optional[Dict[str, "DecompilerInterface"]] = None,
|
|
1209
|
+
**interface_kwargs
|
|
1210
|
+
) -> Optional["DecompilerInterface"]:
|
|
1211
|
+
"""
|
|
1212
|
+
This function is a special API helper that will attempt to detect the decompiler it is running in and
|
|
1213
|
+
return the valid DLController for that decompiler. You may also force the chosen deci.
|
|
1214
|
+
|
|
1215
|
+
@param force_decompiler: The optional string used to force a specific decompiler interface
|
|
1216
|
+
@param interface_overrides: The optional dict used to override the class of a decompiler interface
|
|
1217
|
+
@return: The DecompilerInterface associated with the current decompiler env
|
|
1218
|
+
"""
|
|
1219
|
+
if force_decompiler and force_decompiler not in SUPPORTED_DECOMPILERS:
|
|
1220
|
+
raise ValueError(f"Unsupported decompiler {force_decompiler}")
|
|
1221
|
+
|
|
1222
|
+
if force_decompiler:
|
|
1223
|
+
if force_decompiler not in SUPPORTED_DECOMPILERS:
|
|
1224
|
+
raise ValueError(f"Unsupported decompiler {force_decompiler}, please use one of {SUPPORTED_DECOMPILERS}")
|
|
1225
|
+
current_decompiler = force_decompiler
|
|
1226
|
+
else:
|
|
1227
|
+
current_decompiler = DecompilerInterface.find_current_decompiler(force=force_decompiler)
|
|
1228
|
+
|
|
1229
|
+
# `project_dir` is a user-facing kwarg that translates to the
|
|
1230
|
+
# backend-specific cache/project location. Backends without a concept
|
|
1231
|
+
# of this simply ignore it.
|
|
1232
|
+
project_dir = interface_kwargs.pop("project_dir", None)
|
|
1233
|
+
|
|
1234
|
+
if current_decompiler == IDA_DECOMPILER:
|
|
1235
|
+
from declib.decompilers.ida.interface import IDAInterface
|
|
1236
|
+
deci_class = IDAInterface
|
|
1237
|
+
extra_kwargs = {}
|
|
1238
|
+
if project_dir:
|
|
1239
|
+
extra_kwargs["project_dir"] = project_dir
|
|
1240
|
+
elif current_decompiler == BINJA_DECOMPILER:
|
|
1241
|
+
from declib.decompilers.binja.interface import BinjaInterface
|
|
1242
|
+
deci_class = BinjaInterface
|
|
1243
|
+
extra_kwargs = {"bv": DecompilerInterface._find_global_in_call_frames('bv')}
|
|
1244
|
+
elif current_decompiler == ANGR_DECOMPILER:
|
|
1245
|
+
from declib.decompilers.angr.interface import AngrInterface
|
|
1246
|
+
deci_class = AngrInterface
|
|
1247
|
+
extra_kwargs = {"workspace": DecompilerInterface._find_global_in_call_frames('workspace')}
|
|
1248
|
+
elif current_decompiler == GHIDRA_DECOMPILER:
|
|
1249
|
+
from declib.decompilers.ghidra.interface import GhidraDecompilerInterface
|
|
1250
|
+
deci_class = GhidraDecompilerInterface
|
|
1251
|
+
extra_kwargs = {"flat_api": DecompilerInterface._find_global_in_call_frames('__this__')}
|
|
1252
|
+
if project_dir:
|
|
1253
|
+
extra_kwargs["project_location"] = project_dir
|
|
1254
|
+
else:
|
|
1255
|
+
raise ValueError("Please use DecLib with our supported decompiler set!")
|
|
1256
|
+
|
|
1257
|
+
if interface_overrides is not None and current_decompiler in interface_overrides:
|
|
1258
|
+
deci_class = interface_overrides[current_decompiler]
|
|
1259
|
+
|
|
1260
|
+
interface_kwargs.update(extra_kwargs)
|
|
1261
|
+
return deci_class(**interface_kwargs)
|