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
8 changes: 6 additions & 2 deletions .github/workflows/bump-revengai-sdk.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,8 @@ jobs:
with:
python-version: '3.14'

- name: Install packaging
run: pip install packaging
- name: Install tooling
run: pip install packaging uv

- name: Inspect existing bump PR
id: existing
Expand Down Expand Up @@ -75,6 +75,10 @@ jobs:
if: env.ABORT != 'true' && steps.bump.outputs.changed != 'true'
run: echo "revengai is already at ${{ steps.bump.outputs.current_sdk }}; nothing to do."

- name: Sync uv lockfile
if: env.ABORT != 'true' && steps.bump.outputs.changed == 'true'
run: uv lock --upgrade-package revengai

- name: Open or update bump PR
if: env.ABORT != 'true' && steps.bump.outputs.changed == 'true'
uses: peter-evans/create-pull-request@22a9089034f40e5a961c8808d113e2c98fb63676 # v7.0.11
Expand Down
5 changes: 1 addition & 4 deletions .github/workflows/publish.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -52,10 +52,7 @@ jobs:
- name: Build vendor dependencies
run: |
rm -rf images
mkdir -p reai_toolkit/vendor
uv pip install . --target reai_toolkit/vendor --no-cache-dir
rm -rf reai_toolkit/vendor/reai_toolkit
rm -rf reai_toolkit/vendor/reai_toolkit-*.dist-info
./scripts/build_vendor.sh
shell: bash

