Skip to content

Commit a1315da

Browse files
committed
Support for new automated builds system for the native plugin
1 parent 0b75efa commit a1315da

File tree

3 files changed

+64
-110
lines changed

3 files changed

+64
-110
lines changed

__init__.py

Lines changed: 62 additions & 106 deletions
Original file line numberDiff line numberDiff line change
@@ -1,95 +1,60 @@
11
import os
22
import sys
33
import requests
4-
import bs4
54
import binaryninja
6-
7-
# Logic:
8-
# ver, hash = native_plugins_data folder exists ? (current_native_plugin_data file exists ? ver, hash : none) : none
9-
# plugin_found = current_native_plugin file exists ? true : false
10-
# if !plugin_found -> download plugin && create (or overwrite) current_native_plugin_data file containing latest ver and latest hash
11-
# if plugin_found && ver != latest_ver && ver != none && hash == downloaded plugin hash -> alert user he is using outdated plugin (with link to latest release)
12-
# if plugin_found && ver != latest_ver && ver != none && hash != downloaded plugin hash -> delete current_native_plugin_data file &&
13-
# download latest plugin to temp, calculate its hash &&
14-
# if latest_hash == downloaded plugin hash -> save latest_hash and latest_ver to current_native_plugin_data file &&
15-
# delete file in temp
16-
# if plugin_found && ver == latest_ver && hash != downloaded plugin hash -> delete current_native_plugin_data file && download latest plugin to temp, calculate its hash &&
17-
# if latest_hash == downloaded plugin hash -> save latest_hash and latest_ver to current_native_plugin_data file &&
18-
# delete file in temp
19-
# if latest_hash != downloaded plugin hash -> alert to update (with link to latest release)
20-
# if plugin_found && ver == none && hash == none -> download latest plugin to temp, calculate its hash &&
21-
# if latest_hash == downloaded plugin hash -> save latest_hash and latest_ver to current_native_plugin_data file &&
22-
# delete file in temp
23-
# if latest_hash != downloaded plugin hash -> alert to update (with link to latest release)
5+
import re
246

257
# Plugin details
268
plugin_name = 'native-predicate-solver'
9+
plugin_filename_base = 'NativePredicateSolver'
2710

2811
# Repository details
2912
repo_owner = 'ScriptWare-Software'
3013
repo_name = 'native-predicate-solver'
31-
file_url = 'https://github.com/{}/{}/releases/latest/download'.format(repo_owner, repo_name)
32-
33-
# File names in release section on github along with Binary Ninja versions for which they were compiled (leave whole variable blank if platform not supported)
34-
# Both version variables are inclusive meaning any Binary Ninja version in between is supported, DO NOT include '-dev' suffix so instead of '3.4.4189-dev', use just '3.4.4189')
35-
# You can also support all dev version by replacing both versions with 'DEV' (example below), this is useful because new dev versions roll out almost on daily basis
36-
# but the problem is when dev version becomes stable, the loader must be updated accordingly
37-
# Example:
38-
# win_files = [
39-
# ('3.1.3469', '3.3.3996', 'sigscan.dll'), # anything in between 3.1.3469 and 3.3.3996 (inclusive) - specific stable versions
40-
# ('3.4.4169', '3.4.4189', 'sigscan_dev.dll'), # anything in between 3.4.4169 and 3.4.4189 (inclusive) - specific dev versions
41-
# ('DEV', 'DEV', 'sigscan_dev2.dll'), # anything in between 3.4.4169 and 3.4.4189 (inclusive) - all dev versions
42-
# ]
43-
win_files = [
44-
('5.0.7290', '5.0.7290', '7290NativePredicateSolver.dll'),
45-
('5.0.7648', '5.0.7648', '7648NativePredicateSolver.dll'),
46-
('DEV', 'DEV', 'NativePredicateSolver-dev.dll')
47-
]
48-
linux_files = [
49-
('5.0.7290', '5.0.7290', '7290libNativePredicateSolver.so'),
50-
('5.0.7648', '5.0.7648', '7648libNativePredicateSolver.so'),
51-
('DEV', 'DEV', 'libNativePredicateSolver-dev.so')
52-
]
53-
darwin_files = [
54-
('5.0.7290', '5.0.7290', '7290libNativePredicateSolver.dylib'),
55-
('5.0.7648', '5.0.7648', '7648libNativePredicateSolver.dylib'),
56-
('DEV', 'DEV', 'libNativePredicateSolver-dev.dylib')
57-
]
58-
59-
# Function that determines whether Binary Ninja version is supported (returns None if not, according file name if yes)
60-
def is_version_supported(files):
61-
# Get current Binary Ninja version
62-
version_numbers = binaryninja.core_version().split()[0].split('-')[0].split('.')
63-
major, minor, build = map(int, version_numbers)
64-
dev_file = None
65-
66-
# Loop through files for current platform and see if our version is supported by any
67-
for entry in files:
68-
min_ver, max_ver, file = entry
69-
70-
# first check all non dev versions (there might be specific binary for specific dev versions so use that and if none found then we can use binary for all dev versions)
71-
if (min_ver != 'DEV' and max_ver != 'DEV'):
72-
min_parts = min_ver.split('.')
73-
max_parts = max_ver.split('.')
74-
75-
major_match = (major >= int(min_parts[0]) and major <= int(max_parts[0]))
76-
minor_match = (minor >= int(min_parts[1]) and minor <= int(max_parts[1]))
77-
build_match = (build >= int(min_parts[2]) and build <= int(max_parts[2]))
78-
79-
if major_match and minor_match and build_match:
80-
return file
81-
else:
82-
dev_file = file
83-
84-
# If we are on dev, check if there is a file for all dev versions
85-
if ('-dev' in binaryninja.core_version() and dev_file != None and len(dev_file) > 0):
86-
return dev_file
8714

