Skip to content

Commit 85208c3

Browse files
authored
Merge pull request #4 from RevEngAI/dev_ai_decompiler
Feat: AI Decompiler
2 parents b470297 + 1afe0e5 commit 85208c3

46 files changed

Lines changed: 972 additions & 269 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
reait>=1.0.1
1+
reait>=1.2.5
22
requests>=2.25.1
33
PySide6>=6.0.0
44
libbs==2.15.1

revengai/features/__init__.py

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,16 @@
44
from .choose_source import ChooseSourceFeature
55
from .match_functions import MatchFunctionsFeature
66
from .match_current_function import MatchCurrentFunctionFeature
7-
from .view_function_in_portal import ViewFunctionInPortalFeature
7+
from .view_function_in_portal import ViewFunctionInPortalFeature
8+
from .ai_decompiler import AIDecompilerFeature
89

9-
__all__ = ['ConfigurationFeature', 'UploadFeature', 'AutoUnstripFeature', 'ChooseSourceFeature', 'MatchFunctionsFeature', 'MatchCurrentFunctionFeature', 'ViewFunctionInPortalFeature']
10+
__all__ = [
11+
'ConfigurationFeature',
12+
'UploadFeature',
13+
'AutoUnstripFeature',
14+
'ChooseSourceFeature',
15+
'MatchFunctionsFeature',
16+
'MatchCurrentFunctionFeature',
17+
'ViewFunctionInPortalFeature',
18+
'AIDecompilerFeature'
19+
]
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
from PySide6.QtCore import Qt
2+
from binaryninjaui import UIContext
3+
from .ai_decompiler import AIDecompiler
4+
from PySide6.QtWidgets import QDockWidget
5+
from revengai.utils import BaseAuthFeature
6+
from .ai_decompiler_dialog import AIDecompilerDialog
7+
from binaryninja import PluginCommand, log_info, BinaryView, log_error
8+
9+
class AIDecompilerFeature(BaseAuthFeature):
10+
def __init__(self, config=None):
11+
super().__init__(config)
12+
self.ai_decompiler = AIDecompiler(config)
13+
self.dock_widget = None
14+
self.widget = None
15+
log_info("RevEng.AI | AIDecompiler Feature initialized")
16+
17+
def register(self):
18+
PluginCommand.register_for_address(
19+
"RevEng.AI\\7 - AI Decompiler",
20+
"Get the AI decompiler for the current function",
21+
self.show_ai_decompiler_dialog,
22+
self.is_valid
23+
)
24+
log_info("RevEng.AI | AIDecompiler Feature registered")
25+
26+
def show_ai_decompiler_dialog(self, bv: BinaryView, func):
27+
try:
28+
log_info("RevEng.AI | Opening AI Decompiler Dock")
29+
30+
ctx = UIContext.activeContext()
31+
if not ctx:
32+
log_error("RevEng.AI | No active UI context.")
33+
return
34+
35+
main_win = ctx.mainWindow()
36+
if not main_win:
37+
log_error("RevEng.AI | No main window found.")
38+
return
39+
40+
if self.dock_widget is not None and self.dock_widget.parent() is not None:
41+
self.dock_widget.raise_()
42+
log_info(f"RevEng.AI | AI Decompiler Dock already open, adding tab 0x{func:x}")
43+
if self.widget is not None:
44+
self.widget.pre_tab_setup(bv, func)
45+
return
46+
47+
self.dock_widget = QDockWidget("RevEng.AI | AI Decompiler", main_win)
48+
self.widget = AIDecompilerDialog(self.config, self.ai_decompiler, bv, func)
49+
self.dock_widget.setObjectName("RevEng.AI | AI Decompiler")
50+
self.dock_widget.setWidget(self.widget)
51+
main_win.addDockWidget(Qt.RightDockWidgetArea, self.dock_widget)
52+
self.dock_widget.raise_()
53+
54+
self.dock_widget.visibilityChanged.connect(self.on_dock_closed)
55+
log_info("RevEng.AI | AI Decompiler Dock displayed.")
56+
57+
except Exception as e:
58+
log_error(f"RevEng.AI | Error opening AI Decompiler Dock: {e}")
59+
return
60+
61+
def on_dock_closed(self, visible):
62+
if not visible:
63+
log_info("RevEng.AI | AI Decompiler Dock closed")
64+
self.dock_widget = None
65+
self.widget = None
66+
self.ai_decompiler.stop_address_tracking()
67+
68+
def is_valid(self, bv: BinaryView, func):
69+
return self.config.is_configured == True
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
from typing import Dict, Optional, Callable
2+
from binaryninja import BinaryView, log_info, log_error
3+
from reait.api import RE_poll_ai_decompilation, RE_begin_ai_decompilation
4+
from revengai.utils import get_function_id_by_addr as get_function_id_by_addr_util, AddressChangeMonitor, AIDecompilerChecker
5+
6+
class AIDecompiler:
7+
def __init__(self, config):
8+
self.config = config
9+
self._current_checker = None
10+
self._track_timer = None
11+
self.tracking_enabled = False
12+
self._address_monitor = None
13+
self._timer_monitor = None
14+
self.dialog = None
15+
16+
def stop_ai_decompiler(self):
17+
try:
18+
if self._current_checker:
19+
self._current_checker.stop()
20+
self._current_checker = None
21+
log_info("RevEng.AI | Stopped AI decompiler")
22+
except Exception as e:
23+
log_error(f"RevEng.AI | Error stopping AI decompiler: {str(e)}")
24+
25+
def stop_tracking(self):
26+
try:
27+
if self._track_timer:
28+
self._track_timer.stop()
29+
self._track_timer = None
30+
log_info("RevEng.AI | Stopped active line tracking")
31+
except Exception as e:
32+
log_error(f"RevEng.AI | Error stopping active line tracking: {str(e)}")
33+
34+
def start_address_tracking(self, callback: Optional[Callable] = None, use_timer: bool = True):
35+
try:
36+
self.stop_address_tracking()
37+
self._address_monitor = AddressChangeMonitor(self.address_change_callback)
38+
log_info("RevEng.AI | Started notification-based address tracking")
39+
40+
except Exception as e:
41+
log_error(f"RevEng.AI | Error starting address tracking: {str(e)}")
42+
43+
def stop_address_tracking(self):
44+
try:
45+
if self._address_monitor:
46+
self._address_monitor.unregister()
47+
self._address_monitor = None
48+
log_info("RevEng.AI | Stopped notification-based address tracking")
49+
50+
if self._timer_monitor:
51+
self._timer_monitor.stop()
52+
self._timer_monitor = None
53+
log_info("RevEng.AI | Stopped timer-based address tracking")
54+
55+
except Exception as e:
56+
log_error(f"RevEng.AI | Error stopping address tracking: {str(e)}")
57+
58+
def set_address_tracking_callback(self, callback: Callable):
59+
try:
60+
if self._address_monitor:
61+
self._address_monitor.set_callback(callback)
62+
if self._timer_monitor:
63+
self._timer_monitor.set_callback(callback)
64+
else:
65+
self.start_address_tracking(callback)
66+
except Exception as e:
67+
log_error(f"RevEng.AI | Error setting address tracking callback: {str(e)}")
68+
69+
def address_change_callback(self, context, view, addr, change_type):
70+
if change_type == "address_changed" and addr is not None:
71+
log_info(f"RevEng.AI | Address changed to 0x{addr:x} - could trigger AI decompilation here")
72+
if self.dialog:
73+
log_info(f"RevEng.AI | Pre-tab setup for address 0x{addr:x}")
74+
bv = view.getCurrentViewInterface().getData()
75+
self.dialog.pre_tab_setup(bv, addr)
76+
77+
def start_ai_decompiler(self, bv: BinaryView, options: Dict) -> None:
78+
try:
79+
if not self.tracking_enabled:
80+
self.start_address_tracking(self.address_change_callback)
81+
self.tracking_enabled = True
82+
83+
log_info("RevEng.AI | Starting function searching in portal")
84+
editor = options.get("editor")
85+
tab_name = options.get("tab_name")
86+
function = options.get("function")
87+
callback = options.get("callback")
88+
binary_id = self.config.get_binary_id(bv)
89+
function_id = get_function_id_by_addr_util(bv, function.start, binary_id)
90+
91+
res = RE_poll_ai_decompilation(
92+
function_id,
93+
summarise=True,
94+
).json()
95+
96+
if not res.get("status", False):
97+
callback(editor, "AI Decompilation failed.")
98+
return
99+
100+
poll_status = res.get("data").get("status", "uninitialised")
101+
log_info(f"RevEng.AI | Polling AI decompilation: {poll_status}")
102+
103+
if poll_status == "uninitialised":
104+
log_info(f"RevEng.AI | Starting AI Decompilation for function at 0x{function.start:x}")
105+
106+
try:
107+
res2 = RE_begin_ai_decompilation(
108+
function_id
109+
).json()
110+
except Exception as e:
111+
log_error(f"RevEng.AI | Error beginning AI decompilation: {str(e)}")
112+
callback(editor, "AI Decompilation failed.")
113+
return
114+
115+
if not res2.get("status", False):
116+
callback(editor, "AI Decompilation failed.")
117+
return
118+
119+
log_info("RevEng.AI | AI Decompilation started")
120+
periodic_checker = AIDecompilerChecker()
121+
periodic_checker.start_ai_decompiler_checking(function_id, callback, editor, tab_name)
122+
self._current_checker = periodic_checker
123+
124+
if poll_status == "success":
125+
log_info(f"RevEng.AI | AI Decompilation for function at 0x{function.start:x} is completed")
126+
callback(editor, res.get("data").get("decompilation"))
127+
128+
if poll_status == "error":
129+
log_info(f"RevEng.AI | AI Decompilation for function at 0x{function.start:x} failed")
130+
callback(editor, "AI Decompilation failed.")
131+
132+
except Exception as e:
133+
log_error(f"RevEng.AI | Error in AI decompiler: {str(e)}")
134+
return False, str(e)
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
from PySide6.QtCore import QCoreApplication
2+
from binaryninja import log_error, log_info
3+
from revengai.utils import create_progress_dialog, get_function_by_addr, CHighlighter
4+
from PySide6.QtWidgets import QVBoxLayout, QCheckBox, QWidget, QTabWidget, QPlainTextEdit
5+
6+
class AIDecompilerDialog(QWidget):
7+
def __init__(self, config, ai_decompiler, bv, func):
8+
super().__init__()
9+
self.config = config
10+
self.ai_decompiler = ai_decompiler
11+
self.ai_decompiler.dialog = self
12+
self.bv = bv
13+
self.func = func
14+
self.tabs = QTabWidget()
15+
self.number_of_clicks = 0
16+
self.initial_setup_done = False
17+
self.init_ui()
18+
19+
def init_ui(self):
20+
self.setWindowTitle("RevEng.AI: AI Decompiler")
21+
layout = QVBoxLayout()
22+
self.setLayout(layout)
23+
layout.addWidget(self.tabs)
24+
self.address_tracking_checkbox = QCheckBox("Address Tracking")
25+
self.address_tracking_checkbox.setChecked(True)
26+
layout.addWidget(self.address_tracking_checkbox)
27+
self.address_tracking_checkbox.stateChanged.connect(self.toggle_address_tracking)
28+
self.tabs.tabCloseRequested.connect(self.close_tab)
29+
30+
def showEvent(self, event):
31+
super().showEvent(event)
32+
if not self.initial_setup_done:
33+
self.initial_setup_done = True
34+
QCoreApplication.processEvents()
35+
self.pre_tab_setup(self.bv, self.func)
36+
37+
def toggle_address_tracking(self, state):
38+
log_info(f"RevEng.AI | Address tracking checkbox state changed to {state}")
39+
if state == 2:
40+
log_info(f"RevEng.AI | Starting address tracking")
41+
self.ai_decompiler.start_address_tracking()
42+
else:
43+
log_info(f"RevEng.AI | Stopping address tracking")
44+
self.ai_decompiler.stop_address_tracking()
45+
46+
def pre_tab_setup(self, bv, func):
47+
try:
48+
progress_dialog = create_progress_dialog(self, "RevEng.AI", "Setting up AI Decompiler...")
49+
progress_dialog.show()
50+
QCoreApplication.processEvents()
51+
52+
function = get_function_by_addr(bv, func)
53+
tab_name = str(f"0x{function.start:x}")
54+
log_info(f"RevEng.AI | Given address 0x{func:x} is function: {function.name} at 0x{function.start:x}")
55+
for i in range(self.tabs.count()):
56+
if self.tabs.tabText(i) == tab_name:
57+
log_info(f"RevEng.AI | Tab {tab_name} already exists, switching to it.")
58+
self.tabs.setCurrentIndex(i)
59+
progress_dialog.close()
60+
return
61+
62+
log_info(f"RevEng.AI | Adding tab {tab_name}")
63+
tab = QWidget()
64+
layout = QVBoxLayout()
65+
66+
editor = QPlainTextEdit()
67+
editor.setPlainText("Starting AI Decompiler...")
68+
editor.setReadOnly(True)
69+
70+
editor.show()
71+
QCoreApplication.processEvents()
72+
73+
layout.addWidget(editor)
74+
tab.setLayout(layout)
75+
76+
index = self.tabs.addTab(tab, tab_name)
77+
self.tabs.setCurrentIndex(index)
78+
79+
if self.tabs.count() > 1:
80+
self.tabs.setTabsClosable(True)
81+
82+
options = {
83+
"editor": editor,
84+
"tab_name": tab_name,
85+
"function": function,
86+
"callback": self.edit_editor
87+
}
88+
89+
self.ai_decompiler.start_ai_decompiler(self.bv, options)
90+
progress_dialog.close()
91+
except Exception as e:
92+
if 'progress_dialog' in locals():
93+
progress_dialog.close()
94+
log_error(f"RevEng.AI | Error during pre_tab_setup: {str(e)}")
95+
96+
def close_tab(self, index):
97+
log_info(f"RevEng.AI | Closing tab {index} of {self.tabs.count()} tabs")
98+
99+
try:
100+
self.ai_decompiler.stop_ai_decompiler()
101+
except Exception as e:
102+
log_error(f"RevEng.AI | Error stopping AI decompiler on tab close: {str(e)}")
103+
104+
self.tabs.removeTab(index)
105+
if self.tabs.count() == 1:
106+
self.tabs.setTabsClosable(False)
107+
108+
def closeEvent(self, event):
109+
try:
110+
if hasattr(self, 'ai_decompiler'):
111+
log_info(f"RevEng.AI | Stopping AI decompiler")
112+
self.ai_decompiler.stop_ai_decompiler()
113+
except Exception as e:
114+
log_error(f"RevEng.AI | Error during cleanup: {str(e)}")
115+
116+
super().closeEvent(event)
117+
118+
def edit_editor(self, editor, text):
119+
editor.setPlainText(text)
120+
CHighlighter(editor.document())
121+
122+
123+

revengai/features/auto_unstrip/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
from binaryninja import PluginCommand, log_info, BinaryView
21
from .auto_unstrip import AutoUnstrip
3-
from .auto_unstrip_dialog import AutoUnstripDialog
42
from revengai.utils import BaseAuthFeature
3+
from .auto_unstrip_dialog import AutoUnstripDialog
4+
from binaryninja import PluginCommand, log_info, BinaryView
55

66
class AutoUnstripFeature(BaseAuthFeature):
77
def __init__(self, config=None):

0 commit comments

Comments
 (0)