declib 3.8.0__py3-none-any.whl
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/__init__.py +9 -0
- declib/__main__.py +190 -0
- declib/api/__init__.py +13 -0
- declib/api/artifact_dict.py +153 -0
- declib/api/artifact_lifter.py +161 -0
- declib/api/decompiler_client.py +1219 -0
- declib/api/decompiler_interface.py +1261 -0
- declib/api/decompiler_server.py +782 -0
- declib/api/server_registry.py +171 -0
- declib/api/type_definition_parser.py +201 -0
- declib/api/type_parser.py +409 -0
- declib/api/utils.py +31 -0
- declib/artifacts/__init__.py +93 -0
- declib/artifacts/artifact.py +311 -0
- declib/artifacts/comment.py +49 -0
- declib/artifacts/context.py +61 -0
- declib/artifacts/decompilation.py +35 -0
- declib/artifacts/enum.py +53 -0
- declib/artifacts/formatting.py +27 -0
- declib/artifacts/func.py +433 -0
- declib/artifacts/global_variable.py +31 -0
- declib/artifacts/patch.py +49 -0
- declib/artifacts/segment.py +37 -0
- declib/artifacts/stack_variable.py +50 -0
- declib/artifacts/struct.py +184 -0
- declib/artifacts/typedef.py +59 -0
- declib/cli/__init__.py +3 -0
- declib/cli/decompiler_cli.py +1487 -0
- declib/configuration.py +184 -0
- declib/decompiler_stubs/__init__.py +0 -0
- declib/decompiler_stubs/angr_declib/__init__.py +4 -0
- declib/decompiler_stubs/binja_declib/__init__.py +4 -0
- declib/decompiler_stubs/ida_declib.py +8 -0
- declib/decompilers/__init__.py +8 -0
- declib/decompilers/angr/__init__.py +11 -0
- declib/decompilers/angr/artifact_lifter.py +46 -0
- declib/decompilers/angr/compat.py +262 -0
- declib/decompilers/angr/interface.py +949 -0
- declib/decompilers/binja/__init__.py +0 -0
- declib/decompilers/binja/artifact_lifter.py +32 -0
- declib/decompilers/binja/hooks.py +201 -0
- declib/decompilers/binja/interface.py +795 -0
- declib/decompilers/ghidra/__init__.py +0 -0
- declib/decompilers/ghidra/artifact_lifter.py +60 -0
- declib/decompilers/ghidra/compat/__init__.py +0 -0
- declib/decompilers/ghidra/compat/headless.py +156 -0
- declib/decompilers/ghidra/compat/imports.py +78 -0
- declib/decompilers/ghidra/compat/state.py +54 -0
- declib/decompilers/ghidra/compat/transaction.py +30 -0
- declib/decompilers/ghidra/hooks.py +242 -0
- declib/decompilers/ghidra/interface.py +1433 -0
- declib/decompilers/ida/__init__.py +0 -0
- declib/decompilers/ida/artifact_lifter.py +51 -0
- declib/decompilers/ida/compat.py +2054 -0
- declib/decompilers/ida/hooks.py +700 -0
- declib/decompilers/ida/ida_ui.py +80 -0
- declib/decompilers/ida/interface.py +659 -0
- declib/logger.py +101 -0
- declib/plugin_installer.py +259 -0
- declib/skills/__init__.py +24 -0
- declib/skills/decompiler/SKILL.md +316 -0
- declib/ui/__init__.py +33 -0
- declib/ui/qt_objects.py +146 -0
- declib/ui/utils.py +115 -0
- declib/ui/version.py +14 -0
- declib-3.8.0.dist-info/METADATA +138 -0
- declib-3.8.0.dist-info/RECORD +71 -0
- declib-3.8.0.dist-info/WHEEL +5 -0
- declib-3.8.0.dist-info/entry_points.txt +3 -0
- declib-3.8.0.dist-info/licenses/LICENSE +24 -0
- declib-3.8.0.dist-info/top_level.txt +1 -0
declib/__init__.py
ADDED
declib/__main__.py
ADDED
|
@@ -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()
|
declib/api/__init__.py
ADDED
|
@@ -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
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import typing
|
|
3
|
+
|
|
4
|
+
from declib.artifacts import StackVariable, Artifact, FunctionArgument, StructMember, Typedef, Enum, Struct
|
|
5
|
+
from declib.api.type_parser import CTypeParser
|
|
6
|
+
|
|
7
|
+
if typing.TYPE_CHECKING:
|
|
8
|
+
from declib.api import DecompilerInterface
|
|
9
|
+
|
|
10
|
+
_l = logging.getLogger(name=__name__)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class ArtifactLifter:
|
|
14
|
+
SCOPE_DELIMITER = "::"
|
|
15
|
+
|
|
16
|
+
def __init__(self, deci: "DecompilerInterface", types=None):
|
|
17
|
+
self.deci = deci
|
|
18
|
+
self.type_parser = CTypeParser(extra_types=types)
|
|
19
|
+
|
|
20
|
+
#
|
|
21
|
+
# Public API
|
|
22
|
+
#
|
|
23
|
+
|
|
24
|
+
def lift(self, artifact: Artifact):
|
|
25
|
+
return self._lift_or_lower_artifact(artifact, "lift")
|
|
26
|
+
|
|
27
|
+
def lower(self, artifact: Artifact):
|
|
28
|
+
return self._lift_or_lower_artifact(artifact, "lower")
|
|
29
|
+
|
|
30
|
+
#
|
|
31
|
+
# Special handlers for scopes
|
|
32
|
+
#
|
|
33
|
+
|
|
34
|
+
@staticmethod
|
|
35
|
+
def parse_scoped_type(type_str: str) -> tuple[str, str | None]:
|
|
36
|
+
"""
|
|
37
|
+
Parses a scoped type string into its base type and scope.
|
|
38
|
+
Note: the scope can be None if the type is not scoped.
|
|
39
|
+
|
|
40
|
+
Examples:
|
|
41
|
+
'stdint::uint32_t' -> ('uint32_t', 'stdint')
|
|
42
|
+
'uint32_t' -> ('uint32_t', None)
|
|
43
|
+
"""
|
|
44
|
+
if not type_str:
|
|
45
|
+
return "", None
|
|
46
|
+
|
|
47
|
+
# check if the type is scoped
|
|
48
|
+
scope = None
|
|
49
|
+
deli = ArtifactLifter.SCOPE_DELIMITER
|
|
50
|
+
if deli in type_str:
|
|
51
|
+
scope_parts = type_str.split(deli)
|
|
52
|
+
base_type = scope_parts[-1]
|
|
53
|
+
scope = deli.join(scope_parts[:-1])
|
|
54
|
+
else:
|
|
55
|
+
base_type = type_str
|
|
56
|
+
|
|
57
|
+
return base_type, scope
|
|
58
|
+
|
|
59
|
+
@staticmethod
|
|
60
|
+
def scoped_type_to_str(name: str, scope: str | None = None) -> str:
|
|
61
|
+
"""
|
|
62
|
+
Converts a name and scope into a scoped type string.
|
|
63
|
+
Note: the scope can be None if the type is not scoped.
|
|
64
|
+
"""
|
|
65
|
+
return name if not scope else f"{scope}::{name}"
|
|
66
|
+
|
|
67
|
+
#
|
|
68
|
+
# Override Mandatory Funcs
|
|
69
|
+
#
|
|
70
|
+
|
|
71
|
+
def lift_type(self, type_str: str) -> str:
|
|
72
|
+
return type_str
|
|
73
|
+
|
|
74
|
+
def lift_addr(self, addr: int) -> int:
|
|
75
|
+
base_addr = self.deci.binary_base_addr
|
|
76
|
+
if addr < base_addr:
|
|
77
|
+
self.deci.warning(f"Lifting an address that appears already lifted: {addr}...")
|
|
78
|
+
|
|
79
|
+
return addr - base_addr
|
|
80
|
+
|
|
81
|
+
def lift_stack_offset(self, offset: int, func_addr: int) -> int:
|
|
82
|
+
pass
|
|
83
|
+
|
|
84
|
+
def lower_type(self, type_str: str) -> str:
|
|
85
|
+
if self.SCOPE_DELIMITER in type_str and not self.deci.supports_type_scopes:
|
|
86
|
+
type_str, scope = self.scoped_type_to_str(type_str)
|
|
87
|
+
|
|
88
|
+
return type_str
|
|
89
|
+
|
|
90
|
+
def lower_addr(self, addr: int) -> int:
|
|
91
|
+
base_addr = self.deci.binary_base_addr
|
|
92
|
+
if addr >= base_addr != 0:
|
|
93
|
+
self.deci.warning(f"Lowering an address that appears already lowered: {addr}...")
|
|
94
|
+
|
|
95
|
+
return addr + base_addr
|
|
96
|
+
|
|
97
|
+
def lower_stack_offset(self, offset: int, func_addr: int) -> int:
|
|
98
|
+
pass
|
|
99
|
+
|
|
100
|
+
#
|
|
101
|
+
# Private
|
|
102
|
+
#
|
|
103
|
+
|
|
104
|
+
def _lift_or_lower_artifact(self, artifact, mode):
|
|
105
|
+
target_attrs = ("name", "type", "offset", "addr", "func_addr", "line_map")
|
|
106
|
+
if mode not in ("lower", "lift"):
|
|
107
|
+
return None
|
|
108
|
+
|
|
109
|
+
if not isinstance(artifact, Artifact):
|
|
110
|
+
return artifact
|
|
111
|
+
lifted_art = artifact.copy()
|
|
112
|
+
# correct simple properties in the artifact
|
|
113
|
+
for attr in target_attrs:
|
|
114
|
+
if hasattr(lifted_art, attr):
|
|
115
|
+
curr_val = getattr(lifted_art, attr)
|
|
116
|
+
if curr_val is None:
|
|
117
|
+
continue
|
|
118
|
+
|
|
119
|
+
# special handling for stack variables
|
|
120
|
+
if attr == "offset":
|
|
121
|
+
if not isinstance(artifact, StackVariable):
|
|
122
|
+
continue
|
|
123
|
+
lifting_func = getattr(self, f"{mode}_stack_offset")
|
|
124
|
+
setattr(lifted_art, attr, lifting_func(curr_val, lifted_art.addr))
|
|
125
|
+
# special handling for decompilation
|
|
126
|
+
elif attr == "line_map":
|
|
127
|
+
lifted_line_map = {}
|
|
128
|
+
lift_or_lower_func = self.lift_addr if mode == "lift" else self.lower_addr
|
|
129
|
+
for k, v in curr_val.items():
|
|
130
|
+
lifted_line_map[k] = {lift_or_lower_func(_v) for _v in v}
|
|
131
|
+
|
|
132
|
+
setattr(lifted_art, attr, lifted_line_map)
|
|
133
|
+
# special handling for types that have names
|
|
134
|
+
elif attr == "name":
|
|
135
|
+
if not isinstance(artifact, (Typedef, Enum, Struct)):
|
|
136
|
+
continue
|
|
137
|
+
lifted_type = self.lift_type(curr_val) if mode == "lift" else self.lower_type(curr_val)
|
|
138
|
+
setattr(lifted_art, attr, lifted_type)
|
|
139
|
+
else:
|
|
140
|
+
attr_func_name = attr if attr != "func_addr" else "addr"
|
|
141
|
+
lifting_func = getattr(self, f"{mode}_{attr_func_name}")
|
|
142
|
+
setattr(lifted_art, attr, lifting_func(curr_val))
|
|
143
|
+
|
|
144
|
+
# recursively correct nested artifacts
|
|
145
|
+
for attr in lifted_art.__slots__:
|
|
146
|
+
attr_val = getattr(lifted_art, attr)
|
|
147
|
+
if not attr_val:
|
|
148
|
+
continue
|
|
149
|
+
|
|
150
|
+
# nested function headers
|
|
151
|
+
if attr == "header":
|
|
152
|
+
setattr(lifted_art, attr, self._lift_or_lower_artifact(attr_val, mode))
|
|
153
|
+
# nested args, stack_vars, or struct_members
|
|
154
|
+
elif isinstance(attr_val, dict):
|
|
155
|
+
nested_arts = {}
|
|
156
|
+
for k, v in attr_val.items():
|
|
157
|
+
nested_art = self._lift_or_lower_artifact(v, mode)
|
|
158
|
+
nested_arts[nested_art.offset if isinstance(nested_art, (StackVariable, FunctionArgument, StructMember)) else k] = nested_art
|
|
159
|
+
setattr(lifted_art, attr, nested_arts)
|
|
160
|
+
|
|
161
|
+
return lifted_art
|