Skip to content

Commit bd06512

Browse files
committed
initial commit
0 parents  commit bd06512

14 files changed

Lines changed: 564 additions & 0 deletions

File tree

README.md

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
# <img src="./images/logo.png" width=20> RevEng.AI Binary Ninja Plugin
2+
3+
A Binary Ninja plugin for integrating with the RevEng.AI platform for binary analysis and function renaming.
4+
5+
## Features
6+
7+
- Upload binaries for analysis to RevEng.AI platform
8+
- Download and check analysis status
9+
- Rename functions based on similar functions found in other binaries
10+
- Batch analyze entire binaries for function renaming
11+
- Configuration management for API settings
12+
13+
## Installation
14+
15+
1. Ensure you have Python 3.9 or later installed
16+
2. Install the required dependencies:
17+
```bash
18+
pip install -r requirements.txt
19+
```
20+
3. Copy the `revengai_bn` directory to your Binary Ninja plugins directory:
21+
- Linux: `~/.binaryninja/plugins/`
22+
- Windows: `%APPDATA%\Binary Ninja\plugins\`
23+
- macOS: `~/Library/Application Support/Binary Ninja/plugins/`
24+
25+
26+
## Requirements
27+
28+
- Binary Ninja 5.0 or later
29+
- Python 3.9 or later
30+
- Internet connection for API access
31+
- RevEng.AI API key
32+
33+
## License
34+
35+
This plugin is released under the GPL-2.0 license.

__init__.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
from binaryninja import log_info
2+
from .revengai import RevengAIPlugin
3+
4+
plugin = RevengAIPlugin()
5+
log_info("RevEng.AI Plugin loaded successfully")

features/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
from .configuration import ConfigurationFeature
2+
from .upload import UploadFeature
3+
4+
__all__ = ['ConfigurationFeature', 'UploadFeature']

features/configuration/__init__.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
from binaryninja import PluginCommand, log_info
2+
from .config import Config
3+
from .config_dialog import ApiKeyWizard
4+
5+
class ConfigurationFeature:
6+
def __init__(self):
7+
self.config = Config()
8+
log_info("RevEng.AI Configuration Feature initialized")
9+
10+
def register(self):
11+
PluginCommand.register(
12+
"RevEng.AI\\Configure",
13+
"Configure RevEng.AI settings",
14+
self.show_configuration
15+
)
16+
log_info("RevEng.AI Configuration Feature registered")
17+
18+
def show_configuration(self, bv):
19+
log_info("Opening RevEng.AI configuration wizard")
20+
wizard = ApiKeyWizard(self.config)
21+
wizard.exec_()
22+
23+
def get_config(self):
24+
return self.config

features/configuration/config.py

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
from binaryninja import Settings, log_info
2+
from reait.api import re_conf, RE_authentication
3+
4+
class Config:
5+
def __init__(self):
6+
self.settings = Settings()
7+
self.api_key = ""
8+
self.host = ""
9+
self.current_analysis = None
10+
self._load_config()
11+
12+
13+
def _load_config(self):
14+
settings = Settings()
15+
settings.register_group("revengai", "RevEng.AI")
16+
settings.register_setting("revengai.host",
17+
'{"title" : "Host URL",\
18+
"type" : "string",\
19+
"default" : "https://api.reveng.ai",\
20+
"description" : "RevEng.AI Host URL"}')
21+
settings.register_setting("revengai.api_key",
22+
'{"title" : "API Key",\
23+
"type" : "string",\
24+
"default" : "",\
25+
"description" : "API Key"}')
26+
settings.register_setting("revengai.current_analysis",
27+
'{"title" : "Current Analysis ID",\
28+
"type" : "string",\
29+
"default" : "",\
30+
"description" : "Current Analysis ID"}')
31+
32+
self.host = settings.get_string("revengai.host", None)
33+
self.api_key = settings.get_string("revengai.api_key", None)
34+
self.current_analysis = settings.get_string("revengai.current_analysis", None)
35+
36+
re_conf["apikey"] = self.api_key
37+
re_conf["host"] = self.host
38+
39+
40+
def save_config(self) -> bool:
41+
log_info(f"Saving configuration: {self.host} {self.api_key[:4]}...")
42+
if self.current_analysis:
43+
settings.set_string("revengai.current_analysis", self.current_analysis)
44+
45+
try:
46+
re_conf["apikey"] = self.api_key
47+
re_conf["host"] = self.host
48+
log_info(f"Saving configuration: {re_conf['apikey']} {re_conf['host']}")
49+
RE_authentication()
50+
log_info(f"Authentication successful")
51+
settings = Settings()
52+
settings.set_string("revengai.host", self.host)
53+
settings.set_string("revengai.api_key", self.api_key)
54+
return True
55+
56+
except Exception as e:
57+
log_info(f"Failed to save API key: {str(e)}")
58+
return False
59+
60+
61+
def clear_config(self):
62+
self.api_key = ""
63+
self.host = ""
64+
self.current_analysis = None
65+
self.save_config()
66+
67+
68+
def is_configured(self):
69+
return bool(self.api_key and self.host)
Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
from PySide6.QtWidgets import (QDialog, QVBoxLayout, QHBoxLayout, QLabel,
2+
QLineEdit, QPushButton, QMessageBox, QProgressDialog, QProgressBar)
3+
from PySide6.QtCore import Qt
4+
from PySide6.QtGui import QPixmap
5+
from binaryninja import log_info, log_error, log_warn
6+
import os
7+
8+
class ApiKeyWizard(QDialog):
9+
def __init__(self, config):
10+
super().__init__()
11+
self.config = config
12+
self.init_ui()
13+
14+
def init_ui(self):
15+
self.setWindowTitle("RevEng.AI Configuration Wizard")
16+
self.setMinimumWidth(500)
17+
18+
layout = QVBoxLayout()
19+
20+
header_layout = QHBoxLayout()
21+
22+
logo_label = QLabel()
23+
logo_path = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), "images", "logo.png")
24+
if os.path.exists(logo_path):
25+
pixmap = QPixmap(logo_path)
26+
pixmap = pixmap.scaled(100, 100, Qt.KeepAspectRatio, Qt.SmoothTransformation)
27+
logo_label.setPixmap(pixmap)
28+
else:
29+
log_info("RevEng.AI logo not found at: " + logo_path)
30+
header_layout.addWidget(logo_label)
31+
32+
welcome_layout = QVBoxLayout()
33+
title_label = QLabel("Welcome to RevEng.AI!")
34+
title_label.setStyleSheet("font-size: 18px; font-weight: bold;")
35+
description_label = QLabel(
36+
"To get started, you'll need to configure your API key and host URL.\n"
37+
"You can get your API key from your RevEng.AI account settings."
38+
)
39+
description_label.setWordWrap(True)
40+
welcome_layout.addWidget(title_label)
41+
welcome_layout.addWidget(description_label)
42+
header_layout.addLayout(welcome_layout, stretch=1)
43+
44+
layout.addLayout(header_layout)
45+
layout.addSpacing(20)
46+
47+
api_key_label = QLabel("API Key:")
48+
api_key_label.setStyleSheet("font-weight: bold;")
49+
self.api_key_input = QLineEdit()
50+
self.api_key_input.setPlaceholderText("Enter your RevEng.AI API key")
51+
self.api_key_input.setText(self.config.api_key if self.config.api_key else "")
52+
53+
layout.addWidget(api_key_label)
54+
layout.addWidget(self.api_key_input)
55+
layout.addSpacing(10)
56+
57+
host_label = QLabel("Host URL:")
58+
host_label.setStyleSheet("font-weight: bold;")
59+
self.host_input = QLineEdit()
60+
self.host_input.setPlaceholderText("Enter the RevEng.AI host URL")
61+
self.host_input.setText(self.config.host if self.config.host else "")
62+
63+
layout.addWidget(host_label)
64+
layout.addWidget(self.host_input)
65+
layout.addSpacing(20)
66+
67+
button_layout = QHBoxLayout()
68+
self.save_button = QPushButton("Save Configuration")
69+
self.save_button.setStyleSheet("""
70+
QPushButton {
71+
background-color: #007bff;
72+
color: white;
73+
padding: 8px 16px;
74+
border-radius: 4px;
75+
}
76+
QPushButton:hover {
77+
background-color: #4400ff;
78+
}
79+
""")
80+
self.save_button.clicked.connect(self.save_config)
81+
self.cancel_button = QPushButton("Cancel")
82+
self.cancel_button.setStyleSheet("""
83+
QPushButton {
84+
padding: 8px 16px;
85+
border-radius: 4px;
86+
}
87+
""")
88+
self.cancel_button.clicked.connect(self.reject)
89+
90+
button_layout.addWidget(self.save_button)
91+
button_layout.addWidget(self.cancel_button)
92+
layout.addLayout(button_layout)
93+
94+
self.setLayout(layout)
95+
96+
def save_config(self):
97+
api_key = self.api_key_input.text().strip()
98+
host = self.host_input.text().strip()
99+
100+
if not api_key:
101+
log_warn("API Key field is empty")
102+
QMessageBox.warning(
103+
self,
104+
"RevEng.AI Configuration",
105+
"API Key is required to use RevEng.AI services.",
106+
QMessageBox.Ok
107+
)
108+
return
109+
110+
if not host:
111+
log_warn("Host URL field is empty")
112+
QMessageBox.warning(
113+
self,
114+
"RevEng.AI Configuration",
115+
"Host URL is required to connect to RevEng.AI services.",
116+
QMessageBox.Ok
117+
)
118+
return
119+
120+
try:
121+
progress = QProgressDialog("Testing API key...", None, 0, 0, self)
122+
progress.setWindowTitle("RevEng.AI Configuration")
123+
progress.setWindowModality(Qt.WindowModal)
124+
progress.setCancelButton(None)
125+
progress.setMinimumWidth(400)
126+
progress.setMinimumHeight(100)
127+
progress.findChild(QProgressBar).setMinimumWidth(250)
128+
progress.findChild(QProgressBar).setMinimumHeight(20)
129+
progress.setStyleSheet("""
130+
QProgressDialog {
131+
background-color: white;
132+
color: black;
133+
}
134+
QProgressBar {
135+
border: 1px solid #cccccc;
136+
border-radius: 4px;
137+
text-align: center;
138+
background-color: #f0f0f0;
139+
min-width: 250px;
140+
min-height: 20px;
141+
}
142+
QProgressBar::chunk {
143+
background-color: #007bff;
144+
border-radius: 3px;
145+
}
146+
QLabel {
147+
color: black;
148+
font-size: 13px;
149+
padding: 10px;
150+
}
151+
""")
152+
progress.show()
153+
154+
self.config.api_key = api_key
155+
self.config.host = host
156+
key_try = self.config.save_config()
157+
158+
progress.close()
159+
160+
if not key_try:
161+
raise Exception("API key not valid or host not reachable.")
162+
163+
log_info("RevEng.AI configuration saved successfully!")
164+
QMessageBox.information(
165+
self,
166+
"RevEng.AI Configuration",
167+
"Configuration saved successfully!\nYou can now start using RevEng.AI services.",
168+
QMessageBox.Ok
169+
)
170+
self.accept()
171+
172+
except Exception as e:
173+
log_error(f"Failed to save RevEng.AI configuration: {str(e)}")
174+
QMessageBox.critical(
175+
self,
176+
"RevEng.AI Configuration Error",
177+
f"Failed to save configuration: {str(e)}",
178+
QMessageBox.Ok
179+
)

features/upload/__init__.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
from binaryninja import PluginCommand, log_info, log_error
2+
from .upload import BinaryUploader
3+
from .upload_dialog import UploadDialog
4+
from ...utils.config_validator import validate_config
5+
6+
class UploadFeature:
7+
def __init__(self, config=None):
8+
self.config = config
9+
self.uploader = BinaryUploader(config)
10+
log_info("RevEng.AI Process Feature initialized")
11+
12+
def register(self):
13+
PluginCommand.register(
14+
"RevEng.AI\\Process Binary",
15+
"Process current binary to RevEng.AI for analysis",
16+
self.show_upload_dialog
17+
)
18+
log_info("RevEng.AI Process feature registered")
19+
20+
def show_upload_dialog(self, bv):
21+
if not validate_config(self.config):
22+
return
23+
dialog = UploadDialog(self.config, self.uploader, bv)
24+
if dialog.exec() == UploadDialog.Accepted:
25+
self.uploader.upload_binary(bv, dialog.get_upload_options())

features/upload/upload.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
from binaryninja import BinaryView, log_info, log_error
2+
from reait.api import RE_models
3+
4+
class BinaryUploader:
5+
def __init__(self, config):
6+
self.config = config
7+
8+
9+
def get_models(self):
10+
try:
11+
models = RE_models().json()
12+
return set([model["model_name"] for model in models["models"]])
13+
except Exception as e:
14+
log_error(f"Failed to get models: {str(e)}")
15+
return set()
16+
17+
18+
def upload_binary(self, bv: BinaryView, options: dict):
19+
try:
20+
21+
# TODO: Implement actual upload logic here
22+
# This will involve:
23+
# 1. Getting the binary data from bv
24+
# 2. Use the RE_upload_binary function to upload the binary
25+
# 3. Handling the response
26+
27+
log_info(f"Binary {bv.file.filename} uploaded successfully with options: {options}")
28+
return True
29+
30+
except Exception as e:
31+
log_error(f"Failed to upload binary: {str(e)}")
32+
return False

0 commit comments

Comments
 (0)