Skip to content

Commit aec3178

Browse files
committed
improving search tab
1 parent f0d01b7 commit aec3178

3 files changed

Lines changed: 204 additions & 51 deletions

File tree

features/match_functions/match_functions.py

Lines changed: 107 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,16 @@ def __init__(self, config):
1111
self.binary_id = None
1212
self.analyzed_functions = []
1313

14-
def search_collections(self, search_term: str = "") -> List[Dict]:
15-
"""Search for collections in RevEng.AI database"""
14+
def search_collections(self, bv: BinaryView, search_term: str = "") -> List[Dict]:
1615
try:
1716
log_info(f"RevEng.AI | Searching collections with term: '{search_term}'")
17+
query = self._parse_search_query(search_term)
18+
log_info(f"RevEng.AI | Query: {query}")
1819

1920
# Since RE_collections might not be available, we'll use RE_search to find binaries
2021
# and simulate collections based on search results
21-
search_results = RE_search(fpath=search_term if search_term else "").json()
22+
"""
23+
search_results = RE_search(fpath=bv.file.filename, search_term=search_term).json()
2224
2325
if "query_results" not in search_results:
2426
log_error("RevEng.AI | No search results found")
@@ -42,10 +44,10 @@ def search_collections(self, search_term: str = "") -> List[Dict]:
4244
4345
log_info(f"RevEng.AI | Found {len(collections)} collections")
4446
return collections
45-
47+
"""
4648
except Exception as e:
4749
log_error(f"RevEng.AI | Error searching collections: {str(e)}")
48-
return []
50+
return False, str(e)
4951

5052
def get_collection_functions(self, collection_id: str) -> List[Dict]:
5153
"""Get functions from a specific collection (simulated)"""
@@ -180,4 +182,103 @@ def get_function_details(self, bv: BinaryView, function_address: int) -> Optiona
180182

181183
except Exception as e:
182184
log_error(f"RevEng.AI | Error getting function details: {str(e)}")
183-
return None
185+
return None
186+
187+
def _parse_search_query(self, query):
188+
"""
189+
Parse a search query with special selectors.
190+
191+
Args:
192+
query (str): The search query string to parse
193+
194+
Returns:
195+
dict: A dictionary containing parsed query components
196+
197+
Raises:
198+
ValueError: If multiple non-tag selectors or a selector with raw
199+
query are used
200+
"""
201+
# Initialize the result dictionary with default empty values
202+
result = {
203+
'query': None,
204+
'sha_256_hash': None,
205+
'tags': None,
206+
'binary_name': None,
207+
'collection_name': None,
208+
'function_name': None,
209+
'model_name': None
210+
}
211+
212+
# List of possible selectors (excluding 'tag')
213+
single_selectors = [
214+
'sha_256_hash',
215+
'binary_name',
216+
'collection_name',
217+
'function_name',
218+
'model_name'
219+
]
220+
221+
# Parse selector-based queries
222+
def extract_selector_value(query, selector):
223+
"""Helper function to extract selector value"""
224+
selector_pattern = f"{selector}:"
225+
selector_match = query.find(selector_pattern)
226+
227+
if selector_match != -1:
228+
# Extract the value after the selector
229+
start = selector_match + len(selector_pattern)
230+
end = query.find(' ', start)
231+
232+
# If no space found, take till the end of string
233+
if end == -1:
234+
end = len(query)
235+
236+
# Extract the value and the full selector part
237+
value = query[start:end].strip()
238+
full_selector_part = query[selector_match:end].strip()
239+
240+
return value, full_selector_part
241+
242+
return None, None
243+
244+
# Process tags first (can be multiple)
245+
def process_tags(query):
246+
tags = []
247+
while True:
248+
tag_value, tag_part = extract_selector_value(query, 'tag')
249+
if not tag_value:
250+
break
251+
tags.append(tag_value)
252+
query = query.replace(tag_part, '').strip()
253+
if len(tags) == 0:
254+
tags = None
255+
return tags, query
256+
257+
# Process tags
258+
result['tags'], query = process_tags(query)
259+
260+
# Process other single selectors
261+
for selector in single_selectors:
262+
value, selector_part = extract_selector_value(query, selector)
263+
264+
if value:
265+
# Check if this selector was already set
266+
if result[selector] is not None:
267+
raise ValueError(
268+
f"Only one {selector} selector can be used.")
269+
270+
result[selector] = value
271+
query = query.replace(selector_part, '').strip()
272+
273+
# Validation checks for additional text
274+
query = query.strip()
275+
if query:
276+
# If query is not empty after removing selectors
277+
if any(result[selector] is not None for selector in
278+
single_selectors):
279+
raise ValueError(
280+
"Selector cannot be used with additional text.")
281+
# If no other selectors, treat as raw query
282+
result['query'] = query
283+
284+
return result

