From 74ead6248587caaf25cb21e9b4e72dd10a1fe810 Mon Sep 17 00:00:00 2001 From: Zion Date: Tue, 25 Oct 2022 12:43:08 -0700 Subject: [PATCH] Pythonic Installer w/ Ghidra (#46) * Python Installer init * remove old installer * update paths for d2d * bump versions since this breaks old versions * updates to installer * update readme --- .gitignore | 4 +- MANIFEST.in | 4 + README.md | 29 +++--- decomp2dbg.py => d2d.py | 0 decomp2dbg/__init__.py | 9 +- decomp2dbg/__main__.py | 30 +++++++ decomp2dbg/installer.py | 90 +++++++++++++++++++ install.sh | 192 ---------------------------------------- setup.cfg | 37 ++++++++ setup.py | 80 ++++++++++++----- 10 files changed, 249 insertions(+), 226 deletions(-) create mode 100644 MANIFEST.in rename decomp2dbg.py => d2d.py (100%) create mode 100644 decomp2dbg/__main__.py create mode 100644 decomp2dbg/installer.py delete mode 100755 install.sh create mode 100644 setup.cfg diff --git a/.gitignore b/.gitignore index 9e96d01..0ca1fae 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,6 @@ *.egg-info build* *.pyc -*.gdb_history \ No newline at end of file +*.gdb_history +decomp2dbg/decompilers +decomp2dbg/d2d.py diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..123aaa3 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,4 @@ +include LICENSE +include README.md +include d2d.py +recursive-include decompilers *.py *.json diff --git a/README.md b/README.md index 18e2e45..c9c49f8 100644 --- a/README.md +++ b/README.md @@ -12,25 +12,32 @@ Currently supported in GDB with any additional plugin like [GEF](https://github. [![Discord](https://img.shields.io/discord/900841083532087347?label=Discord&style=plastic)](https://discord.gg/wZSCeXnEvR) -## Install (script/fast) -The easiest and fastest way to install is using the `install.sh` script! +## Install +Install through pip, then use the built-in installer for decompilers: ```bash -./install.sh --ida /path/to/ida/plugins +pip3 install decomp2dbg && decomp2dbg --install ``` -Make sure to define the correct option for your decompiler of choice. Use `--help` for more info! -Note: You may need to allow inbound connections on port 3662, or the port you use, for decomp2dbg to connect -to the decompiler. +This will open a prompt where you be asked to input the path to your decompiler of choice. For Ghidra installs, +you must follow the extra steps to enable extensions [here](). -> Note: If you are installing decomp2dbg with GEF or pwndbg it's important that in your ~/.gdbinit the -> decomp2dbg.py file is sourced after GEF or pwndbg. +**Note**: You may need to allow inbound connections on port 3662, or the port you use, for decomp2dbg to connect +to the decompiler. If you are installing decomp2dbg with GEF or pwndbg it's important that in your `~/.gdbinit` the +`d2d.py` file is sourced after GEF or pwndbg. -## Install (manual) -If you can't use the script (non-WSL Windows install for the decompiler), follow the steps below: +## Manual Install + +Skip this if you were able to use the above install with no errors. +If you can't use the above built-in script (non-WSL Windows install for the decompiler), follow the steps below: If you only need the decompiler side of things, copy the associated decompiler plugin to the decompiler's plugin folder. Here is how you do it in IDA: +First, clone the repo: +``` +git clone https://github.com/mahaloz/decomp2dbg.git +``` + Copy all the files in `./decompilers/d2g_ida/` into your ida `plugins` folder: ```bash cp -r ./decompilers/d2d_ida/* /path/to/ida/plugins/ @@ -39,7 +46,7 @@ cp -r ./decompilers/d2d_ida/* /path/to/ida/plugins/ If you also need to install the gdb side of things, use the line below: ```bash pip3 install . && \ -cp decomp2dbg.py ~/.decomp2dbg.py && echo "source ~/.decomp2dbg.py" >> ~/.gdbinit +cp d2d.py ~/.d2d.py && echo "source ~/.d2d.py" >> ~/.gdbinit ``` ## Usage diff --git a/decomp2dbg.py b/d2d.py similarity index 100% rename from decomp2dbg.py rename to d2d.py diff --git a/decomp2dbg/__init__.py b/decomp2dbg/__init__.py index c8be3c3..f185b88 100644 --- a/decomp2dbg/__init__.py +++ b/decomp2dbg/__init__.py @@ -1,2 +1,7 @@ -from .clients.client import DecompilerClient -from .clients import GDBClient, GDBDecompilerClient +__version__ = "3.0.0" + +try: + from .clients.client import DecompilerClient + from .clients import GDBClient, GDBDecompilerClient +except ImportError: + pass diff --git a/decomp2dbg/__main__.py b/decomp2dbg/__main__.py new file mode 100644 index 0000000..49d1894 --- /dev/null +++ b/decomp2dbg/__main__.py @@ -0,0 +1,30 @@ +import argparse + +from .installer import Decomp2dbgInstaller + + +def main(): + parser = argparse.ArgumentParser( + description=""" + The decomp2dbg Command Line Util. + """, + epilog=""" + Examples: + decomp2dbg --install + """ + ) + parser.add_argument( + "--install", action="store_true", help=""" + Install the decomp2dbg core to supported decompilers as plugins. This option will start an interactive + prompt asking for install paths for all supported decompilers. Each install path is optional and + will be skipped if not path is provided during install. + """ + ) + args = parser.parse_args() + + if args.install: + Decomp2dbgInstaller().install() + + +if __name__ == "__main__": + main() diff --git a/decomp2dbg/installer.py b/decomp2dbg/installer.py new file mode 100644 index 0000000..6ea81a2 --- /dev/null +++ b/decomp2dbg/installer.py @@ -0,0 +1,90 @@ +import textwrap +from urllib.request import urlretrieve + +import pkg_resources +from pathlib import Path + +from binsync.installer import Installer + + +class Decomp2dbgInstaller(Installer): + def __init__(self): + super(Decomp2dbgInstaller, self).__init__(targets=Installer.DECOMPILERS + ('gdb',)) + self.plugins_path = Path( + pkg_resources.resource_filename("decomp2dbg", f"decompilers") + ) + + def display_prologue(self): + print(textwrap.dedent(""" + __ ___ ____ + ____/ /__ _________ ____ ___ ____ |__ \ ____/ / /_ ____ _ + / __ / _ \/ ___/ __ \/ __ `__ \/ __ \__/ // __ / __ \/ __ `/ + / /_/ / __/ /__/ /_/ / / / / / / /_/ / __// /_/ / /_/ / /_/ / + \__,_/\___/\___/\____/_/ /_/ /_/ .___/____/\__,_/_.___/\__, / + /_/ /____/ + Now installing decomp2dbg... + Please input decompiler/debugger install paths as prompted. Enter nothing to either use + the default install path if one exist, or to skip. + """)) + + def install_gdb(self, path=None): + path = super().install_gdb(path=None) + if path is None: + return None + + d2d_script_path_pkg = self.plugins_path.parent.joinpath("d2d.py") + with open(path, "r") as fp: + init_contents = fp.read() + + write_str = f"source {str(d2d_script_path_pkg.absolute())}" + if write_str in init_contents: + self.warn("gdbinit already contains d2d source...") + return None + + with open(path, "a") as fp: + fp.write(f"\n{write_str}\n") + + return path + + def install_ida(self, path=None): + ida_plugin_path = super().install_ida(path=path) + if ida_plugin_path is None: + return + + src_d2d_ida_pkg = self.plugins_path.joinpath("d2d_ida").joinpath("d2d_ida") + src_d2d_ida_py = self.plugins_path.joinpath("d2d_ida").joinpath("d2d_ida.py") + dst_d2d_ida_pkg = ida_plugin_path.joinpath("d2d_ida") + dst_d2d_ida_py = ida_plugin_path.joinpath("d2d_ida.py") + self.link_or_copy(src_d2d_ida_pkg, dst_d2d_ida_pkg, is_dir=True) + self.link_or_copy(src_d2d_ida_py, dst_d2d_ida_py) + return dst_d2d_ida_pkg + + def install_angr(self, path=None): + angr_plugin_path = super().install_angr(path=path) + if angr_plugin_path is None: + return None + + src_d2d_angr_pkg = self.plugins_path.joinpath("d2d_angr") + dst_d2d_angr_pkg = angr_plugin_path.joinpath("d2d_angr") + self.link_or_copy(src_d2d_angr_pkg, dst_d2d_angr_pkg, is_dir=True) + return dst_d2d_angr_pkg + + def install_ghidra(self, path=None): + ghidra_path = super().install_ghidra(path=path) + if ghidra_path is None: + return None + + download_url = "https://github.com/mahaloz/decomp2dbg/releases/latest/download/d2d-ghidra-plugin.zip" + dst_path = ghidra_path.joinpath("d2d-ghidra-plugin.zip") + urlretrieve(download_url, dst_path) + return dst_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("d2d_binja") + dst_path = binja_plugin_path.joinpath("d2d_binja") + self.link_or_copy(src_path, dst_path, is_dir=True) + return dst_path diff --git a/install.sh b/install.sh deleted file mode 100755 index e20d265..0000000 --- a/install.sh +++ /dev/null @@ -1,192 +0,0 @@ -#!/bin/bash - -print_help() { - echo "\ -OVERVIEW: decomp2dbg server & client installer - -USAGE: install.sh [options] - -OPTIONS: - -Decompiler Install Options: - -i --ida PATH - Install for IDA where PATH should be the PATH to the 'plugins' folder in your IDA install. - -b --binja PATH - Install for Binary Ninja where PATH should be the PATH to the 'plugins' folder in your Binary Ninja install. - -a --angr PATH - Install for angr (angr-management), where PATH should be the PATH to the 'plugins' folder in your angr-management install. - - For help finding your 'plugins' folder, see the docs for advice. - -Generic Options: - --link - Link files with ln instead of copying them. Do this only if you plan on keeping the decomp2dbg GitHub repo in the - the same place for a while. Useful if you update decomp2dbg frequently, since you will only need to pull. - --no-client - Do not install the client (gdb and pip install). Useful if you are using a decompiler on another machine or VM! - --version - Display the version of this program - --help - Display available options - -EXAMPLES: - install.sh --ida /home/mahaloz/idapro-7.6/plugins/ - install.sh --binja /home/mahaloz/.binaryninja/plugins/ - install.sh --angr /home/mahaloz/angr-dev/angr-management/angrmanagement/plugins/ --link - install.sh --ida /home/mahaloz/idapro-7.6/plugins/ --no-client - install.sh --help - - -Please only use this script inside the decomp2dbg repo directory. -" -exit 1 -} - -LOC="$(realpath .)" -validate_in_decomp2dbg() { - if test -f "${LOC}/decomp2dbg.py"; then - : - else - echo "ERROR: not in decomp2dbg repo directory!" - print_help - fi -} - -version() { - VERSION=$(grep -o "[0-9].[0-9].[0-9]" setup.py) - echo "VERSION: ${VERSION}" - exit 0 -} - -install_ida() { - if [ -z "${LINK}" ]; then - echo "INSTALLING: IDA plugin without linking to ${IDA_PATH}" - cp -r ${LOC}/decompilers/d2d_ida/* "$IDA_PATH" && \ - echo "IDA install was successful!" && \ - return - else - echo "INSTALLING: IDA plugin WITH linking to ${IDA_PATH}" - ln -s "${LOC}/decompilers/d2d_ida/d2d_ida.py" "$IDA_PATH" && \ - ln -s "${LOC}/decompilers/d2d_ida/d2d_ida/" "$IDA_PATH" && \ - echo "IDA install was successful!" && \ - return - fi - echo -e "ERROR: IDA install failed!\n" -} - -install_binja() { - if [ -z "${LINK}" ]; then - echo "INSTALLING: Binja plugin without linking to ${BINJA_PATH}" - cp -r "${LOC}/decompilers/d2d_binja/" "$BINJA_PATH" && \ - echo "Binja install was successful!" && \ - return - else - echo "INSTALLING: Binja plugin WITH linking to ${BINJA_PATH}" - ln -s "${LOC}/decompilers/d2d_binja/" "$BINJA_PATH" && \ - echo "Binja install was successful!" && \ - return - fi - echo -e "ERROR: Binja install failed!\n" -} - -install_angr() { - if [ -z "${LINK}" ]; then - echo "INSTALLING: angr plugin without linking to ${ANGR_PATH}" - cp -r "${LOC}/decompilers/d2d_angr/" "$ANGR_PATH" && \ - echo "angr install was successful!" && \ - return - else - echo "INSTALLING: angr plugin WITH linking to ${ANGR_PATH}" - ln -s "${LOC}/decompilers/d2d_angr/" "$ANGR_PATH" && \ - echo "angr install was successful!" && \ - return - fi - echo -e "ERROR: angr install failed!\n" -} - -install_client() { - pip3 install . - if [ -z "${LINK}" ]; then - echo "INSTALLING: gdb client without linking." - cp "${LOC}/decomp2dbg.py" ~/.decomp2dbg.py && \ - echo "source ~/.decomp2dbg.py" >> ~/.gdbinit && \ - echo "gdb client install was successful!" - return - else - echo "INSTALLING: gdb client WITH linking." - ln -s "${LOC}/decomp2dbg.py" ~/.decomp2dbg.py && \ - echo "source ~/.decomp2dbg.py" >> ~/.gdbinit && \ - echo "gdb client install was successful!" - return - fi - echo -e "ERROR: gdb client install failed!\n" -} - -POSITIONAL_ARGS=() - -while [[ $# -gt 0 ]]; do - case $1 in - -i|--ida) - IDA_PATH="$2" - shift # past argument - shift # past value - ;; - -b|--binja) - BINJA_PATH="$2" - shift # past argument - shift # past value - ;; - -a|--angr) - ANGR_PATH="$2" - shift # past argument - shift # past value - ;; - --link) - LINK=YES - shift # past argument - ;; - --no-client) - NO_CLIENT=YES - shift # past argument - ;; - -v|--version) - SHOW_VERSION=YES - shift # past argument - ;; - -h|--help) - SHOW_HELP=YES - shift # past argument - ;; - -*|--*) - echo "Unknown option $1" - print_help - exit 1 - ;; - *) - POSITIONAL_ARGS+=("$1") # save positional arg - shift # past argument - ;; - esac -done - -set -- "${POSITIONAL_ARGS[@]}" # restore positional parameters - -if [[ -n $1 ]]; then - print_help -else - if [ -n "${SHOW_HELP}" ]; then - print_help - elif [ -n "${SHOW_VERSION}" ]; then - version - exit 0 - fi - - # decompilers - if [ -n "${IDA_PATH}" ]; then - install_ida - fi - if [ -n "${BINJA_PATH}" ]; then - install_binja - fi - if [ -n "${ANGR_PATH}" ]; then - install_angr - fi - - # client - if [ -z ${NO_CLIENT} ]; then - install_client - fi -fi diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..ad8741b --- /dev/null +++ b/setup.cfg @@ -0,0 +1,37 @@ +[metadata] +name = decomp2dbg +version = attr: decomp2dbg.__version__ +url = https://github.com/mahaloz/decomp2dbg +classifiers = + License :: OSI Approved :: BSD License + Programming Language :: Python :: 3 + Programming Language :: Python :: 3.6 +license = BSD 2 Clause +license_files = LICENSE +description = Symbol syncing framework for decompilers and debuggers +long_description = file: README.md +long_description_content_type = text/markdown + +[options] +install_requires = + sortedcontainers + pyelftools + binsync + +python_requires = >= 3.5 +include_package_data = True +packages = find: + +[options.package_data] +decomp2dbg = + d2d.py + decompilers/*.py + decompilers/d2d_angr/*.py + decompilers/d2d_binja/*.py + decompilers/d2d_binja/*.json + decompilers/d2d_ida/*.py + decompilers/d2d_ida/d2d_ida/*.py + +[options.entry_points] +console_scripts = + decomp2dbg = decomp2dbg.__main__:main diff --git a/setup.py b/setup.py index e0d5c3b..3bcaaed 100644 --- a/setup.py +++ b/setup.py @@ -1,20 +1,60 @@ -try: - from setuptools import setup - from setuptools import find_packages - packages = find_packages() -except ImportError: - from distutils.core import setup - import os - packages = [x.strip('./').replace('/','.') for x in os.popen('find -name "__init__.py" | xargs -n1 dirname').read().strip().split('\n')] - -setup( - name='decomp2dbg', - version='2.2.0', - packages=packages, - install_requires=[ - "sortedcontainers", - "pyelftools" - ], - description='Syncing framework for decompilers and debuggers', - url='https://github.com/mahaloz/decomp2gef', -) +# pylint: disable=missing-class-docstring +import os +import platform +import shutil +from pathlib import Path +import sys +from distutils.util import get_platform + +from setuptools import setup +from setuptools.command.develop import develop as st_develop + + +def _copy_decomp_plugins(): + local_plugins = Path("decompilers").absolute() + decomp2dbg_loc = Path("decomp2dbg").absolute() + pip_e_plugins = decomp2dbg_loc.joinpath("decompilers").absolute() + + local_d2d = Path("d2d.py").absolute() + + # clean the install location of symlink or folder + shutil.rmtree(pip_e_plugins, ignore_errors=True) + try: + os.unlink(pip_e_plugins) + os.unlink(decomp2dbg_loc.joinpath(local_d2d.name)) + except: + pass + + # first attempt a symlink, if it works, exit early + try: + os.symlink(local_plugins, pip_e_plugins, target_is_directory=True) + os.symlink(local_d2d, decomp2dbg_loc.joinpath(local_d2d.name)) + return + except: + pass + + # copy if symlinking is not available on target system + shutil.copytree("decompilers", "decomp2dbg/decompilers") + shutil.copy("d2d.py", "decomp2dbg/d2d.py") + + +class develop(st_develop): + def run(self, *args): + self.execute(_copy_decomp_plugins, (), msg="Linking or copying local plugins folder") + super().run(*args) + + +cmdclass = { + "develop": develop, +} + +if 'bdist_wheel' in sys.argv and '--plat-name' not in sys.argv: + sys.argv.append('--plat-name') + name = get_platform() + if 'linux' in name: + sys.argv.append('manylinux2014_' + platform.machine()) + else: + # https://www.python.org/dev/peps/pep-0425/ + sys.argv.append(name.replace('.', '_').replace('-', '_')) + +setup(cmdclass=cmdclass)