Skip to content

Commit 6c8743c

Browse files
authored
Merge pull request #3 from RevEngAI/dev_view_functions_in_portal
feat: view function in portal added!
2 parents ba0eec8 + 7b1c238 commit 6c8743c

6 files changed

Lines changed: 164 additions & 54 deletions

File tree

revengai/features/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,6 @@
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
78

8-
__all__ = ['ConfigurationFeature', 'UploadFeature', 'AutoUnstripFeature', 'ChooseSourceFeature', 'MatchFunctionsFeature', 'MatchCurrentFunctionFeature']
9+
__all__ = ['ConfigurationFeature', 'UploadFeature', 'AutoUnstripFeature', 'ChooseSourceFeature', 'MatchFunctionsFeature', 'MatchCurrentFunctionFeature', 'ViewFunctionInPortalFeature']

revengai/features/match_current_function/match_current_function.py

Lines changed: 1 addition & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -123,60 +123,9 @@ def match_functions(self, bv: BinaryView, options: Dict[str, Any]) -> List[Dict]
123123

124124
except Exception as e:
125125
log_error(f"RevEng.AI | Error in function matching: {str(e)}")
126-
raise
127-
128-
def parse_date(date_str: str) -> str:
129-
dt = datetime.strptime(date_str, "%Y-%m-%dT%H:%M:%S.%f")
130-
return dt.strftime("%Y-%m-%d %H:%M:%S")
131-
132-
def fetch_results(api_func, label: str) -> List[Dict[str, Any]]:
133-
try:
134-
log_info(f"RevEng.AI | Query: {query}")
135-
response = api_func(query=query, page=1, page_size=1024).json()
136-
results = response.get("data", {}).get("results", [])
137-
log_info(f"Found {len(results)} {label.lower()}s")
138-
return results
139-
140-
except Exception as e:
141-
log_error(f"RevEng.AI | Getting information failed. Reason: {str(e)}")
142-
return []
143-
144-
def build_items(items_list: List[Dict[str, Any]], item_type: str) -> List[Tuple]:
145-
items = []
146-
for item in items_list:
147-
name_key = "collection_name" if item_type == "Collection" else "binary_name"
148-
date_key = "last_updated_at" if item_type == "Collection" else "created_at"
149-
id_key = "collection_id" if item_type == "Collection" else "binary_id"
150-
icon = "lock.png" if item_type == "Collection" and item["scope"] == "PRIVATE" else \
151-
"unlock.png" if item_type == "Collection" else "file.png"
152-
153-
items.append({
154-
"name": item[name_key],
155-
"icon": icon,
156-
"type": item_type,
157-
"date": parse_date(item[date_key]),
158-
"model_name": item["model_name"],
159-
"owner": item["owned_by"],
160-
"id": item[id_key]
161-
})
162-
return items
163-
164-
try:
165-
166-
log_info(f"RevEng.AI | Searching for collections with '{query or 'N/A'}'")
167-
168-
collections_data = fetch_results(RE_collections_search, "collection")
169-
binaries_data = fetch_results(RE_binaries_search, "binary")
170-
171-
table_items = build_items(collections_data, "Collection")
172-
table_items += build_items(binaries_data, "Binary")
173-
174-
return table_items
175-
176-
except Exception as e:
177-
log_error("Getting collections failed. Reason: %s", str(e))
178126
return False, str(e)
179127

