From 907e5c563f2d74cee824da625190dec888147b99 Mon Sep 17 00:00:00 2001 From: Ryan Kurtz Date: Tue, 25 Mar 2025 09:28:03 -0400 Subject: [PATCH] GP-5521: pyghidra.open_program() now accepts a program_name parameter, which can be used to override the program name derived from the binary_path parameter --- .../Features/PyGhidra/src/main/py/README.md | 9 ++++++-- .../src/main/py/src/pyghidra/__init__.py | 2 +- .../PyGhidra/src/main/py/src/pyghidra/core.py | 23 ++++++++++++------- 3 files changed, 23 insertions(+), 11 deletions(-) diff --git a/Ghidra/Features/PyGhidra/src/main/py/README.md b/Ghidra/Features/PyGhidra/src/main/py/README.md index 9af09ff411..4282ba2707 100644 --- a/Ghidra/Features/PyGhidra/src/main/py/README.md +++ b/Ghidra/Features/PyGhidra/src/main/py/README.md @@ -105,7 +105,8 @@ def open_program( analyze=True, language: str = None, compiler: str = None, - loader: Union[str, JClass] = None + loader: Union[str, JClass] = None, + program_name: str = None ) -> ContextManager["FlatProgramAPI"]: # type: ignore """ Opens given binary path in Ghidra and returns FlatProgramAPI object. @@ -122,6 +123,8 @@ def open_program( (Defaults to the Language's default compiler) :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) + :param program_name: The name to of the program to open in Ghidra. + (Defaults to None, which results in the name being derived from "binary_path") :return: A Ghidra FlatProgramAPI object. :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`. @@ -306,7 +309,9 @@ import pdb # imports Python's pdb import pdb_ # imports Ghidra's pdb ``` ## Change History -__2.0.2:__ +__2.1.0:__ +* [`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. * [`pyghidra.open_program()`](#pyghidraopen_program) now properly throws an exception if the project exists and is locked. diff --git a/Ghidra/Features/PyGhidra/src/main/py/src/pyghidra/__init__.py b/Ghidra/Features/PyGhidra/src/main/py/src/pyghidra/__init__.py index e33ceab124..e988bb3b88 100644 --- a/Ghidra/Features/PyGhidra/src/main/py/src/pyghidra/__init__.py +++ b/Ghidra/Features/PyGhidra/src/main/py/src/pyghidra/__init__.py @@ -13,7 +13,7 @@ # See the License for the specific language governing permissions and # limitations under the License. ## -__version__ = "2.0.2" +__version__ = "2.1.0" # stub for documentation and typing # this is mostly to hide the function parameter diff --git a/Ghidra/Features/PyGhidra/src/main/py/src/pyghidra/core.py b/Ghidra/Features/PyGhidra/src/main/py/src/pyghidra/core.py index 9674a4d075..1fd90e73f7 100644 --- a/Ghidra/Features/PyGhidra/src/main/py/src/pyghidra/core.py +++ b/Ghidra/Features/PyGhidra/src/main/py/src/pyghidra/core.py @@ -81,13 +81,16 @@ def _setup_project( project_name: str = None, language: str = None, compiler: str = None, - loader: Union[str, JClass] = None + loader: Union[str, JClass] = None, + program_name: str = None ) -> Tuple["GhidraProject", "Program"]: from ghidra.base.project import GhidraProject from java.lang import ClassLoader # type:ignore @UnresolvedImport from ghidra.framework.model import ProjectLocator # type:ignore @UnresolvedImport if binary_path is not None: binary_path = Path(binary_path) + if program_name is None and binary_path is not None: + program_name = binary_path.name if project_location: project_location = Path(project_location) else: @@ -117,9 +120,9 @@ def _setup_project( else: project_location.mkdir(exist_ok=True, parents=True) project = GhidraProject.createProject(project_location, project_name, False) - if binary_path is not None: - if project.getRootFolder().getFile(binary_path.name): - program = project.openProgram("/", binary_path.name, False) + if program_name is not None: + if project.getRootFolder().getFile(program_name): + program = project.openProgram("/", program_name, False) # NOTE: GhidraProject.importProgram behaves differently when a loader is provided # loaderClass may not be null so we must use the correct method override @@ -146,7 +149,7 @@ def _setup_project( else: message += f"The provided language ({language}) may be invalid." raise ValueError(message) - project.saveAs(program, "/", program.getName(), True) + project.saveAs(program, "/", program_name, True) return project, program @@ -198,10 +201,11 @@ def open_program( analyze=True, language: str = None, compiler: str = None, - loader: Union[str, JClass] = None + loader: Union[str, JClass] = None, + program_name: str = None ) -> 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 project_location: Location of Ghidra project to open/create. @@ -215,6 +219,8 @@ def open_program( (Defaults to the Language's default compiler) :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) + :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") :return: A Ghidra FlatProgramAPI object. :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`. @@ -234,7 +240,8 @@ def open_program( project_name, language, compiler, - loader + loader, + program_name ) GhidraScriptUtil.acquireBundleHostReference()