Skip to content

Commit e95aaef

Browse files
authored
Merge pull request #8 from RevEngAI/feat-PLU-210
feat(PLU-210): sync functions
2 parents a088c6f + 3846925 commit e95aaef

6 files changed

Lines changed: 145 additions & 6 deletions

File tree

reai_toolkit/features/choose_source/choose_source.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
11
import revengai
22
from reai_toolkit.utils import get_sha256
3+
from reai_toolkit.utils.core.sync import AnalysisSyncService
4+
from reai_toolkit.features.configuration.config import Config
35
from binaryninja import BinaryView, log_info, log_error
46

57
class ChooseSource:
6-
def __init__(self, config):
7-
self.config = config
8+
def __init__(self, config: Config):
9+
self.config: Config = config
10+
11+
self.sync_service = AnalysisSyncService(config)
812

913
def choose_source(self, bv: BinaryView, chose: str):
1014
try:
@@ -23,6 +27,8 @@ def choose_source(self, bv: BinaryView, chose: str):
2327
log_info(f"RevEng.AI | Changing Model ID to {new_model_id}")
2428
self.config.set_current_info(new_binary_id, new_analysis_id, new_model_id)
2529

30+
self.sync_service.sync_analysis_data(analysis_id=new_analysis_id, bv=bv)
31+
2632
return True, "Binary ID changed successfully."
2733
except Exception as e:
2834
log_error(f"RevEng.AI | Failed to choose source: {str(e)}")

reai_toolkit/features/configuration/config.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from reai_toolkit.utils import get_sha256
44
from binaryninja.interaction import InteractionHandler
55
from binaryninja import Settings, log_info, log_error, BinaryView
6+
from reai_toolkit.utils.core.sync import AnalysisSyncService
67

78

89
class Config:
@@ -145,6 +146,7 @@ def init_config(self, bv: BinaryView):
145146
self.binary_id = all_analyses[self.sha256]["binary_id"]
146147
self.analysis_id = all_analyses[self.sha256]["analysis_id"]
147148
self.model_id = all_analyses[self.sha256]["model_id"]
149+
AnalysisSyncService(self).sync_analysis_data(analysis_id=self.analysis_id, bv=bv)
148150
else:
149151
log_info(f"RevEng.AI | Binary not found in saved configurations, searching in RevEng.AI...")
150152
with revengai.ApiClient(self.api_config) as api_client:
@@ -156,6 +158,7 @@ def init_config(self, bv: BinaryView):
156158
self.binary_id = api_response.data.results[0].binary_id
157159
self.analysis_id = api_response.data.results[0].analysis_id
158160
self.model_id = api_response.data.results[0].model_id
161+
AnalysisSyncService(self).sync_analysis_data(analysis_id=self.analysis_id, bv=bv)
159162
log_info(f"RevEng.AI | Binary found in RevEng.AI, binary_id: {self.binary_id}")
160163
self.set_current_info(self.binary_id, self.analysis_id, self.model_id)
161164

reai_toolkit/features/upload/upload.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from binaryninja import BinaryView, log_info, log_error
44
from os.path import basename
55
from reai_toolkit.utils import PeriodicChecker
6+
from reai_toolkit.utils.core.sync import AnalysisSyncService
67

78
class BinaryUploader:
89
def __init__(self, config):
@@ -87,14 +88,14 @@ def upload_binary(self, bv: BinaryView, options: dict):
8788
analysis_scope=revengai.AnalysisScope.PRIVATE if options["is_private"] else revengai.AnalysisScope.PUBLIC,
8889
symbols=symbols
8990
)
90-
91+
9192
analysis_result = analyses_client.create_analysis(
9293
analysis_create_request=analysis_create_request
9394
)
9495

9596
log_info(f"RevEng.AI | Analysis started successfully. Analysis ID: {analysis_result.data.analysis_id}, Binary ID: {analysis_result.data.binary_id}")
9697

97-
PeriodicChecker().start_checking(bv, analysis_result.data.analysis_id, analysis_result.data.binary_id, self.config.set_current_info, self.config.api_config)
98+
PeriodicChecker(self.config).start_checking(bv, analysis_result.data.analysis_id, analysis_result.data.binary_id, self.config.set_current_info, self.config.api_config)
9899

99100
return True, "Analysis started successfully."
100101

