Skip to content

Commit

Permalink
0.1.5 release
Browse files Browse the repository at this point in the history
  • Loading branch information
dc3-tsd committed Aug 29, 2022
1 parent 1e5662b commit 472da45
Show file tree
Hide file tree
Showing 14 changed files with 161 additions and 78 deletions.
10 changes: 9 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# Changelog

## [0.1.5] - 2022-08-29
- Add script path to `sys.path` while running a script to allow importing other scripts in the same directory.
- Added PyhidraBasics example script.
- Prevent exception during shutdown from checking a Java exceptions type after the JVM has terminated.
- Automatically alias Java packages by applying an underscore suffix to simplify importing when there is a name conflict.
- Fixed bug causing the extension metadata to be written as a dictionary to the extension name field.

## [0.1.4] - 2022-06-01
- Corrected server JVM library locating for openjdk on MAC
- Ignore unmatched lines in application.properties
Expand Down Expand Up @@ -27,7 +34,8 @@
## 0.1.0 - 2021-06-14
- Initial release

[Unreleased]: https://github.com/dod-cyber-crime-center/pyhidra/compare/0.1.4...HEAD
[Unreleased]: https://github.com/dod-cyber-crime-center/pyhidra/compare/0.1.5...HEAD
[0.1.5]: https://github.com/dod-cyber-crime-center/pyhidra/compare/0.1.4...0.1.5
[0.1.4]: https://github.com/dod-cyber-crime-center/pyhidra/compare/0.1.3...0.1.4
[0.1.3]: https://github.com/dod-cyber-crime-center/pyhidra/compare/0.1.2...0.1.3
[0.1.2]: https://github.com/dod-cyber-crime-center/pyhidra/compare/0.1.1...0.1.2
Expand Down
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -138,5 +138,13 @@ Ghidra **must** be started via `pyhidraw` and the plugin must be enabled for the

