Skip to content

Commit fa5064d

Browse files
committed
Add basic Ghidra-Bridge backed iPython kernel
This kernel uses the ghidra-bridge to talk to Ghidra.
0 parents  commit fa5064d

File tree

8 files changed

+279
-0
lines changed

8 files changed

+279
-0
lines changed

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2021 TorgoTorgo
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

README.md

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
# Ghidra Notebook
2+
3+
Ghidra Notebook is a [Jupyter](https://jupyter.org)
4+
[kernel](https://jupyter.readthedocs.io/en/latest/projects/kernels.html)
5+
that combines the flexibility of Ghidra's API with the Jupyter notebook
6+
system to create reproducable, scriptable notes.
7+
8+
This allows you to create notes that can also be run
9+
to show both your thinking and your work.
10+
11+
# Installation
12+
13+
This project depends on the (excellent) ghidra-bridge
14+
project. You'll need to
15+
[install Ghidra Bridge](https://github.com/justfoxing/ghidra_bridge#install-the-ghidra-bridge-package-and-server-scripts) before you can use ghidra-notebook.
16+
17+
```bash
18+
# Install the package
19+
pip3 install --user .
20+
# Install the iPython kernel
21+
python3 -m ghidra-notebook.install
22+
```
23+
24+
You can use any Jupyter notebook client you like!
25+
See [install Jupyter](https://jupyter.org/install.html)
26+
for setting these up.
27+
28+
# Usage
29+
30+
[Start the ghidra-bridge server](https://github.com/justfoxing/ghidra_bridge#start-server).
31+
When you start the Ghidra kernel in Jupyter it will connect to ghidra-bridge and you'll have
32+
access to the Ghidra API in whatever context the bridge was started in.
33+
34+
Once ghidra-bridge is started, you can launch a Jupyter Lab and select "Ghidra" to start the
35+
kernel, and connect to ghidra-bridge.
36+
37+
To run Jupyter lab:
38+
39+
```bash
40+
pip install jupyterlab
41+
jupyter-lab
42+
```
43+
44+
Or launch directly in the terminal
45+
```bash
46+
jupyter console --kernel ghidra
47+
```
48+
49+
Once you're in the Jupyter shell, try something like:
50+
51+
```python3
52+
print(currentProgram)
53+
print(currentAddress)
54+
```
55+
56+
You'll have access to the
57+
[FlatAPI](https://ghidra.re/ghidra_docs/api/ghidra/program/flatapi/FlatProgramAPI.html)
58+
along with the rest of the Ghidra API, just as you would in the normal Ghidra python terminal,
59+
except thanks to Ghidra Bridge you'll have Python3.

ghidra-notebook/__init__.py

Whitespace-only changes.

ghidra-notebook/__main__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
from ipykernel.kernelapp import IPKernelApp
2+
from .ghidra_kernel import GhidraKernel
3+
IPKernelApp.launch_instance(kernel_class=GhidraKernel)

ghidra-notebook/ghidra_kernel.py

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
#!/usr/bin/env python3
2+
from ghidra_bridge import GhidraBridge
3+
from ipykernel.kernelbase import Kernel
4+
import logging
5+
import traceback
6+
from io import StringIO
7+
from contextlib import redirect_stdout
8+
9+
class GhidraKernel(Kernel):
10+
implementation = 'ghidra_kernel'
11+
implementation_version = '1.0.0'
12+
language_info = {
13+
'name': 'Ghidra Python',
14+
'mimetype': 'text/x-python3',
15+
'file_extension': '.py',
16+
'pygments_lexer': 'python3',
17+
'codemirror_mode': 'Python',
18+
}
19+
help_links = [
20+
{'text': "Ghidra", 'url': "https://ghidra-sre.org"},
21+
{'text': "Ghidra Bridge", "url": "https://github.com/justfoxing/ghidra_bridge"},
22+
{"text": "Ghidra API", "url": "https://ghidra.re/ghidra_docs/api/ghidra/program/flatapi/FlatProgramAPI.html"}
23+
]
24+
25+
_bridge: GhidraBridge = None
26+
@property
27+
def bridge(self):
28+
if self._bridge is None:
29+
# Reset any state
30+
self._globals = {}
31+
self._locals = {}
32+
print("Connecting to the bridge")
33+
self._bridge = GhidraBridge(namespace=self._globals,
34+
interactive_mode=True,
35+
hook_import=True)
36+
return self._bridge
37+
else:
38+
return self._bridge
39+
_banner: str = None
40+
@property
41+
def banner(self):
42+
if self._banner is None:
43+
self._banner = "Ghidra Python3 Kernel"
44+
return self._banner
45+
else:
46+
return self._banner
47+
_globals: dict = {}
48+
_locals: dict = {}
49+
50+
51+
def __init__(self, **kwargs):
52+
print("Starting GhidraKernel...")
53+
Kernel.__init__(self, **kwargs)
54+
_ = self.bridge
55+
56+
def do_execute(self, code, silent, store_history=True, user_expressions=None, allow_stdin=False):
57+
_ = self.bridge
58+
out_buff = StringIO()
59+
try:
60+
with redirect_stdout(out_buff):
61+
exec(code, self._globals, self._locals)
62+
result = out_buff.getvalue()
63+
content = {
64+
'name': 'stdout',
65+
'text': str(result)
66+
}
67+
except Exception as e:
68+
result = traceback.format_exc()
69+
content = {'name': 'stderr', 'text': str(result)}
70+
self.send_response(self.iopub_socket, 'stream', content)
71+
72+
return {
73+
'status': 'ok',
74+
'execution_count': self.execution_count,
75+
'payload': [],
76+
'user_expressions': {}
77+
}
78+
79+
def do_shutdown(self, restart):
80+
# We probably don't want to _actually_
81+
# shut down the server within Ghidra
82+
# self.bridge.remote_shutdown()
83+
self._bridge = None
84+
self._locals = {}
85+
self._globals = {}
86+
87+
if restart:
88+
_ = self.bridge
89+
return {
90+
'status': 'ok',
91+
'restart': restart
92+
}
93+
if __name__ == '__main__':
94+
from ipykernel.kernelapp import IPKernelApp
95+
IPKernelApp.launch_instance(kernel_class=GhidraKernel)

ghidra-notebook/install.py

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
#!/usr/bin/env python3
2+
import argparse
3+
import sys
4+
import os
5+
import json
6+
from tempfile import TemporaryDirectory
7+
from jupyter_client.kernelspec import KernelSpecManager
8+
9+
kernel_json = {"argv": [sys.executable, "-m", "ghidra-notebook", "-f", "{connection_file}"],
10+
"display_name": "Ghidra",
11+
"language": "python3",
12+
"codemirror_mode": "python3",
13+
"env": {}
14+
}
15+
16+
17+
def install_my_kernel_spec(user=True, prefix=None):
18+
with TemporaryDirectory() as td:
19+
os.chmod(td, 0o755) # Starts off as 700, not user readable
20+
with open(os.path.join(td, 'kernel.json'), 'w') as f:
21+
json.dump(kernel_json, f, sort_keys=True)
22+
# TODO: Copy resources once they're specified
23+
24+
print('Installing IPython kernel spec')
25+
KernelSpecManager().install_kernel_spec(td, 'ghidra', user=user, prefix=prefix)
26+
27+
def _is_root():
28+
try:
29+
return os.geteuid() == 0
30+
except AttributeError:
31+
return False # assume not an admin on non-Unix platform s
32+
33+
def main(argv=None):
34+
parser = argparse.ArgumentParser(
35+
description='Install KernelSpec for Bash Kernel'
36+
)
37+
prefix_locations = parser.add_mutually_exclusive_group()
38+
39+
prefix_locations.add_argument(
40+
'--user',
41+
help='Install KernelSpec in user homedirectory',
42+
action='store_true'
43+
)
44+
prefix_locations.add_argument(
45+
'--sys-prefix',
46+
help='Install KernelSpec in sys.prefix. Useful in conda / virtualenv',
47+
action='store_true',
48+
dest='sys_prefix'
49+
)
50+
prefix_locations.add_argument(
51+
'--prefix',
52+
help='Install KernelSpec in this prefix',
53+
default=None
54+
)
55+
56+
args = parser.parse_args(argv)
57+
58+
user = False
59+
prefix = None
60+
if args.sys_prefix:
61+
prefix = sys.prefix
62+
elif args.prefix:
63+
prefix = args.prefix
64+
elif args.user or not _is_root():
65+
user = True
66+
67+
install_my_kernel_spec(user=user, prefix=prefix)
68+
69+
if __name__ == '__main__':
70+
main()

requirements.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
ghidra_bridge
2+
IPython
3+
ipykernel
4+
jupyter_client
5+
jupyter

setup.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
#!/usr/bin/env python3
2+
try:
3+
from setuptools import setup, find_packages
4+
except ImportError:
5+
from distutils.core import setup, find_packages
6+
7+
def main():
8+
setup(
9+
name='ghidra-notebook',
10+
version='1.0.0',
11+
description='Jupyter kernel for Ghidra\'s Python Interpreter',
12+
author='torgo',
13+
author_email='[email protected]',
14+
license='MIT',
15+
install_requires=[
16+
'IPython',
17+
'ipykernel',
18+
'jupyter_client',
19+
'ghidra-bridge',
20+
'jupyter',
21+
],
22+
packages=find_packages()
23+
)
24+
25+
if __name__ == '__main__':
26+
main()

0 commit comments

Comments
 (0)