128+
180129
def rename_function(self, bv: BinaryView, selected_result: Dict) -> List[Dict]:
181130
try:
182131
log_info(f"RevEng.AI | Starting function renaming for {len(selected_result)} functions")
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
from binaryninja import PluginCommand, log_info, BinaryView
2+
from .view_function_in_portal import ViewFunctionInPortal
3+
from .view_function_in_portal_dialog import ViewFunctionInPortalDialog
4+
from revengai.utils import BaseAuthFeature
5+
6+
class ViewFunctionInPortalFeature(BaseAuthFeature):
7+
def __init__(self, config=None):
8+
super().__init__(config)
9+
self.view_function_in_portal = ViewFunctionInPortal(config)
10+
log_info("RevEng.AI | ViewFunctionInPortal Feature initialized")
11+
12+
def register(self):
13+
PluginCommand.register_for_address(
14+
"RevEng.AI\\7 - View Function in Portal",
15+
"View the current function in the RevEng.AI portal",
16+
self.show_match_current_function_dialog,
17+
self.is_valid
18+
)
19+
log_info("RevEng.AI | ViewFunctionInPortal Feature registered")
20+
21+
def show_match_current_function_dialog(self, bv: BinaryView, func):
22+
log_info("RevEng.AI | Opening MatchCurrentFunction dialog")
23+
dialog = ViewFunctionInPortalDialog(self.config, self.view_function_in_portal, bv, func)
24+
dialog.exec_()
25+
26+
def is_valid(self, bv: BinaryView, func):
27+
return self.config.is_configured == True
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
from binaryninja import BinaryView, log_info, log_error, Symbol, SymbolType, interaction
2+
from binaryninja.interaction import InteractionHandler
3+
from reait.api import RE_authentication, RE_search, RE_nearest_symbols_batch, RE_analyze_functions, RE_name_score, RE_functions_data_types, RE_functions_data_types_poll, RE_get_analysis_id_from_binary_id, RE_get_functions_from_analysis
4+
from concurrent.futures import ThreadPoolExecutor, as_completed
5+
from typing import List, Dict, Tuple
6+
import math
7+
from revengai.utils.datatypes import apply_data_types as apply_data_types_util
8+
import time
9+
from revengai.utils import rename_function as rename_function_util
10+
from libbs.api import DecompilerInterface
11+
from libbs.decompilers.binja.interface import BinjaInterface
12+
from libbs.artifacts import _art_from_dict
13+
from libbs.artifacts import (
14+
Function,
15+
FunctionArgument,
16+
GlobalVariable,
17+
Enum,
18+
Struct,
19+
Typedef,
20+
)
21+
22+
class ViewFunctionInPortal:
23+
def __init__(self, config):
24+
self.config = config
25+
26+
27+
def view_function_in_portal(self, bv: BinaryView, options: Dict) -> None:
28+
"""Match functions from the binary against RevEng.AI database"""
29+
try:
30+
log_info("RevEng.AI | Starting function searching in portal")
31+
function_addr = options.get("function", None)
32+
33+
functions_containing = bv.get_functions_containing(function_addr)
34+
35+
if not functions_containing:
36+
log_error(f"RevEng.AI | Function not found at 0x{function_addr:x}")
37+
raise Exception("Function not found at address")
38+
39+
function = functions_containing[0]
40+
log_info(f"RevEng.AI | Function: {function.name} at 0x{function.start:x} (Clicked address: 0x{function_addr:x})")
41+
42+
binary_id = self.config.get_binary_id(bv)
43+
if not binary_id:
44+
raise Exception("Analysis not found. Please choose one using 'Choose Source' feature.")
45+
46+
analysis = RE_get_analysis_id_from_binary_id(binary_id).json()
47+
analyzed_functions = RE_get_functions_from_analysis(analysis["analysis_id"]).json()["data"]["functions"]
48+
49+
analyzed_function = next((f for f in analyzed_functions if (f["function_vaddr"] + bv.image_base) == function.start), None)
50+
if not analyzed_function:
51+
log_error(f"RevEng.AI | Function {function.name} not found in analyzed functions")
52+
raise Exception("Function not found in analyzed functions")
53+
54+
url = f"https://portal.reveng.ai/function/{analyzed_function['function_id']}"
55+
InteractionHandler().open_url(url)
56+
57+
return True, "Function found in portal"
58+
59+
except Exception as e:
60+
log_error(f"RevEng.AI | Error in function matching: {str(e)}")
61+
return False, str(e)
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
from binaryninja import log_error
2+
from PySide6.QtWidgets import (QDialog, QVBoxLayout, QHBoxLayout,
3+
QPushButton, QLabel, QCheckBox)
4+
from PySide6.QtCore import Qt
5+
from PySide6.QtGui import QPixmap
6+
from PySide6.QtCore import QCoreApplication
7+
from PySide6.QtWidgets import QMessageBox
8+
from PySide6.QtWidgets import QProgressBar
9+
from revengai.utils import create_progress_dialog
10+
from revengai.utils.data_thread import DataThread
11+
import os
12+
13+
class ViewFunctionInPortalDialog(QDialog):
14+
def __init__(self, config, view_function_in_portal, bv, func):
15+
super().__init__()
16+
self.config = config
17+
self.view_function_in_portal = view_function_in_portal
18+
self.bv = bv
19+
self.func = func
20+
self.init_ui()
21+
22+
def init_ui(self):
23+
self.setWindowTitle("RevEng.AI: View Function in Portal")
24+
self.setWindowModality(Qt.WindowModal)
25+
self.setMinimumSize(400, 100)
26+
self.resize(400, 100)
27+
28+
layout = QVBoxLayout()
29+
30+
title_label = QLabel("Searching...")
31+
title_label.setStyleSheet("font-size: 18px;")
32+
layout.addWidget(title_label)
33+
34+
progress_bar = QProgressBar()
35+
progress_bar.setMinimumWidth(250)
36+
progress_bar.setMinimumHeight(20)
37+
layout.addWidget(progress_bar)
38+
39+
self.setLayout(layout)
40+
41+
self.setStyleSheet("""
42+
QProgressBar {
43+
border: 1px solid #cccccc;
44+
border-radius: 4px;
45+
text-align: center;
46+
background-color: #f0f0f0;
47+
min-width: 250px;
48+
min-height: 20px;
49+
}
50+
QProgressBar::chunk {
51+
background-color: #007bff;
52+
border-radius: 3px;
53+
}
54+
""")
55+
56+
options = {
57+
"function": self.func
58+
}
59+
60+
self.view_function_in_portal_thread = DataThread(self.view_function_in_portal.view_function_in_portal, self.bv, options)
61+
self.view_function_in_portal_thread.finished.connect(self._on_view_function_in_portal_finished)
62+
self.view_function_in_portal_thread.start()
63+
64+
def _on_view_function_in_portal_finished(self, success, message):
65+
if success:
66+
self.accept()
67+
else:
68+
log_error(f"RevEng.AI | Failed: {message}")
69+
QMessageBox.critical(self, "RevEng.AI View Function in Portal Error", f"Failed to find function in portal: {message}", QMessageBox.Ok)
70+
self.reject()

revengai/revengai.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from .features import ChooseSourceFeature
66
from .features import MatchFunctionsFeature
77
from .features import MatchCurrentFunctionFeature
8+
from .features import ViewFunctionInPortalFeature
89

910
class RevengAIPlugin:
1011
def __init__(self):
@@ -15,6 +16,7 @@ def __init__(self):
1516
self.choose_source_feature = ChooseSourceFeature(self.config_feature.get_config())
1617
self.match_functions_feature = MatchFunctionsFeature(self.config_feature.get_config())
1718
self.match_current_function_feature = MatchCurrentFunctionFeature(self.config_feature.get_config())
19+
self.view_function_in_portal_feature = ViewFunctionInPortalFeature(self.config_feature.get_config())
1820
self._register_features()
1921

2022
def _register_features(self):
@@ -25,4 +27,4 @@ def _register_features(self):
2527
self.choose_source_feature.register()
2628
self.match_functions_feature.register()
2729
self.match_current_function_feature.register()
28-
30+
self.view_function_in_portal_feature.register()

0 commit comments

Comments
 (0)