Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ dependencies = [
"requests>=2.32",
"loguru>=0.7",
"pydantic",
"revengai>=4.20.0",
"libbs>=2.16.5",
"revengai>=4.72.2",
"libbs>=3.8.1",
]

[build-system]
Expand Down
8 changes: 4 additions & 4 deletions reai_toolkit/app/components/dialogs/matching_dialog.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from loguru import logger

from revengai.models import (
BinarySearchResult,
BinarySearchResultBody,
CollectionListItemBody,
FunctionMatch,
MatchedFunction,
Expand Down Expand Up @@ -178,7 +178,7 @@ def __init__(
self._initCollectionsHeader()

# ----------------- Binaries state -----------------
self._selected_binaries: dict[str, BinarySearchResult] = {}
self._selected_binaries: dict[str, BinarySearchResultBody] = {}
self._binariesDebounce = QtCore.QTimer(self)
self._binariesDebounce.setSingleShot(True)
self._binariesDebounce.timeout.connect(self._performBinariesSearch)
Expand Down Expand Up @@ -557,7 +557,7 @@ def _performBinariesSearch(self):
text = getattr(self.ui, "editBinaries", None)
query = text.text().strip() if text else ""

response: GenericApiReturn[List[BinarySearchResult]] = (
response: GenericApiReturn[List[BinarySearchResultBody]] = (
self.matching_service.search_binaries(text_input=query)
)

Expand Down Expand Up @@ -593,7 +593,7 @@ def _initBinariesHeader(self):
else:
hdr.setSectionResizeMode(i, QtWidgets.QHeaderView.Stretch)

def _fillBinariesPopup(self, query: str, rows: list[BinarySearchResult]):
def _fillBinariesPopup(self, query: str, rows: list[BinarySearchResultBody]):
if not hasattr(self.ui, "binariesPopupView"):
return
view = self.ui.binariesPopupView
Expand Down
55 changes: 47 additions & 8 deletions reai_toolkit/app/core/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
import json
from pathlib import Path

from loguru import logger

from libbs.decompilers.ida.compat import execute_read, execute_write

import ida_funcs
Expand All @@ -14,6 +16,10 @@
from revengai.models.symbols import Symbols


MAX_MANGLED_NAME_LENGTH = 4096
MAX_FUNCTION_BOUNDARIES = 200000


def parse_exception(exception: ApiException) -> BaseResponse | None:
"""
If the exception body is JSON and matches BaseResponse, return it.
Expand Down Expand Up @@ -67,10 +73,38 @@ def demangle(mangled_name: str, attr: int = idc.INF_SHORT_DN) -> str:
return demangled_name if demangled_name else mangled_name


def build_function_boundary(
mangled_name: str | None, start_address: int, end_address: int
) -> FunctionBoundary | None:
if not mangled_name:
return None

if start_address < 0 or end_address < 0:
return None

return FunctionBoundary(
mangled_name=mangled_name[:MAX_MANGLED_NAME_LENGTH],
start_address=start_address,
end_address=end_address,
)


def build_symbols(base_address: int, funcs: list[FunctionBoundary]) -> Symbols:
if len(funcs) > MAX_FUNCTION_BOUNDARIES:
logger.warning(
f"RevEng.AI: Truncating function boundaries from {len(funcs)} to "
f"{MAX_FUNCTION_BOUNDARIES}"
)
funcs = funcs[:MAX_FUNCTION_BOUNDARIES]

return Symbols(base_address=max(int(base_address), 0), function_boundaries=funcs)


@execute_read
def collect_symbols_from_ida(inclusive_end: bool = False) -> Symbols | None:
base: int = idaapi.get_imagebase() or 0
funcs: list[FunctionBoundary] = []
skipped: int = 0

plt_section: idaapi.segment_t | None = idaapi.get_segm_by_name(".plt")

Expand All @@ -86,14 +120,19 @@ def collect_symbols_from_ida(inclusive_end: bool = False) -> Symbols | None:
end_ea: int = f.end_ea - 1 if inclusive_end else f.end_ea
mangled_name: str = idc.get_func_name(start_ea)

funcs.append(
FunctionBoundary(
mangled_name=mangled_name,
start_address=start_ea,
end_address=end_ea,
)
boundary: FunctionBoundary | None = build_function_boundary(
mangled_name, start_ea, end_ea
)
if boundary is None:
skipped += 1
continue

funcs.append(boundary)

symbols = Symbols(base_address=int(base), function_boundaries=funcs)
if skipped:
logger.warning(
f"RevEng.AI: Skipped {skipped} function boundaries with an empty name "
"or a negative address"
)

return symbols
return build_symbols(base, funcs)
8 changes: 3 additions & 5 deletions reai_toolkit/app/services/ai_decomp/ai_decomp_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,8 @@
from revengai.models.summary_data import SummaryData
from revengai.models.task_status import TaskStatus
from revengai.models.token import Token
from revengai.models.upsert_ai_decomplation_rating_request import (
UpsertAiDecomplationRatingRequest,
)
from revengai.models.upsert_overrides_input_body import UpsertOverridesInputBody
from revengai.models.upsert_rating_input_body import UpsertRatingInputBody
from revengai.models.workflow_progress import WorkflowProgress

from reai_toolkit.app.core.netstore_service import SimpleNetStore
Expand Down Expand Up @@ -239,9 +237,9 @@ def _run_rate_decomp(
) -> None:
try:
with self.yield_api_client(sdk_config=self.sdk_config) as api_client:
FunctionsAIDecompilationApi(api_client).upsert_ai_decompilation_rating(
FunctionsAIDecompilationApi(api_client).v3_upsert_ai_decompilation_rating(
function_id=function_id,
upsert_ai_decomplation_rating_request=UpsertAiDecomplationRatingRequest(
upsert_rating_input_body=UpsertRatingInputBody(
rating=rating, reason=None
),
)
Expand Down
8 changes: 8 additions & 0 deletions reai_toolkit/app/services/ai_decomp/stream.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
EVENT_RENAME_APPLIED = "rename_applied"
EVENT_SOURCE_DELTA = "source_delta"
EVENT_SOURCE_RESET = "source_reset"
EVENT_TYPES_APPLIED = "types_applied"
EVENT_TYPES_SUGGESTED = "types_suggested"
EVENT_WARNING = "warning"

Expand All @@ -41,6 +42,7 @@ class DecompEvent:
applied: int = 0
types: int = 0
members: int = 0
skipped: int = 0


def _as_int(value) -> int:
Expand Down Expand Up @@ -71,6 +73,7 @@ def normalize_event(event_name: Optional[str], payload: dict) -> Optional[Decomp
applied=_as_int(payload.get("applied")),
types=_as_int(payload.get("types")),
members=_as_int(payload.get("members")),
skipped=_as_int(payload.get("skipped")),
)


Expand Down Expand Up @@ -110,6 +113,8 @@ class StreamState:
names_applied: int = 0
types_suggested: int = 0
members_suggested: int = 0
types_applied: int = 0
types_skipped: int = 0

def reset_attempt(self, attempt: int) -> None:
self.attempt = attempt
Expand Down Expand Up @@ -139,6 +144,9 @@ def apply(self, event: DecompEvent) -> None:
elif event.type == EVENT_TYPES_SUGGESTED:
self.types_suggested = event.types
self.members_suggested = event.members
elif event.type == EVENT_TYPES_APPLIED:
self.types_applied = event.types
self.types_skipped = event.skipped
elif event.type == EVENT_DECOMP_FINISHED:
self.decomp_finished = True
elif event.type == EVENT_ATTEMPT_FAILED:
Expand Down
20 changes: 11 additions & 9 deletions reai_toolkit/app/services/analysis_status/analysis_status.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@
from loguru import logger
from revengai import (
AnalysesCoreApi,
BaseResponseStatus,
Configuration,
CreateMetadata,
GetAnalysisLogsOutputBody,
StatusOutput,
OperationCreateMetadataCreateResult,
)

from reai_toolkit.app.core.netstore_service import SimpleNetStore
Expand Down Expand Up @@ -37,20 +37,22 @@ def start_polling(self, analysis_id: int, thread_callback: Callable[..., Any]) -
args=(analysis_id,),
)

def _api_get_status(self, analysis_id: int) -> StatusOutput | None:
def _api_get_status(self, analysis_id: int) -> CreateMetadata | None:
"""
Calls the API to get the analysis status.
Returns GenericApiReturn with status string on success.
"""
with self.yield_api_client(sdk_config=self.sdk_config) as api_client:
analyses_client = AnalysesCoreApi(api_client)

analysis_status: BaseResponseStatus = analyses_client.get_analysis_status(analysis_id=analysis_id)
status: StatusOutput | None = analysis_status.data
if status:
self.netstore_service.put_analysis_status(status.analysis_status)
operation: OperationCreateMetadataCreateResult = (
analyses_client.v3_get_analysis_operation(analysis_id=analysis_id)
)
metadata: CreateMetadata | None = operation.metadata
if metadata:
self.netstore_service.put_analysis_status(metadata.status)

return analysis_status.data
return metadata

def _api_get_logs(self, analysis_id: int) -> GetAnalysisLogsOutputBody | None:
with self.yield_api_client(sdk_config=self.sdk_config) as api_client:
Expand Down Expand Up @@ -83,7 +85,7 @@ def _poll_analysis_status(self, stop_event: threading.Event, analysis_id: int) -
for entry in (logs.entries or []):
logger.debug(f"RevEng.AI Remote Analysis - {entry.text}")

if get_status_response.data and (status := get_status_response.data.analysis_status):
if get_status_response.data and (status := get_status_response.data.status):
logger.info(f"RevEng.AI: Status - {status}")
if status == "Complete":
self.call_callback(
Expand Down
17 changes: 8 additions & 9 deletions reai_toolkit/app/services/analysis_sync/analysis_sync.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,9 @@
from revengai import (
AnalysesCoreApi,
AnalysisBasicInfoOutputBody,
BaseResponseAnalysisFunctionMapping,
Configuration,
FunctionsCoreApi,
GetFunctionMapsOutputBody,
)

from reai_toolkit.app.core.netstore_service import SimpleNetStore
Expand Down Expand Up @@ -59,7 +60,7 @@ def start_syncing(self, func_map: FunctionMapping, callback: Callable[[GenericAp
args=(func_map, callback),
)

def get_function_matches(self, callback: Callable[[FunctionMapping], None]) -> None:
def get_function_matches(self, callback: Callable[[GetFunctionMapsOutputBody], None]) -> None:
analysis_id: int | None = self.netstore_service.get_analysis_id()
if analysis_id is None:
return
Expand All @@ -72,7 +73,7 @@ def get_function_matches(self, callback: Callable[[FunctionMapping], None]) -> N
logger.error("failed to retrieve model id")
return

response: GenericApiReturn[FunctionMapping] = self.api_request_returning(
response: GenericApiReturn[GetFunctionMapsOutputBody] = self.api_request_returning(
fn=lambda: self._fetch_function_map(analysis_id=analysis_id)
)

Expand Down Expand Up @@ -118,17 +119,15 @@ def _get_current_base_address(self) -> int:
def _rebase_program(self, base_address_delta: int) -> None:
idaapi.rebase_program(base_address_delta, idaapi.MSF_FIXONCE)

def _fetch_function_map(self, analysis_id: int) -> FunctionMapping:
def _fetch_function_map(self, analysis_id: int) -> GetFunctionMapsOutputBody:
"""
Fetches the function map for the given analysis ID.
"""
with self.yield_api_client(sdk_config=self.sdk_config) as api_client:
analyses_client = AnalysesCoreApi(api_client)
functions_client = FunctionsCoreApi(api_client)

function_map: BaseResponseAnalysisFunctionMapping = analyses_client.get_analysis_function_map(analysis_id=analysis_id)
if function_map.data:
func_map: FunctionMapping = function_map.data.function_maps
self.netstore_service.put_function_mapping(func_map)
func_map: GetFunctionMapsOutputBody = functions_client.v3_get_analysis_func_maps(analysis_id=analysis_id)
self.netstore_service.put_function_mapping(func_map)

return func_map

Expand Down
2 changes: 1 addition & 1 deletion reai_toolkit/app/services/auth/auth_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ def verify(self) -> Tuple[bool, str]:
if hasattr(self.sdk_config, "user_agent"):
api_client.user_agent = self.sdk_config.user_agent
try:
ConfigApi(api_client).get_config()
ConfigApi(api_client).v3_get_config()
logger.info("RevEng.AI: User authenticated successfully.")
self._is_authed = True
self.get_me(force_refresh=True)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,7 @@
from typing import List, Optional

from revengai import AnalysesCoreApi, Configuration
from revengai.models import (
AnalysisRecord,
AppApiRestV2AnalysesEnumsOrderBy,
Order,
StatusInput,
Workspace,
)
from revengai.models import AnalysisRecordBody

from reai_toolkit.app.core.netstore_service import SimpleNetStore
from reai_toolkit.app.core.shared_schema import GenericApiReturn
Expand All @@ -22,28 +16,28 @@ class ExistingAnalysesService(IThreadService):
def __init__(self, netstore_service: SimpleNetStore, sdk_config: Configuration):
super().__init__(netstore_service=netstore_service, sdk_config=sdk_config)

def _fetch_analysis_records(self, sha256_hash: str) -> List[AnalysisRecord]:
def _fetch_analysis_records(self, sha256_hash: str) -> List[AnalysisRecordBody]:
"""
Helper method to fetch analysis records by SHA256 hash.
Returns a list of AnalysisRecord.
Returns a list of AnalysisRecordBody.
"""

with self.yield_api_client(sdk_config=self.sdk_config) as api_client:
analyses_client = AnalysesCoreApi(api_client)

result = analyses_client.list_analyses(
workspace=[Workspace.PERSONAL],
status=[StatusInput.COMPLETE],
result = analyses_client.v3_list_analyses(
analysis_scope=["PRIVATE"],
status=["Complete"],
sha256_hash=sha256_hash,
order_by=AppApiRestV2AnalysesEnumsOrderBy.CREATED,
order=Order.DESC,
order_by="created",
order="DESC",
)

return result.data.results
return result.results or []

def fetch_analyses_same_hash(
self, file_path: str
) -> GenericApiReturn[List[AnalysisRecord]]:
) -> GenericApiReturn[List[AnalysisRecordBody]]:
"""
Fetches analyses with the same hash as the given file.
Returns a list of analysis summaries.
Expand Down
Loading
Loading