ghidra-decomp 0.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,2 @@
1
+ # Prevent Git from mangling binary test fixtures
2
+ tests/fixtures/sample binary
@@ -0,0 +1,32 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ branches: [main]
8
+
9
+ jobs:
10
+ lint:
11
+ runs-on: ubuntu-latest
12
+ steps:
13
+ - uses: actions/checkout@v4
14
+ - uses: actions/setup-python@v5
15
+ with:
16
+ python-version: "3.12"
17
+ - run: pip install ruff
18
+ - run: ruff check src/ tests/
19
+ - run: ruff format --check src/ tests/
20
+
21
+ typecheck:
22
+ runs-on: ubuntu-latest
23
+ steps:
24
+ - uses: actions/checkout@v4
25
+ - uses: actions/setup-python@v5
26
+ with:
27
+ python-version: "3.12"
28
+ - run: pip install click mypy
29
+ - run: mypy src/ghidra_decomp/ --ignore-missing-imports
30
+
31
+ # Note: e2e tests require a Ghidra installation and are not run in CI.
32
+ # Run them locally with: pytest tests/
@@ -0,0 +1,19 @@
1
+ name: Publish to PyPI
2
+
3
+ on:
4
+ release:
5
+ types: [published]
6
+
7
+ jobs:
8
+ publish:
9
+ runs-on: ubuntu-latest
10
+ steps:
11
+ - uses: actions/checkout@v4
12
+ - uses: actions/setup-python@v5
13
+ with:
14
+ python-version: "3.12"
15
+ - run: pip install build
16
+ - run: python -m build
17
+ - uses: pypa/gh-action-pypi-publish@release/v1
18
+ with:
19
+ password: ${{ secrets.PYPI_API_TOKEN }}
@@ -0,0 +1,15 @@
1
+ __pycache__/
2
+ *.pyc
3
+ *.egg-info/
4
+ dist/
5
+ build/
6
+ .eggs/
7
+ *.egg
8
+ .venv/
9
+ .claude/
10
+ CLAUDE.md
11
+ *_ghidra/
12
+ .mypy_cache/
13
+ .ruff_cache/
14
+ *.so
15
+ *.dylib
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 totekuh
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.
@@ -0,0 +1,124 @@
1
+ Metadata-Version: 2.4
2
+ Name: ghidra-decomp
3
+ Version: 0.2.0
4
+ Summary: Bulk-decompile binaries via Ghidra into a browsable source tree
5
+ Project-URL: Homepage, https://github.com/totekuh/ghidra-decomp
6
+ Project-URL: Repository, https://github.com/totekuh/ghidra-decomp
7
+ Project-URL: Issues, https://github.com/totekuh/ghidra-decomp/issues
8
+ Author-email: totekuh <totekuh@protonmail.com>
9
+ License-Expression: MIT
10
+ License-File: LICENSE
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Operating System :: OS Independent
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Topic :: Security
20
+ Classifier: Topic :: Software Development :: Disassemblers
21
+ Requires-Python: >=3.10
22
+ Requires-Dist: click
23
+ Requires-Dist: pyghidra
24
+ Description-Content-Type: text/markdown
25
+
26
+ # ghidra-decomp
27
+
28
+ Bulk-decompile binaries into browsable source trees using Ghidra.
29
+
30
+ Takes a binary, runs Ghidra's decompiler on every function, and produces a directory of `.c` files and JSON indexes — ready for grep, code review, or AI-assisted analysis.
31
+
32
+ ## Why
33
+
34
+ Reverse engineering through Ghidra's GUI (or MCP) means looking at one function at a time. This tool dumps everything upfront so you can treat the binary like a normal codebase: grep for patterns, read call graphs, search strings — all without waiting for decompilation round-trips.
35
+
36
+ ## Install
37
+
38
+ Requires [Ghidra](https://ghidra-sre.org/) 11.0+ (which bundles pyghidra).
39
+
40
+ ```bash
41
+ pip install -e .
42
+ ```
43
+
44
+ Set `GHIDRA_INSTALL_DIR` to your Ghidra installation, or pass `--ghidra-path`.
45
+
46
+ ## Usage
47
+
48
+ ```bash
49
+ ghidra-decomp ./firmware.bin -o ./firmware_decomp
50
+ ```
51
+
52
+ Options:
53
+
54
+ | Flag | Description | Default |
55
+ |------|-------------|---------|
56
+ | `-o, --output` | Output directory | `<binary>_decomp/` |
57
+ | `--timeout` | Per-function decompilation timeout (seconds) | 60 |
58
+ | `--ghidra-path` | Path to Ghidra install | `$GHIDRA_INSTALL_DIR` |
59
+ | `--base-addr` | Rebase binary to this address before analysis (e.g. `0x80000000`) | none |
60
+ | `--entry` | Mark this address as the entry point and disassemble from it before analysis (e.g. `0x31000`). Useful for raw binaries. | none |
61
+ | `--language` | Force Ghidra language ID (e.g. `ARM:LE:32:v7`). Use when auto-detect fails. | auto |
62
+ | `--compiler` | Force compiler spec ID (e.g. `gcc`, `default`, `windows`). Requires `--language`. | auto |
63
+ | `--list-languages` | List all available language IDs and compiler specs, then exit. | |
64
+
65
+ For raw binaries or unknown formats, Ghidra will refuse to auto-load. Discover the right spec then pass it:
66
+
67
+ ```bash
68
+ ghidra-decomp --list-languages | grep -i arm
69
+ ghidra-decomp ./firmware.bin --language ARM:LE:32:v7 --compiler default
70
+ ```
71
+
72
+ Raw binaries typically also need a known image base and an entry point for auto-analysis to reach every function. Both are applied *before* analysis runs, so function boundaries, xrefs, and switch-table recovery all happen at the real addresses:
73
+
74
+ ```bash
75
+ ghidra-decomp ./dal_ivm.mod \
76
+ --language x86:LE:32:default \
77
+ --compiler gcc \
78
+ --base-addr 0x00031000 \
79
+ --entry 0x00031000
80
+ ```
81
+
82
+ ## Output
83
+
84
+ ```
85
+ firmware_decomp/
86
+ ├── functions/
87
+ │ ├── 00010000_main.c
88
+ │ ├── 00010234_parse_config.c
89
+ │ └── ...
90
+ ├── all_functions.c # everything in one file
91
+ ├── types.json # recovered structs, enums, unions, typedefs
92
+ ├── functions.json # function index with address ranges + signatures
93
+ ├── callgraph.json # who calls who
94
+ ├── strings.json # strings + xrefs to functions
95
+ ├── imports.json # external library functions
96
+ ├── exports.json # exported entry points
97
+ ├── symbols.json # globals, labels, data
98
+ ├── sections.json # memory map with r/w/x permissions
99
+ └── metadata.json # binary info + stats
100
+ ```
101
+
102
+ Each `.c` file includes a metadata header:
103
+
104
+ ```c
105
+ // Function: parse_config
106
+ // Address: 00010234
107
+ // Size: 284 bytes
108
+ // Calling: __stdcall
109
+ // Params: 3
110
+
111
+ void parse_config(char *param_1, int param_2, int param_3) {
112
+ ...
113
+ }
114
+ ```
115
+
116
+ ## Intended workflow
117
+
118
+ 1. **Dump** the binary with `ghidra-decomp`
119
+ 2. **Analyze** the output like source code — grep, glob, read
120
+ 3. **Write back** renames/annotations to Ghidra via MCP (separate tool)
121
+
122
+ ## License
123
+
124
+ MIT
@@ -0,0 +1,99 @@
1
+ # ghidra-decomp
2
+
3
+ Bulk-decompile binaries into browsable source trees using Ghidra.
4
+
5
+ Takes a binary, runs Ghidra's decompiler on every function, and produces a directory of `.c` files and JSON indexes — ready for grep, code review, or AI-assisted analysis.
6
+
7
+ ## Why
8
+
9
+ Reverse engineering through Ghidra's GUI (or MCP) means looking at one function at a time. This tool dumps everything upfront so you can treat the binary like a normal codebase: grep for patterns, read call graphs, search strings — all without waiting for decompilation round-trips.
10
+
11
+ ## Install
12
+
13
+ Requires [Ghidra](https://ghidra-sre.org/) 11.0+ (which bundles pyghidra).
14
+
15
+ ```bash
16
+ pip install -e .
17
+ ```
18
+
19
+ Set `GHIDRA_INSTALL_DIR` to your Ghidra installation, or pass `--ghidra-path`.
20
+
21
+ ## Usage
22
+
23
+ ```bash
24
+ ghidra-decomp ./firmware.bin -o ./firmware_decomp
25
+ ```
26
+
27
+ Options:
28
+
29
+ | Flag | Description | Default |
30
+ |------|-------------|---------|
31
+ | `-o, --output` | Output directory | `<binary>_decomp/` |
32
+ | `--timeout` | Per-function decompilation timeout (seconds) | 60 |
33
+ | `--ghidra-path` | Path to Ghidra install | `$GHIDRA_INSTALL_DIR` |
34
+ | `--base-addr` | Rebase binary to this address before analysis (e.g. `0x80000000`) | none |
35
+ | `--entry` | Mark this address as the entry point and disassemble from it before analysis (e.g. `0x31000`). Useful for raw binaries. | none |
36
+ | `--language` | Force Ghidra language ID (e.g. `ARM:LE:32:v7`). Use when auto-detect fails. | auto |
37
+ | `--compiler` | Force compiler spec ID (e.g. `gcc`, `default`, `windows`). Requires `--language`. | auto |
38
+ | `--list-languages` | List all available language IDs and compiler specs, then exit. | |
39
+
40
+ For raw binaries or unknown formats, Ghidra will refuse to auto-load. Discover the right spec then pass it:
41
+
42
+ ```bash
43
+ ghidra-decomp --list-languages | grep -i arm
44
+ ghidra-decomp ./firmware.bin --language ARM:LE:32:v7 --compiler default
45
+ ```
46
+
47
+ Raw binaries typically also need a known image base and an entry point for auto-analysis to reach every function. Both are applied *before* analysis runs, so function boundaries, xrefs, and switch-table recovery all happen at the real addresses:
48
+
49
+ ```bash
50
+ ghidra-decomp ./dal_ivm.mod \
51
+ --language x86:LE:32:default \
52
+ --compiler gcc \
53
+ --base-addr 0x00031000 \
54
+ --entry 0x00031000
55
+ ```
56
+
57
+ ## Output
58
+
59
+ ```
60
+ firmware_decomp/
61
+ ├── functions/
62
+ │ ├── 00010000_main.c
63
+ │ ├── 00010234_parse_config.c
64
+ │ └── ...
65
+ ├── all_functions.c # everything in one file
66
+ ├── types.json # recovered structs, enums, unions, typedefs
67
+ ├── functions.json # function index with address ranges + signatures
68
+ ├── callgraph.json # who calls who
69
+ ├── strings.json # strings + xrefs to functions
70
+ ├── imports.json # external library functions
71
+ ├── exports.json # exported entry points
72
+ ├── symbols.json # globals, labels, data
73
+ ├── sections.json # memory map with r/w/x permissions
74
+ └── metadata.json # binary info + stats
75
+ ```
76
+
77
+ Each `.c` file includes a metadata header:
78
+
79
+ ```c
80
+ // Function: parse_config
81
+ // Address: 00010234
82
+ // Size: 284 bytes
83
+ // Calling: __stdcall
84
+ // Params: 3
85
+
86
+ void parse_config(char *param_1, int param_2, int param_3) {
87
+ ...
88
+ }
89
+ ```
90
+
91
+ ## Intended workflow
92
+
93
+ 1. **Dump** the binary with `ghidra-decomp`
94
+ 2. **Analyze** the output like source code — grep, glob, read
95
+ 3. **Write back** renames/annotations to Ghidra via MCP (separate tool)
96
+
97
+ ## License
98
+
99
+ MIT
@@ -0,0 +1,42 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "ghidra-decomp"
7
+ dynamic = ["version"]
8
+ description = "Bulk-decompile binaries via Ghidra into a browsable source tree"
9
+ readme = "README.md"
10
+ license = "MIT"
11
+ requires-python = ">=3.10"
12
+ authors = [{name = "totekuh", email = "totekuh@protonmail.com"}]
13
+ classifiers = [
14
+ "Development Status :: 4 - Beta",
15
+ "Intended Audience :: Developers",
16
+ "License :: OSI Approved :: MIT License",
17
+ "Operating System :: OS Independent",
18
+ "Programming Language :: Python :: 3.10",
19
+ "Programming Language :: Python :: 3.11",
20
+ "Programming Language :: Python :: 3.12",
21
+ "Programming Language :: Python :: 3.13",
22
+ "Topic :: Security",
23
+ "Topic :: Software Development :: Disassemblers",
24
+ ]
25
+ dependencies = [
26
+ "pyghidra",
27
+ "click",
28
+ ]
29
+
30
+ [project.scripts]
31
+ ghidra-decomp = "ghidra_decomp.cli:main"
32
+
33
+ [project.urls]
34
+ Homepage = "https://github.com/totekuh/ghidra-decomp"
35
+ Repository = "https://github.com/totekuh/ghidra-decomp"
36
+ Issues = "https://github.com/totekuh/ghidra-decomp/issues"
37
+
38
+ [tool.hatch.build.targets.wheel]
39
+ packages = ["src/ghidra_decomp"]
40
+
41
+ [tool.hatch.version]
42
+ path = "src/ghidra_decomp/__init__.py"
@@ -0,0 +1,3 @@
1
+ """Bulk-decompile binaries via Ghidra into a browsable source tree."""
2
+
3
+ __version__ = "0.2.0"
@@ -0,0 +1,5 @@
1
+ """Allow running as python -m ghidra_decomp."""
2
+
3
+ from .cli import main
4
+
5
+ main()
@@ -0,0 +1,128 @@
1
+ """CLI entry point for ghidra-decomp."""
2
+
3
+ import click
4
+ from pathlib import Path
5
+
6
+ from .decompiler import decompile_binary, list_languages
7
+
8
+
9
+ @click.command()
10
+ @click.argument(
11
+ "binary",
12
+ type=click.Path(exists=True, dir_okay=False, path_type=Path),
13
+ required=False,
14
+ )
15
+ @click.option(
16
+ "-o",
17
+ "--output",
18
+ type=click.Path(path_type=Path),
19
+ default=None,
20
+ help="Output directory. Defaults to <binary_name>_decomp/",
21
+ )
22
+ @click.option(
23
+ "--timeout",
24
+ type=int,
25
+ default=60,
26
+ show_default=True,
27
+ help="Decompilation timeout per function (seconds).",
28
+ )
29
+ @click.option(
30
+ "--ghidra-path",
31
+ type=click.Path(exists=True, file_okay=False, path_type=Path),
32
+ envvar="GHIDRA_INSTALL_DIR",
33
+ default=None,
34
+ help="Path to Ghidra installation. Falls back to GHIDRA_INSTALL_DIR env var.",
35
+ )
36
+ @click.option(
37
+ "--base-addr",
38
+ type=str,
39
+ default=None,
40
+ help="Rebase the binary to this address before analysis (e.g. 0x80000000).",
41
+ )
42
+ @click.option(
43
+ "--entry",
44
+ type=str,
45
+ default=None,
46
+ help="Mark this address as the program entry point and disassemble from it "
47
+ "before analysis (e.g. 0x31000). Useful for raw binaries.",
48
+ )
49
+ @click.option(
50
+ "--language",
51
+ type=str,
52
+ default=None,
53
+ help="Force Ghidra language/processor ID (e.g. ARM:LE:32:v7). "
54
+ "Use when auto-detect fails. See --list-languages.",
55
+ )
56
+ @click.option(
57
+ "--compiler",
58
+ type=str,
59
+ default=None,
60
+ help="Force compiler spec ID (e.g. gcc, default, windows). "
61
+ "Requires --language. See --list-languages.",
62
+ )
63
+ @click.option(
64
+ "--list-languages",
65
+ "list_langs",
66
+ is_flag=True,
67
+ default=False,
68
+ help="List all available Ghidra language IDs and their compiler specs, then exit.",
69
+ )
70
+ def main(
71
+ binary: Path | None,
72
+ output: Path | None,
73
+ timeout: int,
74
+ ghidra_path: Path | None,
75
+ base_addr: str | None,
76
+ entry: str | None,
77
+ language: str | None,
78
+ compiler: str | None,
79
+ list_langs: bool,
80
+ ):
81
+ """Decompile BINARY into a browsable source tree.
82
+
83
+ Produces one .c file per function, plus JSON indexes for strings,
84
+ call graph, imports, and exports.
85
+ """
86
+ if list_langs:
87
+ list_languages(ghidra_path=ghidra_path)
88
+ return
89
+
90
+ if binary is None:
91
+ raise click.UsageError("Missing argument 'BINARY'.")
92
+
93
+ if compiler is not None and language is None:
94
+ raise click.UsageError("--compiler requires --language.")
95
+
96
+ binary = binary.resolve()
97
+ if output is None:
98
+ output = Path.cwd() / f"{binary.stem}_decomp"
99
+
100
+ output = output.resolve()
101
+
102
+ click.echo(f"Binary: {binary}")
103
+ click.echo(f"Output: {output}")
104
+ if base_addr is not None:
105
+ click.echo(f"Base: {base_addr}")
106
+ if entry is not None:
107
+ click.echo(f"Entry: {entry}")
108
+ if language is not None:
109
+ click.echo(f"Lang: {language}")
110
+ if compiler is not None:
111
+ click.echo(f"CSpec: {compiler}")
112
+
113
+ decompile_binary(
114
+ binary,
115
+ output,
116
+ timeout=timeout,
117
+ ghidra_path=ghidra_path,
118
+ base_addr=base_addr,
119
+ entry=entry,
120
+ language=language,
121
+ compiler=compiler,
122
+ )
123
+
124
+ click.echo(f"\nDone. Output at: {output}")
125
+
126
+
127
+ if __name__ == "__main__":
128
+ main()