Merge branch 'GP-5627_ryanmkurtz_pyghidra-projects' (Closes #8040)

This commit is contained in:
Ryan Kurtz 2025-04-30 11:22:57 -04:00
commit 5e825ebb5a
3 changed files with 53 additions and 10 deletions

View file

@ -107,10 +107,11 @@ def open_program(
language: str = None, language: str = None,
compiler: str = None, compiler: str = None,
loader: Union[str, JClass] = None, loader: Union[str, JClass] = None,
program_name: str = None program_name: str = None,
nested_project_location = True
) -> ContextManager["FlatProgramAPI"]: # type: ignore ) -> ContextManager["FlatProgramAPI"]: # type: ignore
""" """
Opens given binary path in Ghidra and returns FlatProgramAPI object. Opens given binary path (or optional program name) in Ghidra and returns FlatProgramAPI object.
:param binary_path: Path to binary file, may be None. :param binary_path: Path to binary file, may be None.
:param project_location: Location of Ghidra project to open/create. :param project_location: Location of Ghidra project to open/create.
@ -124,8 +125,13 @@ def open_program(
(Defaults to the Language's default compiler) (Defaults to the Language's default compiler)
:param loader: The `ghidra.app.util.opinion.Loader` class to use when importing the program. :param loader: The `ghidra.app.util.opinion.Loader` class to use when importing the program.
This may be either a Java class or its path. (Defaults to None) This may be either a Java class or its path. (Defaults to None)
:param program_name: The name to of the program to open in Ghidra. :param program_name: The name of the program to open in Ghidra.
(Defaults to None, which results in the name being derived from "binary_path") (Defaults to None, which results in the name being derived from "binary_path")
:param nested_project_location: If True, assumes "project_location" contains an extra nested
directory named "project_name", which contains the actual Ghidra project files/directories.
By default, PyGhidra creates Ghidra projects with this nested layout, but the standalone
Ghidra program does not. Nested project locations are True by default to maintain backwards
compatibility with older versions of PyGhidra.
:return: A Ghidra FlatProgramAPI object. :return: A Ghidra FlatProgramAPI object.
:raises ValueError: If the provided language, compiler or loader is invalid. :raises ValueError: If the provided language, compiler or loader is invalid.
:raises TypeError: If the provided loader does not implement `ghidra.app.util.opinion.Loader`. :raises TypeError: If the provided loader does not implement `ghidra.app.util.opinion.Loader`.
@ -191,6 +197,8 @@ def run_script(
lang: str = None, lang: str = None,
compiler: str = None, compiler: str = None,
loader: Union[str, JClass] = None, loader: Union[str, JClass] = None,
program_name = None,
nested_project_location = True,
*, *,
install_dir: Path = None install_dir: Path = None
): ):
@ -215,6 +223,13 @@ def run_script(
:param install_dir: The path to the Ghidra installation directory. This parameter is only :param install_dir: The path to the Ghidra installation directory. This parameter is only
used if Ghidra has not been started yet. used if Ghidra has not been started yet.
(Defaults to the GHIDRA_INSTALL_DIR environment variable) (Defaults to the GHIDRA_INSTALL_DIR environment variable)
:param program_name: The name of the program to open in Ghidra.
(Defaults to None, which results in the name being derived from "binary_path")
:param nested_project_location: If True, assumes "project_location" contains an extra nested
directory named "project_name", which contains the actual Ghidra project files/directories.
By default, PyGhidra creates Ghidra projects with this nested layout, but the standalone
Ghidra program does not. Nested project locations are True by default to maintain backwards
compatibility with older versions of PyGhidra.
:raises ValueError: If the provided language, compiler or loader is invalid. :raises ValueError: If the provided language, compiler or loader is invalid.
:raises TypeError: If the provided loader does not implement `ghidra.app.util.opinion.Loader`. :raises TypeError: If the provided loader does not implement `ghidra.app.util.opinion.Loader`.
""" """
@ -310,6 +325,12 @@ import pdb # imports Python's pdb
import pdb_ # imports Ghidra's pdb import pdb_ # imports Ghidra's pdb
``` ```
## Change History ## Change History
__2.2.0:__
* [`pyghidra.open_program()`](#pyghidraopen_program) and
[`pyghidra.run_script()`](#pyghidrarun_script) now accept a `nested_project_location` parameter
which can be set to `False` to open existing Ghidra projects that were created with the
Ghidra GUI.
__2.1.0:__ __2.1.0:__
* [`pyghidra.open_program()`](#pyghidraopen_program) now accepts a `program_name` parameter, which * [`pyghidra.open_program()`](#pyghidraopen_program) now accepts a `program_name` parameter, which
can be used to override the program name derived from the `binary_path` parameter. can be used to override the program name derived from the `binary_path` parameter.

View file

@ -13,7 +13,7 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
## ##
__version__ = "2.1.0" __version__ = "2.2.0"
# stub for documentation and typing # stub for documentation and typing
# this is mostly to hide the function parameter # this is mostly to hide the function parameter

View file

@ -82,7 +82,8 @@ def _setup_project(
language: str = None, language: str = None,
compiler: str = None, compiler: str = None,
loader: Union[str, JClass] = None, loader: Union[str, JClass] = None,
program_name: str = None program_name: str = None,
nested_project_location = True
) -> Tuple["GhidraProject", "Program"]: ) -> Tuple["GhidraProject", "Program"]:
from ghidra.base.project import GhidraProject from ghidra.base.project import GhidraProject
from java.lang import ClassLoader # type:ignore @UnresolvedImport from java.lang import ClassLoader # type:ignore @UnresolvedImport
@ -97,7 +98,8 @@ def _setup_project(
project_location = binary_path.parent project_location = binary_path.parent
if not project_name: if not project_name:
project_name = f"{binary_path.name}_ghidra" project_name = f"{binary_path.name}_ghidra"
project_location /= project_name if nested_project_location:
project_location /= project_name
if isinstance(loader, str): if isinstance(loader, str):
from java.lang import ClassNotFoundException # type:ignore @UnresolvedImport from java.lang import ClassNotFoundException # type:ignore @UnresolvedImport
@ -202,7 +204,8 @@ def open_program(
language: str = None, language: str = None,
compiler: str = None, compiler: str = None,
loader: Union[str, JClass] = None, loader: Union[str, JClass] = None,
program_name: str = None program_name: str = None,
nested_project_location = True
) -> ContextManager["FlatProgramAPI"]: # type: ignore ) -> ContextManager["FlatProgramAPI"]: # type: ignore
""" """
Opens given binary path (or optional program name) in Ghidra and returns FlatProgramAPI object. Opens given binary path (or optional program name) in Ghidra and returns FlatProgramAPI object.
@ -221,6 +224,11 @@ def open_program(
This may be either a Java class or its path. (Defaults to None) This may be either a Java class or its path. (Defaults to None)
:param program_name: The name of the program to open in Ghidra. :param program_name: The name of the program to open in Ghidra.
(Defaults to None, which results in the name being derived from "binary_path") (Defaults to None, which results in the name being derived from "binary_path")
:param nested_project_location: If True, assumes "project_location" contains an extra nested
directory named "project_name", which contains the actual Ghidra project files/directories.
By default, PyGhidra creates Ghidra projects with this nested layout, but the standalone
Ghidra program does not. Nested project locations are True by default to maintain backwards
compatibility with older versions of PyGhidra.
:return: A Ghidra FlatProgramAPI object. :return: A Ghidra FlatProgramAPI object.
:raises ValueError: If the provided language, compiler or loader is invalid. :raises ValueError: If the provided language, compiler or loader is invalid.
:raises TypeError: If the provided loader does not implement `ghidra.app.util.opinion.Loader`. :raises TypeError: If the provided loader does not implement `ghidra.app.util.opinion.Loader`.
@ -241,7 +249,8 @@ def open_program(
language, language,
compiler, compiler,
loader, loader,
program_name program_name,
nested_project_location
) )
GhidraScriptUtil.acquireBundleHostReference() GhidraScriptUtil.acquireBundleHostReference()
@ -268,6 +277,8 @@ def _flat_api(
language: str = None, language: str = None,
compiler: str = None, compiler: str = None,
loader: Union[str, JClass] = None, loader: Union[str, JClass] = None,
program_name: str = None,
nested_project_location = True,
*, *,
install_dir: Path = None install_dir: Path = None
): ):
@ -308,7 +319,9 @@ def _flat_api(
project_name, project_name,
language, language,
compiler, compiler,
loader loader,
program_name,
nested_project_location
) )
from ghidra.app.script import GhidraScriptUtil from ghidra.app.script import GhidraScriptUtil
@ -340,6 +353,8 @@ def run_script(
lang: str = None, lang: str = None,
compiler: str = None, compiler: str = None,
loader: Union[str, JClass] = None, loader: Union[str, JClass] = None,
program_name = None,
nested_project_location = True,
*, *,
install_dir: Path = None install_dir: Path = None
): ):
@ -364,10 +379,17 @@ def run_script(
:param install_dir: The path to the Ghidra installation directory. This parameter is only :param install_dir: The path to the Ghidra installation directory. This parameter is only
used if Ghidra has not been started yet. used if Ghidra has not been started yet.
(Defaults to the GHIDRA_INSTALL_DIR environment variable) (Defaults to the GHIDRA_INSTALL_DIR environment variable)
:param program_name: The name of the program to open in Ghidra.
(Defaults to None, which results in the name being derived from "binary_path")
:param nested_project_location: If True, assumes "project_location" contains an extra nested
directory named "project_name", which contains the actual Ghidra project files/directories.
By default, PyGhidra creates Ghidra projects with this nested layout, but the standalone
Ghidra program does not. Nested project locations are True by default to maintain backwards
compatibility with older versions of PyGhidra.
:raises ValueError: If the provided language, compiler or loader is invalid. :raises ValueError: If the provided language, compiler or loader is invalid.
:raises TypeError: If the provided loader does not implement `ghidra.app.util.opinion.Loader`. :raises TypeError: If the provided loader does not implement `ghidra.app.util.opinion.Loader`.
""" """
script_path = str(script_path) script_path = str(script_path)
args = binary_path, project_location, project_name, verbose, analyze, lang, compiler, loader args = binary_path, project_location, project_name, verbose, analyze, lang, compiler, loader, program_name, nested_project_location
with _flat_api(*args, install_dir=install_dir) as script: with _flat_api(*args, install_dir=install_dir) as script:
script.run(script_path, script_args) script.run(script_path, script_args)