Skip to content

Commit 3bde1dc

Browse files
committed
feat(PLU-210): sync functions - saved
1 parent a088c6f commit 3bde1dc

4 files changed

Lines changed: 131 additions & 2 deletions

File tree

reai_toolkit/features/choose_source/choose_source.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
import revengai
22
from reai_toolkit.utils import get_sha256
3+
from reai_toolkit.utils.core.sync import AnalysisSyncService
34
from binaryninja import BinaryView, log_info, log_error
45

56
class ChooseSource:
67
def __init__(self, config):
78
self.config = config
89

10+
self.sync_service = AnalysisSyncService(config)
11+
912
def choose_source(self, bv: BinaryView, chose: str):
1013
try:
1114
log_info(f"RevEng.AI | Item: {chose}")
@@ -23,6 +26,8 @@ def choose_source(self, bv: BinaryView, chose: str):
2326
log_info(f"RevEng.AI | Changing Model ID to {new_model_id}")
2427
self.config.set_current_info(new_binary_id, new_analysis_id, new_model_id)
2528

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

reai_toolkit/features/upload/upload.py

Lines changed: 2 additions & 1 deletion
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,7 +88,7 @@ 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
)

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, sdk_config: Configuration):
11+
self.sdk_config = sdk_config
12+
13+
def _get_current_base_address(self, bv) -> int:
14+
return bv.start
15+
16+
def _rebase_program(self, bv, base_address_delta: int) -> None:
17+
bv.rebase(bv.start + base_address_delta)
18+
19+
def _fetch_basic_and_rebase(self, bv: BinaryView, analysis_id: int) -> BaseResponseBasic:
20+
"""
21+
Fetches basic analysis information and rebases the program if necessary.
22+
"""
23+
with ApiClient(self.sdk_config) as api_client:
24+
analyses_client = AnalysesCoreApi(api_client)
25+
analysis_details: BaseResponseBasic = analyses_client.get_analysis_basic_info(
26+
analysis_id=analysis_id
27+
)
28+
29+
local_base_address: int = self._get_current_base_address(bv)
30+
31+
if analysis_details.data and analysis_details.data.base_address is not None:
32+
remote_base_address: int = analysis_details.data.base_address
33+
34+
if local_base_address != remote_base_address:
35+
base_address_delta: int = remote_base_address - local_base_address
36+
self._rebase_program(bv, base_address_delta)
37+
38+
def _fetch_function_map(self, analysis_id: int) -> FunctionMapping:
39+
"""
40+
Fetches the function map for the given analysis ID.
41+
"""
42+
with ApiClient(self.sdk_config) as api_client:
43+
analyses_client = AnalysesCoreApi(api_client)
44+
45+
function_map = analyses_client.get_analysis_function_map(
46+
analysis_id=analysis_id
47+
)
48+
func_map = function_map.data.function_maps
49+
self.safe_put_function_mapping(func_map=func_map)
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+
# Rename local function
80+
new_symbol = Symbol(SymbolType.FunctionSymbol, start_ea, new_name)
81+
bv.define_user_symbol(new_symbol)
82+
83+
matched_functions.append(
84+
(int(inverse_function_map[str(start_ea)]), start_ea)
85+
)
86+
local_function_vaddrs_matched.add(start_ea)
87+
else:
88+
unmatched_local_functions.append(start_ea)
89+
90+
unmatched_portal_map = {}
91+
# Track remote functions not matched
92+
for func_id_str, func_vaddr in function_map.items():
93+
if int(func_vaddr) not in local_function_vaddrs_matched:
94+
unmatched_remote_functions.append((int(func_vaddr), int(func_id_str)))
95+
unmatched_portal_map[int(func_vaddr)] = int(func_id_str)
96+
97+
log_info(f"RevEng.AI | Matched {len(matched_functions)} functions")
98+
log_info(
99+
f"RevEng.AI | {len(unmatched_local_functions)} local functions not matched"
100+
)
101+
log_info(
102+
f"RevEng.AI | {len(unmatched_remote_functions)} remote functions not matched"
103+
)
104+
105+
def sync_analysis_data(
106+
self, analysis_id: int, bv: BinaryView
107+
) -> None:
108+
"""
109+
Syncs the analysis data until completion or failure.
110+
"""
111+
response = self.api_request_returning(
112+
fn=lambda: self._fetch_function_map(analysis_id=analysis_id)
113+
)
114+
115+
if not response.success:
116+
self.call_callback(generic_return=response)
117+
return
118+
119+
function_mapping: FunctionMapping = response.data
120+
121+
self._match_functions(func_map=function_mapping, bv=bv)
122+
123+
self._fetch_basic_and_rebase(bv=bv, analysis_id=analysis_id)

0 commit comments

Comments
 (0)