-
Notifications
You must be signed in to change notification settings - Fork 9
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- v4.4.0
- v4.2.2
- v2.8.2
- v2.8.1
- v2.8.0
- v2.7.0
- v2.6.0
- v2.5.0
- v2.4.0
- v2.3.1
- v2.3.0
- v2.2.1
- v2.2.0
- v2.1.1
- v2.1.0
- v2.0.3
- v2.0.2
- v2.0.1
- v2.0.0
- v1.26.0
- v1.25.1
- v1.25.0
- v1.24.1
- v1.24.0
- v1.23.1
- v1.23.0
- v1.22.1
- v1.22.0
- v1.21.0
- v1.20.1
- v1.20.0
- v1.19.2
- v1.19.1
- v1.19.0
- v1.18.4
- v1.18.2
- v1.18.1
- v1.18.0
- v1.17.1
- v1.17.0
- v1.16.0
- v1.15.0
- v1.14.1
- v1.14.0
- v1.13.0
- v1.12.1
- v1.11.0
- v1.10.2
- v1.10.1
- v1.10.0
- v1.9.2
- v1.9.1
- v1.9.0
- v1.8.0
- v1.7.2
- v1.7.1
- v1.7.0
- v1.6.2
- v1.6.1
- v1.6.0
- v1.5.2
- v1.5.1
- v1.5.0
- v1.3.3
- v1.3.2
- v1.3.1
- v1.2.7
- v1.2.6
- v1.2.5
- v1.2.4
- v1.2.3
- v1.2.2
- v1.2.1
- v1.2.0
- v1.1.0
- v1.0.0
- v0.25.1
- v0.25.0
- v0.24.0
- v0.23.0
- v0.22.1
- v0.22.0
- v0.21.1
- v0.21.0
- v0.20.0
- v0.19.0
- v0.18.0
- v0.17.0
- v0.15.0
- v0.14.0
- v0.13.0
- v0.12.0
- v0.11.0
- v0.10.0
- v0.9.0
- v0.7.0
- v0.6.10
- v0.6.9
- v0.6.8
- v0.6.7
- v0.6.6
- v0.6.5
- v0.6.0
- v0.4.0
- v0.2.0
- v0.1.0
Showing
22 changed files
with
3,575 additions
and
16 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -32,4 +32,7 @@ console_scripts = | |
test = | ||
angr-management | ||
pytest-qt | ||
pyside6 | ||
|
||
ghidra = | ||
ghidra_bridge | ||
PySide6-Essentials>=6.4.2 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,61 @@ | ||
import argparse | ||
import sys | ||
import logging | ||
from pathlib import Path | ||
import importlib | ||
import importlib.resources | ||
|
||
from yodalib.installer import YODAInstaller | ||
|
||
_l = logging.getLogger(__name__) | ||
|
||
|
||
def run_ghidra_ui(): | ||
yodalib_path = Path(str(importlib.resources.files("yodalib"))).absolute() | ||
decompilers_path = yodalib_path / "decompilers" | ||
if not decompilers_path.exists(): | ||
_l.error("Known plugins path does not exist, which means BinSync did not install correctly!") | ||
return False | ||
|
||
sys.path.insert(1, str(decompilers_path)) | ||
plugin = importlib.import_module(f"ghidra") | ||
_l.debug(f"Executing Ghidra UI...") | ||
return plugin.start_ui() | ||
|
||
|
||
def install(): | ||
YODAInstaller().install() | ||
|
||
|
||
def main(): | ||
pass | ||
parser = argparse.ArgumentParser( | ||
description=""" | ||
The YODA Command Line Util. This is the script interface to YODA that allows you to install and run | ||
the Ghidra UI for running plugins. | ||
""", | ||
epilog=""" | ||
Examples: | ||
yodalib --install | ||
""" | ||
) | ||
parser.add_argument( | ||
"--install", action="store_true", help=""" | ||
Install all the YODA plugins to every decompiler. | ||
""" | ||
) | ||
parser.add_argument( | ||
"--run-ghidra-ui", action="store_true", help=""" | ||
Execute the Ghidra file selector UI for running YODA scripts. | ||
""" | ||
) | ||
args = parser.parse_args() | ||
|
||
if args.install: | ||
install() | ||
|
||
if args.run_ghidra_ui: | ||
return run_ghidra_ui() | ||
|
||
|
||
if __name__ == "__main__": | ||
main() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
16 changes: 16 additions & 0 deletions
16
yodalib/decompiler_stubs/ghidra_yodalib/ghidra_yodalib_run.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
# Starts the YODA backend and selects a generic YODA Python script to run | ||
# @author YODALib | ||
# @category YODALib | ||
# @menupath Tools.YODALib.Run YODA Backend | ||
|
||
import subprocess | ||
from yodalib_vendored.ghidra_bridge_server import GhidraBridgeServer | ||
|
||
|
||
def start_bs_ui(): | ||
subprocess.Popen("yodalib --run-ghidra-ui".split(" ")) | ||
|
||
|
||
if __name__ == "__main__": | ||
GhidraBridgeServer.run_server(background=True) | ||
start_bs_ui() |
15 changes: 15 additions & 0 deletions
15
yodalib/decompiler_stubs/ghidra_yodalib/ghidra_yodalib_shutdown.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
# Shutdown the YODALib backend server. | ||
# @author YODALib | ||
# @category YODALib | ||
# @menupath Tools.YODALib.Shutdown YODA Backend | ||
|
||
from yodalib_vendored.jfx_bridge import bridge | ||
from yodalib_vendored.ghidra_bridge_port import DEFAULT_SERVER_PORT | ||
|
||
if __name__ == "__main__": | ||
print("Requesting server shutdown...") | ||
b = bridge.BridgeClient( | ||
connect_to_host="127.0.0.1", connect_to_port=DEFAULT_SERVER_PORT | ||
) | ||
|
||
print(b.remote_shutdown()) |
Empty file.
1 change: 1 addition & 0 deletions
1
yodalib/decompiler_stubs/ghidra_yodalib/yodalib_vendored/ghidra_bridge_port.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
DEFAULT_SERVER_PORT = 4768 |
203 changes: 203 additions & 0 deletions
203
yodalib/decompiler_stubs/ghidra_yodalib/yodalib_vendored/ghidra_bridge_server.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,203 @@ | ||
import logging | ||
import subprocess | ||
import sys | ||
from jfx_bridge import bridge | ||
from ghidra_bridge_port import DEFAULT_SERVER_PORT | ||
|
||
# NOTE: we definitely DON'T want to exclude ghidra from ghidra_bridge :P | ||
import ghidra | ||
|
||
|
||
class GhidraBridgeServer(object): | ||
""" Class mostly used to collect together functions and variables that we don't want contaminating the global namespace | ||
variables set in remote clients | ||
NOTE: this class needs to be excluded from ghidra_bridge - it doesn't need to be in the globals, if people want it and | ||
know what they're doing, they can get it from the BridgedObject for the main module | ||
""" | ||
|
||
class PrintAccumulator(object): | ||
""" Class to handle capturing print output so we can send it across the bridge, by hooking sys.stdout.write(). | ||
Not multithreading aware, it'll just capture whatever is printed from the moment it hooks to the moment | ||
it stops. | ||
""" | ||
|
||
output = None | ||
old_stdout = None | ||
|
||
def __init__(self): | ||
self.output = "" | ||
|
||
def write(self, output): | ||
self.output += output | ||
|
||
def get_output(self): | ||
return self.output | ||
|
||
def hook(self): | ||
self.old_stdout = sys.stdout | ||
sys.stdout = self | ||
|
||
def unhook(self): | ||
if self.old_stdout is not None: | ||
sys.stdout = self.old_stdout | ||
|
||
def __enter__(self): | ||
self.hook() | ||
|
||
return self | ||
|
||
def __exit__(self, type, value, traceback): | ||
self.unhook() | ||
|
||
@staticmethod | ||
def ghidra_help(param=None): | ||
""" call the ghidra help method, capturing the print output with PrintAccumulator, and return it as a string """ | ||
with GhidraBridgeServer.PrintAccumulator() as help_output: | ||
help(param) | ||
|
||
return help_output.get_output() | ||
|
||
class InteractiveListener(ghidra.framework.model.ToolListener): | ||
""" Class to handle registering for plugin events associated with the GUI | ||
environment, and sending them back to clients running in interactive mode | ||
so they can update their variables | ||
We define the interactive listener on the server end, so it can | ||
cleanly recover from bridge failures when trying to send messages back. If we | ||
let it propagate exceptions up into Ghidra, the GUI gets unhappy and can stop | ||
sending tool events out | ||
""" | ||
|
||
tool = None | ||
callback_fn = None | ||
|
||
def __init__(self, tool, callback_fn): | ||
""" Create with the tool to listen to (from state.getTool() - won't change during execution) | ||
and the callback function to notify on the client end (should be the update_vars function) """ | ||
self.tool = tool | ||
self.callback_fn = callback_fn | ||
|
||
# register the listener against the remote tool | ||
tool.addToolListener(self) | ||
|
||
def stop_listening(self): | ||
# we're done, make sure we remove the tool listener | ||
self.tool.removeToolListener(self) | ||
|
||
def processToolEvent(self, plugin_event): | ||
""" Called by the ToolListener interface """ | ||
try: | ||
self.callback_fn._bridge_conn.logger.debug( | ||
"InteractiveListener got event: " + str(plugin_event) | ||
) | ||
|
||
event_name = plugin_event.getEventName() | ||
if "Location" in event_name: | ||
self.callback_fn( | ||
currentProgram=plugin_event.getProgram(), | ||
currentLocation=plugin_event.getLocation(), | ||
) | ||
elif "Selection" in event_name: | ||
self.callback_fn( | ||
currentProgram=plugin_event.getProgram(), | ||
currentSelection=plugin_event.getSelection(), | ||
) | ||
elif "Highlight" in event_name: | ||
self.callback_fn( | ||
currentProgram=plugin_event.getProgram(), | ||
currentHighlight=plugin_event.getHighlight(), | ||
) | ||
except Exception as e: | ||
# any exception, we just want to bail and shut down the listener. | ||
# most likely case is the bridge connection has gone down. | ||
self.stop_listening() | ||
self.callback_fn._bridge_conn.logger.error( | ||
"InteractiveListener failed trying to callback client: " + str(e) | ||
) | ||
|
||
@staticmethod | ||
def run_server( | ||
server_host=bridge.DEFAULT_HOST, | ||
server_port=DEFAULT_SERVER_PORT, | ||
response_timeout=bridge.DEFAULT_RESPONSE_TIMEOUT, | ||
background=True, | ||
): | ||
""" Run a ghidra_bridge_server (forever) | ||
server_host - what address the server should listen on | ||
server_port - what port the server should listen on | ||
response_timeout - default timeout in seconds before a response is treated as "failed" | ||
background - false to run the server in this thread (script popup will stay), true for a new thread (script popup disappears) | ||
""" | ||
server = bridge.BridgeServer( | ||
server_host=server_host, | ||
server_port=server_port, | ||
loglevel=logging.INFO, | ||
response_timeout=response_timeout, | ||
) | ||
|
||
if background: | ||
server.start() | ||
server.logger.info( | ||
"Server launching in background - will continue to run after launch script finishes..." | ||
) | ||
else: | ||
server.run() | ||
|
||
@staticmethod | ||
def run_script_across_ghidra_bridge(script_file, python="python", argstring=""): | ||
""" Spin up a ghidra_bridge_server and spawn the script in external python to connect back to it. Useful in scripts being triggered from | ||
inside ghidra that need to use python3 or packages that don't work in jython | ||
The called script needs to handle the --connect_to_host and --connect_to_port command-line arguments and use them to start | ||
a ghidra_bridge client to talk back to the server. | ||
Specify python to control what the script gets run with. Defaults to whatever python is in the shell - if changing, specify a path | ||
or name the shell can find. | ||
Specify argstring to pass further arguments to the script when it starts up. | ||
""" | ||
|
||
# spawn a ghidra bridge server - use server port 0 to pick a random port | ||
server = bridge.BridgeServer( | ||
server_host="127.0.0.1", server_port=0, loglevel=logging.INFO | ||
) | ||
# start it running in a background thread | ||
server.start() | ||
|
||
try: | ||
# work out where we're running the server | ||
server_host, server_port = server.server.bridge.get_server_info() | ||
|
||
print("Running " + script_file) | ||
|
||
# spawn an external python process to run against it | ||
|
||
try: | ||
output = subprocess.check_output( | ||
"{python} {script} --connect_to_host={host} --connect_to_port={port} {argstring}".format( | ||
python=python, | ||
script=script_file, | ||
host=server_host, | ||
port=server_port, | ||
argstring=argstring, | ||
), | ||
stderr=subprocess.STDOUT, | ||
shell=True, | ||
) | ||
print(output) | ||
except subprocess.CalledProcessError as exc: | ||
print("Failed ({}):{}".format(exc.returncode, exc.output)) | ||
|
||
print(script_file + " completed") | ||
|
||
finally: | ||
# when we're done with the script, shut down the server | ||
server.shutdown() | ||
|
||
|
||
if __name__ == "__main__": | ||
# legacy version - run the server in the foreground, so we don't break people's expectations | ||
GhidraBridgeServer.run_server( | ||
response_timeout=bridge.DEFAULT_RESPONSE_TIMEOUT, background=False | ||
) | ||
|
1 change: 1 addition & 0 deletions
1
yodalib/decompiler_stubs/ghidra_yodalib/yodalib_vendored/jfx_bridge/__init__.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
from .bridge import __version__ |
2,233 changes: 2,233 additions & 0 deletions
2,233
yodalib/decompiler_stubs/ghidra_yodalib/yodalib_vendored/jfx_bridge/bridge.py
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
## Installation | ||
To install our Ghidra backend you need to do the following steps: | ||
1. Install BinSync with the extra Ghidra dependencies: `pip install binsync[ghidra]` | ||
2. Install the BinSync python stubs: `binsync --install` | ||
3. Open a binary in Ghidra, then open `Window -> Script Manager` tab | ||
4. Click on the left hand side `BinSync`, then click the check marks next to both scripts in the folder | ||
|
||
BinSync is now under your `Tools -> BinSync`. Use the `Start BinSync` to connect and go! |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
from .file_selector import start_ui |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
import logging | ||
|
||
from yodalib.api import ArtifactLifter | ||
|
||
l = logging.getLogger(name=__name__) | ||
|
||
|
||
class GhidraArtifactLifter(ArtifactLifter): | ||
lift_map = {} | ||
|
||
def __init__(self, controller): | ||
super(GhidraArtifactLifter, self).__init__(controller) | ||
|
||
def lift_addr(self, addr: int) -> int: | ||
#return self.controller.rebase_addr(addr) | ||
return addr | ||
|
||
def lift_type(self, type_str: str) -> str: | ||
return type_str | ||
|
||
def lift_stack_offset(self, offset: int, func_addr: int) -> int: | ||
return offset | ||
|
||
def lower_addr(self, addr: int) -> int: | ||
#return self.controller.rebase_addr(addr, up=True) | ||
return addr | ||
|
||
def lower_type(self, type_str: str) -> str: | ||
return type_str | ||
|
||
def lower_stack_offset(self, offset: int, func_addr: int) -> int: | ||
return offset |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
import sys | ||
from pathlib import Path | ||
import subprocess | ||
|
||
from yodalib.ui.version import set_ui_version | ||
set_ui_version("PySide6") | ||
from yodalib.ui.qt_objects import ( | ||
QApplication, QWidget, QLabel, QMainWindow, QVBoxLayout, QPushButton, QFileDialog, QGridLayout, QDialog | ||
) | ||
|
||
class FileSelectorDialog(QDialog): | ||
def __init__(self): | ||
super(FileSelectorDialog, self).__init__() | ||
self.setWindowTitle("Run a YODA Python script") | ||
|
||
self._init_widgets() | ||
|
||
# | ||
# Private methods | ||
# | ||
|
||
def _init_widgets(self): | ||
self._layout = QGridLayout() | ||
row = 0 | ||
|
||
# make a button to open a dialog that selects a file | ||
self._button = QPushButton("File Path") | ||
self._button.clicked.connect(self._select_file) | ||
self._layout.addWidget(self._button, row, 0) | ||
|
||
# make a label to show the selected file | ||
self._label = QLabel() | ||
self._layout.addWidget(self._label, row, 1) | ||
row += 1 | ||
|
||
# make a run and cancel button | ||
self._run_button = QPushButton("Run") | ||
self._run_button.clicked.connect(self._run) | ||
self._layout.addWidget(self._run_button, row, 0) | ||
|
||
self._cancel_button = QPushButton("Cancel") | ||
self._cancel_button.clicked.connect(self._cancel) | ||
self._layout.addWidget(self._cancel_button, row, 1) | ||
|
||
self.setLayout(self._layout) | ||
|
||
def _select_file(self): | ||
# open a dialog to select a file | ||
file_name, _ = QFileDialog.getOpenFileName(self, "Select a YODA Python script") | ||
|
||
# update the label to show the selected file | ||
self._label.setText(file_name) | ||
|
||
def _run(self): | ||
file_path = self._label.text() | ||
if not file_path: | ||
return | ||
|
||
file_path = Path(file_path).absolute() | ||
if not file_path.exists(): | ||
return | ||
|
||
subprocess.Popen(f"python3 {file_path}".split(" ")) | ||
self.close() | ||
|
||
def _cancel(self): | ||
self.close() | ||
|
||
def closeEvent(self, event): | ||
sys.exit(0) | ||
|
||
|
||
def start_ui(): | ||
app = QApplication() | ||
file_selector = FileSelectorDialog() | ||
file_selector.show() | ||
file_selector.exec_() | ||
app.exec_() | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
import time | ||
import logging | ||
|
||
import ghidra_bridge | ||
|
||
l = logging.getLogger(__name__) | ||
|
||
class GhidraAPIWrapper: | ||
def __init__(self, controller, connection_timeout=10): | ||
self._controller = controller | ||
self._connection_timeout = connection_timeout | ||
|
||
self.bridge = None | ||
self._ghidra_bridge_attrs = {} | ||
self._imports = {} | ||
|
||
self.connected = self._connect_ghidra_bridge() | ||
if not self.connected: | ||
return | ||
|
||
def __getattr__(self, item): | ||
if item in self._ghidra_bridge_attrs: | ||
return self._ghidra_bridge_attrs[item] | ||
else: | ||
return self.__getattribute__(item) | ||
|
||
def import_module_object(self, module_name: str, obj_name: str): | ||
module = self.import_module(module_name) | ||
try: | ||
module_obj = getattr(module, obj_name) | ||
except AttributeError: | ||
l.critical(f"Failed to import {module}.{obj_name}") | ||
module_obj = None | ||
|
||
return module_obj | ||
|
||
def import_module(self, module_name: str): | ||
if module_name not in self._imports: | ||
self._imports[module_name] = self.bridge.remote_import(module_name) | ||
|
||
return self._imports[module_name] | ||
|
||
def _connect_ghidra_bridge(self): | ||
start_time = time.time() | ||
successful = False | ||
while time.time() - start_time < self._connection_timeout: | ||
try: | ||
self.bridge = ghidra_bridge.GhidraBridge(namespace=self._ghidra_bridge_attrs, interactive_mode=True) | ||
successful = True | ||
except ConnectionError: | ||
time.sleep(1) | ||
|
||
if successful: | ||
break | ||
|
||
return successful |
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,305 @@ | ||
import os | ||
from pathlib import Path | ||
import textwrap | ||
import pkg_resources | ||
import shutil | ||
|
||
from prompt_toolkit import prompt | ||
from prompt_toolkit.completion.filesystem import PathCompleter | ||
|
||
class Color: | ||
""" | ||
Used to colorify terminal output. | ||
Taken from: https://github.com/hugsy/gef/blob/dev/tests/utils.py | ||
""" | ||
NORMAL = "\x1b[0m" | ||
GRAY = "\x1b[1;38;5;240m" | ||
LIGHT_GRAY = "\x1b[0;37m" | ||
RED = "\x1b[31m" | ||
GREEN = "\x1b[32m" | ||
YELLOW = "\x1b[33m" | ||
BLUE = "\x1b[34m" | ||
PINK = "\x1b[35m" | ||
CYAN = "\x1b[36m" | ||
BOLD = "\x1b[1m" | ||
UNDERLINE = "\x1b[4m" | ||
UNDERLINE_OFF = "\x1b[24m" | ||
HIGHLIGHT = "\x1b[3m" | ||
HIGHLIGHT_OFF = "\x1b[23m" | ||
BLINK = "\x1b[5m" | ||
BLINK_OFF = "\x1b[25m" | ||
|
||
|
||
class Installer: | ||
DECOMPILERS = ( | ||
"ida", | ||
"binja", | ||
"ghidra", | ||
"angr" | ||
) | ||
|
||
DEBUGGERS = ( | ||
"gdb", | ||
) | ||
|
||
def __init__(self, targets=None, target_install_paths=None): | ||
self.targets = targets or self.DECOMPILERS+self.DEBUGGERS | ||
self._home = Path(os.getenv("HOME") or "~/").expanduser().absolute() | ||
self.target_install_paths = target_install_paths or {} #or self._populate_installs_from_config() | ||
|
||
def _populate_installs_from_config(self): | ||
config = GlobalConfig.update_or_make(self._home) | ||
if not config: | ||
return {} | ||
|
||
return { | ||
attr: getattr(config, attr) for attr in config.__slots__ | ||
} | ||
|
||
def install(self): | ||
self.display_prologue() | ||
try: | ||
self.install_all_targets() | ||
except Exception as e: | ||
print(f"Stopping Install... because: {e}") | ||
self.display_epilogue() | ||
|
||
def display_prologue(self): | ||
pass | ||
|
||
def display_epilogue(self): | ||
self.good("Install completed! If anything was skipped by mistake, please manually install it.") | ||
|
||
@staticmethod | ||
def info(msg): | ||
print(f"{Color.BLUE}{msg}{Color.NORMAL}") | ||
|
||
@staticmethod | ||
def good(msg): | ||
print(f"{Color.GREEN}[+] {msg}{Color.NORMAL}") | ||
|
||
@staticmethod | ||
def warn(msg): | ||
print(f"{Color.YELLOW}[!] {msg}{Color.NORMAL}") | ||
|
||
@staticmethod | ||
def ask_path(question): | ||
Installer.info(question) | ||
filepath = prompt("", completer=PathCompleter(expanduser=True)) | ||
|
||
if not filepath: | ||
return None | ||
|
||
filepath = Path(filepath).expanduser().absolute() | ||
if not filepath.exists(): | ||
Installer.warn(f"Provided filepath {filepath} does not exist.") | ||
return None | ||
|
||
return filepath | ||
|
||
@staticmethod | ||
def link_or_copy(src, dst, is_dir=False, symlink=False): | ||
# clean the install location | ||
shutil.rmtree(dst, ignore_errors=True) | ||
try: | ||
os.unlink(dst) | ||
except: | ||
pass | ||
|
||
if not symlink: | ||
# copy if symlinking is not available on target system | ||
if is_dir: | ||
shutil.copytree(src, dst) | ||
else: | ||
shutil.copy(src, dst) | ||
else: | ||
# first attempt a symlink, if it works, exit early | ||
try: | ||
os.symlink(src, dst, target_is_directory=is_dir) | ||
return | ||
except: | ||
pass | ||
|
||
def install_all_targets(self): | ||
for target in self.targets: | ||
try: | ||
installer = getattr(self, f"install_{target}") | ||
except AttributeError: | ||
continue | ||
|
||
path = self.target_install_paths.get(f"{target}_path", None) | ||
if path: | ||
path = Path(path).expanduser().absolute() | ||
|
||
res = installer(path=path) | ||
if res is None: | ||
self.warn(f"Skipping or failed install for {target}... {Color.NORMAL}\n") | ||
else: | ||
self.good(f"Installed {target} to {res}\n") | ||
#GlobalConfig.update_or_make(self._home, **{f"{target}_path": res.parent}) | ||
|
||
def install_ida(self, path=None): | ||
ida_path = path or self._home.joinpath(".idapro") | ||
ida_plugin_path = ida_path.joinpath("plugins").expanduser() | ||
default_str = f" [default = {ida_plugin_path}]" | ||
if not ida_plugin_path.exists(): | ||
if ida_path.exists(): | ||
os.makedirs(ida_plugin_path) | ||
else: | ||
ida_plugin_path = None | ||
default_str = "" | ||
|
||
path = self.ask_path(f"IDA Plugins Path{default_str}:") | ||
if not path: | ||
if not ida_plugin_path: | ||
return None | ||
|
||
path = ida_plugin_path | ||
|
||
if not path.absolute().exists(): | ||
return None | ||
|
||
return path | ||
|
||
def install_ghidra(self, path=None): | ||
path = self._home.joinpath('ghidra_scripts').expanduser() | ||
default_str = f" [default = {path}]" | ||
ghidra_default_path = path | ||
path = self.ask_path(f"Ghidra Scripts Path{default_str}:") | ||
if path: | ||
path = path.joinpath("Extensions").joinpath("Ghidra") | ||
elif ghidra_default_path: | ||
path = ghidra_default_path | ||
else: | ||
return None | ||
|
||
if not path.absolute().exists(): | ||
return None | ||
|
||
return path | ||
|
||
def install_binja(self, path=None): | ||
binja_install_path = path or self._home.joinpath(".binaryninja").joinpath("plugins").expanduser() | ||
default_str = f" [default = {binja_install_path}]" | ||
if not binja_install_path.exists(): | ||
binja_install_path = None | ||
default_str = "" | ||
|
||
path = self.ask_path(f"Binary Ninja Plugins Path{default_str}:") | ||
if not path: | ||
if not binja_install_path: | ||
return None | ||
|
||
path = binja_install_path | ||
|
||
return path | ||
|
||
def install_angr(self, path=None): | ||
# look for a default install | ||
# attempt to resolve through packaging | ||
angr_resolved = True | ||
try: | ||
import angrmanagement | ||
except ImportError: | ||
angr_resolved = False | ||
|
||
default_str = "" | ||
angr_install_path = None | ||
if angr_resolved: | ||
angr_install_path = Path(angrmanagement.__file__).parent | ||
default_str = f" [default = {angr_install_path}]" | ||
elif path: | ||
angr_install_path = path | ||
default_str = f" [default = {angr_install_path}]" | ||
|
||
# use the default if possible | ||
# TODO: this needs to be changed so we can still provide the install path if non-interactive, | ||
# but also introduce a default_path kwarg to all these installers | ||
path = self.ask_path(f"angr-management Install Path{default_str}:") if path is None else path | ||
if not path: | ||
if not angr_install_path: | ||
return None | ||
|
||
path = angr_install_path | ||
|
||
path = path.joinpath("plugins") | ||
if not path.absolute().exists(): | ||
return None | ||
|
||
return path | ||
|
||
def install_gdb(self, path=None): | ||
default_gdb_path = path or self._home.joinpath(".gdbinit").expanduser() | ||
default_str = f" [default = {default_gdb_path}]" | ||
path = self.ask_path(f"gdbinit path{default_str}:") if path is None else path | ||
if not path: | ||
if not default_gdb_path: | ||
return None | ||
|
||
path = default_gdb_path | ||
|
||
return path | ||
|
||
|
||
class YODAInstaller(Installer): | ||
def __init__(self): | ||
super().__init__(targets=Installer.DECOMPILERS) | ||
self.plugins_path = Path( | ||
pkg_resources.resource_filename("yodalib", f"decompiler_stubs") | ||
) | ||
|
||
def display_prologue(self): | ||
print(textwrap.dedent(""" | ||
Now installing YODALib plugins for all supported decompilers... | ||
Please input decompiler/debugger install paths as prompted. Enter nothing to either use | ||
the default install path if one exists, or to skip. | ||
""")) | ||
|
||
def install_ida(self, path=None): | ||
ida_plugin_path = super().install_ida(path=path) | ||
if ida_plugin_path is None: | ||
return None | ||
|
||
src_ida_yodalib_py = self.plugins_path.joinpath("ida_yodalib.py") | ||
dst_ida_yodalib_py = ida_plugin_path.joinpath("ida_yodalib.py") | ||
self.link_or_copy(src_ida_yodalib_py, dst_ida_yodalib_py) | ||
return dst_ida_yodalib_py | ||
|
||
def install_angr(self, path=None): | ||
angr_plugin_path = super().install_angr(path=path) | ||
if angr_plugin_path is None: | ||
return None | ||
|
||
src_angr_yodalib_pkg = self.plugins_path.joinpath("angr_yodalib") | ||
dst_angr_yodalib_pkg = angr_plugin_path.joinpath("angr_yodalib") | ||
self.link_or_copy(src_angr_yodalib_pkg, dst_angr_yodalib_pkg, is_dir=True) | ||
return dst_angr_yodalib_pkg | ||
|
||
def install_ghidra(self, path=None): | ||
ghidra_path = super().install_ghidra(path=path) | ||
if ghidra_path is None: | ||
return None | ||
|
||
src_ghidra_yodalib_pkg = self.plugins_path.joinpath("ghidra_yodalib") | ||
src_vendored = src_ghidra_yodalib_pkg.joinpath("yodalib_vendored") | ||
src_script = src_ghidra_yodalib_pkg.joinpath("ghidra_yodalib_run.py") | ||
src_script_shutdown = src_ghidra_yodalib_pkg.joinpath("ghidra_yodalib_shutdown.py") | ||
|
||
dst_ghidra_yodalib_pkg = ghidra_path.joinpath("yodalib_vendored") | ||
dst_ghidra_script = ghidra_path.joinpath("ghidra_yodalib_run.py") | ||
dst_script_shutdown = ghidra_path.joinpath("ghidra_yodalib_shutdown.py") | ||
|
||
self.link_or_copy(src_vendored, dst_ghidra_yodalib_pkg, is_dir=True) | ||
self.link_or_copy(src_script, dst_ghidra_script) | ||
self.link_or_copy(src_script_shutdown, dst_script_shutdown) | ||
return ghidra_path | ||
|
||
def install_binja(self, path=None): | ||
binja_plugin_path = super().install_binja(path=path) | ||
if binja_plugin_path is None: | ||
return None | ||
|
||
src_path = self.plugins_path.joinpath("binja_yodalib") | ||
dst_path = binja_plugin_path.joinpath("binja_yodalib") | ||
self.link_or_copy(src_path, dst_path, is_dir=True) | ||
return binja_plugin_path |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,100 @@ | ||
from yodalib.ui.version import ui_version | ||
|
||
if ui_version == "PySide6": | ||
from PySide6.QtCore import ( | ||
QDir, Qt, Signal, QAbstractTableModel, QModelIndex, QSortFilterProxyModel, QPersistentModelIndex, | ||
QEvent, QThread, Slot, QObject, QPropertyAnimation, QAbstractAnimation, QParallelAnimationGroup | ||
) | ||
from PySide6.QtWidgets import ( | ||
QAbstractItemView, | ||
QCheckBox, | ||
QComboBox, | ||
QDialog, | ||
QFileDialog, | ||
QGridLayout, | ||
QGroupBox, | ||
QHBoxLayout, | ||
QHeaderView, | ||
QLabel, | ||
QLineEdit, | ||
QMenu, | ||
QMessageBox, | ||
QPushButton, | ||
QStatusBar, | ||
QTableWidget, | ||
QTableWidgetItem, | ||
QTabWidget, | ||
QVBoxLayout, | ||
QWidget, | ||
QDialogButtonBox, | ||
QTableView, | ||
QFontDialog, | ||
QCheckBox, | ||
QMainWindow, | ||
QApplication, | ||
QFrame, | ||
QWidget, | ||
QSizePolicy, | ||
QScrollArea, | ||
QToolButton, | ||
QProgressBar | ||
) | ||
from PySide6.QtGui import ( | ||
QFontDatabase, | ||
QColor, | ||
QKeyEvent, | ||
QFocusEvent, | ||
QIntValidator, | ||
QAction, | ||
QImage, | ||
) | ||
else: | ||
from PyQt5.QtCore import ( | ||
QDir, Qt, QAbstractTableModel, QModelIndex, QSortFilterProxyModel, QPersistentModelIndex, | ||
QEvent, QThread, QObject, QPropertyAnimation, QAbstractAnimation, QParallelAnimationGroup | ||
) | ||
from PyQt5.QtCore import pyqtSignal as Signal | ||
from PyQt5.QtCore import pyqtSlot as Slot | ||
from PyQt5.QtWidgets import ( | ||
QAbstractItemView, | ||
QCheckBox, | ||
QComboBox, | ||
QDialog, | ||
QFileDialog, | ||
QGridLayout, | ||
QGroupBox, | ||
QHBoxLayout, | ||
QHeaderView, | ||
QLabel, | ||
QLineEdit, | ||
QMenu, | ||
QMessageBox, | ||
QPushButton, | ||
QStatusBar, | ||
QTableWidget, | ||
QTableWidgetItem, | ||
QTabWidget, | ||
QVBoxLayout, | ||
QWidget, | ||
QDialogButtonBox, | ||
QTableView, | ||
QAction, | ||
QFontDialog, | ||
QCheckBox, | ||
QMainWindow, | ||
QApplication, | ||
QFrame, | ||
QWidget, | ||
QSizePolicy, | ||
QScrollArea, | ||
QToolButton, | ||
QProgressBar, | ||
) | ||
from PyQt5.QtGui import ( | ||
QFontDatabase, | ||
QColor, | ||
QKeyEvent, | ||
QFocusEvent, | ||
QIntValidator, | ||
QImage | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
ui_version = "PySide6" | ||
|
||
|
||
def set_ui_version(version): | ||
global ui_version | ||
valid_version = [ | ||
"PyQt5", | ||
"PySide6" | ||
] | ||
|
||
if version in valid_version: | ||
ui_version = version | ||
else: | ||
raise Exception("Failed to set BinSync UI version") |