- name: Verify vendor folder contents
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ name = "plugin-binary-ninja"
version = "0.0.1"
requires-python = ">=3.10"
dependencies = [
"revengai>=4.4.0",
"revengai>=4.72.2",
"urllib3>=2.0.0,<2.3.0",
"libbs==2.15.1",
"pydantic>=2.12.5",
Expand Down
6 changes: 3 additions & 3 deletions reai_toolkit/features/choose_source/choose_source.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,9 @@ def get_analysis(self, bv: BinaryView):
sha256 = get_sha256(bv.file.filename)

with self.config.create_api_client() as api_client:
api_instance = revengai.SearchApi(api_client)
api_response = api_instance.search_binaries(partial_sha256 = sha256, user_files_only = True)
results = api_response.data.results
api_instance = revengai.BinariesApi(api_client)
api_response = api_instance.v3_search_binaries(partial_sha256 = sha256, user_files_only = True)
results = api_response.results or []
if not len(results):
raise Exception("Binary not found in RevEng.AI, try processing the binary again.")

Expand Down
14 changes: 7 additions & 7 deletions reai_toolkit/features/configuration/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ def check_auth(self):
self.init_api_config()
with self.create_api_client() as api_client:
api_instance = revengai.ConfigApi(api_client)
api_instance.get_config()
api_instance.v3_get_config()
log_info("RevEng.AI | User authenticated successfully.")
return True
except Exception as e:
Expand Down Expand Up @@ -172,14 +172,14 @@ def init_config(self, bv: BinaryView):
else:
log_info(f"RevEng.AI | Binary not found in saved configurations, searching in RevEng.AI...")
with self.create_api_client() as api_client:
api_instance = revengai.SearchApi(api_client)
api_response = api_instance.search_binaries(partial_sha256 = self.sha256, user_files_only = True)
if not len(api_response.data.results):
api_instance = revengai.BinariesApi(api_client)
api_response = api_instance.v3_search_binaries(partial_sha256 = self.sha256, user_files_only = True)
if not len(api_response.results):
raise Exception("Binary not found in RevEng.AI, try processing the binary again.")
else:
self.binary_id = api_response.data.results[0].binary_id
self.analysis_id = api_response.data.results[0].analysis_id
self.model_id = api_response.data.results[0].model_id
self.binary_id = api_response.results[0].binary_id
self.analysis_id = api_response.results[0].analysis_id
self.model_id = api_response.results[0].model_id
bv = AnalysisSyncService(self).sync_analysis_data(analysis_id=self.analysis_id, bv=bv)
log_info(f"RevEng.AI | Binary found in RevEng.AI, binary_id: {self.binary_id}")
self.set_current_info(self.binary_id, self.analysis_id, self.model_id)
Expand Down
12 changes: 6 additions & 6 deletions reai_toolkit/features/upload/upload.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,9 @@ def get_user_tier(self):
def get_models(self, bv: BinaryView):
try:
with self.config.create_api_client() as api_client:
api_instance = revengai.ModelsApi(api_client)
api_response = api_instance.get_models()
models = api_response.data.models
api_instance = revengai.ConfigApi(api_client)
api_response = api_instance.v3_get_models()
models = api_response.models or []
log_info(f"RevEng.AI | Models: {models}")
return True, models
except Exception as e:
Expand All @@ -60,20 +60,20 @@ def upload_binary(self, bv: BinaryView, options: dict):
file = Path(bv.file.filename).read_bytes()
if bv.parent_view.length > 10 * 1024 ** 2:
log_error(f"RevEng.AI | File size is too large. Please upload a file smaller than 10MB.")
return False
return False, "File size is too large. Please upload a file smaller than 10MB."

success, sha_256_hash = self.upload_file(bv.file.filename, revengai.UploadFileType.BINARY)
if not success:
log_error(f"RevEng.AI | Failed to upload binary {bv.file.filename}.")
return False
return False, sha_256_hash

log_info(f"RevEng.AI | Binary {bv.file.filename} uploaded successfully. SHA-256 hash: {sha_256_hash}")

if options["debug_info"]:
success, debug_info_hash = self.upload_file(options["debug_info"], revengai.UploadFileType.DEBUG)
if not success:
log_error(f"RevEng.AI | Failed to upload debug info {options['debug_info']}.")
return False
return False, debug_info_hash

log_info(f"RevEng.AI | Debug info {options['debug_info']} uploaded successfully. SHA-256 hash: {debug_info_hash}")

Expand Down
20 changes: 9 additions & 11 deletions reai_toolkit/utils/core/sync.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from binaryninja import log_info, BinaryView
from revengai import AnalysesCoreApi, Configuration, FunctionMapping
from revengai import BaseResponseBasic, AnalysesCoreApi, ApiClient
from revengai import AnalysesCoreApi, Configuration, FunctionsCoreApi
from revengai import AnalysisBasicInfoOutputBody, GetFunctionMapsOutputBody, ApiClient
from binaryninja import BinaryView, log_error, log_info, Symbol, SymbolType, Function

class AnalysisSyncService:
Expand All @@ -26,37 +26,35 @@ def _fetch_basic_and_rebase(self, bv: BinaryView, analysis_id: int) -> BinaryVie
"""
with ApiClient(self.sdk_config) as api_client:
analyses_client = AnalysesCoreApi(api_client)
analysis_details: BaseResponseBasic = analyses_client.get_analysis_basic_info(
analysis_details: AnalysisBasicInfoOutputBody = analyses_client.get_analysis_basic_info_0(
analysis_id=analysis_id
)

if not (analysis_details.data and analysis_details.data.base_address is not None):
if analysis_details.base_address is None:
return bv

remote_base_address: int = analysis_details.data.base_address
remote_base_address: int = analysis_details.base_address
local_base_address: int = self._get_current_base_address(bv)

if local_base_address == remote_base_address:
return bv

return self._rebase_program(bv, remote_base_address - local_base_address)

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 ApiClient(self.sdk_config) as api_client:
analyses_client = AnalysesCoreApi(api_client)
functions_client = FunctionsCoreApi(api_client)

function_map = analyses_client.get_analysis_function_map(
return functions_client.v3_get_analysis_func_maps(
analysis_id=analysis_id
)
func_map = function_map.data.function_maps
return func_map

def _match_functions(
self,
func_map: FunctionMapping,
func_map: GetFunctionMapsOutputBody,
bv: BinaryView,
) -> None:
function_map = func_map.function_map
Expand Down
13 changes: 6 additions & 7 deletions reai_toolkit/utils/features/matching.py
Original file line number Diff line number Diff line change
Expand Up @@ -131,17 +131,16 @@ def _search_binaries(self, query: Dict[str, Any] = {}):
try:
log_info("RevEng.AI | Searching for binaries")
with self.config.create_api_client() as api_client:
api_instance = revengai.SearchApi(api_client)
api_response = api_instance.search_binaries(
page = 1,
page_size = 20,
api_instance = revengai.BinariesApi(api_client)
api_response = api_instance.v3_search_binaries(
partial_name = query.get("binary_name") ,
partial_sha256 = query.get("sha_256_hash"),
tags = query.get("tags"),
model_name = query.get("model_name")

model_name = query.get("model_name"),
limit = 20,
offset = 0,
)
for binary in api_response.data.results:
for binary in api_response.results or []:
item = {
"name": binary.binary_name,
"binary_id": str(binary.binary_id),
Expand Down
11 changes: 6 additions & 5 deletions reai_toolkit/utils/monitoring/process_binary_monitor.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,9 @@ def _worker(bv: BinaryView, bid: int, aid: int):
try:
with self.config.create_api_client() as api_client:
api_instance = revengai.AnalysesCoreApi(api_client)
api_response = api_instance.get_analysis_status(aid)
status = api_response.data.analysis_status
api_response = api_instance.v3_get_analysis_operation(aid)
metadata = api_response.metadata
status = metadata.status if metadata else None
log_info(
f"RevEng.AI | Current status for analysis [Binary ID: {bid}] [Analysis ID: {aid}]: {status}"
)
Expand All @@ -61,12 +62,12 @@ def _worker(bv: BinaryView, bid: int, aid: int):
# Analysis is complete, fetch model_id and invoke callback
with self.config.create_api_client() as api_client:
api_instance = revengai.AnalysesCoreApi(api_client)
analysis_details: revengai.BaseResponseBasic = (
api_instance.get_analysis_basic_info(
analysis_details: revengai.AnalysisBasicInfoOutputBody = (
api_instance.get_analysis_basic_info_0(
analysis_id=analysis_id
)
)
model_id = analysis_details.data.model_id
model_id = analysis_details.model_id
callback(bid, aid, model_id)

bv = self.sync_service.sync_analysis_data(
Expand Down
30 changes: 30 additions & 0 deletions scripts/build_vendor.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
#!/usr/bin/env bash
set -euo pipefail

cd "$(dirname "${BASH_SOURCE[0]}")/.."

VENDOR="reai_toolkit/vendor"
REQ="$(mktemp -t reai-vendor-req.XXXXXX)"
trap 'rm -f "$REQ"' EXIT

if ! command -v uv >/dev/null 2>&1; then
echo "uv is not on PATH; see https://docs.astral.sh/uv/getting-started/install/" >&2
exit 1
fi

echo "==> Exporting locked runtime dependencies from uv.lock"
uv export --frozen --no-dev --no-emit-project --no-hashes -o "$REQ" >/dev/null

echo "==> Rebuilding vendor tree at $VENDOR"
rm -rf "$VENDOR"
mkdir -p "$VENDOR"
uv pip install -r "$REQ" --target "$VENDOR" --no-cache-dir

echo "==> Dropping the self-install so the checkout is not shadowed"
rm -rf "$VENDOR/reai_toolkit" "$VENDOR"/reai_toolkit-*.dist-info

echo "==> Verifying the vendor tree matches uv.lock"
uv run --no-project --with packaging python scripts/check_vendor.py

echo "==> $VENDOR is prepended to sys.path ahead of site-packages,"
echo " so rerun this after any dependency change or the old tree wins."
86 changes: 86 additions & 0 deletions scripts/check_vendor.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import re
import subprocess
import sys
from pathlib import Path

try:
from packaging.markers import Marker
except ModuleNotFoundError:
sys.exit(
"check_vendor.py needs the 'packaging' module to evaluate environment markers.\n"
"Run it the way scripts/build_vendor.sh does:\n"
" uv run --no-project --with packaging python scripts/check_vendor.py"
)

ROOT = Path(__file__).resolve().parent.parent
VENDOR = ROOT / "reai_toolkit" / "vendor"
PIN = re.compile(r"^([A-Za-z0-9][A-Za-z0-9._-]*)==([^\s;]+)\s*(?:;\s*(.+))?$")


def normalise(name):
return re.sub(r"[-_.]+", "-", name).lower()


def locked():
out = subprocess.run(
["uv", "export", "--frozen", "--no-dev", "--no-emit-project", "--no-hashes"],
cwd=ROOT,
capture_output=True,
text=True,
check=True,
).stdout
pinned = {}
for line in out.splitlines():
line = line.strip()
if line.startswith("#") or not line:
continue
match = PIN.match(line)
if not match:
continue
name, version, marker = match.groups()
if marker and not Marker(marker).evaluate():
continue
pinned[normalise(name)] = version
return pinned


def vendored():
found = {}
for dist in VENDOR.glob("*.dist-info"):
name, _, version = dist.name[: -len(".dist-info")].rpartition("-")
if name:
found[normalise(name)] = version
return found


def main():
if not VENDOR.is_dir():
print(f"vendor tree missing at {VENDOR}; run scripts/build_vendor.sh", file=sys.stderr)
return 1

want = locked()
have = vendored()
problems = []

for name, version in sorted(want.items()):
if name not in have:
problems.append(f" missing: {name}=={version}")
elif have[name] != version:
problems.append(f" mismatch: {name} vendored {have[name]}, lock wants {version}")

for name, version in sorted(have.items()):
if name not in want:
problems.append(f" unexpected: {name}=={version} is vendored but not in uv.lock")

if problems:
print("vendor tree does not match uv.lock:", file=sys.stderr)
print("\n".join(problems), file=sys.stderr)
print("\nrun scripts/build_vendor.sh to rebuild", file=sys.stderr)
return 1

print(f"vendor tree matches uv.lock ({len(want)} packages)")
return 0


if __name__ == "__main__":
raise SystemExit(main())
4 changes: 2 additions & 2 deletions tests/headless/test_sync.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ def test_rebase_returns_view_at_analysis_base(bv, mocker):
mocker.patch.object(sync_mod, "ApiClient")
api = mocker.patch.object(sync_mod, "AnalysesCoreApi").return_value
target = bv.start + 0x100000
api.get_analysis_basic_info.return_value.data.base_address = target
api.get_analysis_basic_info_0.return_value.base_address = target

rebased = service._fetch_basic_and_rebase(bv, analysis_id=1)

Expand All @@ -59,7 +59,7 @@ def test_rebase_skipped_when_already_aligned(bv, mocker):
service = sync_mod.AnalysisSyncService(MagicMock())
mocker.patch.object(sync_mod, "ApiClient")
api = mocker.patch.object(sync_mod, "AnalysesCoreApi").return_value
api.get_analysis_basic_info.return_value.data.base_address = bv.start
api.get_analysis_basic_info_0.return_value.base_address = bv.start

result = service._fetch_basic_and_rebase(bv, analysis_id=1)

Expand Down
Loading
Loading