![](https://raw.githubusercontent.com/Defense-Cyber-Crime-Center/pyhidra/master/images/image-20220111152440065.png)

### Handling Package Name Conflicts

There may be some Python modules and Java packages with the same import path. When this occurs the Python module takes precedence.
While jpype has its own mechanism for handling this situation, pyhidra automatically makes the Java package accessible by allowing
it to be imported with an underscore appended to the package name.

```python
import pdb # imports Python's pdb
import pdb_ # imports Ghidra's pdb
```
4 changes: 2 additions & 2 deletions pyhidra/__init__.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@

__version__ = "0.1.4"
__version__ = "0.1.5"

# Expose API
from .ghidra import run_script, start, open_program
from .gui import get_current_interpreter
from .script import get_current_interpreter
from .launcher import DeferredPyhidraLauncher, HeadlessPyhidraLauncher, GuiPyhidraLauncher


Expand Down
14 changes: 10 additions & 4 deletions pyhidra/constants.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
import os
import pathlib

GHIDRA_INSTALL_DIR = pathlib.Path(os.environ["GHIDRA_INSTALL_DIR"])
LAUNCH_PROPERTIES = GHIDRA_INSTALL_DIR / "support" / "launch.properties"
UTILITY_JAR = GHIDRA_INSTALL_DIR / "Ghidra" / "Framework" / "Utility" / "lib" / "Utility.jar"
LAUNCHSUPPORT = GHIDRA_INSTALL_DIR / "support" / "LaunchSupport.jar"
if "GHIDRA_INSTALL_DIR" in os.environ:
GHIDRA_INSTALL_DIR = pathlib.Path(os.environ["GHIDRA_INSTALL_DIR"])
LAUNCH_PROPERTIES = GHIDRA_INSTALL_DIR / "support" / "launch.properties"
UTILITY_JAR = GHIDRA_INSTALL_DIR / "Ghidra" / "Framework" / "Utility" / "lib" / "Utility.jar"
LAUNCHSUPPORT = GHIDRA_INSTALL_DIR / "support" / "LaunchSupport.jar"
else:
GHIDRA_INSTALL_DIR = None
LAUNCH_PROPERTIES = None
UTILITY_JAR = None
LAUNCHSUPPORT = None
32 changes: 7 additions & 25 deletions pyhidra/gui.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import os
import sys
import warnings

from pyhidra import get_current_interpreter as _get_current_interpreter

def gui():
"""
Expand All @@ -16,29 +18,9 @@ def gui():


def get_current_interpreter():
"""
Gets the underlying GhidraScript for the focused Pyhidra InteractiveConsole.
This will always return None unless it is being access from a function
called from within the interactive console.
warnings.warn(
"get_current_interpreter has been moved. Please use pyhidra.get_current_interpreter",
DeprecationWarning
)
return _get_current_interpreter()

:return: The GhidraScript for the active interactive console.
"""

try:
from ghidra.framework.main import AppInfo
project = AppInfo.getActiveProject()
if project is None:
return None
ts = project.getToolServices()
tool = None
for t in ts.getRunningTools():
if t.getActiveWindow().isFocused():
tool = t
break
if tool is None:
return None
for plugin in tool.getManagedPlugins():
if plugin.name == 'PyhidraPlugin':
return plugin.script
except ImportError:
return None
File renamed without changes.
26 changes: 15 additions & 11 deletions pyhidra/java/plugin/handler.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
import shutil
from pathlib import Path

from java.lang import ClassLoader
from utility.application import ApplicationLayout

from ghidra import GhidraLauncher
from pyhidra.java.plugin.plugin import PyPhidraPlugin
from pyhidra.javac import java_compile
from pyhidra.version import get_current_application, ExtensionDetails
from utility.application import ApplicationLayout



PACKAGE = "dc3.pyhidra.plugin"
PLUGIN_NAME = "pyhidra"
_SCRIPTS_FOLDER = "ghidra_scripts"


def _get_extension_details(layout: ApplicationLayout):
Expand All @@ -23,21 +22,26 @@ def _get_extension_details(layout: ApplicationLayout):
)


def install():
def install(launcher):
"""
Install the plugin in Ghidra
"""
path = get_current_application().extension_path / "pyhidra"
path = get_current_application().extension_path / PLUGIN_NAME
ext = path / "extension.properties"
manifest = path / "Module.manifest"
root = Path(__file__).parent
if not manifest.exists():
jar_path = path / "lib" / (PLUGIN_NAME + ".jar")
java_compile(Path(__file__).parent.parent, jar_path)
ClassLoader.getSystemClassLoader().addPath(jar_path.absolute())
java_compile(root.parent, jar_path)

if not manifest.exists():
ext.write_text(str(ExtensionDetails()))
# required empty file, might be usable for version control in the future

# required empty file
manifest.touch()

shutil.copytree(root / _SCRIPTS_FOLDER, path / _SCRIPTS_FOLDER)

# "restart" Ghidra
launcher.layout = GhidraLauncher.initializeGhidraEnvironment()

PyPhidraPlugin.register()
47 changes: 22 additions & 25 deletions pyhidra/launcher.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import contextlib
import logging
import os
import platform
import re
import shutil
Expand All @@ -27,6 +26,11 @@ def _jvm_args():
suffix = "_" + platform.system().upper()
option_pattern: re.Pattern = re.compile(fr"VMARGS(?:{suffix})?=(.+)")
properties = []

if GHIDRA_INSTALL_DIR is None:
# reported in the launcher so it is displayed properly when using the gui_script
return None

with open(LAUNCH_PROPERTIES, "r", encoding='utf-8') as fd:
# this file is small so just read it at once
for line in fd.readlines():
Expand Down Expand Up @@ -62,11 +66,13 @@ def _silence_java_output(stdout=True, stderr=True):
System.setOut(out)
System.setErr(err)


def _get_libjvm_path(java_home: Path) -> Path:
for p in java_home.glob("*/server/*jvm.*"):
if p.suffix != ".debuginfo":
return p


class _PyhidraImportLoader:
""" (internal) Finder hook for importlib to handle Python mod conflicts. """

Expand All @@ -85,6 +91,7 @@ def create_module(self, spec):
def exec_module(self, fullname):
pass


class PyhidraLauncher:
"""
Base pyhidra launcher
Expand All @@ -110,22 +117,6 @@ def add_vmargs(self, *args):
"""
self.vm_args += args

@classmethod
def _copy_script_dir(self):
"""
Copies the Ghidra script included with Pyhidra into the Extension
folder. This needs to happen before Ghidra is launched in order for
the script manager to recognize them.
"""
ghidra_path = get_current_application().extension_path / "pyhidra"
new_script_dir = ghidra_path / "ghidra_scripts"

if not os.path.exists(new_script_dir):
pyhidra_path = Path(__file__).parent
lib_script_dir = pyhidra_path / "ghidra_scripts"

shutil.copytree(lib_script_dir, new_script_dir)

@classmethod
def _report_fatal_error(cls, title: str, msg: str) -> NoReturn:
sys.exit(f"{title}: {msg}")
Expand Down Expand Up @@ -166,6 +157,15 @@ def start(self):
"""
if not jpype.isJVMStarted():

if GHIDRA_INSTALL_DIR is None:
self._report_fatal_error(
"GHIDRA_INSTALL_DIR is not set",
textwrap.dedent("""\
Please set the GHIDRA_INSTALL_DIR environment variable
to the directory where Ghidra is installed
""").rstrip()
)

self.check_ghidra_version()

if self.java_home is None:
Expand All @@ -174,10 +174,6 @@ def start(self):

jvm = _get_libjvm_path(self.java_home)

# this needs to happen before Ghidra is launched so that the script
# directory is recognized and included in the Script Manager
self._copy_script_dir()

jpype.startJVM(
str(jvm),
*self.vm_args,
Expand All @@ -199,7 +195,7 @@ def start(self):

from pyhidra.java.plugin import install

install()
install(self)

# import properties to register the property customizer
from . import properties as _
Expand Down Expand Up @@ -301,8 +297,9 @@ def _launch(self):
if t is not None:
try:
t.join()
except RuntimeError as e:
if "java.lang.InterruptedException" not in e.args:
raise
except Exception:
# only possible if the JVM dies
# handled by uncaught exception handler
pass
finally:
jpype.shutdownGuiEnvironment()
Loading

0 comments on commit 472da45

Please sign in to comment.