From 32f3969e09a5d29197cabbc28d93dc9c572d7e45 Mon Sep 17 00:00:00 2001 From: Mike Hunhoff Date: Fri, 26 Jan 2024 17:49:08 -0700 Subject: [PATCH] configure Ghidrathon using absolute path of Python interpreter used to install Jep (#85) * configure Ghidrathon using absolute path of Python interpreter * update license * fixup README --- .github/workflows/tests.yml | 77 ++++++ LICENSE.txt | 2 +- README.md | 102 +++---- build.gradle | 35 +-- data/GhidrathonConfig.xml | 5 - data/python/jepeval.py | 2 +- data/python/jeprunscript.py | 2 +- data/python/jepwelcome.py | 2 +- data/python/tests/hello.py | 16 ++ data/python/tests/runall.py | 4 +- data/python/tests/test_cpython.py | 2 +- data/python/tests/test_jepbridge.py | 2 +- doc/building.md | 19 ++ extension.properties | 2 +- ghidra_scripts/ghidrathon_example.py | 4 +- os/linux_x86_64/README.txt | 3 - os/mac_x86_64/README.txt | 3 - os/win_x86_64/README.txt | 3 - .../ghidrathon/GhidrathonClassEnquirer.java | 2 +- .../java/ghidrathon/GhidrathonConfig.java | 2 +- .../GhidrathonConsoleInputThread.java | 2 +- .../java/ghidrathon/GhidrathonPlugin.java | 2 +- .../java/ghidrathon/GhidrathonScript.java | 2 +- .../ghidrathon/GhidrathonScriptProvider.java | 2 +- src/main/java/ghidrathon/GhidrathonUtils.java | 2 +- .../interpreter/GhidrathonInterpreter.java | 255 +++++++++++++++--- util/configure_jep_native_binaries.py | 127 --------- util/ghidrathon_configure.py | 60 +++++ 28 files changed, 435 insertions(+), 306 deletions(-) create mode 100644 .github/workflows/tests.yml create mode 100644 data/python/tests/hello.py create mode 100644 doc/building.md delete mode 100644 os/linux_x86_64/README.txt delete mode 100644 os/mac_x86_64/README.txt delete mode 100644 os/win_x86_64/README.txt delete mode 100644 util/configure_jep_native_binaries.py create mode 100644 util/ghidrathon_configure.py diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 0000000..d85ed75 --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,77 @@ +name: CI + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + +jobs: + + tests: + name: Tests in ${{ matrix.python-version }} on ${{ matrix.os }} + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + ghidra-release-url: ["https://github.com/NationalSecurityAgency/ghidra/releases/download/Ghidra_10.3.2_build/ghidra_10.3.2_PUBLIC_20230711.zip", "https://github.com/NationalSecurityAgency/ghidra/releases/download/Ghidra_11.0_build/ghidra_11.0_PUBLIC_20231222.zip"] + jep-jar-release-url: ["https://github.com/ninia/jep/releases/download/v4.2.0/jep-4.2.0.jar"] + os: [ubuntu-latest, windows-latest, macos-latest] + python-version: ["3.8", "3.12"] + steps: + - name: Checkout Ghidrathon + uses: actions/checkout@v4 + - name: Setup Java + uses: actions/setup-java@v4 + with: + distribution: "temurin" + java-version: "17" + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v3 + with: + gradle-version: "7.3" + - name: Setup Python ${{ matrix.python-version }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + - name: Configure temp folder + run: mkdir ../tmp + - name: Install Python Jep + run: | + pip install numpy + pip install jep==4.2.0 + python -c "import importlib.util;import pathlib;print(pathlib.Path(importlib.util.find_spec('jep').origin).parent)" + - name: Download dependencies Linux/macOS + if : ${{ matrix.os != 'windows-latest' }} + run: | + wget ${{ matrix.ghidra-release-url }} -O ../tmp/ghidra.zip + unzip ../tmp/ghidra.zip -d ../tmp/ghidra + mv ../tmp/ghidra/$(ls ../tmp/ghidra) ../tmp/ghidra/ghidra_PUBLIC + wget ${{ matrix.jep-jar-release-url }} -O ./lib/jep-4.2.0.jar + - name: Download dependencies Windows + if : ${{ matrix.os == 'windows-latest' }} + shell: pwsh + run: | + Invoke-WebRequest -URI "${{ matrix.ghidra-release-url }}" -OutFile "../tmp/ghidra.zip" + mkdir ../tmp/ghidra + tar -xf ../tmp/ghidra.zip -C ../tmp/ghidra + Rename-Item -Path "../tmp/ghidra/$((Get-ChildItem -Path "../tmp/ghidra").Name)" -NewName "ghidra_PUBLIC" + Invoke-WebRequest -URI "${{ matrix.jep-jar-release-url }}" -OutFile "./lib/jep-4.2.0.jar" + - name: Build Ghidrathon + run: gradle -PGHIDRA_INSTALL_DIR=${{ github.workspace }}/../tmp/ghidra/ghidra_PUBLIC + - name: Install Ghidrathon Linux/macOS + if : ${{ matrix.os != 'windows-latest' }} + run: | + unzip ./dist/$(ls ./dist) -d ../tmp/ghidra/ghidra_PUBLIC/Ghidra/Extensions/ + - name: Install Ghidrathon Windows + if : ${{ matrix.os == 'windows-latest' }} + shell: pwsh + run: | + Rename-Item -Path "./dist/$((Get-ChildItem -Path "./dist").Name)" -NewName "Ghidrathon.zip" + tar -xf ./dist/Ghidrathon.zip -C ../tmp/ghidra/ghidra_PUBLIC/Ghidra/Extensions/ + - name: Set Ghidrathon Python interpreter + run: python util/ghidrathon_configure.py ../tmp/ghidra/ghidra_PUBLIC + - name: Run tests + run: | + ../tmp/ghidra/ghidra_PUBLIC/support/analyzeHeadless ${{ github.workspace }}/../tmp/ghidra test -Import ${{ github.workspace }}/../tmp/ghidra/ghidra_PUBLIC/GPL/DemanglerGnu/os/linux_x86_64/demangler_gnu_v2_24 -PostScript ${{ github.workspace }}/data/python/tests/hello.py -PostScript ${{ github.workspace }}/data/python/tests/runall.py + python -c "import pathlib, sys; sys.exit(0 if pathlib.Path('hello.txt').exists() else -1)" diff --git a/LICENSE.txt b/LICENSE.txt index 20aa701..73677f0 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -187,7 +187,7 @@ same "printed page" as the copyright notice for easier identification within third-party archives. - Copyright (C) 2022 Mandiant, Inc. + Copyright (C) 2024 Mandiant, Inc. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/README.md b/README.md index 336d951..5bd6dc7 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,9 @@ # Ghidrathon [![License](https://img.shields.io/badge/license-Apache--2.0-green.svg)](LICENSE.txt) +[![CI](https://github.com/mandiant/ghidrathon/actions/workflows/tests.yml/badge.svg)](https://github.com/mandiant/ghidrathon/actions/workflows/tests.yml) -Ghidrathon is a Ghidra extension that adds Python 3 scripting capabilities to Ghidra. Why? Ghidra natively supports scripting in Java and Jython. Unfortunately many open-source analysis tools, like [capa](https://github.com/mandiant/capa), [Unicorn Engine](https://github.com/unicorn-engine/unicorn), [angr](https://github.com/angr/angr), etc., are written in Python 3 making it difficult, and in some cases, impossible to use these tools in Ghidra. More so the security community has released several great plugins for other SRE frameworks like IDA Pro and Binary Ninja, but again, because many of these plugins use Python 3 it is difficult to port them to Ghidra. Ghidrathon helps you use existing and develop new Python 3 tooling in Ghidra and script Ghidra using modern Python in a way that tightly integrates with Ghidra's UI. +Ghidrathon is a Ghidra extension that adds Python 3 scripting capabilities to Ghidra. Why? Ghidra natively supports scripting in Java and Jython. Unfortunately, many open-source analysis tools, like [capa](https://github.com/mandiant/capa), [Unicorn Engine](https://github.com/unicorn-engine/unicorn), [angr](https://github.com/angr/angr), etc., are written in Python 3 making it difficult, and in some cases, impossible to use these tools in Ghidra. More so the security community has released several great plugins for other SRE frameworks like IDA Pro and Binary Ninja, but again, because many of these plugins use Python 3 it is difficult to port them to Ghidra. Ghidrathon helps you use existing and develop new Python 3 tooling in Ghidra and script Ghidra using modern Python in a way that tightly integrates with Ghidra's UI. Check out: @@ -54,7 +55,7 @@ INFO REPORT: Post-analysis succeeded for file: /example.o (HeadlessAnalyzer) INFO REPORT: Save succeeded for processed file: /example.o (HeadlessAnalyzer) ``` -For more information on running Ghidra in headless mode check out `/support/analyzeHeadlessREADME.html`. +For more information on running Ghidra in headless mode check out `/support/analyzeHeadlessREADME.html`. ## Third-Party Python Modules @@ -74,87 +75,44 @@ Ghidrathon links your local Python installation to Ghidra using the open-source For more information on how Jep works to embed Python in Java see their documentation [here](https://github.com/ninia/jep/wiki/How-Jep-Works). -## OS Support - -Ghidrathon supports the following operating systems: - -* Linux -* Windows -* macOS (x86_64) - -## Requirements - -The following tools are needed to build, install, and run Ghidrathon: +## Installing Ghidrathon +### Requirements Tool | Version |Source | |---|---|---| -| Ghidra | `>= 10.3` | https://ghidra-sre.org | -| Jep | `4.2.0` | https://github.com/ninia/jep | -| Gradle | `>= 7.3` | https://gradle.org/releases | -| Python | `>= 3.8` | https://www.python.org/downloads | - -Note: Ghidra >= 10.2 requires [JDK 17 64-bit](https://adoptium.net/temurin/releases/). - -## Python Virtual Environments +| Ghidrathon | `>= 4.0.0` | https://github.com/mandiant/Ghidrathon/releases | +| Python | `>= 3.8.0` | https://www.python.org/downloads | +| Jep | `4.2.0` | https://github.com/ninia/jep/releases | +| Ghidra | `>= 10.3.2` | https://github.com/NationalSecurityAgency/ghidra/releases | +| Java | `>= 17.0.0` | https://adoptium.net/temurin/releases/ | -Ghidrathon supports Python virtual environments. To use a Python virtual environment, simply build Ghidrathon inside your virtual environment **and** execute Ghidra inside the **same** virtual environment. - -## Building Ghidrathon - -**Note:** Review [Python Virtual Environments](#python-virtual-environments) before building if you would like to use a Python virtual environment for Ghidrathon. - -**Note**: Building Ghidrathon requires building Jep. If you are running Windows, this requires installing the Microsoft C++ Build Tools found [here](https://visualstudio.microsoft.com/visual-cpp-build-tools/). See Jep's documentation [here](https://github.com/ninia/jep/wiki/Windows) for more information on installing Jep on Windows. - -Use the following steps to build Ghidrathon for your environment: - -* Install Ghidra using the documentation [here](https://htmlpreview.github.io/?https://github.com/NationalSecurityAgency/ghidra/blob/stable/GhidraDocs/InstallationGuide.html#InstallationNotes) -* Install Gradle from [here](https://gradle.org/releases) -* Download the latest Ghidrathon source release from [here](https://github.com/mandiant/Ghidrathon/releases) -* Run the following command from the Ghidrathon source directory: - * **Note:** Ghidrathon defaults to the Python binary found in your path. You can specify a different Python binary by adding the optional argument `-PPYTHON_BIN=` to the command below - * **Note:** you may optionally set an environment variable named `GHIDRA_INSTALL_DIR` instead of specifying `-PGHIDRA_INSTALL_DIR` +Use the following steps to install Ghidrathon to your Ghidra environment: +1. Install Jep: ``` -$ gradle -PGHIDRA_INSTALL_DIR= +$ python -m pip install jep==4.2.0 ``` +2. Execute `ghidrathon_configure.py`: +``` +$ python ghidrathon_configure.py +``` +3. Download and unzip the latest Ghidrathon [release](https://github.com/mandiant/Ghidrathon/releases) +4. Install the Ghidrathon extension (`.zip`) into Ghidra: + * Using Ghidra's UI: + * Navigate to `File > Install Extensions...` + * Click the green `+` button + * Navigate to the Ghidrathon extension (`.zip`) + * Click `Ok` + * Using a limited environment: + * Extract the Ghidrathon extension (`.zip`) to `\Ghidra\Extensions` -This command installs Jep, configures Ghidrathon with the necessary Jep binaries, and builds Ghidrathon. If successful, you will find a new directory in your Ghidrathon source directory named `dist` containing your Ghidrathon extension (`.zip`). Please open a new issue if you experience any issues building Ghidrathon. - -## Installing Ghidrathon - -Use the following steps to install your Ghidrathon extension using the Ghidra UI: - -* Start Ghidra -* Navigate to `File > Install Extensions...` -* Click the green `+` button -* Navigate to your Ghidrathon extension built earlier (`.zip`) -* Click `Ok` -* Restart Ghidra - -**OR** - -Extract your Ghidrathon extension (`.zip`) directly to `\Ghidra\Extensions` to automatically enable Ghidrathon the next time that Ghidra is started. This method works great if you do not have access to the Ghidra UI when installing Ghidrathon. - -### Disabling Jython - -Ghidrathon disables the built-in Jython script provider to avoid conflicts when Ghidra decides which provider should handle scripts with the `.py` file extension. This means existing Jython scripts cannot be executed with Ghidrathon installed. We recommend completely disabling the Jython extension. - -Use the following steps to disable the Jython extension: - -* Open a CodeBrowser window (*not the Project Manager window*) -* Navigate to `File > Configure...` -* Click `Ghidra Core` -* Un-check `PythonPlugin` - -Use the following steps to enable the Jython extension: +### Switching Python Interpreters -* Uninstall Ghidrathon -* Enable the Jython extension using the steps outlined above -* Restart Ghidra +You can switch Ghidrathon to use a different Python interpreter by running `ghidrathon_configure.py` using the new Python interpreter. -## Using Ghidrathon +### Python Virtual Environments -See [Python 3 Interpreter Window](#python-3-interpreter-window), [Ghidra Script Manager Integration](#ghidra-script-manager-integration), and [Ghidra Headless Mode](#ghidra-headless-mode) for more information about using Ghidrathon. +Ghidrathon supports Python virtual environments. To use a Python virtual environment, complete steps `1` and `2` using the Python interpreter that is configured for your environment. Do the same when running `ghidrathon_configure.py` to switch the Ghidrathon to use a different interpreter. ## Considerations diff --git a/build.gradle b/build.gradle index 3fe7290..ed7a655 100644 --- a/build.gradle +++ b/build.gradle @@ -1,4 +1,4 @@ -// Copyright (C) 2022 Mandiant, Inc. All Rights Reserved. +// Copyright (C) 2024 Mandiant, Inc. All Rights Reserved. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at: [package root]/LICENSE.txt @@ -22,39 +22,6 @@ // application.gradle.version property in /Ghidra/application.properties // for the correction version of Gradle to use for the Ghidra installation you specify. -def javaHome -def pythonBin - -if (project.hasProperty("PYTHON_BIN")) { - pythonBin = project.getProperty("PYTHON_BIN") -} -else { - pythonBin = "python" -} - -// we need to install Jep; this requires C++ build tools on Windows (see README); we need to define -// the env variable "JAVA_HOME" containing absolute path to Java JDK configured for Ghidra -task installJep(type: Exec) { - environment "JAVA_HOME", System.getProperty("java.home") - commandLine pythonBin, '-m', 'pip', 'install', 'jep==4.2' -} - -// we need to copy the Jep native binaries built in installJep to our extension directory; we use a small -// utility script written in Python -task copyJepNativeBinaries(type: Exec) { - dependsOn installJep - workingDir "${projectDir}" - commandLine pythonBin, "util${File.separator}configure_jep_native_binaries.py" -} - -// make all tasks not matching copyJepNativeBinaries or installJep depend on copyJepNativeBinaries; mostly -// used to ensure our tasks run before Ghidra buildExtension task -tasks.matching { it.name != 'copyJepNativeBinaries' && it.name != 'installJep' }.all { Task task -> - task.dependsOn copyJepNativeBinaries -} - -// from here we use the standard Gradle build provided by Ghidra framework - //----------------------START "DO NOT MODIFY" SECTION------------------------------ def ghidraInstallDir diff --git a/data/GhidrathonConfig.xml b/data/GhidrathonConfig.xml index 401022b..4839439 100644 --- a/data/GhidrathonConfig.xml +++ b/data/GhidrathonConfig.xml @@ -3,10 +3,5 @@ - - - - - diff --git a/data/python/jepeval.py b/data/python/jepeval.py index 0d5eb34..c40f323 100644 --- a/data/python/jepeval.py +++ b/data/python/jepeval.py @@ -1,4 +1,4 @@ -# Copyright (C) 2022 Mandiant, Inc. All Rights Reserved. +# Copyright (C) 2024 Mandiant, Inc. All Rights Reserved. # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at: [package root]/LICENSE.txt diff --git a/data/python/jeprunscript.py b/data/python/jeprunscript.py index da6fdda..f36558a 100644 --- a/data/python/jeprunscript.py +++ b/data/python/jeprunscript.py @@ -1,4 +1,4 @@ -# Copyright (C) 2022 Mandiant, Inc. All Rights Reserved. +# Copyright (C) 2024 Mandiant, Inc. All Rights Reserved. # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at: [package root]/LICENSE.txt diff --git a/data/python/jepwelcome.py b/data/python/jepwelcome.py index 7923a58..5013a17 100644 --- a/data/python/jepwelcome.py +++ b/data/python/jepwelcome.py @@ -1,4 +1,4 @@ -# Copyright (C) 2022 Mandiant, Inc. All Rights Reserved. +# Copyright (C) 2024 Mandiant, Inc. All Rights Reserved. # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at: [package root]/LICENSE.txt diff --git a/data/python/tests/hello.py b/data/python/tests/hello.py new file mode 100644 index 0000000..d8fa3c9 --- /dev/null +++ b/data/python/tests/hello.py @@ -0,0 +1,16 @@ +# Run Ghidrathon CI tests. +# @author Mike Hunhoff (mehunhoff@google.com) +# @category Python 3 +# Copyright (C) 2024 Mandiant, Inc. All Rights Reserved. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at: [package root]/LICENSE.txt +# Unless required by applicable law or agreed to in writing, software distributed under the License +# is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and limitations under the License. + +import pathlib +import jep + +path = pathlib.Path("hello.txt") +path.write_text(f"Hello from Jep {jep.__version__}", encoding="utf-8") diff --git a/data/python/tests/runall.py b/data/python/tests/runall.py index 07cd7dc..3584b1d 100644 --- a/data/python/tests/runall.py +++ b/data/python/tests/runall.py @@ -1,7 +1,7 @@ # Run Ghidrathon unit tests. -# @author Mike Hunhoff (michael.hunhoff@mandiant.com) +# @author Mike Hunhoff (mehunhoff@google.com) # @category Python 3 -# Copyright (C) 2022 Mandiant, Inc. All Rights Reserved. +# Copyright (C) 2024 Mandiant, Inc. All Rights Reserved. # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at: [package root]/LICENSE.txt diff --git a/data/python/tests/test_cpython.py b/data/python/tests/test_cpython.py index c4cf4c8..6892ddc 100644 --- a/data/python/tests/test_cpython.py +++ b/data/python/tests/test_cpython.py @@ -1,4 +1,4 @@ -# Copyright (C) 2022 Mandiant, Inc. All Rights Reserved. +# Copyright (C) 2024 Mandiant, Inc. All Rights Reserved. # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at: [package root]/LICENSE.txt diff --git a/data/python/tests/test_jepbridge.py b/data/python/tests/test_jepbridge.py index 89fd9a9..e86151c 100644 --- a/data/python/tests/test_jepbridge.py +++ b/data/python/tests/test_jepbridge.py @@ -1,4 +1,4 @@ -# Copyright (C) 2022 Mandiant, Inc. All Rights Reserved. +# Copyright (C) 2024 Mandiant, Inc. All Rights Reserved. # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at: [package root]/LICENSE.txt diff --git a/doc/building.md b/doc/building.md new file mode 100644 index 0000000..e63c27c --- /dev/null +++ b/doc/building.md @@ -0,0 +1,19 @@ +# Building Ghidrathon + +## Requirements + +Tool | Version |Source | +|---|---|---| +| Ghidrathon | `>= 4.0.0` | https://github.com/mandiant/Ghidrathon/releases | +| Ghidra | `>= 10.3.2` | https://github.com/NationalSecurityAgency/ghidra/releases | +| Java | `>= 17.0.0` | https://adoptium.net/temurin/releases/ | +| Gradle | `>= 7.3` | https://gradle.org/releases | + +Use the following steps to build Ghidrathon: +1. Download the [supported Jep JAR release](https://github.com/ninia/jep/releases/download/v4.2.0/jep-4.2.0.jar) to `\lib` +2. Execute gradle from ``: +``` +$ gradle -PGHIDRA_INSTALL_DIR= +``` + +The extension is stored in `\dist`. diff --git a/extension.properties b/extension.properties index 22dd8a2..3f1b217 100644 --- a/extension.properties +++ b/extension.properties @@ -1,5 +1,5 @@ name=Ghidrathon description=The FLARE team's open-source extension to add Python 3 scripting to Ghidra. -author=Mike Hunhoff (michael.hunhoff@mandiant.com) +author=Mike Hunhoff (mehunhoff@google.com) createdOn=03/31/2022 version=@extversion@ diff --git a/ghidra_scripts/ghidrathon_example.py b/ghidra_scripts/ghidrathon_example.py index 97b3181..581d07e 100644 --- a/ghidra_scripts/ghidrathon_example.py +++ b/ghidra_scripts/ghidrathon_example.py @@ -1,8 +1,8 @@ # Print function basic block and instruction counts. -# @author Mike Hunhoff (michael.hunhoff@mandiant.com) +# @author Mike Hunhoff (mehunhoff@google.com) # @category Python 3 -# Copyright (C) 2022 Mandiant, Inc. All Rights Reserved. +# Copyright (C) 2024 Mandiant, Inc. All Rights Reserved. # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at: [package root]/LICENSE.txt diff --git a/os/linux_x86_64/README.txt b/os/linux_x86_64/README.txt deleted file mode 100644 index 7dd33ce..0000000 --- a/os/linux_x86_64/README.txt +++ /dev/null @@ -1,3 +0,0 @@ -The "os/linux_x86_64" directory is intended to hold Linux native binaries -which this module is dependent upon. This directory may be eliminated for a specific -module if native binaries are not provided for the corresponding platform. diff --git a/os/mac_x86_64/README.txt b/os/mac_x86_64/README.txt deleted file mode 100644 index 3b37fb0..0000000 --- a/os/mac_x86_64/README.txt +++ /dev/null @@ -1,3 +0,0 @@ -The "os/mac_x86_64" directory is intended to hold macOS native binaries -which this module is dependent upon. This directory may be eliminated for a specific -module if native binaries are not provided for the corresponding platform. diff --git a/os/win_x86_64/README.txt b/os/win_x86_64/README.txt deleted file mode 100644 index e035995..0000000 --- a/os/win_x86_64/README.txt +++ /dev/null @@ -1,3 +0,0 @@ -The "os/win_x86_64" directory is intended to hold MS Windows native binaries (.exe) -which this module is dependent upon. This directory may be eliminated for a specific -module if native binaries are not provided for the corresponding platform. diff --git a/src/main/java/ghidrathon/GhidrathonClassEnquirer.java b/src/main/java/ghidrathon/GhidrathonClassEnquirer.java index 9b7773f..51842e3 100644 --- a/src/main/java/ghidrathon/GhidrathonClassEnquirer.java +++ b/src/main/java/ghidrathon/GhidrathonClassEnquirer.java @@ -1,4 +1,4 @@ -// Copyright (C) 2022 Mandiant, Inc. All Rights Reserved. +// Copyright (C) 2024 Mandiant, Inc. All Rights Reserved. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at: [package root]/LICENSE.txt diff --git a/src/main/java/ghidrathon/GhidrathonConfig.java b/src/main/java/ghidrathon/GhidrathonConfig.java index 260a38d..2d8dd77 100644 --- a/src/main/java/ghidrathon/GhidrathonConfig.java +++ b/src/main/java/ghidrathon/GhidrathonConfig.java @@ -1,4 +1,4 @@ -// Copyright (C) 2022 Mandiant, Inc. All Rights Reserved. +// Copyright (C) 2024 Mandiant, Inc. All Rights Reserved. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at: [package root]/LICENSE.txt diff --git a/src/main/java/ghidrathon/GhidrathonConsoleInputThread.java b/src/main/java/ghidrathon/GhidrathonConsoleInputThread.java index b744e42..b7a9f47 100644 --- a/src/main/java/ghidrathon/GhidrathonConsoleInputThread.java +++ b/src/main/java/ghidrathon/GhidrathonConsoleInputThread.java @@ -1,4 +1,4 @@ -// Copyright (C) 2022 Mandiant, Inc. All Rights Reserved. +// Copyright (C) 2024 Mandiant, Inc. All Rights Reserved. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at: [package root]/LICENSE.txt diff --git a/src/main/java/ghidrathon/GhidrathonPlugin.java b/src/main/java/ghidrathon/GhidrathonPlugin.java index e621ad9..b5fe0fe 100644 --- a/src/main/java/ghidrathon/GhidrathonPlugin.java +++ b/src/main/java/ghidrathon/GhidrathonPlugin.java @@ -1,4 +1,4 @@ -// Copyright (C) 2022 Mandiant, Inc. All Rights Reserved. +// Copyright (C) 2024 Mandiant, Inc. All Rights Reserved. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at: [package root]/LICENSE.txt diff --git a/src/main/java/ghidrathon/GhidrathonScript.java b/src/main/java/ghidrathon/GhidrathonScript.java index 34a98cb..9b9f0e0 100644 --- a/src/main/java/ghidrathon/GhidrathonScript.java +++ b/src/main/java/ghidrathon/GhidrathonScript.java @@ -1,4 +1,4 @@ -// Copyright (C) 2022 Mandiant, Inc. All Rights Reserved. +// Copyright (C) 2024 Mandiant, Inc. All Rights Reserved. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at: [package root]/LICENSE.txt diff --git a/src/main/java/ghidrathon/GhidrathonScriptProvider.java b/src/main/java/ghidrathon/GhidrathonScriptProvider.java index 89f0422..6344ad5 100644 --- a/src/main/java/ghidrathon/GhidrathonScriptProvider.java +++ b/src/main/java/ghidrathon/GhidrathonScriptProvider.java @@ -1,4 +1,4 @@ -// Copyright (C) 2022 Mandiant, Inc. All Rights Reserved. +// Copyright (C) 2024 Mandiant, Inc. All Rights Reserved. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at: [package root]/LICENSE.txt diff --git a/src/main/java/ghidrathon/GhidrathonUtils.java b/src/main/java/ghidrathon/GhidrathonUtils.java index c66cbeb..f74c245 100644 --- a/src/main/java/ghidrathon/GhidrathonUtils.java +++ b/src/main/java/ghidrathon/GhidrathonUtils.java @@ -1,4 +1,4 @@ -// Copyright (C) 2022 Mandiant, Inc. All Rights Reserved. +// Copyright (C) 2024 Mandiant, Inc. All Rights Reserved. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at: [package root]/LICENSE.txt diff --git a/src/main/java/ghidrathon/interpreter/GhidrathonInterpreter.java b/src/main/java/ghidrathon/interpreter/GhidrathonInterpreter.java index 0a40015..82c79ae 100644 --- a/src/main/java/ghidrathon/interpreter/GhidrathonInterpreter.java +++ b/src/main/java/ghidrathon/interpreter/GhidrathonInterpreter.java @@ -1,4 +1,4 @@ -// Copyright (C) 2022 Mandiant, Inc. All Rights Reserved. +// Copyright (C) 2024 Mandiant, Inc. All Rights Reserved. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at: [package root]/LICENSE.txt @@ -14,24 +14,27 @@ import ghidra.app.script.GhidraScript; import ghidra.app.script.GhidraScriptUtil; import ghidra.framework.Application; +import ghidra.util.Msg; import ghidrathon.GhidrathonClassEnquirer; import ghidrathon.GhidrathonConfig; import ghidrathon.GhidrathonScript; import ghidrathon.GhidrathonUtils; -import java.io.File; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.PrintWriter; +import java.io.*; import java.lang.reflect.*; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.stream.Collectors; import jep.Jep; import jep.JepConfig; import jep.JepException; import jep.MainInterpreter; +import jep.PyConfig; /** Utility class used to configure a Jep instance to access Ghidra */ public class GhidrathonInterpreter { + private static final String GHIDRATHON_SAVE_FILENAME = "ghidrathon.save"; + private static final String SUPPORTED_JEP_VERSION = "4.2.0"; + private Jep jep_ = null; private PrintWriter out = null; private PrintWriter err = null; @@ -42,7 +45,12 @@ public class GhidrathonInterpreter { private static final GhidrathonClassEnquirer ghidrathonClassEnquirer = new GhidrathonClassEnquirer(); private static final AtomicBoolean jepConfigInitialized = new AtomicBoolean(false); - private static final AtomicBoolean jepNativeBinaryInitialized = new AtomicBoolean(false); + private static final AtomicBoolean jepMainInterpreterInitialized = new AtomicBoolean(false); + private static final AtomicBoolean jepPythonSysModuleInitialized = new AtomicBoolean(false); + + private static File jepPythonPackageDir = null; + private static File jepNativeFile = null; + private static File pythonFile = null; /** * Create and configure a new GhidrathonInterpreter instance. @@ -56,13 +64,13 @@ private GhidrathonInterpreter(GhidrathonConfig config) throws JepException, IOEx this.err = config.getStdErr(); this.config = config; - // we must set the native Jep library once before creating a Jep instance - if (jepNativeBinaryInitialized.get() == false) { - setJepNativeBinaryPath(); - jepNativeBinaryInitialized.set(true); + // we must configure jep.MainInterpreter once before creating our first jep.SharedInterpreter + if (jepMainInterpreterInitialized.get() == false) { + configureJepMainInterpreter(); + jepMainInterpreterInitialized.set(true); } - // we must set JepConfig once before creating the first SharedInterpreter + // we must set JepConfig once before creating the first jep.SharedInterpreter if (jepConfigInitialized.get() == false) { setJepConfig(); jepConfigInitialized.set(true); @@ -71,6 +79,41 @@ private GhidrathonInterpreter(GhidrathonConfig config) throws JepException, IOEx // create new Jep SharedInterpreter instance jep_ = new jep.SharedInterpreter(); + // we must configure Python sys module AFTER the first jep.SharedInterpreter is created + if (jepPythonSysModuleInitialized.get() == false) { + jep_.eval( + String.format("import sys;sys.executable=sys._base_executable=r\"%s\"", this.pythonFile)); + // site module configures other necessary sys vars, e.g. sys.prefix, using sys.executable + jep_.eval("import site;site.main()"); + jep_.eval( + String.format( + "sys.path.extend([r\"%s\"])", + Application.getModuleDataSubDirectory(GhidrathonUtils.THIS_EXTENSION_NAME, "python") + .getAbsolutePath())); + + // print embedded interpreter configuration to application.log + Msg.info(GhidrathonInterpreter.class, "Embedded Python configuration:"); + Msg.info( + GhidrathonInterpreter.class, String.format("Python %s", jep_.getValue("sys.version"))); + + String[] sysVars = { + "sys.executable", + "sys._base_executable", + "sys.prefix", + "sys.base_prefix", + "sys.exec_prefix", + "sys.base_exec_prefix" + }; + + for (String sysVar : sysVars) { + Msg.info( + GhidrathonInterpreter.class, + String.format("%s = \"%s\"", sysVar, jep_.getValue(sysVar))); + } + + jepPythonSysModuleInitialized.set(true); + } + // now that everything is configured, we should be able to run some utility scripts // to help us further configure the Python environment setJepWrappers(); @@ -82,30 +125,16 @@ private GhidrathonInterpreter(GhidrathonConfig config) throws JepException, IOEx setJepRunScript(); } - /** Configure JepConfig for ALL Jep SharedInterpreters */ + /** Configure jep.JepConfig for ALL Jep SharedInterpreters */ private void setJepConfig() { // configure the Python includes path with the user's Ghidra script directory String paths = ""; - // add data/python/ to Python includes directory - try { - paths += - Application.getModuleDataSubDirectory(GhidrathonUtils.THIS_EXTENSION_NAME, "python") - + File.pathSeparator; - } catch (IOException e) { - e.printStackTrace(this.err); - throw new RuntimeException(e); - } - - // add paths specified in Ghidrathon config - for (String path : this.config.getPythonIncludePaths()) { - - paths += path + File.pathSeparator; - } + // add Jep parent directory + paths += this.jepPythonPackageDir.getParentFile().getAbsolutePath() + File.pathSeparator; // configure Java names that will be ignored when importing from Python for (String name : this.config.getJavaExcludeLibs()) { - ghidrathonClassEnquirer.addJavaExcludeLib(name); } @@ -140,39 +169,183 @@ private void extendPythonSysPath() { } /** - * Configure native Jep library. - * - *

User must build and include native Jep library in the appropriate OS folder prior to - * building this extension. Requires os/win64/libjep.dll for Windows Requires os/linux64/libjep.so - * for Linux + * Configure jep.MainInterpreter * * @throws JepException * @throws FileNotFoundException */ - private void setJepNativeBinaryPath() throws JepException, FileNotFoundException { + private void configureJepMainInterpreter() throws JepException, FileNotFoundException { + + File ghidrathonSaveFile = + new File( + Application.getApplicationRootDirectory().getParentFile().getFile(false), + GhidrathonInterpreter.GHIDRATHON_SAVE_FILENAME); + if (!(ghidrathonSaveFile.exists() && ghidrathonSaveFile.isFile())) { + throw new JepException( + String.format( + "Failed to find %s. Please configure Ghidrathon before running it.", + ghidrathonSaveFile.getAbsolutePath())); + } - File nativeJep; + Msg.info( + GhidrathonInterpreter.class, + String.format("Using save file at %s.", ghidrathonSaveFile.getAbsolutePath())); - try { + // read absolute path of Python interpreter from save file + try (BufferedReader reader = new BufferedReader(new FileReader(ghidrathonSaveFile))) { + String pythonFilePath = reader.readLine().trim(); + if (pythonFilePath != null && !pythonFilePath.isEmpty()) { + this.pythonFile = new File(pythonFilePath); + } + } catch (IOException e) { + throw new JepException( + String.format("Failed to read %s (%s)", ghidrathonSaveFile.getAbsolutePath(), e)); + } + + // validate Python file path exists and is a file + if (this.pythonFile == null || !(this.pythonFile.exists() && this.pythonFile.isFile())) { + throw new JepException( + String.format( + "Python path %s is not valid. Please configure Ghidrathon before running it.", + this.pythonFile.getAbsolutePath())); + } + + Msg.info( + GhidrathonInterpreter.class, + String.format("Using Python interpreter at %s.", this.pythonFile.getAbsolutePath())); + + String jepPythonPackagePath = findJepPackageDir(); + if (jepPythonPackagePath.isEmpty()) { + throw new JepException( + "Could not find Jep Python package. Please install Jep before running Ghidrathon."); + } + this.jepPythonPackageDir = new File(jepPythonPackagePath); + + // validate Jep Python package directory is valid and exists + if (!(this.jepPythonPackageDir.exists() && this.jepPythonPackageDir.isDirectory())) { + throw new JepException( + String.format( + "Jep Python package path %s is not valid. Please verify your Jep installation works" + + " before running Ghidrathon.", + this.jepPythonPackageDir.getAbsolutePath())); + } - nativeJep = Application.getOSFile(GhidrathonUtils.THIS_EXTENSION_NAME, "libjep.so"); + Msg.info( + GhidrathonInterpreter.class, + String.format( + "Using Jep Python package at %s.", this.jepPythonPackageDir.getAbsolutePath())); + + // find our native Jep file + // https://github.com/ninia/jep/blob/dd2bf345392b1b66fd6c9aeb12c234a557690ba1/src/main/java/jep/LibraryLocator.java#L86C1-L93C10 + String libraryName = System.mapLibraryName("jep"); + if (libraryName.endsWith(".dylib")) { + /* + * OS X uses a different extension for System.loadLibrary and + * System.mapLibraryName + */ + libraryName = libraryName.replace(".dylib", ".jnilib"); + } + this.jepNativeFile = new File(this.jepPythonPackageDir, libraryName); + + // validate our native Jep file exists and is a file + if (!(this.jepNativeFile.exists() && this.jepNativeFile.isFile())) { + throw new JepException( + String.format( + "Jep native file path %s is not valid. Please verify your Jep installation works" + + " before running Ghidrathon.", + this.jepNativeFile.getAbsolutePath())); + } - } catch (FileNotFoundException e) { + Msg.info( + GhidrathonInterpreter.class, + String.format("Using Jep native file at %s.", this.jepNativeFile.getAbsolutePath())); - // whoops try Windows - nativeJep = Application.getOSFile(GhidrathonUtils.THIS_EXTENSION_NAME, "jep.dll"); + File jepVersionFile = new File(this.jepPythonPackageDir, "version.py"); + + if (!(jepVersionFile.exists() && jepVersionFile.isFile())) { + throw new JepException( + String.format( + "%s is not valid - could not check Jep version. Please verify your jep installation" + + " works before running Ghidrathon.", + jepVersionFile.getAbsolutePath())); } + boolean isCorrectJepVersion = false; + try (BufferedReader br = new BufferedReader(new FileReader(jepVersionFile))) { + for (String line; (line = br.readLine()) != null; ) { + if (line.contains(GhidrathonInterpreter.SUPPORTED_JEP_VERSION)) { + isCorrectJepVersion = true; + break; + } + } + } catch (IOException e) { + throw new JepException( + String.format("Failed to read %s (%s).", jepVersionFile.getAbsolutePath(), e)); + } + + if (!isCorrectJepVersion) { + throw new JepException( + String.format( + "Please install Jep version %s before running Ghidrathon.", + GhidrathonInterpreter.SUPPORTED_JEP_VERSION)); + } + + Msg.info( + GhidrathonInterpreter.class, + String.format("Using Jep version %s.", GhidrathonInterpreter.SUPPORTED_JEP_VERSION)); + try { + MainInterpreter.setJepLibraryPath(this.jepNativeFile.getAbsolutePath()); + + PyConfig config = new PyConfig(); - MainInterpreter.setJepLibraryPath(nativeJep.getAbsolutePath()); + // we can't auto import the site module becuase we are running an embedded Python interpreter + config.setNoSiteFlag(1); + config.setIgnoreEnvironmentFlag(1); + MainInterpreter.setInitParams(config); } catch (IllegalStateException e) { e.printStackTrace(this.err); throw new RuntimeException(e); } } + private String findJepPackageDir() { + String output = + execCmd( + this.pythonFile.getAbsolutePath(), + "-c", + "import importlib.util;import" + + " pathlib;print(pathlib.Path(importlib.util.find_spec('jep').origin).parent)"); + return output.trim(); + } + + // DANGER: DO NOT PASS DYNAMIC COMMANDS HERE! + private String execCmd(String... commands) { + Runtime runtime = Runtime.getRuntime(); + Process process = null; + try { + process = runtime.exec(commands); + } catch (IOException e) { + Msg.error(GhidrathonInterpreter.class, "error: " + e.toString()); + return ""; + } + + BufferedReader lineReader = + new BufferedReader(new java.io.InputStreamReader(process.getInputStream())); + String output = String.join("\n", lineReader.lines().collect(Collectors.toList())); + + BufferedReader errorReader = + new BufferedReader(new java.io.InputStreamReader(process.getErrorStream())); + String error = String.join("\n", errorReader.lines().collect(Collectors.toList())); + + if (error.length() > 0) { + Msg.error(GhidrathonInterpreter.class, error); + } + + return output; + } + /** * Configure wrapper functions in Python land. * diff --git a/util/configure_jep_native_binaries.py b/util/configure_jep_native_binaries.py deleted file mode 100644 index d36ca85..0000000 --- a/util/configure_jep_native_binaries.py +++ /dev/null @@ -1,127 +0,0 @@ -# Copyright (C) 2022 Mandiant, Inc. All Rights Reserved. -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at: [package root]/LICENSE.txt -# Unless required by applicable law or agreed to in writing, software distributed under the License -# is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and limitations under the License. - -import subprocess -import argparse -import logging -import shutil -import glob -import sys - -from pathlib import Path - -JEP_PY_FOLDER_NAME = "jep" -JEP_OS_LIB_NAME_WINDOWS = "jep.dll" -JEP_OS_LIB_NAME_LINUX = "libjep.so" -JEP_OS_LIB_NAME_DARWIN = "jep.cpython-%d%d-darwin.so" % sys.version_info[:2] - -GHIDRA_JAVA_LIB_PATH = "lib" -GHIDRA_OS_LIB_PATH_WINDOWS = "os/win_x86_64/jep.dll" -GHIDRA_OS_LIB_PATH_LINUX = "os/linux_x86_64/libjep.so" -GHIDRA_OS_LIB_PATH_DARWIN = "os/mac_x86_64/libjep.so" - - -logger = logging.getLogger(__name__) - -handler = logging.StreamHandler() -formatter = logging.Formatter("%(asctime)s %(levelname)-8s %(message)s") - -handler.setFormatter(formatter) - -logger.addHandler(handler) -logger.setLevel(logging.INFO) - - -def find_jep_dir(): - """attempt to locate Jep Python module directory - - we use naive method of checking each path in sys.path for a folder named jep - """ - for path in sys.path: - jep_dir = Path(path) / JEP_PY_FOLDER_NAME - logger.debug("Checking if %s exists" % jep_dir) - if jep_dir.is_dir(): - return jep_dir - return Path() - - -def main(args): - """ """ - if args.debug: - logger.setLevel(logging.DEBUG) - - if sys.platform in ("darwin",): - logger.debug("Detected macOS") - - os_lib_name = JEP_OS_LIB_NAME_DARWIN - os_lib_path = Path(GHIDRA_OS_LIB_PATH_DARWIN) - elif sys.platform in ("win32", "cygwin"): - logger.debug("Detected Windows OS") - - os_lib_name = JEP_OS_LIB_NAME_WINDOWS - os_lib_path = Path(GHIDRA_OS_LIB_PATH_WINDOWS) - else: - logger.debug("Detected Linux OS") - - os_lib_name = JEP_OS_LIB_NAME_LINUX - os_lib_path = Path(GHIDRA_OS_LIB_PATH_LINUX) - - logger.info("Searching for Jep Python module directory") - - if args.path: - jep_dir = Path(args.path) - if not jep_dir.is_dir(): - logger.error("Python module directory %s does not exist!" % args.path) - return -1 - else: - jep_dir = find_jep_dir() - if not jep_dir: - logger.error("Could not find Jep Python module directory!") - return -1 - - logger.info("Found Jep Python module directory at %s" % jep_dir) - - try: - jep_java_lib_name = glob.glob(str(Path(jep_dir) / "*.jar"), recursive=False)[0] - except IndexError: - logger.error("Could not find Jep JAR file in directory %s" % jep_dir) - return -1 - - logger.info("Copying %s and %s to extension folders" % (os_lib_name, jep_java_lib_name)) - - # copy the Jep JAR file to the appropriate extension folder - logger.debug("Copying %s to %s" % (Path(jep_dir) / jep_java_lib_name, Path(GHIDRA_JAVA_LIB_PATH))) - try: - shutil.copy(Path(jep_dir) / jep_java_lib_name, Path(GHIDRA_JAVA_LIB_PATH), follow_symlinks=True) - except Exception as e: - logger.error("%s" % e) - return -1 - - # copy the Jep OS-dependent file to the appopriate extension folder - logger.debug("Copying %s to %s" % (Path(jep_dir) / os_lib_name, os_lib_path)) - try: - shutil.copy(Path(jep_dir) / os_lib_name, os_lib_path, follow_symlinks=True) - except Exception as e: - logger.error("%s" % e) - return -1 - - logger.info("Done") - - return 0 - - -if __name__ == "__main__": - """ """ - parser = argparse.ArgumentParser( - description="Locate Jep module directory and copy necessary files to Ghidrathon extension directories." - ) - - parser.add_argument("-p", "--path", type=str, help="Full path to Jep Python module directory") - parser.add_argument("-d", "--debug", action="store_true", help="Show debug messages") - - sys.exit(main(parser.parse_args())) diff --git a/util/ghidrathon_configure.py b/util/ghidrathon_configure.py new file mode 100644 index 0000000..c2a9247 --- /dev/null +++ b/util/ghidrathon_configure.py @@ -0,0 +1,60 @@ +# Copyright (C) 2024 Mandiant, Inc. All Rights Reserved. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at: [package root]/LICENSE.txt +# Unless required by applicable law or agreed to in writing, software distributed under the License +# is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and limitations under the License. + +import argparse +import pathlib +import logging +import sys + + +logger = logging.getLogger(__name__) + +handler = logging.StreamHandler() +formatter = logging.Formatter("%(asctime)s %(levelname)-8s %(message)s") + +handler.setFormatter(formatter) + +logger.addHandler(handler) +logger.setLevel(logging.INFO) + + +def main(args): + """ """ + if args.debug: + logger.setLevel(logging.DEBUG) + + install_path: pathlib.Path = args.ghidrathon_install_directory + if not all((install_path.exists(), install_path.is_dir())): + logger.error('"%s" does not exist or is not a directory.', str(install_path)) + return -1 + + save_path: pathlib.Path = install_path / "ghidrathon.save" + try: + save_path.write_text(sys.executable, encoding="utf-8") + except Exception as e: + logger.error('Failed to write "%s" to "%s" (%s).', sys.executable, str(save_path), e) + return -1 + + logger.debug('Wrote "%s" to "%s".', sys.executable, str(save_path)) + logger.info("Please restart Ghidra for these changes to take effect.") + + return 0 + + +if __name__ == "__main__": + """ """ + parser: argparse.ArgumentParser = argparse.ArgumentParser( + description="Configure the running Python interpreter for Ghidrathon" + ) + + parser.add_argument( + "ghidrathon_install_directory", type=pathlib.Path, help="Absolute path of Ghidra install directory" + ) + parser.add_argument("-d", "--debug", action="store_true", help="Show debug messages") + + sys.exit(main(parser.parse_args()))