15+
def get_latest_release_info():
16+
api_url = f'https://api.github.com/repos/{repo_owner}/{repo_name}/releases/latest'
17+
try:
18+
response = requests.get(api_url)
19+
if response.status_code == 200:
20+
return response.json()
21+
return None
22+
except Exception:
23+
return None
24+
25+
def parse_binary_filename(filename, extension):
26+
pattern = rf'^{plugin_filename_base}-(.+)\.{extension}$'
27+
match = re.match(pattern, filename)
28+
if match:
29+
return match.group(1)
8830
return None
8931

90-
# Function that determines whether system is supported
91-
def is_system_supported(file_name):
92-
return file_name != None and len(file_name) > 0
32+
def find_compatible_binary(release_data, platform_extension):
33+
current_version = binaryninja.core_version().split()[0].split('-')[0]
34+
is_dev = '-dev' in binaryninja.core_version()
35+
36+
assets = release_data.get('assets', [])
37+
38+
exact_match = None
39+
dev_match = None
40+
41+
for asset in assets:
42+
filename = asset['name']
43+
version = parse_binary_filename(filename, platform_extension)
44+
45+
if version:
46+
if version == 'dev' and is_dev:
47+
dev_match = asset
48+
elif version == current_version:
49+
exact_match = asset
50+
break
51+
52+
if exact_match:
53+
return exact_match
54+
elif dev_match and is_dev:
55+
return dev_match
56+
57+
return None
9358

9459
# Function that determines whether native_plugins_data folder exists
9560
def data_folder_exists():
@@ -180,43 +145,34 @@ def alert_user(description):
180145
binaryninja.interaction.show_message_box('{} (Native plugin loader)'.format(plugin_name), description, binaryninja.enums.MessageBoxButtonSet.OKButtonSet, binaryninja.enums.MessageBoxIcon.InformationIcon)
181146

182147
# Function that does the actual work
183-
def check_for_updates(repo_owner, repo_name, file_url, win_files, linux_files, darwin_files):
184-
# Determine OS we are running on
148+
def check_for_updates():
185149
platform = sys.platform.lower()
186150

187-
# Windows
188151
if platform.startswith('win'):
189-
files = win_files
190-
# Linux
152+
platform_ext = 'dll'
191153
elif platform.startswith('linux'):
192-
files = linux_files
193-
# Mac
154+
platform_ext = 'so'
194155
elif platform.startswith('darwin'):
195-
files = darwin_files
156+
platform_ext = 'dylib'
196157
else:
197-
alert_user(plugin_name, 'Unsupported platform')
158+
alert_user('Unsupported platform')
198159
return
199-
200-
# Check Binary Ninja version and possible get file name for current version
201-
file = is_version_supported(files)
202-
if not file:
203-
version_numbers = binaryninja.core_version().split()[0].split('-')[0].split('.')
204-
major, minor, build = map(int, version_numbers)
205-
alert_user('Current version of Binary Ninja ({}) is not supported.'.format(str(major) + '.' + str(minor) + '.' + str(build)))
160+
161+
release_data = get_latest_release_info()
162+
if not release_data:
163+
alert_user('Failed to fetch release information from GitHub')
206164
return
207165

208-
# Create url for file we need
209-
file_url = '{}/{}'.format(file_url, file)
166+
latest_version = release_data.get('tag_name', 'Unknown')
210167

211-
# Retrieve the HTML of the release page
212-
release_url = 'https://github.com/{}/{}/releases/latest'.format(repo_owner, repo_name)
213-
response = requests.get(release_url)
214-
html = response.content
168+
compatible_asset = find_compatible_binary(release_data, platform_ext)
169+
if not compatible_asset:
170+
current_version = binaryninja.core_version().split()[0]
171+
alert_user(f'No compatible binary found for Binary Ninja version {current_version}')
172+
return
215173

216-
# Parse the HTML to extract the release version
217-
soup = bs4.BeautifulSoup(html, 'html.parser')
218-
latest_version_tag = getattr(soup.find('span', {'class': 'css-truncate-target'}), 'text', None)
219-
latest_version = latest_version_tag.strip() if latest_version_tag else None
174+
file = compatible_asset['name']
175+
file_url = compatible_asset['browser_download_url']
220176

221177
# Make sure we have data folder
222178
if not data_folder_exists():
@@ -234,7 +190,7 @@ def check_for_updates(repo_owner, repo_name, file_url, win_files, linux_files, d
234190
return
235191

236192
# Verify we have correct file
237-
if (is_system_supported(file) and latest_version != None):
193+
if file and latest_version:
238194
plugin_data = (read_data_file() if data_file_exists() else None) if data_folder_exists() else None
239195
# Check if we have all required data (version, hash, file name)
240196
if plugin_data == None or len(plugin_data) != 3 or plugin_data[0] == None or plugin_data[1] == None or plugin_data[2] == None:
@@ -339,7 +295,7 @@ def __init__(self):
339295
binaryninja.BackgroundTaskThread.__init__(self, 'Native plugin loader - checking for updates on: {}'.format(plugin_name), True)
340296

341297
def run(self):
342-
check_for_updates(repo_owner, repo_name, file_url, win_files, linux_files, darwin_files)
298+
check_for_updates()
343299

344300
obj = Updater()
345301
obj.start()

plugin.json

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,7 @@
2525
},
2626
"dependencies":{
2727
"pip":[
28-
"requests",
29-
"bs4"
28+
"requests"
3029
]
3130
},
3231
"version":"1.0.1",

requirements.txt

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1 @@
1-
requests
2-
bs4
1+
requests

0 commit comments

Comments
 (0)