Skip to content

Commit

Permalink
0.2.0 release
Browse files Browse the repository at this point in the history
  • Loading branch information
dc3-tsd committed Sep 28, 2022
1 parent 472da45 commit a49a6fb
Show file tree
Hide file tree
Showing 25 changed files with 732 additions and 239 deletions.
11 changes: 10 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
# Changelog

## [0.2.0] - 2022-09-27
- Fixed issue with terminal being taken over when running `pyhidraw` on Linux/Mac.
- Added cancel and reset buttons to the pyhidra interpreter in the Ghidra plugin.
- Force the pyhidra interpreter thread to finish and exit when the Ghidra plugin is closed.
- Honor the `safe_path` option introduced in Python 3.11. When set the script path will not be added to `sys.path` when running a script.
- Enforce command line interface requirement that the pyhidra script must be the last positional argument before the script arguments.
- Fixed bug causing `print` to be redirected in headless mode.

## [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.
Expand Down Expand Up @@ -34,7 +42,8 @@
## 0.1.0 - 2021-06-14
- Initial release

[Unreleased]: https://github.com/dod-cyber-crime-center/pyhidra/compare/0.1.5...HEAD
[Unreleased]: https://github.com/dod-cyber-crime-center/pyhidra/compare/0.2.0...HEAD
[0.2.0]: https://github.com/dod-cyber-crime-center/pyhidra/compare/0.1.5...0.2.0
[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
Expand Down
2 changes: 1 addition & 1 deletion pyhidra/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@

__version__ = "0.1.5"
__version__ = "0.2.0"

# Expose API
from .ghidra import run_script, start, open_program
Expand Down
136 changes: 90 additions & 46 deletions pyhidra/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from pathlib import Path

import pyhidra
import pyhidra.ghidra
import pyhidra.gui


Expand All @@ -27,37 +28,58 @@ class PyhidraArgs(argparse.Namespace):
Custom namespace for holding the command line arguments
"""

def __init__(self, **kwargs):
def __init__(self, parser: argparse.ArgumentParser, **kwargs):
super().__init__(**kwargs)
self.parser = parser
self.valid = True
self.verbose = False
self.binary_path = None
self.script_path = None
self.binary_path: Path = None
self.script_path: Path = None
self.project_name = None
self.project_path = None
self.script_args = []
self.project_path: Path = None
self._script_args = []

def func(self):
"""
Run script or enter repl
"""
if not self.valid:
self.parser.print_usage()
return

if self.script_path is not None:
pyhidra.run_script(
self.binary_path,
self.script_path,
project_location=self.project_path,
project_name=self.project_name,
script_args=self.script_args,
verbose=self.verbose
)
try:
pyhidra.run_script(
self.binary_path,
self.script_path,
project_location=self.project_path,
project_name=self.project_name,
script_args=self._script_args,
verbose=self.verbose
)
except KeyboardInterrupt:
# gracefully finish when cancelled
pass
elif self.binary_path is not None:
from .ghidra import _flat_api
args = self.binary_path, self.project_path, self.project_name, self.verbose
with _flat_api(*args) as api:
with pyhidra.ghidra._flat_api(*args) as api:
_interpreter(api)
else:
pyhidra.HeadlessPyhidraLauncher(verbose=self.verbose).start()
_interpreter(globals())

@property
def script_args(self):
return self._script_args

@script_args.setter
def script_args(self, value):
if self._script_args is None:
self._script_args = value
else:
# append any remaining args to the ones which were previously consumed
self._script_args.extend(value)


class PathAction(argparse.Action):
"""
Expand All @@ -67,35 +89,50 @@ class PathAction(argparse.Action):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.nargs = '*'
self.help = "Headless script and/or binary path. "\
"If neither are provided pyhidra will drop into a repl."
self.type = Path

def __call__(self, parser, namespace, values, option_string=None):
count = 0
for p in values:
if p.exists() and p.is_file():
if p.suffix == ".py":
if namespace.script_path is not None:
# assume an additional script is meant to be a parameter to the first one
break
namespace.script_path = p
else:
if namespace.binary_path is not None:
if namespace.script_path is None:
raise ValueError("binary_path specified multiple times")
# assume it is a script parameter
break
namespace.binary_path = p
count += 1
else:
break
if count > 1:
break
values[:] = values[count:]
self.type = str

def __call__(self, parser, namespace: PyhidraArgs, values, option_string=None):

if not values:
return

if namespace.script_path is not None:
# Any arguments after the script path get passed to the script
namespace.script_args = values
return

value = Path(values.pop(0))

if not value.exists():
# File must exist
namespace.valid = False

if value.suffix == ".py":
namespace.script_path = value
namespace.script_args = values
return

if namespace.binary_path is None:
# Peek at the next value, if present, to check if it is a script
# The optional binary file MUST come before the script
if len(values) > 0 and not values[0].endswith(".py"):
namespace.valid = False

namespace.binary_path = value

if not values:
return

# Recurse until all values are consumed
# The remaining arguments in the ArgParser was a lie for pretty help text
# and to pick up trailing optional arguments meant for the script
self(parser, namespace, values)


def _get_parser():
parser = argparse.ArgumentParser(prog="pyhidra")
usage = "pyhidra [-h] [-v] [-g] [-s] [--project-name name] [--project-path path] " \
"[binary_path] [script_path] ..."
parser = argparse.ArgumentParser(prog="pyhidra", usage=usage)
parser.add_argument(
"-v",
"--verbose",
Expand All @@ -121,9 +158,15 @@ def _get_parser():
help="Creates a shortcut that can be pinned to the taskbar (Windows only)"
)
parser.add_argument(
"script_path | binary_path",
metavar="script | binary",
action=PathAction
"binary_path",
action=PathAction,
help="Optional binary path"
)
parser.add_argument(
"script_path",
action=PathAction,
help="Headless script path. The script must have a .py extension. " \
"If a script is not provided, pyhidra will drop into a repl."
)
parser.add_argument(
"script_args",
Expand Down Expand Up @@ -153,7 +196,8 @@ def main():
"""
pyhidra module main function
"""
_get_parser().parse_args(namespace=PyhidraArgs()).func()
parser = _get_parser()
parser.parse_args(namespace=PyhidraArgs(parser)).func()


if __name__ == "__main__":
Expand Down
24 changes: 24 additions & 0 deletions pyhidra/gui.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,33 @@
import contextlib
import os
import platform
import sys
import warnings

from pyhidra import get_current_interpreter as _get_current_interpreter


def _gui():
if platform.system() == 'Windows':
# gui_script works like it is supposed to on windows
gui()
return

pid = os.fork()
if pid != 0:
# original process can exit
return

# close stdin, stdout and stderr so the jvm can't use the terminal
# these must be closed using os.close and not sys.stdout.close()
os.close(sys.stdin.fileno())
os.close(sys.stdout.fileno())
os.close(sys.stderr.fileno())

# run the application
gui()


def gui():
"""
Starts the Ghidra GUI
Expand Down
67 changes: 8 additions & 59 deletions pyhidra/java/plugin/PyhidraPlugin.java
Original file line number Diff line number Diff line change
@@ -1,18 +1,16 @@
package dc3.pyhidra.plugin;

import java.io.PrintWriter;
import java.util.function.Consumer;

import dc3.pyhidra.plugin.interpreter.InterpreterGhidraScript;
import ghidra.MiscellaneousPluginPackage;
import ghidra.app.plugin.core.interpreter.InterpreterPanelService;
import ghidra.app.plugin.PluginCategoryNames;
import ghidra.app.plugin.ProgramPlugin;
import ghidra.app.script.GhidraScript;
import ghidra.app.script.GhidraState;
import ghidra.framework.plugintool.PluginInfo;
import ghidra.framework.plugintool.PluginTool;
import ghidra.framework.plugintool.util.PluginStatus;
import ghidra.program.model.address.Address;
import ghidra.program.model.listing.Program;
import ghidra.program.util.ProgramLocation;
import ghidra.program.util.ProgramSelection;
Expand Down Expand Up @@ -47,9 +45,16 @@ public void init() {
initializer.accept(this);
}

@Override
protected void close() {
script.close();
super.close();
}

@Override
public void dispose() {
finalizer.run();
super.dispose();
}

@Override
Expand Down Expand Up @@ -78,60 +83,4 @@ protected void selectionChanged(ProgramSelection selection) {
protected void highlightChanged(ProgramSelection highlight) {
script.setCurrentHighlight(highlight);
}

public static class InterpreterGhidraScript extends GhidraScript {

private InterpreterGhidraScript() {
}

@Override
public void run() {
}

public Address getCurrentAddress() {
return currentAddress;
}

public ProgramLocation getCurrentLocation() {
return currentLocation;
}

public ProgramSelection getCurrentSelection() {
return currentSelection;
}

public ProgramSelection getCurrentHighlight() {
return currentHighlight;
}

public PrintWriter getWriter() {
return writer;
}

public void setCurrentProgram(Program program) {
currentProgram = program;
state.setCurrentProgram(program);
}

public void setCurrentAddress(Address address) {
currentAddress = address;
state.setCurrentAddress(address);
}

public void setCurrentLocation(ProgramLocation location) {
currentLocation = location;
currentAddress = location != null ? location.getAddress() : null;
state.setCurrentLocation(location);
}

public void setCurrentSelection(ProgramSelection selection) {
currentSelection = selection;
state.setCurrentSelection(selection);
}

public void setCurrentHighlight(ProgramSelection highlight) {
currentHighlight = highlight;
state.setCurrentHighlight(highlight);
}
}
}
3 changes: 3 additions & 0 deletions pyhidra/java/plugin/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1,4 @@
"""
Internal use only
"""
from .handler import install
3 changes: 2 additions & 1 deletion pyhidra/java/plugin/handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
from pathlib import Path

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
Expand Down Expand Up @@ -44,4 +43,6 @@ def install(launcher):
# "restart" Ghidra
launcher.layout = GhidraLauncher.initializeGhidraEnvironment()

# import it at the end so interfaces in our java code may be implemented
from pyhidra.java.plugin.plugin import PyPhidraPlugin
PyPhidraPlugin.register()
Loading

0 comments on commit a49a6fb

Please sign in to comment.