features/match_functions/match_functions_dialog.py

Lines changed: 75 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,12 @@
33
QLabel, QLineEdit, QTableWidget, QTableWidgetItem,
44
QHeaderView, QTabWidget, QWidget, QMessageBox,
55
QCheckBox, QDoubleSpinBox, QSpinBox, QGroupBox,
6-
QSplitter, QTextEdit, QProgressBar)
7-
from PySide6.QtCore import Qt, QTimer
6+
QSplitter, QTextEdit, QProgressBar, QSlider)
7+
from PySide6.QtCore import Qt, QTimer, QCoreApplication
88
from PySide6.QtGui import QIcon
99
from revengai_bn.utils import create_progress_dialog
1010
from .match_functions_thread import MatchFunctionsThread
11+
from .search_collections_thread import SearchCollectionsThread
1112
import os
1213

1314
class MatchFunctionsDialog(QDialog):
@@ -53,14 +54,21 @@ def create_search_tab(self):
5354
layout = QVBoxLayout()
5455

5556
# Search section
56-
search_group = QGroupBox("Search Collections")
57+
#search_group = QGroupBox("Search Collections")
58+
search_group = QGroupBox()
5759
search_layout = QVBoxLayout()
5860

5961
# Search input
6062
search_input_layout = QHBoxLayout()
6163
self.search_input = QLineEdit()
62-
self.search_input.setPlaceholderText("Enter search term (e.g., 'stealc')")
64+
self.search_input.setPlaceholderText("Enter search term")
6365
self.search_input.returnPressed.connect(self.search_collections)
66+
description_label = QLabel(
67+
"Search (e.g. sha_256_hash:{}, tag:{}, binary_name:{}, collection_name:{}, function_name:{}, model_name:{})"
68+
#"Search Syntax: sha_256_hash: {hash}, tag: {tag}, binary_name: {binary_name}, collection_name: {collection_name},function_name: {function_name}, model_name: {model_name}"
69+
)
70+
description_label.setWordWrap(True)
71+
6472

