declib 3.8.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.
- declib-3.8.0/LICENSE +24 -0
- declib-3.8.0/PKG-INFO +138 -0
- declib-3.8.0/README.md +105 -0
- declib-3.8.0/declib/__init__.py +9 -0
- declib-3.8.0/declib/__main__.py +190 -0
- declib-3.8.0/declib/api/__init__.py +13 -0
- declib-3.8.0/declib/api/artifact_dict.py +153 -0
- declib-3.8.0/declib/api/artifact_lifter.py +161 -0
- declib-3.8.0/declib/api/decompiler_client.py +1219 -0
- declib-3.8.0/declib/api/decompiler_interface.py +1261 -0
- declib-3.8.0/declib/api/decompiler_server.py +782 -0
- declib-3.8.0/declib/api/server_registry.py +171 -0
- declib-3.8.0/declib/api/type_definition_parser.py +201 -0
- declib-3.8.0/declib/api/type_parser.py +409 -0
- declib-3.8.0/declib/api/utils.py +31 -0
- declib-3.8.0/declib/artifacts/__init__.py +93 -0
- declib-3.8.0/declib/artifacts/artifact.py +311 -0
- declib-3.8.0/declib/artifacts/comment.py +49 -0
- declib-3.8.0/declib/artifacts/context.py +61 -0
- declib-3.8.0/declib/artifacts/decompilation.py +35 -0
- declib-3.8.0/declib/artifacts/enum.py +53 -0
- declib-3.8.0/declib/artifacts/formatting.py +27 -0
- declib-3.8.0/declib/artifacts/func.py +433 -0
- declib-3.8.0/declib/artifacts/global_variable.py +31 -0
- declib-3.8.0/declib/artifacts/patch.py +49 -0
- declib-3.8.0/declib/artifacts/segment.py +37 -0
- declib-3.8.0/declib/artifacts/stack_variable.py +50 -0
- declib-3.8.0/declib/artifacts/struct.py +184 -0
- declib-3.8.0/declib/artifacts/typedef.py +59 -0
- declib-3.8.0/declib/cli/__init__.py +3 -0
- declib-3.8.0/declib/cli/decompiler_cli.py +1487 -0
- declib-3.8.0/declib/configuration.py +184 -0
- declib-3.8.0/declib/decompiler_stubs/__init__.py +0 -0
- declib-3.8.0/declib/decompiler_stubs/angr_declib/__init__.py +4 -0
- declib-3.8.0/declib/decompiler_stubs/binja_declib/__init__.py +4 -0
- declib-3.8.0/declib/decompiler_stubs/ida_declib.py +8 -0
- declib-3.8.0/declib/decompilers/__init__.py +8 -0
- declib-3.8.0/declib/decompilers/angr/__init__.py +11 -0
- declib-3.8.0/declib/decompilers/angr/artifact_lifter.py +46 -0
- declib-3.8.0/declib/decompilers/angr/compat.py +262 -0
- declib-3.8.0/declib/decompilers/angr/interface.py +949 -0
- declib-3.8.0/declib/decompilers/binja/__init__.py +0 -0
- declib-3.8.0/declib/decompilers/binja/artifact_lifter.py +32 -0
- declib-3.8.0/declib/decompilers/binja/hooks.py +201 -0
- declib-3.8.0/declib/decompilers/binja/interface.py +795 -0
- declib-3.8.0/declib/decompilers/ghidra/__init__.py +0 -0
- declib-3.8.0/declib/decompilers/ghidra/artifact_lifter.py +60 -0
- declib-3.8.0/declib/decompilers/ghidra/compat/__init__.py +0 -0
- declib-3.8.0/declib/decompilers/ghidra/compat/headless.py +156 -0
- declib-3.8.0/declib/decompilers/ghidra/compat/imports.py +78 -0
- declib-3.8.0/declib/decompilers/ghidra/compat/state.py +54 -0
- declib-3.8.0/declib/decompilers/ghidra/compat/transaction.py +30 -0
- declib-3.8.0/declib/decompilers/ghidra/hooks.py +242 -0
- declib-3.8.0/declib/decompilers/ghidra/interface.py +1433 -0
- declib-3.8.0/declib/decompilers/ida/__init__.py +0 -0
- declib-3.8.0/declib/decompilers/ida/artifact_lifter.py +51 -0
- declib-3.8.0/declib/decompilers/ida/compat.py +2054 -0
- declib-3.8.0/declib/decompilers/ida/hooks.py +700 -0
- declib-3.8.0/declib/decompilers/ida/ida_ui.py +80 -0
- declib-3.8.0/declib/decompilers/ida/interface.py +659 -0
- declib-3.8.0/declib/logger.py +101 -0
- declib-3.8.0/declib/plugin_installer.py +259 -0
- declib-3.8.0/declib/skills/__init__.py +24 -0
- declib-3.8.0/declib/skills/decompiler/SKILL.md +316 -0
- declib-3.8.0/declib/ui/__init__.py +33 -0
- declib-3.8.0/declib/ui/qt_objects.py +146 -0
- declib-3.8.0/declib/ui/utils.py +115 -0
- declib-3.8.0/declib/ui/version.py +14 -0
- declib-3.8.0/declib.egg-info/PKG-INFO +138 -0
- declib-3.8.0/declib.egg-info/SOURCES.txt +79 -0
- declib-3.8.0/declib.egg-info/dependency_links.txt +1 -0
- declib-3.8.0/declib.egg-info/entry_points.txt +3 -0
- declib-3.8.0/declib.egg-info/requires.txt +20 -0
- declib-3.8.0/declib.egg-info/top_level.txt +1 -0
- declib-3.8.0/pyproject.toml +62 -0
- declib-3.8.0/setup.cfg +4 -0
- declib-3.8.0/tests/test_artifacts.py +199 -0
- declib-3.8.0/tests/test_cli.py +89 -0
- declib-3.8.0/tests/test_client_server.py +703 -0
- declib-3.8.0/tests/test_decompiler_cli.py +1078 -0
- declib-3.8.0/tests/test_decompilers.py +922 -0
declib-3.8.0/LICENSE
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
BSD 2-Clause License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2023, BinSync
|
|
4
|
+
|
|
5
|
+
Redistribution and use in source and binary forms, with or without
|
|
6
|
+
modification, are permitted provided that the following conditions are met:
|
|
7
|
+
|
|
8
|
+
1. Redistributions of source code must retain the above copyright notice, this
|
|
9
|
+
list of conditions and the following disclaimer.
|
|
10
|
+
|
|
11
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
12
|
+
this list of conditions and the following disclaimer in the documentation
|
|
13
|
+
and/or other materials provided with the distribution.
|
|
14
|
+
|
|
15
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
16
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
17
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
18
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
19
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
20
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
21
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
22
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
23
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
24
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
declib-3.8.0/PKG-INFO
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: declib
|
|
3
|
+
Version: 3.8.0
|
|
4
|
+
Summary: Your Only Decompiler API Lib - A generic API to script in and out of decompilers
|
|
5
|
+
License: BSD 2 Clause
|
|
6
|
+
Project-URL: Homepage, https://github.com/binsync/declib
|
|
7
|
+
Classifier: License :: OSI Approved :: BSD License
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
11
|
+
Requires-Python: >=3.10
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
License-File: LICENSE
|
|
14
|
+
Requires-Dist: toml
|
|
15
|
+
Requires-Dist: ply
|
|
16
|
+
Requires-Dist: pycparser~=3.0
|
|
17
|
+
Requires-Dist: setuptools
|
|
18
|
+
Requires-Dist: prompt_toolkit
|
|
19
|
+
Requires-Dist: tqdm
|
|
20
|
+
Requires-Dist: psutil
|
|
21
|
+
Requires-Dist: pyghidra
|
|
22
|
+
Requires-Dist: platformdirs
|
|
23
|
+
Requires-Dist: filelock
|
|
24
|
+
Requires-Dist: networkx
|
|
25
|
+
Provides-Extra: test
|
|
26
|
+
Requires-Dist: pytest; extra == "test"
|
|
27
|
+
Requires-Dist: angr; extra == "test"
|
|
28
|
+
Requires-Dist: requests; extra == "test"
|
|
29
|
+
Requires-Dist: ipdb; extra == "test"
|
|
30
|
+
Provides-Extra: ghidra
|
|
31
|
+
Requires-Dist: PySide6-Essentials!=6.7.0,>=6.4.2; extra == "ghidra"
|
|
32
|
+
Dynamic: license-file
|
|
33
|
+
|
|
34
|
+
# DecLib
|
|
35
|
+
The decompiler API that works everywhere!
|
|
36
|
+
|
|
37
|
+
DecLib is an abstracted decompiler API that enables you to write plugins/scripts that work, with minimal edit,
|
|
38
|
+
in every decompiler supported by DecLib. DecLib was originally designed to work with [BinSync](https://binsync.net), and is the backbone
|
|
39
|
+
for all BinSync based plugins.
|
|
40
|
+
As an example, with the same script, you can [redefine the types of function variables with custom structs](./examples/struct_and_variable_use.py), all in less
|
|
41
|
+
than 30 lines, in any supported decompilers.
|
|
42
|
+
|
|
43
|
+
## Install
|
|
44
|
+
```bash
|
|
45
|
+
pip install declib
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
The minimum Python version is **3.10**.
|
|
49
|
+
|
|
50
|
+
## Supported Decompilers
|
|
51
|
+
- IDA Pro: **>= 8.4** (if you have an older version, use `v1.26.0`)
|
|
52
|
+
- Binary Ninja: **>= 2.4**
|
|
53
|
+
- angr-management: **>= 9.0**
|
|
54
|
+
- Ghidra: **>= 12.0** (started in PyGhidra mode)
|
|
55
|
+
|
|
56
|
+
## Usage
|
|
57
|
+
DecLib exposes all decompiler API through the abstract class `DecompilerInterface`. The `DecompilerInterface`
|
|
58
|
+
can be used in either the default mode, which assumes a GUI, or `headless` mode. In `headless` mode, the interface will
|
|
59
|
+
start a new process using a specified decompiler.
|
|
60
|
+
|
|
61
|
+
You can find various examples using DecLib in the [examples](./examples) folder. Examples that are plugins show off
|
|
62
|
+
more of the complicated API that allows you to use an abstracted UI, artifacts, and more.
|
|
63
|
+
|
|
64
|
+
If you want a simplified command line interface (especially well-suited for LLMs), see the
|
|
65
|
+
[`decompiler` CLI guide](./docs/decompiler_cli.md).
|
|
66
|
+
|
|
67
|
+
### UI Mode (default)
|
|
68
|
+
To use the same script everywhere, use the convenience function `DecompilerInterface.discover_interface()`, which will
|
|
69
|
+
auto find the correct interface. Copy the below code into any supported decompiler and it should run without edit.
|
|
70
|
+
|
|
71
|
+
```python
|
|
72
|
+
from declib.api import DecompilerInterface
|
|
73
|
+
|
|
74
|
+
deci = DecompilerInterface.discover()
|
|
75
|
+
for addr in deci.functions:
|
|
76
|
+
function = deci.functions[addr]
|
|
77
|
+
if function.header.type == "void":
|
|
78
|
+
function.header.type = "int"
|
|
79
|
+
deci.functions[function.addr] = function
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Note that for Ghidra in UI mode you must first start it in PyGhidra mode. You can do this by going to your install dir
|
|
83
|
+
and running `./support/pyghidraRun`.
|
|
84
|
+
|
|
85
|
+
### Headless Mode
|
|
86
|
+
To use headless mode you must specify a decompiler to use. You can get the traditional interface using the following:
|
|
87
|
+
|
|
88
|
+
```python
|
|
89
|
+
from declib.api import DecompilerInterface
|
|
90
|
+
|
|
91
|
+
deci = DecompilerInterface.discover(force_decompiler="ghidra", headless=True)
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
In the case of Ghidra, you must have the environment variable `GHIDRA_INSTALL_DIR` set to the path of the Ghidra
|
|
95
|
+
installation (the place the `ghidraRun` script is located).
|
|
96
|
+
|
|
97
|
+
### Artifact Access Caveats
|
|
98
|
+
In designing the dictionaries that contain all Artifacts in a decompiler, we had a clash between ease-of-use and speed.
|
|
99
|
+
When accessing some artifacts like a `Function`, we must decompile the function. Decompiling is slow. Due to this issue
|
|
100
|
+
we slightly changed how these dictionaries work to fast accessing.
|
|
101
|
+
|
|
102
|
+
The only way to access a **full** artifact is to use the `getitem` interface of a dictionary. In practice this
|
|
103
|
+
looks like the following:
|
|
104
|
+
```python
|
|
105
|
+
for func_addr, light_func in deci.functions.items():
|
|
106
|
+
full_function = deci.function[func_addr]
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
Notice, when using the `items` function the function is `light`, meaning it does not contain stack vars and other
|
|
110
|
+
info. This also means using `keys`, `values`, or `list` on an artifact dictionary will have the same affect.
|
|
111
|
+
|
|
112
|
+
### Serializing Artifacts
|
|
113
|
+
All artifacts are serializable to the TOML and JSON formats. Serialization is done like so:
|
|
114
|
+
```python
|
|
115
|
+
from declib.artifacts import Function
|
|
116
|
+
import json
|
|
117
|
+
|
|
118
|
+
my_func = Function(name="my_func", addr=0x4000, size=0x10)
|
|
119
|
+
json_str = my_func.dumps(fmt="json")
|
|
120
|
+
loaded_dict = json.loads(json_str) # now loadable through normal JSON parsing
|
|
121
|
+
loaded_func = Function.loads(json_str, fmt="json")
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
## Sponsors
|
|
125
|
+
BinSync and its associated projects would not be possible without sponsorship.
|
|
126
|
+
In no particular order, we'd like to thank all the organizations that have previously or are currently sponsoring
|
|
127
|
+
one of the many BinSync projects.
|
|
128
|
+
|
|
129
|
+
<p align="center">
|
|
130
|
+
<img src="https://github.com/binsync/binsync/blob/main/assets/images/sponsors/nsf.png?raw=true" alt="NSF" style="height: 100px; display: inline-block; vertical-align: middle; margin-right: 40px;">
|
|
131
|
+
<br>
|
|
132
|
+
<img src="https://github.com/binsync/binsync/blob/main/assets/images/sponsors/darpa.png?raw=true" alt="DARPA" style="height: 70px; display: inline-block; vertical-align: middle; margin-right: 40px;">
|
|
133
|
+
<br>
|
|
134
|
+
<img src="https://github.com/binsync/binsync/blob/main/assets/images/sponsors/arpah.svg?raw=true" alt="ARPA-H" style="height: 50px; display: inline-block; vertical-align: middle; margin-right: 40px;">
|
|
135
|
+
<br>
|
|
136
|
+
<img src="https://github.com/binsync/binsync/blob/main/assets/images/sponsors/reveng_ai.svg?raw=true" alt="RevEng AI" style="height: 50px; display: inline-block; vertical-align: middle;">
|
|
137
|
+
</p>
|
|
138
|
+
|
declib-3.8.0/README.md
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# DecLib
|
|
2
|
+
The decompiler API that works everywhere!
|
|
3
|
+
|
|
4
|
+
DecLib is an abstracted decompiler API that enables you to write plugins/scripts that work, with minimal edit,
|
|
5
|
+
in every decompiler supported by DecLib. DecLib was originally designed to work with [BinSync](https://binsync.net), and is the backbone
|
|
6
|
+
for all BinSync based plugins.
|
|
7
|
+
As an example, with the same script, you can [redefine the types of function variables with custom structs](./examples/struct_and_variable_use.py), all in less
|
|
8
|
+
than 30 lines, in any supported decompilers.
|
|
9
|
+
|
|
10
|
+
## Install
|
|
11
|
+
```bash
|
|
12
|
+
pip install declib
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
The minimum Python version is **3.10**.
|
|
16
|
+
|
|
17
|
+
## Supported Decompilers
|
|
18
|
+
- IDA Pro: **>= 8.4** (if you have an older version, use `v1.26.0`)
|
|
19
|
+
- Binary Ninja: **>= 2.4**
|
|
20
|
+
- angr-management: **>= 9.0**
|
|
21
|
+
- Ghidra: **>= 12.0** (started in PyGhidra mode)
|
|
22
|
+
|
|
23
|
+
## Usage
|
|
24
|
+
DecLib exposes all decompiler API through the abstract class `DecompilerInterface`. The `DecompilerInterface`
|
|
25
|
+
can be used in either the default mode, which assumes a GUI, or `headless` mode. In `headless` mode, the interface will
|
|
26
|
+
start a new process using a specified decompiler.
|
|
27
|
+
|
|
28
|
+
You can find various examples using DecLib in the [examples](./examples) folder. Examples that are plugins show off
|
|
29
|
+
more of the complicated API that allows you to use an abstracted UI, artifacts, and more.
|
|
30
|
+
|
|
31
|
+
If you want a simplified command line interface (especially well-suited for LLMs), see the
|
|
32
|
+
[`decompiler` CLI guide](./docs/decompiler_cli.md).
|
|
33
|
+
|
|
34
|
+
### UI Mode (default)
|
|
35
|
+
To use the same script everywhere, use the convenience function `DecompilerInterface.discover_interface()`, which will
|
|
36
|
+
auto find the correct interface. Copy the below code into any supported decompiler and it should run without edit.
|
|
37
|
+
|
|
38
|
+
```python
|
|
39
|
+
from declib.api import DecompilerInterface
|
|
40
|
+
|
|
41
|
+
deci = DecompilerInterface.discover()
|
|
42
|
+
for addr in deci.functions:
|
|
43
|
+
function = deci.functions[addr]
|
|
44
|
+
if function.header.type == "void":
|
|
45
|
+
function.header.type = "int"
|
|
46
|
+
deci.functions[function.addr] = function
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Note that for Ghidra in UI mode you must first start it in PyGhidra mode. You can do this by going to your install dir
|
|
50
|
+
and running `./support/pyghidraRun`.
|
|
51
|
+
|
|
52
|
+
### Headless Mode
|
|
53
|
+
To use headless mode you must specify a decompiler to use. You can get the traditional interface using the following:
|
|
54
|
+
|
|
55
|
+
```python
|
|
56
|
+
from declib.api import DecompilerInterface
|
|
57
|
+
|
|
58
|
+
deci = DecompilerInterface.discover(force_decompiler="ghidra", headless=True)
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
In the case of Ghidra, you must have the environment variable `GHIDRA_INSTALL_DIR` set to the path of the Ghidra
|
|
62
|
+
installation (the place the `ghidraRun` script is located).
|
|
63
|
+
|
|
64
|
+
### Artifact Access Caveats
|
|
65
|
+
In designing the dictionaries that contain all Artifacts in a decompiler, we had a clash between ease-of-use and speed.
|
|
66
|
+
When accessing some artifacts like a `Function`, we must decompile the function. Decompiling is slow. Due to this issue
|
|
67
|
+
we slightly changed how these dictionaries work to fast accessing.
|
|
68
|
+
|
|
69
|
+
The only way to access a **full** artifact is to use the `getitem` interface of a dictionary. In practice this
|
|
70
|
+
looks like the following:
|
|
71
|
+
```python
|
|
72
|
+
for func_addr, light_func in deci.functions.items():
|
|
73
|
+
full_function = deci.function[func_addr]
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Notice, when using the `items` function the function is `light`, meaning it does not contain stack vars and other
|
|
77
|
+
info. This also means using `keys`, `values`, or `list` on an artifact dictionary will have the same affect.
|
|
78
|
+
|
|
79
|
+
### Serializing Artifacts
|
|
80
|
+
All artifacts are serializable to the TOML and JSON formats. Serialization is done like so:
|
|
81
|
+
```python
|
|
82
|
+
from declib.artifacts import Function
|
|
83
|
+
import json
|
|
84
|
+
|
|
85
|
+
my_func = Function(name="my_func", addr=0x4000, size=0x10)
|
|
86
|
+
json_str = my_func.dumps(fmt="json")
|
|
87
|
+
loaded_dict = json.loads(json_str) # now loadable through normal JSON parsing
|
|
88
|
+
loaded_func = Function.loads(json_str, fmt="json")
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## Sponsors
|
|
92
|
+
BinSync and its associated projects would not be possible without sponsorship.
|
|
93
|
+
In no particular order, we'd like to thank all the organizations that have previously or are currently sponsoring
|
|
94
|
+
one of the many BinSync projects.
|
|
95
|
+
|
|
96
|
+
<p align="center">
|
|
97
|
+
<img src="https://github.com/binsync/binsync/blob/main/assets/images/sponsors/nsf.png?raw=true" alt="NSF" style="height: 100px; display: inline-block; vertical-align: middle; margin-right: 40px;">
|
|
98
|
+
<br>
|
|
99
|
+
<img src="https://github.com/binsync/binsync/blob/main/assets/images/sponsors/darpa.png?raw=true" alt="DARPA" style="height: 70px; display: inline-block; vertical-align: middle; margin-right: 40px;">
|
|
100
|
+
<br>
|
|
101
|
+
<img src="https://github.com/binsync/binsync/blob/main/assets/images/sponsors/arpah.svg?raw=true" alt="ARPA-H" style="height: 50px; display: inline-block; vertical-align: middle; margin-right: 40px;">
|
|
102
|
+
<br>
|
|
103
|
+
<img src="https://github.com/binsync/binsync/blob/main/assets/images/sponsors/reveng_ai.svg?raw=true" alt="RevEng AI" style="height: 50px; display: inline-block; vertical-align: middle;">
|
|
104
|
+
</p>
|
|
105
|
+
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import sys
|
|
3
|
+
import logging
|
|
4
|
+
|
|
5
|
+
from declib.plugin_installer import DecLibPluginInstaller
|
|
6
|
+
|
|
7
|
+
_l = logging.getLogger(__name__)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def install():
|
|
11
|
+
DecLibPluginInstaller().install()
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def start_server(
|
|
15
|
+
socket_path=None, decompiler=None, binary_path=None, headless=False,
|
|
16
|
+
server_id=None, project_dir=None,
|
|
17
|
+
):
|
|
18
|
+
"""Start the DecompilerServer (AF_UNIX socket-based)"""
|
|
19
|
+
try:
|
|
20
|
+
from declib.api.decompiler_server import DecompilerServer
|
|
21
|
+
from declib.api.decompiler_interface import DecompilerInterface
|
|
22
|
+
|
|
23
|
+
# Configure logging
|
|
24
|
+
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
|
25
|
+
|
|
26
|
+
# Prepare interface kwargs
|
|
27
|
+
interface_kwargs = {}
|
|
28
|
+
if decompiler:
|
|
29
|
+
interface_kwargs['force_decompiler'] = decompiler
|
|
30
|
+
if binary_path:
|
|
31
|
+
interface_kwargs['binary_path'] = binary_path
|
|
32
|
+
if headless:
|
|
33
|
+
interface_kwargs['headless'] = headless
|
|
34
|
+
if project_dir:
|
|
35
|
+
interface_kwargs['project_dir'] = project_dir
|
|
36
|
+
|
|
37
|
+
# Create and start server
|
|
38
|
+
if socket_path:
|
|
39
|
+
_l.info(f"Starting AF_UNIX DecompilerServer on {socket_path}")
|
|
40
|
+
else:
|
|
41
|
+
_l.info("Starting AF_UNIX DecompilerServer with auto-generated socket path")
|
|
42
|
+
if interface_kwargs:
|
|
43
|
+
_l.info(f"Interface options: {interface_kwargs}")
|
|
44
|
+
|
|
45
|
+
with DecompilerServer(socket_path=socket_path, server_id=server_id, **interface_kwargs) as server:
|
|
46
|
+
_l.info("Server started successfully. Press Ctrl+C to stop.")
|
|
47
|
+
_l.info("Connect with: DecompilerClient.discover('unix://{}')".format(server.socket_path))
|
|
48
|
+
try:
|
|
49
|
+
server.wait_for_shutdown()
|
|
50
|
+
except KeyboardInterrupt:
|
|
51
|
+
_l.info("Shutting down server...")
|
|
52
|
+
|
|
53
|
+
except ImportError as e:
|
|
54
|
+
_l.error(f"Failed to import required modules: {e}")
|
|
55
|
+
sys.exit(1)
|
|
56
|
+
except Exception as e:
|
|
57
|
+
_l.error(f"Failed to start server: {e}")
|
|
58
|
+
sys.exit(1)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def test_client(server_url=None):
|
|
62
|
+
"""Test the DecompilerClient connection"""
|
|
63
|
+
try:
|
|
64
|
+
from declib.api.decompiler_client import DecompilerClient
|
|
65
|
+
|
|
66
|
+
# Configure logging
|
|
67
|
+
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
|
68
|
+
|
|
69
|
+
if server_url:
|
|
70
|
+
_l.info(f"Testing connection to DecompilerServer at {server_url}")
|
|
71
|
+
else:
|
|
72
|
+
_l.info("Testing connection to auto-discovered DecompilerServer")
|
|
73
|
+
|
|
74
|
+
with DecompilerClient.discover(server_url=server_url) as client:
|
|
75
|
+
_l.info(f"Successfully connected to {client.name} decompiler")
|
|
76
|
+
_l.info(f"Binary path: {client.binary_path}")
|
|
77
|
+
_l.info(f"Binary hash: {client.binary_hash}")
|
|
78
|
+
_l.info(f"Decompiler available: {client.decompiler_available}")
|
|
79
|
+
|
|
80
|
+
# Test fast artifact collections (benchmark performance)
|
|
81
|
+
import time
|
|
82
|
+
start_time = time.time()
|
|
83
|
+
functions = list(client.functions.items())
|
|
84
|
+
end_time = time.time()
|
|
85
|
+
_l.info(f"Retrieved {len(functions)} functions in {end_time - start_time:.3f}s")
|
|
86
|
+
|
|
87
|
+
start_time = time.time()
|
|
88
|
+
comments = list(client.comments.keys())
|
|
89
|
+
end_time = time.time()
|
|
90
|
+
_l.info(f"Retrieved {len(comments)} comment keys in {end_time - start_time:.3f}s")
|
|
91
|
+
|
|
92
|
+
_l.info("✅ Client test completed successfully!")
|
|
93
|
+
|
|
94
|
+
except ImportError as e:
|
|
95
|
+
_l.error(f"Failed to import required modules: {e}")
|
|
96
|
+
sys.exit(1)
|
|
97
|
+
except Exception as e:
|
|
98
|
+
_l.error(f"Client test failed: {e}")
|
|
99
|
+
sys.exit(1)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def main():
|
|
103
|
+
parser = argparse.ArgumentParser(
|
|
104
|
+
description="""
|
|
105
|
+
The DecLib Command Line Util. This is the script interface to DecLib that allows you to install and run
|
|
106
|
+
the Ghidra UI for running plugins, and start the DecompilerServer.
|
|
107
|
+
""",
|
|
108
|
+
epilog="""
|
|
109
|
+
Examples:
|
|
110
|
+
declib --install |
|
|
111
|
+
declib --server --socket-path /tmp/my_server.sock |
|
|
112
|
+
declib --server --decompiler ghidra --binary-path /path/to/binary --headless
|
|
113
|
+
"""
|
|
114
|
+
)
|
|
115
|
+
parser.add_argument(
|
|
116
|
+
"--install", action="store_true", help="""
|
|
117
|
+
Install all the DecLib plugins to every decompiler.
|
|
118
|
+
"""
|
|
119
|
+
)
|
|
120
|
+
parser.add_argument(
|
|
121
|
+
"--single-decompiler-install", nargs=2, metavar=('decompiler', 'path'), help="Install DAILA into a single decompiler. Decompiler must be one of: ida, ghidra, binja, angr."
|
|
122
|
+
)
|
|
123
|
+
parser.add_argument(
|
|
124
|
+
"--server", action="store_true", help="""
|
|
125
|
+
Start the DecompilerServer to expose DecompilerInterface APIs over AF_UNIX sockets.
|
|
126
|
+
"""
|
|
127
|
+
)
|
|
128
|
+
parser.add_argument(
|
|
129
|
+
"--server-url", help="""
|
|
130
|
+
URL of the DecompilerServer to connect to (e.g., unix:///tmp/server.sock).
|
|
131
|
+
If not specified, will auto-discover running servers.
|
|
132
|
+
"""
|
|
133
|
+
)
|
|
134
|
+
parser.add_argument(
|
|
135
|
+
"--socket-path", help="""
|
|
136
|
+
Path for the AF_UNIX socket (default: auto-generated in temp directory).
|
|
137
|
+
"""
|
|
138
|
+
)
|
|
139
|
+
parser.add_argument(
|
|
140
|
+
"--decompiler", choices=["ida", "ghidra", "binja", "angr"], help="""
|
|
141
|
+
Force a specific decompiler for the server. If not specified, auto-detection will be used.
|
|
142
|
+
"""
|
|
143
|
+
)
|
|
144
|
+
parser.add_argument(
|
|
145
|
+
"--binary-path", help="""
|
|
146
|
+
Path to the binary file to analyze (required for headless mode).
|
|
147
|
+
"""
|
|
148
|
+
)
|
|
149
|
+
parser.add_argument(
|
|
150
|
+
"--headless", action="store_true", help="""
|
|
151
|
+
Run the decompiler in headless mode (no GUI). Requires --binary-path.
|
|
152
|
+
"""
|
|
153
|
+
)
|
|
154
|
+
parser.add_argument(
|
|
155
|
+
"--server-id", help="""
|
|
156
|
+
Explicit server ID to use; if omitted, a unique one is generated.
|
|
157
|
+
"""
|
|
158
|
+
)
|
|
159
|
+
parser.add_argument(
|
|
160
|
+
"--project-dir", help="""
|
|
161
|
+
Directory where the backend should store its project/database files
|
|
162
|
+
(Ghidra project, IDA .id*, etc.). If omitted, backend defaults apply
|
|
163
|
+
(Ghidra creates a project next to the binary; IDA writes .id* next
|
|
164
|
+
to the binary).
|
|
165
|
+
"""
|
|
166
|
+
)
|
|
167
|
+
args = parser.parse_args()
|
|
168
|
+
|
|
169
|
+
if args.single_decompiler_install:
|
|
170
|
+
decompiler, path = args.single_decompiler_install
|
|
171
|
+
DecLibPluginInstaller().install(interactive=False, paths_by_target={decompiler: path})
|
|
172
|
+
elif args.install:
|
|
173
|
+
install()
|
|
174
|
+
elif args.server:
|
|
175
|
+
if args.headless and not args.binary_path:
|
|
176
|
+
parser.error("--headless requires --binary-path")
|
|
177
|
+
start_server(
|
|
178
|
+
socket_path=args.socket_path,
|
|
179
|
+
decompiler=args.decompiler,
|
|
180
|
+
binary_path=args.binary_path,
|
|
181
|
+
headless=args.headless,
|
|
182
|
+
server_id=args.server_id,
|
|
183
|
+
project_dir=args.project_dir,
|
|
184
|
+
)
|
|
185
|
+
else:
|
|
186
|
+
parser.print_help()
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
if __name__ == "__main__":
|
|
190
|
+
main()
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
from .artifact_lifter import ArtifactLifter
|
|
2
|
+
from .decompiler_interface import DecompilerInterface
|
|
3
|
+
from .type_parser import CTypeParser, CType
|
|
4
|
+
|
|
5
|
+
from .decompiler_interface import (
|
|
6
|
+
DecompilerInterface
|
|
7
|
+
)
|
|
8
|
+
from .artifact_lifter import (
|
|
9
|
+
ArtifactLifter
|
|
10
|
+
)
|
|
11
|
+
from .type_parser import (
|
|
12
|
+
CTypeParser, CType
|
|
13
|
+
)
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import typing
|
|
2
|
+
import logging
|
|
3
|
+
|
|
4
|
+
from declib.artifacts import (
|
|
5
|
+
Artifact, Comment, Enum, FunctionHeader, Function, FunctionArgument,
|
|
6
|
+
GlobalVariable, Patch, Segment, StackVariable, Struct, StructMember, Typedef
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
if typing.TYPE_CHECKING:
|
|
10
|
+
from declib.api import DecompilerInterface
|
|
11
|
+
|
|
12
|
+
_l = logging.getLogger(__name__)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class ArtifactDict(dict):
|
|
16
|
+
"""
|
|
17
|
+
The ArtifactDict is a Dictionary wrapper around the getting/setting/listing of artifacts in the decompiler. This
|
|
18
|
+
allows for a more pythonic interface to the decompiler artifacts. For example, instead of doing:
|
|
19
|
+
deci._set_function(func)
|
|
20
|
+
|
|
21
|
+
You can do:
|
|
22
|
+
>>> deci.functions[func.addr] = func
|
|
23
|
+
|
|
24
|
+
This class is not meant to be instantiated directly, but rather through the DecompilerInterface class.
|
|
25
|
+
There is currently some interesting affects and caveats to using this class:
|
|
26
|
+
- When you list artifacts, by calling list(), you will get a light copy of the artifacts. This means that if you
|
|
27
|
+
modify the artifacts in the list, they will not be reflected in the decompiler. You also do need get current
|
|
28
|
+
data in the decompiler, only an acknowledgement that the artifact exists.
|
|
29
|
+
- You must reassign the artifact to the dictionary to update the decompiler.
|
|
30
|
+
- When assigning something to the dictionary, it must always be in its lifted form. You will also only get lifted
|
|
31
|
+
artifacts back from the dictionary.
|
|
32
|
+
- For convience, you can access functions by their lowered address
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
def __init__(self, artifact_cls, deci: "DecompilerInterface", error_on_duplicate=False, scopable=False):
|
|
36
|
+
super().__init__()
|
|
37
|
+
|
|
38
|
+
self._deci = deci
|
|
39
|
+
self._error_on_duplicate = error_on_duplicate
|
|
40
|
+
self._scopable = scopable
|
|
41
|
+
self._art_function = {
|
|
42
|
+
# ArtifactType: (setter, getter, lister)
|
|
43
|
+
Function: (self._deci._set_function, self._deci._get_function, self._deci._functions, self._deci._del_function),
|
|
44
|
+
StackVariable: (self._deci._set_stack_variable, self._deci._get_stack_variable, self._deci._stack_variables, self._deci._del_stack_variable),
|
|
45
|
+
GlobalVariable: (self._deci._set_global_variable, self._deci._get_global_var, self._deci._global_vars, self._deci._del_global_var),
|
|
46
|
+
Struct: (self._deci._set_struct, self._deci._get_struct, self._deci._structs, self._deci._del_struct),
|
|
47
|
+
Enum: (self._deci._set_enum, self._deci._get_enum, self._deci._enums, self._deci._del_enum),
|
|
48
|
+
Typedef: (self._deci._set_typedef, self._deci._get_typedef, self._deci._typedefs, self._deci._del_typedef),
|
|
49
|
+
Comment: (self._deci._set_comment, self._deci._get_comment, self._deci._comments, self._deci._del_comment),
|
|
50
|
+
Patch: (self._deci._set_patch, self._deci._get_patch, self._deci._patches, self._deci._del_patch),
|
|
51
|
+
Segment: (self._deci._set_segment, self._deci._get_segment, self._deci._segments, self._deci._del_segment)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
functions = self._art_function.get(artifact_cls, None)
|
|
55
|
+
if functions is None:
|
|
56
|
+
raise ValueError(f"Attempting to create a dict for a Artifact class that is not supported: {artifact_cls}")
|
|
57
|
+
|
|
58
|
+
self._artifact_class = artifact_cls
|
|
59
|
+
self._artifact_setter, self._artifact_getter, self._artifact_lister, self._artifact_remover = functions
|
|
60
|
+
|
|
61
|
+
def __len__(self):
|
|
62
|
+
return len(self._artifact_lister())
|
|
63
|
+
|
|
64
|
+
def _lifted_art_lister(self):
|
|
65
|
+
d = self._artifact_lister()
|
|
66
|
+
d_items = list(d.items())
|
|
67
|
+
if not d_items:
|
|
68
|
+
return {}
|
|
69
|
+
|
|
70
|
+
is_addr = hasattr(d_items[0][1], "addr")
|
|
71
|
+
new_d = {}
|
|
72
|
+
for k, v in d_items:
|
|
73
|
+
if is_addr:
|
|
74
|
+
k = self._deci.art_lifter.lift_addr(k)
|
|
75
|
+
new_d[k] = self._deci.art_lifter.lift(v)
|
|
76
|
+
|
|
77
|
+
return new_d
|
|
78
|
+
|
|
79
|
+
def __getitem__(self, item):
|
|
80
|
+
"""
|
|
81
|
+
Takes a lifted identifier as input and returns a lifted artifact
|
|
82
|
+
"""
|
|
83
|
+
if isinstance(item, int):
|
|
84
|
+
item = self._deci.art_lifter.lower_addr(item)
|
|
85
|
+
if self._scopable and not self._deci.supports_type_scopes:
|
|
86
|
+
item, _ = self._deci.art_lifter.parse_scoped_type(item)
|
|
87
|
+
|
|
88
|
+
art = self._artifact_getter(item)
|
|
89
|
+
if art is None:
|
|
90
|
+
raise KeyError
|
|
91
|
+
|
|
92
|
+
return self._deci.art_lifter.lift(art)
|
|
93
|
+
|
|
94
|
+
def __setitem__(self, key, value):
|
|
95
|
+
"""
|
|
96
|
+
Both key and value must be lifted artifacts
|
|
97
|
+
"""
|
|
98
|
+
if not isinstance(value, self._artifact_class):
|
|
99
|
+
raise ValueError(f"Attempting to set a value of type {type(value)} to a dict of type {self._artifact_class}")
|
|
100
|
+
|
|
101
|
+
if isinstance(key, int):
|
|
102
|
+
key = self._deci.art_lifter.lower_addr(key)
|
|
103
|
+
if self._scopable and not self._deci.supports_type_scopes:
|
|
104
|
+
key, _ = self._deci.art_lifter.parse_scoped_type(key)
|
|
105
|
+
|
|
106
|
+
art = self._deci.art_lifter.lower(value)
|
|
107
|
+
if not self._artifact_setter(art) and self._error_on_duplicate:
|
|
108
|
+
raise ValueError(f"Set value {value} is already present at key {key}")
|
|
109
|
+
|
|
110
|
+
def __contains__(self, item):
|
|
111
|
+
if isinstance(item, int):
|
|
112
|
+
item = self._deci.art_lifter.lower_addr(item)
|
|
113
|
+
if self._scopable and not self._deci.supports_type_scopes:
|
|
114
|
+
item, _ = self._deci.art_lifter.parse_scoped_type(item)
|
|
115
|
+
|
|
116
|
+
data = self._artifact_getter(item)
|
|
117
|
+
return data is not None
|
|
118
|
+
|
|
119
|
+
def __delitem__(self, key):
|
|
120
|
+
if isinstance(key, int):
|
|
121
|
+
key = self._deci.art_lifter.lower_addr(key)
|
|
122
|
+
if self._scopable and not self._deci.supports_type_scopes:
|
|
123
|
+
key, _ = self._deci.art_lifter.parse_scoped_type(key)
|
|
124
|
+
|
|
125
|
+
art = self._artifact_getter(key)
|
|
126
|
+
if isinstance(art, Struct):
|
|
127
|
+
self._artifact_remover(key)
|
|
128
|
+
self._deci.struct_changed(art, deleted=True)
|
|
129
|
+
else:
|
|
130
|
+
self._artifact_remover(key)
|
|
131
|
+
|
|
132
|
+
def __iter__(self):
|
|
133
|
+
return iter(self._lifted_art_lister())
|
|
134
|
+
|
|
135
|
+
def __repr__(self):
|
|
136
|
+
return f"<{self.__class__.__name__}: {self._artifact_class.__name__} len={self.__len__()}>"
|
|
137
|
+
|
|
138
|
+
def __str__(self):
|
|
139
|
+
return f"{self._lifted_art_lister()}"
|
|
140
|
+
|
|
141
|
+
def keys(self):
|
|
142
|
+
return self._lifted_art_lister().keys()
|
|
143
|
+
|
|
144
|
+
def values(self):
|
|
145
|
+
return self._lifted_art_lister().values()
|
|
146
|
+
|
|
147
|
+
def items(self):
|
|
148
|
+
return self._lifted_art_lister().items()
|
|
149
|
+
|
|
150
|
+
def get(self, key, default=None):
|
|
151
|
+
if key in self:
|
|
152
|
+
return self[key]
|
|
153
|
+
return default
|