GP-5637: New PyGhidra API

This commit is contained in:
Ryan Kurtz
2025-01-29 11:31:30 -05:00
parent 894e65589a
commit f5fd0171a2
9 changed files with 622 additions and 59 deletions

View File

@@ -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);
}
}

View File

@@ -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)));
}
}

View File

@@ -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

View File

@@ -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"
]

View 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)

View File

@@ -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()

View File

@@ -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)

View File

@@ -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

View File

@@ -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+?)=(.*)$")