6573
self.search_button = QPushButton("Search")
6674
self.search_button.clicked.connect(self.search_collections)
@@ -70,6 +78,7 @@ def create_search_tab(self):
7078
color: white;
7179
padding: 6px 12px;
7280
border-radius: 4px;
81+
7382
}
7483
QPushButton:hover {
7584
background-color: #0056b3;
@@ -78,7 +87,9 @@ def create_search_tab(self):
7887

7988
search_input_layout.addWidget(self.search_input)
8089
search_input_layout.addWidget(self.search_button)
90+
8191
search_layout.addLayout(search_input_layout)
92+
search_layout.addWidget(description_label)
8293

8394
# Collections table
8495
self.collections_table = QTableWidget()
@@ -94,36 +105,38 @@ def create_search_tab(self):
94105

95106
self.collections_table.setSelectionBehavior(QTableWidget.SelectRows)
96107
self.collections_table.setAlternatingRowColors(True)
108+
self.collections_table.itemSelectionChanged.connect(self.on_result_selection_changed)
97109

98110
search_layout.addWidget(self.collections_table)
99111
search_group.setLayout(search_layout)
100112
layout.addWidget(search_group)
101113

102114
# Match settings section
103-
settings_group = QGroupBox("Match Settings")
115+
#settings_group = QGroupBox("Match Settings")
116+
settings_group = QGroupBox()
104117
settings_layout = QVBoxLayout()
105-
106-
# Distance threshold
107-
distance_layout = QHBoxLayout()
108-
distance_layout.addWidget(QLabel("Distance Threshold:"))
109-
self.distance_spinbox = QDoubleSpinBox()
110-
self.distance_spinbox.setRange(0.01, 1.0)
111-
self.distance_spinbox.setValue(0.1)
112-
self.distance_spinbox.setSingleStep(0.01)
113-
self.distance_spinbox.setDecimals(2)
114-
distance_layout.addWidget(self.distance_spinbox)
115-
distance_layout.addStretch()
116-
settings_layout.addLayout(distance_layout)
117-
118-
# Max matches
119-
matches_layout = QHBoxLayout()
120-
matches_layout.addWidget(QLabel("Max Matches:"))
121-
self.max_matches_spinbox = QSpinBox()
122-
self.max_matches_spinbox.setRange(1, 100)
123-
self.max_matches_spinbox.setValue(10)
124-
matches_layout.addWidget(self.max_matches_spinbox)
125-
matches_layout.addStretch()
126-
settings_layout.addLayout(matches_layout)
118+
119+
# Confidence slider
120+
confidence_layout = QHBoxLayout()
121+
confidence_layout.addWidget(QLabel("Confidence:"))
122+
self.confidenceSlider = QSlider()
123+
self.confidenceSlider.setMaximum(100)
124+
self.confidenceSlider.setPageStep(5)
125+
self.confidenceSlider.setSliderPosition(90)
126+
self.confidenceSlider.setOrientation(Qt.Horizontal)
127+
self.confidenceSlider.setInvertedAppearance(False)
128+
self.confidenceSlider.setInvertedControls(False)
129+
self.confidenceSlider.setTickPosition(QSlider.TicksBothSides)
130+
self.confidenceSlider.setTickInterval(5)
131+
self.confidenceSlider.setObjectName("confidenceSlider")
132+
confidence_layout.addWidget(self.confidenceSlider)
133+
134+
# Add confidence value label
135+
self.confidence_value_label = QLabel("90")
136+
self.confidenceSlider.valueChanged.connect(lambda value: self.confidence_value_label.setText(str(value)))
137+
confidence_layout.addWidget(self.confidence_value_label)
138+
139+
settings_layout.addLayout(confidence_layout)
127140

128141
# Debug symbols checkbox
129142
self.debug_symbols_checkbox = QCheckBox("Limit Matches to Debug Symbols")
@@ -136,27 +149,33 @@ def create_search_tab(self):
136149
# Action buttons
137150
button_layout = QHBoxLayout()
138151

139-
self.match_button = QPushButton("Match Functions")
140-
self.match_button.setStyleSheet("""
141-
QPushButton {
142-
background-color: #28a745;
143-
color: white;
144-
padding: 8px 16px;
145-
border-radius: 4px;
146-
font-weight: bold;
147-
}
148-
QPushButton:hover {
149-
background-color: #218838;
150-
}
151-
QPushButton:disabled {
152-
background-color: #6c757d;
153-
}
154-
""")
155-
self.match_button.clicked.connect(self.start_matching)
152+
self.fetch_results_button = QPushButton("Fetch Results")
153+
self.fetch_data_types_button = QPushButton("Fetch Data Types")
154+
self.rename_selected_button = QPushButton("Rename Selected")
155+
156+
for button in [self.fetch_results_button, self.fetch_data_types_button, self.rename_selected_button]:
157+
button.setStyleSheet("""
158+
QPushButton {
159+
background-color: #6c757d;
160+
color: white;
161+
padding: 6px 12px;
162+
border-radius: 4px;
163+
}
164+
QPushButton:hover {
165+
background-color: #5a6268;
166+
}
167+
""")
168+
169+
self.fetch_results_button.clicked.connect(self.start_matching)
170+
self.fetch_data_types_button.clicked.connect(self.start_matching)
171+
self.rename_selected_button.clicked.connect(self.start_matching)
156172

157-
button_layout.addWidget(self.match_button)
173+
button_layout.addWidget(self.fetch_results_button)
174+
button_layout.addWidget(self.fetch_data_types_button)
175+
button_layout.addWidget(self.rename_selected_button)
158176
button_layout.addStretch()
159177

178+
160179
layout.addLayout(button_layout)
161180

162181
search_widget.setLayout(layout)
@@ -222,6 +241,17 @@ def create_results_tab(self):
222241

223242
results_widget.setLayout(layout)
224243
return results_widget
244+
245+
def _search_collections(self):
246+
log_info("RevEng.AI | Searching collections")
247+
self.progress = create_progress_dialog(self, "RevEng.AI Search Collections", "Searching collections...")
248+
search_term = self.search_input.text().strip()
249+
self.search_collections_thread = SearchCollectionsThread(self.match_functions, self.bv, search_term)
250+
self.search_collections_thread.finished.connect(self._on_search_collections_finished)
251+
self.search_collections_thread.start()
252+
253+
self.progress.show()
254+
QCoreApplication.processEvents()
225255

226256
def search_collections(self):
227257
"""Search for collections based on input"""
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
from PySide6.QtCore import QThread, Signal
2+
from binaryninja import log_info, BinaryView
3+
4+
class SearchCollectionsThread(QThread):
5+
finished = Signal(bool, str) # Signal for success/failure and error message
6+
7+
def __init__(self, match_functions, bv: BinaryView, search_term):
8+
super().__init__()
9+
self.match_functions = match_functions
10+
self.bv = bv
11+
self.search_term = search_term
12+
log_info(f"RevEng.AI | Search term: {search_term}")
13+
14+
def run(self):
15+
try:
16+
success, message = self.match_functions.search_collections(self.bv, self.search_term)
17+
if success:
18+
self.finished.emit(True, message)
19+
else:
20+
self.finished.emit(False, message)
21+
except Exception as e:
22+
self.finished.emit(False, str(e))

0 commit comments

Comments
 (0)