reai_toolkit/utils/core/binary_ninja.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ def rename_function(config, bv: BinaryView, addr: int, new_name: str, new_mangle
3737
log_info(f"RevEng.AI | Function at {hex(addr)} already has name {func.name}")
3838
#return False
3939

40-
new_symbol = Symbol(SymbolType.FunctionSymbol, addr, new_name)
40+
new_symbol = Symbol(SymbolType.FunctionSymbol, addr, new_mangled_name)
4141
bv.define_user_symbol(new_symbol)
4242

4343
_rename_in_portal(config, source_function_id, new_name, new_mangled_name)

reai_toolkit/utils/core/sync.py

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
from binaryninja import log_info, BinaryView
2+
from revengai import AnalysesCoreApi, Configuration, FunctionMapping
3+
from revengai import BaseResponseBasic, AnalysesCoreApi, ApiClient
4+
from binaryninja import BinaryView, log_error, log_info, Symbol, SymbolType, Function
5+
6+
class AnalysisSyncService:
7+
8+
sdk_config: Configuration
9+
10+
def __init__(self, config):
11+
self.config = config
12+
self.sdk_config = config.api_config
13+
14+
def _get_current_base_address(self, bv) -> int:
15+
return bv.start
16+
17+
def _rebase_program(self, bv, base_address_delta: int) -> None:
18+
bv.rebase(bv.start + base_address_delta)
19+
20+
def _fetch_basic_and_rebase(self, bv: BinaryView, analysis_id: int) -> BaseResponseBasic:
21+
"""
22+
Fetches basic analysis information and rebases the program if necessary.
23+
"""
24+
with ApiClient(self.sdk_config) as api_client:
25+
analyses_client = AnalysesCoreApi(api_client)
26+
analysis_details: BaseResponseBasic = analyses_client.get_analysis_basic_info(
27+
analysis_id=analysis_id
28+
)
29+
30+
local_base_address: int = self._get_current_base_address(bv)
31+
32+
if analysis_details.data and analysis_details.data.base_address is not None:
33+
remote_base_address: int = analysis_details.data.base_address
34+
35+
if local_base_address != remote_base_address:
36+
base_address_delta: int = remote_base_address - local_base_address
37+
self._rebase_program(bv, base_address_delta)
38+
39+
def _fetch_function_map(self, analysis_id: int) -> FunctionMapping:
40+
"""
41+
Fetches the function map for the given analysis ID.
42+
"""
43+
with ApiClient(self.sdk_config) as api_client:
44+
analyses_client = AnalysesCoreApi(api_client)
45+
46+
function_map = analyses_client.get_analysis_function_map(
47+
analysis_id=analysis_id
48+
)
49+
func_map = function_map.data.function_maps
50+
return func_map
51+
52+
def _match_functions(
53+
self,
54+
func_map: FunctionMapping,
55+
bv: BinaryView,
56+
) -> None:
57+
function_map = func_map.function_map
58+
inverse_function_map = func_map.inverse_function_map
59+
60+
log_info(
61+
f"RevEng.AI | Retrieved {len(function_map)} function mappings from analysis"
62+
)
63+
64+
# Compute which IDA functions match the revengai analysis functions
65+
matched_functions = []
66+
unmatched_local_functions = []
67+
unmatched_remote_functions = []
68+
69+
# Track local functions matched
70+
local_function_vaddrs_matched = set()
71+
72+
for func in bv.functions:
73+
start_ea = func.start
74+
if str(start_ea) in inverse_function_map:
75+
new_name: str | None = func_map.name_map.get(str(start_ea), None)
76+
if new_name is None:
77+
continue
78+
79+
# Check if function has a user-defined symbol, skip if it does
80+
if func.symbol and func.symbol.auto == False:
81+
log_info(f"RevEng.AI | Skipping user-defined function at 0x{start_ea:x}: {func.name}")
82+
local_function_vaddrs_matched.add(start_ea)
83+
continue
84+
85+
# Rename local function
86+
new_symbol = Symbol(SymbolType.FunctionSymbol, start_ea, new_name)
87+
bv.define_user_symbol(new_symbol)
88+
89+
matched_functions.append(
90+
(int(inverse_function_map[str(start_ea)]), start_ea)
91+
)
92+
local_function_vaddrs_matched.add(start_ea)
93+
else:
94+
unmatched_local_functions.append(start_ea)
95+
96+
unmatched_portal_map = {}
97+
# Track remote functions not matched
98+
for func_id_str, func_vaddr in function_map.items():
99+
if int(func_vaddr) not in local_function_vaddrs_matched:
100+
unmatched_remote_functions.append((int(func_vaddr), int(func_id_str)))
101+
unmatched_portal_map[int(func_vaddr)] = int(func_id_str)
102+
103+
log_info(f"RevEng.AI | Matched {len(matched_functions)} functions")
104+
log_info(
105+
f"RevEng.AI | {len(unmatched_local_functions)} local functions not matched"
106+
)
107+
log_info(
108+
f"RevEng.AI | {len(unmatched_remote_functions)} remote functions not matched"
109+
)
110+
111+
def sync_analysis_data(
112+
self, analysis_id: int, bv: BinaryView
113+
) -> None:
114+
"""
115+
Syncs the analysis data until completion or failure.
116+
"""
117+
response = self._fetch_function_map(analysis_id=analysis_id)
118+
119+
function_mapping: FunctionMapping = response
120+
121+
self._match_functions(func_map=function_mapping, bv=bv)
122+
123+
self._fetch_basic_and_rebase(bv=bv, analysis_id=analysis_id)

reai_toolkit/utils/monitoring/process_binary_monitor.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,18 @@
66
from requests.exceptions import RequestException
77
from PySide6.QtWidgets import QMessageBox
88
from PySide6.QtCore import QObject, Signal
9+
from reai_toolkit.utils.core.sync import AnalysisSyncService
910

1011
class PeriodicChecker(QObject):
1112
update_text_signal = Signal(object, str)
13+
sync_service: AnalysisSyncService
1214

13-
def __init__(self):
15+
def __init__(self, config):
1416
super().__init__()
1517
self._current_timer: Optional[Timer] = None
1618
self.number_of_clicks = 0
1719
self.update_text_signal.connect(self._update_text_slot)
20+
self.sync_service = AnalysisSyncService(config)
1821

1922
def _update_text_slot(self, callback, text):
2023
"""Slot that runs in the main thread to safely update UI"""
@@ -62,6 +65,9 @@ def _worker(bv: BinaryView, bid: int, aid: int):
6265
)
6366
model_id = analysis_details.data.model_id
6467
callback(bid, aid, model_id)
68+
69+
self.sync_service.sync_analysis_data(analysis_id=aid, bv=bv)
70+
6571
log_info(f"RevEng.AI | Analysis completed with status: {status} for Binary ID: {bid} | Analysis ID: {aid} | Model ID: {model_id}")
6672
except RequestException as ex:
6773
log_error(f"RevEng.AI | Error getting binary analysis status: {str(ex)}")

0 commit comments

Comments
 (0)