mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2026-09-28 17:11:11 -09:00
GP-5637: New PyGhidra API
This commit is contained in:
@@ -0,0 +1,29 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
package ghidra.pyghidra;
|
||||
|
||||
import ghidra.framework.data.DefaultProjectData;
|
||||
import ghidra.framework.main.AppInfo;
|
||||
import ghidra.framework.model.ProjectData;
|
||||
import ghidra.framework.project.DefaultProject;
|
||||
|
||||
public class PyGhidraProject extends DefaultProject {
|
||||
|
||||
public PyGhidraProject(PyGhidraProjectManager projectManager, ProjectData projectData) {
|
||||
super(projectManager, (DefaultProjectData) projectData);
|
||||
AppInfo.setActiveProject(this);
|
||||
}
|
||||
}
|
||||
@@ -17,7 +17,7 @@ package ghidra.pyghidra.interpreter;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
|
||||
import ghidra.app.script.*;
|
||||
import ghidra.app.script.GhidraScript;
|
||||
import ghidra.program.model.address.Address;
|
||||
import ghidra.program.model.listing.Program;
|
||||
import ghidra.program.util.ProgramLocation;
|
||||
@@ -83,8 +83,4 @@ public final class InterpreterGhidraScript extends GhidraScript {
|
||||
currentHighlight = highlight;
|
||||
state.setCurrentHighlight(highlight);
|
||||
}
|
||||
|
||||
public void set(GhidraState state, PrintWriter writer) {
|
||||
set(state, new ScriptControls(writer, writer, new InterpreterTaskMonitor(writer)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,33 +21,28 @@ instructions in this document focus on this type of usage.
|
||||
|
||||
To install the PyGhidra Python library:
|
||||
1. Download and install
|
||||
[Ghidra 11.3 or later](https://github.com/NationalSecurityAgency/ghidra/releases) to a desired
|
||||
[Ghidra 12.0 or later](https://github.com/NationalSecurityAgency/ghidra/releases) to a desired
|
||||
location.
|
||||
2. Set the `GHIDRA_INSTALL_DIR` environment variable to point to the directory where Ghidra is
|
||||
installed.
|
||||
3. Install PyGhidra:
|
||||
2. Install PyGhidra:
|
||||
* Online: `pip install pyghidra`
|
||||
* Offline: `python3 -m pip install --no-index -f
|
||||
<GhidraInstallDir>/Ghidra/Features/PyGhidra/pypkg/dist pyghidra`
|
||||
|
||||
Optionally, you can also install the Ghidra type stubs to improve your development experience
|
||||
(assuming your Python editor supports it). The type stubs module is specific to each version of
|
||||
Ghidra:
|
||||
* Online: `pip install ghidra-stubs==<version>`
|
||||
* Offline: `python3 -m pip install --no-index -f <GhidraInstallDir>/docs/ghidra_stubs ghidra-stubs`
|
||||
3. Optionally install the Ghidra type stubs to improve your development experience (assuming your
|
||||
Python editor supports it). The type stubs module is specific to each version of Ghidra:
|
||||
* Online: `pip install ghidra-stubs==<version>`
|
||||
* Offline: `python3 -m pip install --no-index -f <GhidraInstallDir>/docs/ghidra_stubs ghidra-stubs`
|
||||
4. Optionally point PyGhidra at your Ghidra installation by setting the `GHIDRA_INSTALL_DIR`
|
||||
environment variable. If not set, PyGhidra will point itself at the last used installation of
|
||||
Ghidra. Alternatively, you can point PyGhidra at a Ghidra installation with
|
||||
`pyghidra.start(install_dir=<GhidraInstallDir>)` (see below).
|
||||
|
||||
## API
|
||||
The current version of PyGhidra inherits an API from the original "Pyhidra" project that provides an
|
||||
excellent starting point for interacting with a Ghidra installation. __NOTE:__ These functions are
|
||||
subject to change in the future as more thought and feedback is collected on PyGhidra's role in the
|
||||
greater Ghidra ecosystem:
|
||||
The current version of PyGhidra introduces many new API methods with the goal of making the most
|
||||
common Ghidra tasks quick and easy, such as opening a project, getting a program, running a
|
||||
GhidraScript, etc. The inherited API from the original "Pyhidra" project is still available, but at
|
||||
this point it will only receive bug fixes.
|
||||
|
||||
### pyghidra.start()
|
||||
To get a raw connection to Ghidra use the `start()` function. This will setup a JPype connection and
|
||||
initialize Ghidra in headless mode, which will allow you to directly import `ghidra` and `java`.
|
||||
|
||||
__NOTE:__ No projects or programs get setup in this mode.
|
||||
|
||||
```python
|
||||
def start(verbose=False, *, install_dir: Path = None) -> "PyGhidraLauncher":
|
||||
"""
|
||||
@@ -60,23 +55,7 @@ def start(verbose=False, *, install_dir: Path = None) -> "PyGhidraLauncher":
|
||||
"""
|
||||
```
|
||||
|
||||
#### Example:
|
||||
```python
|
||||
import pyghidra
|
||||
pyghidra.start()
|
||||
|
||||
import ghidra
|
||||
from ghidra.app.util.headless import HeadlessAnalyzer
|
||||
from ghidra.program.flatapi import FlatProgramAPI
|
||||
from ghidra.base.project import GhidraProject
|
||||
from java.lang import String
|
||||
|
||||
# do things
|
||||
```
|
||||
|
||||
### pyghidra.started()
|
||||
To check to see if PyGhidra has been started, use the `started()` function.
|
||||
|
||||
```python
|
||||
def started() -> bool:
|
||||
"""
|
||||
@@ -84,14 +63,256 @@ def started() -> bool:
|
||||
"""
|
||||
```
|
||||
|
||||
#### Example:
|
||||
### pyghidra.open_project()
|
||||
```python
|
||||
import pyghidra
|
||||
def open_project(
|
||||
path: Union[str, Path],
|
||||
name: str,
|
||||
create: bool = False
|
||||
) -> "Project": # type: ignore
|
||||
"""
|
||||
Opens the Ghidra project at the given location, optionally creating it if it doesn't exist.
|
||||
|
||||
if pyghidra.started():
|
||||
...
|
||||
:param path: Path of Ghidra project parent directory.
|
||||
:param name: Name of Ghidra project to open/create.
|
||||
:param create: Whether to create the project if it doesn't exist
|
||||
:return: A Ghidra "Project" object.
|
||||
:raises FileNotFoundError: If the project to open was not found and it shouldn't be created.
|
||||
"""
|
||||
```
|
||||
|
||||
### pyghidra.open_filesystem()
|
||||
```python
|
||||
def open_filesystem(
|
||||
path: Union[str, Path]
|
||||
) -> "GFileSystem":
|
||||
"""
|
||||
Opens a filesystem in Ghidra.
|
||||
|
||||
:param path: Path of filesystem to open in Ghidra.
|
||||
:return: A Ghidra "GFileSystem" object.
|
||||
:raises ValueError: If the filesystem to open is not supported by Ghidra.
|
||||
"""
|
||||
```
|
||||
|
||||
### pyghidra.consume_program()
|
||||
```python
|
||||
def consume_program(
|
||||
project: "Project",
|
||||
path: Union[str, Path],
|
||||
consumer: Any = None
|
||||
) -> Tuple["Program", "Object"]:
|
||||
"""
|
||||
Gets the Ghidra program from the given project with the given project path. The returned program
|
||||
must be manually released when it is no longer needed.
|
||||
|
||||
:param project: The Ghidra project that has the program.
|
||||
:param path: The project path of the program (should start with "/")
|
||||
:param consumer: An optional reference to the Java object "consuming" the returned program, used
|
||||
to ensure the underlying DomainObject is only closed when every consumer is done with it. If
|
||||
a consumer is not provided, one will be generated by this function.
|
||||
:return: A 2-element tuple containing the program and a consumer object that must be used to
|
||||
release the program when finished with it (i.e., program.release(consumer). If a consumer
|
||||
object was provided, the same consumer object is returned. Otherwise, a new consumer object
|
||||
is created and returned.
|
||||
:raises FileNotFoundError: If the path does not exist in the project.
|
||||
:raises TypeError: If the path in the project exists but is not a Program.
|
||||
"""
|
||||
```
|
||||
|
||||
### pyghidra.program_context()
|
||||
```python
|
||||
@contextlib.contextmanager
|
||||
def program_context(
|
||||
project: "Project",
|
||||
path: Union[str, Path],
|
||||
) -> "Program":
|
||||
"""
|
||||
Gets the Ghidra program from the given project with the given project path. The returned
|
||||
program's resource cleanup is performed by a context manager.
|
||||
|
||||
:param project: The Ghidra project that has the program.
|
||||
:param path: The project path of the program (should start with "/").
|
||||
:return: The Ghidra program.
|
||||
:raises FileNotFoundError: If the path does not exist in the project.
|
||||
:raises TypeError: If the path in the project exists but is not a Program.
|
||||
"""
|
||||
```
|
||||
|
||||
### pyghidra.analyze()
|
||||
```python
|
||||
def analyze(program: "Program"):
|
||||
"""
|
||||
Analyzes the given program.
|
||||
|
||||
:param program: The Ghidra program to analyze.
|
||||
"""
|
||||
```
|
||||
|
||||
### pyghidra.ghidra_script()
|
||||
```python
|
||||
def ghidra_script(
|
||||
path: Union[str, Path],
|
||||
project: "Project",
|
||||
program: "Program" = None,
|
||||
echo_stdout = True,
|
||||
echo_stderr = True
|
||||
) -> Tuple[str, str]:
|
||||
"""
|
||||
Runs any type of GhidraScript (Java, PyGhidra, Jython, etc).
|
||||
|
||||
:param path: The GhidraScript's path.
|
||||
:param project: The Ghidra project to run the GhidraScript in.
|
||||
:param program: An optional Ghidra program that the GhidraScript will see as its "currentProgram".
|
||||
:param echo_stdout: Whether or not to echo the GhidraScript's standard output.
|
||||
:param echo_stderr: Whether or not to echo the GhidraScript's standard error.
|
||||
:return: A 2 element tuple consisting of the GhidraScript's standard output and standard error.
|
||||
"""
|
||||
```
|
||||
|
||||
### pyghidra.transaction()
|
||||
```python
|
||||
@contextlib.contextmanager
|
||||
def transaction(
|
||||
program: "Program",
|
||||
description: str = "Unnamed Transaction"
|
||||
):
|
||||
"""
|
||||
Creates a context for running a Ghidra transaction.
|
||||
|
||||
:param program: The Ghidra program that will be affected.
|
||||
:param description: The transaction description
|
||||
:return: The transaction ID.
|
||||
"""
|
||||
```
|
||||
|
||||
### pyghidra.analysis_properties()
|
||||
```python
|
||||
def analysis_properties(program: "Program") -> "Options":
|
||||
"""
|
||||
Convenience function to get the Ghidra "Program.ANALYSIS_PROPERTIES" options.
|
||||
|
||||
:return: the Ghidra "Program.ANALYSIS_PROPERTIES" options.
|
||||
"""
|
||||
```
|
||||
|
||||
### pyghidra.program_info()
|
||||
```python
|
||||
def program_info(program: "Program") -> "Options":
|
||||
"""
|
||||
Convenience function to get the Ghidra "Program.PROGRAM_INFO" options.
|
||||
|
||||
:return: the Ghidra "Program.PROGRAM_INFO" options.
|
||||
"""
|
||||
```
|
||||
|
||||
### pyghidra.program_loader()
|
||||
```python
|
||||
def program_loader() -> "ProgramLoader.Builder":
|
||||
"""
|
||||
Convenience function to get a Ghidra "ProgramLoader.Builder" object.
|
||||
|
||||
:return: A Ghidra "ProgramLoader.Builder" object.
|
||||
"""
|
||||
```
|
||||
|
||||
### pyghidra.dummy_monitor()
|
||||
```python
|
||||
def dummy_monitor() -> "TaskMonitor":
|
||||
"""
|
||||
Convenience function to get the Ghidra "TaskMonitor.DUMMY" object.
|
||||
|
||||
:return: The Ghidra "TaskMonitor.DUMMY" object.
|
||||
"""
|
||||
```
|
||||
|
||||
### pyghidra.walk_project()
|
||||
```python
|
||||
def walk_project(
|
||||
project: "Project",
|
||||
callback: Callable[["DomainFile"], None],
|
||||
start: Union[str, Path] = "/",
|
||||
file_filter: Callable[["DomainFile"], bool] = lambda _f: True
|
||||
):
|
||||
"""
|
||||
Walks the the given Ghidra project, calling the provided function when each domain file is
|
||||
encountered.
|
||||
|
||||
:param project: The Ghidra project to walk.
|
||||
:param callback: The callback to process each domain file.
|
||||
:param start: An optional starting project folder path.
|
||||
:param file_filter: A filter used to limit what domain files get processed.
|
||||
:raises FileNotFoundError: If the starting folder is not found in the project.
|
||||
"""
|
||||
```
|
||||
|
||||
### pyghidra.walk_programs()
|
||||
```python
|
||||
def walk_programs(
|
||||
project: "Project",
|
||||
callback: Callable[["DomainFile", "Program"], None],
|
||||
start: Union[str, Path] = "/",
|
||||
program_filter: Callable[["DomainFile", "Program"], bool] = lambda _f, _p: True
|
||||
):
|
||||
"""
|
||||
Walks the the given Ghidra project, calling the provided function when each program is
|
||||
encountered. Non-programs in the project are skipped.
|
||||
|
||||
:param project: The Ghidra project to walk.
|
||||
:param callback: The callback to process each program.
|
||||
:param start: An optional starting project folder path.
|
||||
:param program_filter: A filter used to limit what programs get processed.
|
||||
:raises FileNotFoundError: If the starting folder is not found in the project.
|
||||
"""
|
||||
```
|
||||
|
||||
## Example
|
||||
The following example, while not very useful, showcases much of the API:
|
||||
```python
|
||||
import os, jpype, pyghidra
|
||||
pyghidra.start()
|
||||
|
||||
# Open/create a project
|
||||
with pyghidra.open_project(os.environ["GHIDRA_PROJECT_DIR"], "ExampleProject", create=True) as project:
|
||||
|
||||
# Walk a Ghidra release zip file, load every decompiler binary, and save them to the project
|
||||
with pyghidra.open_filesystem(f"{os.environ['DOWNLOADS_DIR']}/ghidra_11.4_PUBLIC_20250620.zip") as fs:
|
||||
loader = pyghidra.program_loader().project(project)
|
||||
for f in fs.files(lambda f: "os/" in f.path and f.name.startswith("decompile")):
|
||||
loader.source(f.getFSRL()).projectFolderPath("/" + f.parentFile.name)
|
||||
with loader.load() as load_results:
|
||||
load_results.save(pyghidra.dummy_monitor())
|
||||
|
||||
# Analyze the windows decompiler program
|
||||
with pyghidra.program_context(project, "/win_x86_64/decompile.exe") as program:
|
||||
analysis_props = pyghidra.analysis_properties(program)
|
||||
with pyghidra.transaction(program):
|
||||
analysis_props.setBoolean("Non-Returning Functions - Discovered", False)
|
||||
pyghidra.analyze(program)
|
||||
program.save("Analyzed", pyghidra.dummy_monitor())
|
||||
|
||||
# Walk the project and set a propery in each decompiler program
|
||||
def set_property(domain_file, program):
|
||||
with pyghidra.transaction(program):
|
||||
program_info = pyghidra.program_info(program)
|
||||
program_info.setString("PyGhidra Property", "Set by PyGhidra!")
|
||||
program.save("Setting property", pyghidra.dummy_monitor())
|
||||
pyghidra.walk_programs(project, set_property, program_filter=lambda f, p: p.name.startswith("decompile"))
|
||||
|
||||
# Load some bytes as a new program
|
||||
ByteArrayCls = jpype.JArray(jpype.JByte)
|
||||
my_bytes = ByteArrayCls(b"\xaa\xbb\xcc\xdd\xee\xff")
|
||||
loader = pyghidra.program_loader().project(project).source(my_bytes).name("my_bytes")
|
||||
loader.loaders("BinaryLoader").language("DATA:LE:64:default")
|
||||
with loader.load() as load_results:
|
||||
load_results.save(pyghidra.dummy_monitor())
|
||||
|
||||
# Run a GhidraScript
|
||||
pyghidra.ghidra_script(f"{os.environ['GHIDRA_SCRIPTS_DIR']}/HelloWorldScript.java", project)
|
||||
```
|
||||
|
||||
## Legacy API
|
||||
|
||||
### pyghidra.open_program()
|
||||
To have PyGhidra setup a binary file for you, use the `open_program()` function. This will setup a
|
||||
Ghidra project and import the given binary file as a program for you.
|
||||
@@ -325,6 +546,10 @@ import pdb # imports Python's pdb
|
||||
import pdb_ # imports Ghidra's pdb
|
||||
```
|
||||
## Change History
|
||||
__3.0.0:__
|
||||
* Introduced many new functions to the PyGhidra API. PyGhidra 3.0.0 requires Ghidra 12.0 or later
|
||||
to run.
|
||||
|
||||
__2.2.1:__
|
||||
* PyGhidra now launches with the current working directory removed from `sys.path` to prevent
|
||||
the potential for importing invalid modules from random `ghidra/` or `java/` directories that may
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
##
|
||||
__version__ = "2.2.1"
|
||||
__version__ = "3.0.0"
|
||||
|
||||
# stub for documentation and typing
|
||||
# this is mostly to hide the function parameter
|
||||
@@ -55,6 +55,9 @@ debug_callback = _debug_callback
|
||||
|
||||
# Expose API
|
||||
from pyghidra.core import run_script, start, started, open_program
|
||||
from pyghidra.api import open_project, open_filesystem, consume_program, program_context, analyze
|
||||
from pyghidra.api import ghidra_script, transaction, analysis_properties, program_info
|
||||
from pyghidra.api import program_loader, dummy_monitor, walk_project, walk_programs
|
||||
from pyghidra.launcher import DeferredPyGhidraLauncher, GuiPyGhidraLauncher, HeadlessPyGhidraLauncher
|
||||
from pyghidra.script import get_current_interpreter
|
||||
from pyghidra.version import ApplicationInfo, ExtensionDetails
|
||||
@@ -63,5 +66,8 @@ from pyghidra.version import ApplicationInfo, ExtensionDetails
|
||||
__all__ = [
|
||||
"debug_callback", "get_current_interpreter", "open_program", "run_script", "start",
|
||||
"started", "ApplicationInfo", "DeferredPyGhidraLauncher", "ExtensionDetails",
|
||||
"GuiPyGhidraLauncher", "HeadlessPyGhidraLauncher"
|
||||
"GuiPyGhidraLauncher", "HeadlessPyGhidraLauncher", "open_project", "open_filesystem",
|
||||
"consume_program", "program_conext", "analyze", "ghidra_script", "transaction",
|
||||
"analysis_properties", "program_info", "program_loader", "dummy_monitor", "walk_project",
|
||||
"walk_programs"
|
||||
]
|
||||
|
||||
313
Ghidra/Features/PyGhidra/src/main/py/src/pyghidra/api.py
Normal file
313
Ghidra/Features/PyGhidra/src/main/py/src/pyghidra/api.py
Normal file
@@ -0,0 +1,313 @@
|
||||
## ###
|
||||
# IP: GHIDRA
|
||||
#
|
||||
# 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
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# 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 sys
|
||||
import contextlib
|
||||
from typing import Union, TYPE_CHECKING, Tuple, Callable, Any
|
||||
|
||||
from pyghidra.converters import * # pylint: disable=wildcard-import, unused-wildcard-import
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ghidra.program.model.listing import Program
|
||||
from ghidra.program.util import GhidraProgramUtilities
|
||||
from ghidra.framework.model import Project, DomainFile
|
||||
from ghidra.framework.options import Options
|
||||
from ghidra.formats.gfilesystem import GFileSystem
|
||||
from ghidra.formats.gfilesystem import FSUtilities
|
||||
from ghidra.util.task import TaskMonitor
|
||||
from ghidra.app.script import GhidraScript
|
||||
from ghidra.app.util.importer import ProgramLoader
|
||||
from generic.jar import ResourceFile
|
||||
from java.lang import Object # type:ignore @UnresolvedImport
|
||||
|
||||
def open_project(
|
||||
path: Union[str, Path],
|
||||
name: str,
|
||||
create: bool = False
|
||||
) -> "Project": # type: ignore
|
||||
"""
|
||||
Opens the Ghidra project at the given location, optionally creating it if it doesn't exist.
|
||||
|
||||
:param path: Path of Ghidra project parent directory.
|
||||
:param name: Name of Ghidra project to open/create.
|
||||
:param create: Whether to create the project if it doesn't exist
|
||||
:return: A Ghidra "Project" object.
|
||||
:raises FileNotFoundError: If the project to open was not found and it shouldn't be created.
|
||||
"""
|
||||
from ghidra.framework.model import ProjectLocator
|
||||
from ghidra.pyghidra import PyGhidraProjectManager
|
||||
|
||||
projectLocator = ProjectLocator(path, name);
|
||||
projectManager = PyGhidraProjectManager()
|
||||
if projectLocator.exists():
|
||||
return projectManager.openProject(projectLocator, False, True);
|
||||
elif create:
|
||||
return projectManager.createProject(projectLocator, None, True)
|
||||
raise FileNotFoundError(f'Project "{name}" not found at "{path}"!')
|
||||
|
||||
def open_filesystem(
|
||||
path: Union[str, Path]
|
||||
) -> "GFileSystem":
|
||||
"""
|
||||
Opens a filesystem in Ghidra.
|
||||
|
||||
:param path: Path of filesystem to open in Ghidra.
|
||||
:return: A Ghidra "GFileSystem" object.
|
||||
:raises ValueError: If the filesystem to open is not supported by Ghidra.
|
||||
"""
|
||||
from java.io import File # type:ignore @UnresolvedImport
|
||||
from ghidra.formats.gfilesystem import FileSystemService
|
||||
|
||||
service = FileSystemService.getInstance()
|
||||
fsrl = service.getLocalFS().getLocalFSRL(File(path))
|
||||
fs = service.openFileSystemContainer(fsrl, dummy_monitor())
|
||||
if fs is None:
|
||||
raise ValueError(f'"{fsrl}" is not a supported GFileSystem!')
|
||||
return fs
|
||||
|
||||
def consume_program(
|
||||
project: "Project",
|
||||
path: Union[str, Path],
|
||||
consumer: Any = None
|
||||
) -> Tuple["Program", "Object"]:
|
||||
"""
|
||||
Gets the Ghidra program from the given project with the given project path. The returned program
|
||||
must be manually released when it is no longer needed.
|
||||
|
||||
:param project: The Ghidra project that has the program.
|
||||
:param path: The project path of the program (should start with "/")
|
||||
:param consumer: An optional reference to the Java object "consuming" the returned program, used
|
||||
to ensure the underlying DomainObject is only closed when every consumer is done with it. If
|
||||
a consumer is not provided, one will be generated by this function.
|
||||
:return: A 2-element tuple containing the program and a consumer object that must be used to
|
||||
release the program when finished with it (i.e., program.release(consumer). If a consumer
|
||||
object was provided, the same consumer object is returned. Otherwise, a new consumer object
|
||||
is created and returned.
|
||||
:raises FileNotFoundError: If the path does not exist in the project.
|
||||
:raises TypeError: If the path in the project exists but is not a Program.
|
||||
"""
|
||||
from ghidra.program.model.listing import Program
|
||||
from java.lang import Object # type:ignore @UnresolvedImport
|
||||
if consumer is None:
|
||||
consumer = Object()
|
||||
project_data = project.getProjectData()
|
||||
df = project_data.getFile(path)
|
||||
if df is None:
|
||||
raise FileNotFoundError(f'"{path}" does not exist in the Project')
|
||||
dobj = df.getDomainObject(consumer, True, False, dummy_monitor())
|
||||
program_cls = Program.class_
|
||||
if not program_cls.isAssignableFrom(dobj.getClass()):
|
||||
dobj.release(consumer)
|
||||
raise TypeError(f'"{path}" exists but is not a Program')
|
||||
return dobj, consumer
|
||||
|
||||
@contextlib.contextmanager
|
||||
def program_context(
|
||||
project: "Project",
|
||||
path: Union[str, Path],
|
||||
) -> "Program":
|
||||
"""
|
||||
Gets the Ghidra program from the given project with the given project path. The returned
|
||||
program's resource cleanup is performed by a context manager.
|
||||
|
||||
:param project: The Ghidra project that has the program.
|
||||
:param path: The project path of the program (should start with "/").
|
||||
:return: The Ghidra program.
|
||||
:raises FileNotFoundError: If the path does not exist in the project.
|
||||
:raises TypeError: If the path in the project exists but is not a Program.
|
||||
"""
|
||||
program, consumer = consume_program(project, path)
|
||||
try:
|
||||
yield program
|
||||
finally:
|
||||
program.release(consumer)
|
||||
|
||||
def analyze(program: "Program"):
|
||||
"""
|
||||
Analyzes the given program.
|
||||
|
||||
:param program: The Ghidra program to analyze.
|
||||
"""
|
||||
from ghidra.app.script import GhidraScriptUtil
|
||||
from ghidra.program.flatapi import FlatProgramAPI
|
||||
from ghidra.program.util import GhidraProgramUtilities
|
||||
with transaction(program, "Analyze"):
|
||||
GhidraScriptUtil.acquireBundleHostReference()
|
||||
try:
|
||||
FlatProgramAPI(program).analyzeAll(program)
|
||||
GhidraProgramUtilities.markProgramAnalyzed(program)
|
||||
finally:
|
||||
GhidraScriptUtil.releaseBundleHostReference()
|
||||
|
||||
def ghidra_script(
|
||||
path: Union[str, Path],
|
||||
project: "Project",
|
||||
program: "Program" = None,
|
||||
echo_stdout = True,
|
||||
echo_stderr = True
|
||||
) -> Tuple[str, str]:
|
||||
"""
|
||||
Runs any type of GhidraScript (Java, PyGhidra, Jython, etc).
|
||||
|
||||
:param path: The GhidraScript's path.
|
||||
:param project: The Ghidra project to run the GhidraScript in.
|
||||
:param program: An optional Ghidra program that the GhidraScript will see as its "currentProgram".
|
||||
:param echo_stdout: Whether or not to echo the GhidraScript's standard output.
|
||||
:param echo_stderr: Whether or not to echo the GhidraScript's standard error.
|
||||
:return: A 2 element tuple consisting of the GhidraScript's standard output and standard error.
|
||||
"""
|
||||
from generic.jar import ResourceFile
|
||||
from ghidra.app.script import GhidraScriptUtil, GhidraState, ScriptControls
|
||||
from java.io import File, PrintWriter, StringWriter # type:ignore @UnresolvedImport
|
||||
from java.lang import System # type:ignore @UnresolvedImport
|
||||
|
||||
GhidraScriptUtil.acquireBundleHostReference()
|
||||
try:
|
||||
source_file = ResourceFile(File(path))
|
||||
if not source_file.exists():
|
||||
raise TypeError(f'"{str(source_file)}" was not found')
|
||||
provider = GhidraScriptUtil.getProvider(source_file)
|
||||
if provider is None:
|
||||
raise TypeError(f'"{path}" is not a supported GhidraScript')
|
||||
script = provider.getScriptInstance(source_file, PrintWriter(System.out))
|
||||
if script is None:
|
||||
raise TypeError(f'"{str(source_file)}" was not found')
|
||||
state = GhidraState(None, project, program, None, None, None)
|
||||
stdout_string_writer = StringWriter()
|
||||
stderr_string_writer = StringWriter()
|
||||
controls = ScriptControls(
|
||||
PrintWriter(stdout_string_writer, True),
|
||||
PrintWriter(stderr_string_writer, True),
|
||||
dummy_monitor()
|
||||
)
|
||||
script.execute(state, controls)
|
||||
stdout_str = str(stdout_string_writer)
|
||||
stderr_str = str(stderr_string_writer)
|
||||
if echo_stdout:
|
||||
sys.stdout.write(stdout_str)
|
||||
sys.stdout.flush()
|
||||
if echo_stderr:
|
||||
sys.stderr.write(stderr_str)
|
||||
sys.stderr.flush()
|
||||
return stdout_str, stderr_str
|
||||
finally:
|
||||
GhidraScriptUtil.releaseBundleHostReference()
|
||||
|
||||
@contextlib.contextmanager
|
||||
def transaction(
|
||||
program: "Program",
|
||||
description: str = "Unnamed Transaction"
|
||||
):
|
||||
"""
|
||||
Creates a context for running a Ghidra transaction.
|
||||
|
||||
:param program: The Ghidra program that will be affected.
|
||||
:param description: The transaction description
|
||||
:return: The transaction ID.
|
||||
"""
|
||||
transaction_id = program.startTransaction(description)
|
||||
success = True
|
||||
try:
|
||||
yield transaction_id
|
||||
except:
|
||||
success = False
|
||||
finally:
|
||||
program.endTransaction(transaction_id, success)
|
||||
|
||||
def analysis_properties(program: "Program") -> "Options":
|
||||
"""
|
||||
Convenience function to get the Ghidra "Program.ANALYSIS_PROPERTIES" options.
|
||||
|
||||
:return: the Ghidra "Program.ANALYSIS_PROPERTIES" options.
|
||||
"""
|
||||
from ghidra.program.model.listing import Program
|
||||
return program.getOptions(Program.ANALYSIS_PROPERTIES)
|
||||
|
||||
def program_info(program: "Program") -> "Options":
|
||||
"""
|
||||
Convenience function to get the Ghidra "Program.PROGRAM_INFO" options.
|
||||
|
||||
:return: the Ghidra "Program.PROGRAM_INFO" options.
|
||||
"""
|
||||
from ghidra.program.model.listing import Program
|
||||
return program.getOptions(Program.PROGRAM_INFO)
|
||||
|
||||
def program_loader() -> "ProgramLoader.Builder":
|
||||
"""
|
||||
Convenience function to get a Ghidra "ProgramLoader.Builder" object.
|
||||
|
||||
:return: A Ghidra "ProgramLoader.Builder" object.
|
||||
"""
|
||||
from ghidra.app.util.importer import ProgramLoader
|
||||
return ProgramLoader.builder()
|
||||
|
||||
def dummy_monitor() -> "TaskMonitor":
|
||||
"""
|
||||
Convenience function to get the Ghidra "TaskMonitor.DUMMY" object.
|
||||
|
||||
:return: The Ghidra "TaskMonitor.DUMMY" object.
|
||||
"""
|
||||
from ghidra.util.task import TaskMonitor
|
||||
return TaskMonitor.DUMMY
|
||||
|
||||
def walk_project(
|
||||
project: "Project",
|
||||
callback: Callable[["DomainFile"], None],
|
||||
start: Union[str, Path] = "/",
|
||||
file_filter: Callable[["DomainFile"], bool] = lambda _f: True
|
||||
):
|
||||
"""
|
||||
Walks the the given Ghidra project, calling the provided function when each domain file is
|
||||
encountered.
|
||||
|
||||
:param project: The Ghidra project to walk.
|
||||
:param callback: The callback to process each domain file.
|
||||
:param start: An optional starting project folder path.
|
||||
:param file_filter: A filter used to limit what domain files get processed.
|
||||
:raises FileNotFoundError: If the starting folder is not found in the project.
|
||||
"""
|
||||
from ghidra.framework.model import ProjectDataUtils
|
||||
start_folder = project.projectData.getFolder(start)
|
||||
if start_folder is None:
|
||||
raise FileNotFoundError(f'Starting folder "{start}" does not exist in the Project')
|
||||
for file in ProjectDataUtils.DomainFileIterator(start_folder):
|
||||
if file_filter(file):
|
||||
callback(file)
|
||||
|
||||
def walk_programs(
|
||||
project: "Project",
|
||||
callback: Callable[["DomainFile", "Program"], None],
|
||||
start: Union[str, Path] = "/",
|
||||
program_filter: Callable[["DomainFile", "Program"], bool] = lambda _f, _p: True
|
||||
):
|
||||
"""
|
||||
Walks the the given Ghidra project, calling the provided function when each program is
|
||||
encountered. Non-programs in the project are skipped.
|
||||
|
||||
:param project: The Ghidra project to walk.
|
||||
:param callback: The callback to process each program.
|
||||
:param start: An optional starting project folder path.
|
||||
:param program_filter: A filter used to limit what programs get processed.
|
||||
:raises FileNotFoundError: If the starting folder is not found in the project.
|
||||
"""
|
||||
def process(file: "DomainFile"):
|
||||
try:
|
||||
with program_context(project, file.getPathname()) as program:
|
||||
if program_filter(file, program):
|
||||
callback(file, program)
|
||||
except TypeError:
|
||||
pass # skip over non-programs
|
||||
|
||||
walk_project(project, process, start=start)
|
||||
@@ -176,7 +176,7 @@ def _setup_script(project: "GhidraProject", program: "Program"):
|
||||
location = ProgramLocation(program, mem.getMinAddress())
|
||||
state = GhidraState(None, project, program, location, None, None)
|
||||
script = PyGhidraScript()
|
||||
script.set(state, TaskMonitor.DUMMY, PrintWriter(System.out))
|
||||
script.set(state, TaskMonitor.DUMMY, PrintWriter(System.out), PrintWriter(System.err))
|
||||
return script
|
||||
|
||||
|
||||
@@ -187,10 +187,7 @@ def _analyze_program(flat_api, program):
|
||||
GhidraScriptUtil.acquireBundleHostReference()
|
||||
try:
|
||||
flat_api.analyzeAll(program)
|
||||
if hasattr(GhidraProgramUtilities, "markProgramAnalyzed"):
|
||||
GhidraProgramUtilities.markProgramAnalyzed(program)
|
||||
else:
|
||||
GhidraProgramUtilities.setAnalyzedFlag(program, True) # @UndefinedVariable
|
||||
GhidraProgramUtilities.markProgramAnalyzed(program)
|
||||
finally:
|
||||
GhidraScriptUtil.releaseBundleHostReference()
|
||||
|
||||
|
||||
@@ -23,9 +23,11 @@ import threading
|
||||
import types
|
||||
from code import InteractiveConsole
|
||||
|
||||
from ghidra.app.script import ScriptControls
|
||||
from ghidra.framework import Application
|
||||
from ghidra.pyghidra import PyGhidraScriptProvider, PyGhidraPlugin
|
||||
from ghidra.pyghidra.interpreter import PyGhidraConsole
|
||||
from ghidra.util.task import TaskMonitor
|
||||
from java.io import BufferedReader, InputStreamReader # type:ignore @UnresolvedImport
|
||||
from java.lang import String # type:ignore @UnresolvedImport
|
||||
from java.lang import Thread as JThread # type:ignore @UnresolvedImport
|
||||
@@ -160,11 +162,12 @@ class PyConsole(InteractiveConsole):
|
||||
self._out = console.getOutWriter()
|
||||
self._err = console.getErrWriter()
|
||||
self._writer = self._out
|
||||
self._error_writer = self._err
|
||||
self._thread = None
|
||||
self._interact_thread = None
|
||||
self._script = self.locals._script
|
||||
state = self._script.getState()
|
||||
self._script.set(state, self._out)
|
||||
self._script.set(state, ScriptControls(console, TaskMonitor.DUMMY))
|
||||
self._state = ConsoleState.RESET
|
||||
self._completer = PythonCodeCompleter(self)
|
||||
|
||||
|
||||
@@ -205,12 +205,6 @@ class PyGhidraScript(dict):
|
||||
def get_static_view(self):
|
||||
return _StaticMap(self)
|
||||
|
||||
def set(self, state, monitor, writer):
|
||||
"""
|
||||
see GhidraScript.set
|
||||
"""
|
||||
self._script.set(state, monitor, writer)
|
||||
|
||||
def run(self, script_path: str = None, script_args: List[str] = None):
|
||||
"""
|
||||
Run this GhidraScript
|
||||
|
||||
@@ -19,7 +19,7 @@ from pathlib import Path
|
||||
import re
|
||||
|
||||
|
||||
MINIMUM_GHIDRA_VERSION = "11.2"
|
||||
MINIMUM_GHIDRA_VERSION = "12.0"
|
||||
_APPLICATION_PATTERN = re.compile(r"^application\.(\S+?)=(.*)$")
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user