diff --git a/.devcontainer/post-create.sh b/.devcontainer/post-create.sh
old mode 100644
new mode 100755
index b9c7e8c..8b88e36
--- a/.devcontainer/post-create.sh
+++ b/.devcontainer/post-create.sh
@@ -17,8 +17,11 @@ echo $PYI_WHL_RELEASE_URLS
echo $PYI_WHL_DOWNLOAD_URL
pip install "${PYI_WHL_DOWNLOAD_URL}"
-# Download latest Ghidra Bridge
+# Install ghidra-bridge
pip install ghidra_bridge
-# Install bridge scripts
+# Install bridge scripts to local dir
python -m ghidra_bridge.install_server .ghidra_bridge
+
+# Install pyhdira
+pip install pyhidra
\ No newline at end of file
diff --git a/README.md b/README.md
index 96923f0..75fe71e 100644
--- a/README.md
+++ b/README.md
@@ -10,8 +10,9 @@ A skeleton repo to provide a Ghidra Headless (non-GUI) Python scripting environm
- Provisions specified versions Ghidra based on `GHIDRA_VERSION` in [devcontainer.json](.devcontainer/devcontainer.json#L15-L16)
- Auto complete for Ghidra Python script setup and configured
- via pyi typings from [VDOO-Connected-Trust/ghidra-pyi-generator](https://github.com/VDOO-Connected-Trust/ghidra-pyi-generator)
-- IDE debugging over RPC
- - via [justfoxing/ghidra_bridge](https://github.com/justfoxing/ghidra_bridge)
+- IDE debugging (available from either)
+ - [justfoxing/ghidra_bridge](https://github.com/justfoxing/ghidra_bridge) over RPC
+ - [pyhidra](https://github.com/dod-cyber-crime-center/pyhidra) leveraging native CPython interpreter using [jpype](https://jpype.readthedocs.io/en/latest/)
- Demonstrates running python scripts in [various ways](#different-ways-to-run-a-ghidra-headless-script).
## About
@@ -60,18 +61,21 @@ The manual setup essentially has to mimic the following scripts:
Expand for Manual Setup Steps
-
-
1. [Install Ghidra](https://github.com/NationalSecurityAgency/ghidra/releases) yourself.
2. Update `GHIDRA_INSTALL_DIR` and other variables in [settings.json](.vscode/settings.json) with your install paths.
3. Set environment variable with `GHIDRA_VERSION`
- `export GHIDRA_VERSION=10.1.4`
-4. Install `ghidra-stubs` that match your `GHIDRA_VERSION`
+4. Setup `venv`
+ - `python3 -m venv .env`
+5. Install pip packages
- autocomplete
- - `pip install https://github.com/clearbluejar/ghidra-pyi-generator/releases/download/v1.0.3-10.1.4/ghidra_stubs-10.1.4.refs_heads_master-py2.py3-none-any.whl` or `pip install ghidra-stubs` from pypi (this is an outdated version)
+ - `ghidra-stubs` that match your `GHIDRA_VERSION`
+ - `pip install https://github.com/clearbluejar/ghidra-pyi-generator/releases/download/v1.0.3-10.1.4/ghidra_stubs-10.1.4.refs_heads_master-py2.py3-none-any.whl` or `pip install ghidra-stubs` from pypi (this is an outdated version)
- ghidra bridge
- `pip install ghidra-bridge`
- `python -m ghidra_bridge.install_server .ghidra_bridge`
+ - pyhidra
+ - `pip install pyhidra`
@@ -158,15 +162,15 @@ Step 4 runs the script on the imported binary after analysis (*-postscript*) on
There are several ways to run a Ghidra Python script.
1. Run via launch on [run_headless.py](run_headless.py).
- - The most straightforward means to run the script. It simply uses subprocess module with the correct arguments to run the sample.py.
+ - The most straightforward means to run the script. It simply uses subprocess module to call `analyzeHeadless` with the correct arguments to run the [sample.py](sample.py).
- It also creates a properties file needed to pass arguments to some Ghidra API calls.
2. Run the task `Run Current Python Script in Ghidra Jython` within [tasks.json](.vscode/tasks.json).
- To use this task make sure you have open and focused the [sample.py](sample.py).
3. Run via launch on [sample-bridge.py](sample-bridge.py) leveraging `ghidra-bridge`.
- 1. Requires the ghidra-bridge to [start prior to connecting](sample-bridge.py#L43-L49) via bridge.
- 2. Instead of properties file, [passes](sample-bridge.py#L37) `ls` argument to ghidra-bridge server.
-4. Run [sample.py](sample.py) directly in Ghidra via the GUI after copying it to the `ghidra_scripts` directory. If you are doing that, you likely don't need this repo.
-
+ - Requires the ghidra-bridge to [start prior to connecting](sample-bridge.py#L43-L49) via bridge.
+ - Instead of properties file, [passes](sample-bridge.py#L37) `ls` argument to ghidra-bridge server.
+4. Run [sample-pyhidra.py](sample-pyhidra.py) leveraging `pyhidra` (best one! It really just works with the help of `jpype`)
+5. Run [sample.py](sample.py) directly in Ghidra via the GUI after copying it to the `ghidra_scripts` directory. If you are doing that, you likely don't need this repo.
### Sample Outputs
@@ -424,6 +428,92 @@ Shutting down ghidra_bridge_server : 43841
```
+4. Run via launch on sample-pyhidra.py
+
+```terminal
+(.env) vscode ➜ /workspaces/ghidra-python-vscode-devcontainer-skeleton (main ✗) $ cd /workspaces/ghidra-python-vscode-devcontainer-skeleton ; /usr/bin/env /workspaces/ghidra-python-vscode-devcontainer-skeleton/.env/bin/python /home/vscode/.vscode-server/extensions/ms-python.python-2022.12.0/pythonFiles/lib/python/debugpy/adapter/../../debugpy/launcher 40875 -- /workspaces/ghidra-python-vscode-devcontainer-skeleton/sample-pyhidra.py
+/ghidra/Ghidra/Framework/Utility/lib/Utility.jar
+INFO Using log config file: jar:file:/ghidra/Ghidra/Framework/Generic/lib/Generic.jar!/generic.log4j.xml (LoggingInitialization)
+INFO Using log file: /home/vscode/.ghidra/.ghidra_10.1.4_PUBLIC/application.log (LoggingInitialization)
+INFO Loading user preferences: /home/vscode/.ghidra/.ghidra_10.1.4_PUBLIC/preferences (Preferences)
+INFO Class search complete (813 ms) (ClassSearcher)
+INFO Initializing SSL Context (SSLContextInitializer)
+INFO Initializing Random Number Generator... (SecureRandomFactory)
+INFO Random Number Generator initialization complete: NativePRNGNonBlocking (SecureRandomFactory)
+INFO Trust manager disabled, cacerts have not been set (ApplicationTrustManagerFactory)
+INFO Opening project: /workspaces/ghidra-python-vscode-devcontainer-skeleton/.ghidra_projects/sample_project/sample_project/sample_project (DefaultProject)
+INFO DWARF external debug information found: ExternalDebugInfo [filename=1a4999161b8b2da681b80d8bf351e40afc40ad.debug, crc=1816f651, hash=9c1a4999161b8b2da681b80d8bf351e40afc40ad] (ExternalDebugFilesService)
+INFO Unable to find DWARF information, skipping DWARF analysis (DWARFAnalyzer)
+ERROR os/linux_arm_64/decompile does not exist (DecompileProcessFactory)
+INFO Packed database cache: /tmp/vscode-Ghidra/packed-db-cache (PackedDatabaseCache)
+INFO -----------------------------------------------------
+ AARCH64 ELF PLT Thunks 0.017 secs
+ ASCII Strings 0.249 secs
+ Apply Data Archives 0.230 secs
+ Basic Constant Reference Analyzer 1.394 secs
+ Call Convention ID 0.008 secs
+ Call-Fixup Installer 0.004 secs
+ Create Address Tables 0.024 secs
+ Create Function 0.000 secs
+ DWARF 0.017 secs
+ Data Reference 0.037 secs
+ Decompiler Switch Analysis 0.164 secs
+ Demangler GNU 0.214 secs
+ Disassemble Entry Points 0.013 secs
+ Embedded Media 0.013 secs
+ External Entry References 0.000 secs
+ Function Start Search 0.106 secs
+ Function Start Search After Code 0.012 secs
+ Function Start Search After Data 0.031 secs
+ GCC Exception Handlers 0.471 secs
+ Non-Returning Functions - Discovered 0.026 secs
+ Non-Returning Functions - Known 0.019 secs
+ Reference 0.093 secs
+ Shared Return Calls 0.026 secs
+ Stack 0.069 secs
+ Subroutine References 0.036 secs
+-----------------------------------------------------
+ Total Time 3 secs
+-----------------------------------------------------
+ (AutoAnalysisManager)
+Program Info:
+Program: ls: AARCH64:LE:64:v8A_default (Sat Aug 06 02:18:37 UTC 2022)
+
+Memory layout:
+Imagebase: 0x100000
+segment_2.1 [start: 0x1048576, end: 0x1049143]
+.interp [start: 0x1049144, end: 0x1049170]
+.note.gnu.build-id [start: 0x1049172, end: 0x1049207]
+.note.ABI-tag [start: 0x1049208, end: 0x1049239]
+.gnu.hash [start: 0x1049240, end: 0x1049303]
+.dynsym [start: 0x1049304, end: 0x1052423]
+.dynstr [start: 0x1052424, end: 0x1053877]
+.gnu.version [start: 0x1053878, end: 0x1054137]
+.gnu.version_r [start: 0x1054144, end: 0x1054255]
+.rela.dyn [start: 0x1054256, end: 0x1060087]
+.rela.plt [start: 0x1060088, end: 0x1062703]
+.init [start: 0x1062704, end: 0x1062723]
+.plt [start: 0x1062736, end: 0x1064511]
+.text [start: 0x1064512, end: 0x1149231]
+.fini [start: 0x1149232, end: 0x1149247]
+.rodata [start: 0x1149248, end: 0x1168549]
+.eh_frame_hdr [start: 0x1168552, end: 0x1170795]
+.eh_frame [start: 0x1170800, end: 0x1182903]
+.init_array [start: 0x1250024, end: 0x1250031]
+.fini_array [start: 0x1250032, end: 0x1250039]
+.data.rel.ro [start: 0x1250040, end: 0x1252607]
+.dynamic [start: 0x1252608, end: 0x1253119]
+.got [start: 0x1253120, end: 0x1253351]
+.got.plt [start: 0x1253352, end: 0x1254247]
+.data [start: 0x1254248, end: 0x1254935]
+.bss [start: 0x1254936, end: 0x1259735]
+EXTERNAL [start: 0x1261568, end: 0x1262527]
+.gnu_debugaltlink [start: 0x0, end: 0x73]
+.gnu_debuglink [start: 0x0, end: 0x51]
+.shstrtab [start: 0x0, end: 0x279]
+_elfSectionHeaders [start: 0x0, end: 0x1855]
+```
+
## Ghidra Python Headless Scripting Hangups
@@ -431,4 +521,5 @@ Shutting down ghidra_bridge_server : 43841
2. In order to pass arguments to api calls like [askProgram](https://ghidra.re/ghidra_docs/api/ghidra/app/script/GhidraScript.html#askProgram(java.lang.String)) (which sets the current program being analyzed) either:
- a `.properties` file needs to exist with the same name and location as the script being run. In this case a [sample.properties](sample.properties) sets the arguments for [sample.py](sample.py).
- the args have to be passed on the command line when running `analyzeHeadless`. For [sample-bridge.py](sample-bridge.py), the args are awkwardly passed when ghidra_bridge_server [starts](sample-bridge.py#L37), as that server running within the Ghidra context is the only time analyzeHeadless is called. More details [here](https://github.com/justfoxing/ghidra_bridge#headless-analysis-context).
-3. `ghidra-bridge` has to be started and running before you [connect](sample-bridge.py#L53) to it. The bridge can be started outside of sample-bridge.py, but you won't be able to pass arguments to it if neeed. Also, `ghidra-bridge` is slow for large analysis. Its best feature is the ability to step through and inspect the sample-bridge.py script within the IDE.
\ No newline at end of file
+3. `ghidra-bridge` has to be started and running before you [connect](sample-bridge.py#L53) to it. The bridge can be started outside of sample-bridge.py, but you won't be able to pass arguments to it if neeed. Also, `ghidra-bridge` is slow for large analysis. Its best feature is the ability to step through and inspect the sample-bridge.py script within the IDE.
+4. `pyhidra` - Need to be wary of conflicting module names. As python stdlib and Ghidra have some conflicting module names (such as `pdb`), there are sometimes issues getting access to the full Ghidra Script API with `pyhidra`. Python prefers local modules and stdlib over the Java imports. This is due to [this issue](https://jpype.readthedocs.io/en/latest/userguide.html#importing-java-classes) in `jpype`.
\ No newline at end of file
diff --git a/sample-pyhidra.py b/sample-pyhidra.py
new file mode 100644
index 0000000..367b973
--- /dev/null
+++ b/sample-pyhidra.py
@@ -0,0 +1,34 @@
+import os
+import pyhidra
+
+#### Section to make autocomplete work
+try:
+ import ghidra
+ from ghidra_builtins import *
+except:
+ pass
+####
+
+PROJECT_NAME = os.getenv('PROJECT_NAME')
+PROJECT_LOCATION = os.path.join(os.getenv('GHIDRA_PROJECTS_PATH'),PROJECT_NAME)
+
+pyhidra.start(True) # setting Verbose output
+
+with pyhidra.open_program("/bin/ls", project_name=PROJECT_NAME, project_location=PROJECT_LOCATION) as flat_api:
+
+ prog = flat_api.getCurrentProgram()
+
+ print("Program Info:")
+ program_name = prog.getName()
+ creation_date = prog.getCreationDate()
+ language_id = prog.getLanguageID()
+ compiler_spec_id = prog.getCompilerSpec().getCompilerSpecID()
+ print("Program: {}: {}_{} ({})\n".format(program_name, language_id, compiler_spec_id, creation_date))
+
+ # Get info about the current program's memory layout
+ print("Memory layout:")
+ print("Imagebase: " + hex(prog.getImageBase().getOffset()))
+ for block in prog.getMemory().getBlocks():
+ start = block.getStart().getOffset()
+ end = block.getEnd().getOffset()
+ print("{} [start: 0x{}, end: 0x{}]".format(block.getName(), start, end))
\ No newline at end of file