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.
Files changed (71) hide show
  1. declib/__init__.py +9 -0
  2. declib/__main__.py +190 -0
  3. declib/api/__init__.py +13 -0
  4. declib/api/artifact_dict.py +153 -0
  5. declib/api/artifact_lifter.py +161 -0
  6. declib/api/decompiler_client.py +1219 -0
  7. declib/api/decompiler_interface.py +1261 -0
  8. declib/api/decompiler_server.py +782 -0
  9. declib/api/server_registry.py +171 -0
  10. declib/api/type_definition_parser.py +201 -0
  11. declib/api/type_parser.py +409 -0
  12. declib/api/utils.py +31 -0
  13. declib/artifacts/__init__.py +93 -0
  14. declib/artifacts/artifact.py +311 -0
  15. declib/artifacts/comment.py +49 -0
  16. declib/artifacts/context.py +61 -0
  17. declib/artifacts/decompilation.py +35 -0
  18. declib/artifacts/enum.py +53 -0
  19. declib/artifacts/formatting.py +27 -0
  20. declib/artifacts/func.py +433 -0
  21. declib/artifacts/global_variable.py +31 -0
  22. declib/artifacts/patch.py +49 -0
  23. declib/artifacts/segment.py +37 -0
  24. declib/artifacts/stack_variable.py +50 -0
  25. declib/artifacts/struct.py +184 -0
  26. declib/artifacts/typedef.py +59 -0
  27. declib/cli/__init__.py +3 -0
  28. declib/cli/decompiler_cli.py +1487 -0
  29. declib/configuration.py +184 -0
  30. declib/decompiler_stubs/__init__.py +0 -0
  31. declib/decompiler_stubs/angr_declib/__init__.py +4 -0
  32. declib/decompiler_stubs/binja_declib/__init__.py +4 -0
  33. declib/decompiler_stubs/ida_declib.py +8 -0
  34. declib/decompilers/__init__.py +8 -0
  35. declib/decompilers/angr/__init__.py +11 -0
  36. declib/decompilers/angr/artifact_lifter.py +46 -0
  37. declib/decompilers/angr/compat.py +262 -0
  38. declib/decompilers/angr/interface.py +949 -0
  39. declib/decompilers/binja/__init__.py +0 -0
  40. declib/decompilers/binja/artifact_lifter.py +32 -0
  41. declib/decompilers/binja/hooks.py +201 -0
  42. declib/decompilers/binja/interface.py +795 -0
  43. declib/decompilers/ghidra/__init__.py +0 -0
  44. declib/decompilers/ghidra/artifact_lifter.py +60 -0
  45. declib/decompilers/ghidra/compat/__init__.py +0 -0
  46. declib/decompilers/ghidra/compat/headless.py +156 -0
  47. declib/decompilers/ghidra/compat/imports.py +78 -0
  48. declib/decompilers/ghidra/compat/state.py +54 -0
  49. declib/decompilers/ghidra/compat/transaction.py +30 -0
  50. declib/decompilers/ghidra/hooks.py +242 -0
  51. declib/decompilers/ghidra/interface.py +1433 -0
  52. declib/decompilers/ida/__init__.py +0 -0
  53. declib/decompilers/ida/artifact_lifter.py +51 -0
  54. declib/decompilers/ida/compat.py +2054 -0
  55. declib/decompilers/ida/hooks.py +700 -0
  56. declib/decompilers/ida/ida_ui.py +80 -0
  57. declib/decompilers/ida/interface.py +659 -0
  58. declib/logger.py +101 -0
  59. declib/plugin_installer.py +259 -0
  60. declib/skills/__init__.py +24 -0
  61. declib/skills/decompiler/SKILL.md +316 -0
  62. declib/ui/__init__.py +33 -0
  63. declib/ui/qt_objects.py +146 -0
  64. declib/ui/utils.py +115 -0
  65. declib/ui/version.py +14 -0
  66. declib-3.8.0.dist-info/METADATA +138 -0
  67. declib-3.8.0.dist-info/RECORD +71 -0
  68. declib-3.8.0.dist-info/WHEEL +5 -0
  69. declib-3.8.0.dist-info/entry_points.txt +3 -0
  70. declib-3.8.0.dist-info/licenses/LICENSE +24 -0
  71. declib-3.8.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,311 @@
1
+ import json
2
+ from typing import Dict, Optional, List
3
+ import datetime
4
+
5
+ import toml
6
+
7
+ from .formatting import ArtifactFormat, TomlHexEncoder
8
+
9
+ from toml.tz import TomlTz
10
+
11
+ class Artifact:
12
+ """
13
+ The Artifact class acts as the base for all other artifacts that can be produced by a decompiler (or decompiler
14
+ adjacent tool). In general, the comparisons of these derived classes should only be done on the attributes in
15
+ __slots__, except for the last_change property.
16
+ """
17
+ LST_CHNG_ATTR = "last_change"
18
+ ADDR_ATTR = "addr"
19
+ ART_TYPE_STR = "artifact_type"
20
+ SCOPE_ATTR = "scope"
21
+
22
+ ATTR_ATTR_IGNORE_SET = "_attr_ignore_set"
23
+ __slots__ = (
24
+ LST_CHNG_ATTR,
25
+ ATTR_ATTR_IGNORE_SET,
26
+ SCOPE_ATTR
27
+ )
28
+
29
+ def __init__(self, last_change: Optional[datetime.datetime] = None, scope: Optional[str] = None):
30
+ self.last_change = last_change
31
+ self.scope = scope
32
+ self._attr_ignore_set = set()
33
+
34
+ @staticmethod
35
+ def _normalize_datetime(dt):
36
+ """
37
+ Convert TomlTz datetime objects to standard Python datetime objects.
38
+ TomlTz objects from TOML deserialization don't pickle correctly.
39
+ """
40
+ if not isinstance(dt, datetime.datetime):
41
+ return dt
42
+
43
+ # If the datetime has a TomlTz tzinfo, convert it to standard timezone
44
+ if dt.tzinfo is not None and isinstance(dt.tzinfo, TomlTz):
45
+ # Get the offset and convert to standard timezone
46
+ offset = dt.utcoffset()
47
+ if offset is not None:
48
+ std_tz = datetime.timezone(offset)
49
+ # Replace the TomlTz with standard timezone
50
+ return dt.replace(tzinfo=std_tz)
51
+
52
+ return dt
53
+
54
+ def __getstate__(self) -> Dict:
55
+ state = {}
56
+ for k in self.slots:
57
+ value = getattr(self, k)
58
+ # Normalize datetime objects to ensure they pickle correctly
59
+ if isinstance(value, datetime.datetime):
60
+ value = self._normalize_datetime(value)
61
+ state[k] = value
62
+ return state
63
+
64
+ def __setstate__(self, state):
65
+ # When pickle calls __setstate__, __init__ is never called, so we need to
66
+ # initialize _attr_ignore_set before accessing self.slots (which uses it)
67
+ if not hasattr(self, '_attr_ignore_set'):
68
+ self._attr_ignore_set = set()
69
+
70
+ for k in self.slots:
71
+ if k in state:
72
+ setattr(self, k, state[k])
73
+
74
+ def __eq__(self, other):
75
+ if not isinstance(other, self.__class__):
76
+ return False
77
+
78
+ for k in self.slots:
79
+ if k == self.LST_CHNG_ATTR:
80
+ continue
81
+ elif k == self.SCOPE_ATTR:
82
+ # special case scopes: a scope of None indicates that the artifact could be in any scope
83
+ this_scope = getattr(self, k)
84
+ that_scope = getattr(other, k)
85
+ if this_scope is None or that_scope is None:
86
+ continue
87
+
88
+ if getattr(self, k) != getattr(other, k):
89
+ return False
90
+
91
+ return True
92
+
93
+ def __hash__(self):
94
+ long_str = ""
95
+ for attr in self.slots:
96
+ long_str += str(getattr(self, attr))
97
+
98
+ return hash(long_str)
99
+
100
+ def __repr__(self):
101
+ return self.__str__()
102
+
103
+ @property
104
+ def slots(self):
105
+ return [s for s in self.__slots__ if s != self.ATTR_ATTR_IGNORE_SET and s not in self._attr_ignore_set]
106
+
107
+ def copy(self) -> "Artifact":
108
+ new_obj = self.__class__()
109
+ for attr in self.slots:
110
+ attr_v = getattr(self, attr)
111
+ if isinstance(attr_v, list):
112
+ new_list = []
113
+ for lobj in attr_v:
114
+ if hasattr(lobj, "copy"):
115
+ new_list.append(lobj.copy())
116
+ setattr(new_obj, attr, new_list)
117
+ elif isinstance(attr_v, dict):
118
+ new_dict = {}
119
+ for dk, dv in attr_v.items():
120
+ new_dk = dk.copy() if hasattr(dk, "copy") else dk
121
+ new_dv = dv.copy() if hasattr(dv, "copy") else dv
122
+ new_dict[new_dk] = new_dv
123
+ setattr(new_obj, attr, new_dict)
124
+ elif isinstance(attr_v, Artifact):
125
+ setattr(new_obj, attr, attr_v.copy())
126
+ else:
127
+ setattr(new_obj, attr, attr_v)
128
+
129
+ return new_obj
130
+
131
+ #
132
+ # Serialization
133
+ #
134
+
135
+ def _to_c_string(self):
136
+ raise NotImplementedError
137
+
138
+ @classmethod
139
+ def _from_c_string(cls, cstring) -> Dict:
140
+ raise NotImplementedError
141
+
142
+ def dumps(self, fmt=ArtifactFormat.TOML) -> str:
143
+ dict_data = self.__getstate__()
144
+ # encode the artifact type only for JSON format
145
+ if fmt == ArtifactFormat.JSON:
146
+ dict_data.update({self.ART_TYPE_STR: self.__class__.__name__})
147
+
148
+ if fmt == ArtifactFormat.TOML:
149
+ return toml.dumps(dict_data, encoder=TomlHexEncoder())
150
+ elif fmt == ArtifactFormat.JSON:
151
+ return json.dumps(dict_data)
152
+ elif fmt == ArtifactFormat.C_LANG:
153
+ return self._to_c_string()
154
+ else:
155
+ raise ValueError(f"Dumping to format {fmt} is not yet supported.")
156
+
157
+ def dump(self, fp, fmt=ArtifactFormat.TOML):
158
+ data = self.dumps(fmt=fmt)
159
+ fp.write(data)
160
+
161
+ @classmethod
162
+ def loads(cls, string, fmt=ArtifactFormat.TOML) -> "Artifact":
163
+ if fmt == ArtifactFormat.TOML:
164
+ dict_data = toml.loads(string)
165
+ elif fmt == ArtifactFormat.JSON:
166
+ dict_data = json.loads(string)
167
+ elif fmt == ArtifactFormat.C_LANG:
168
+ dict_data = cls._from_c_string(string)
169
+ else:
170
+ raise ValueError(f"Loading from format {fmt} is not yet supported.")
171
+
172
+ # remove the artifact type (if it exists)
173
+ dict_data.pop(Artifact.ART_TYPE_STR, None)
174
+ art = cls()
175
+ art.__setstate__(dict_data)
176
+ return art
177
+
178
+ @classmethod
179
+ def load(cls, fp, fmt=ArtifactFormat.TOML):
180
+ data = fp.read()
181
+ return cls.loads(data, fmt=fmt)
182
+
183
+ @classmethod
184
+ def dumps_many(cls, artifacts: List["Artifact"], key_attr=ADDR_ATTR, fmt=ArtifactFormat.TOML) -> str:
185
+ artifacts_dict = {}
186
+ for art in artifacts:
187
+ k = getattr(art, key_attr)
188
+ if isinstance(k, int):
189
+ k = hex(k)
190
+
191
+ artifacts_dict[k] = art.__getstate__()
192
+
193
+ if fmt == ArtifactFormat.TOML:
194
+ return toml.dumps(artifacts_dict, encoder=TomlHexEncoder())
195
+ elif fmt == ArtifactFormat.JSON:
196
+ return json.dumps(artifacts_dict)
197
+ else:
198
+ raise ValueError(f"Dumping many to format {fmt} is not yet supported.")
199
+
200
+ @classmethod
201
+ def loads_many(cls, string: str, fmt=ArtifactFormat.TOML) -> List["Artifact"]:
202
+ if fmt == ArtifactFormat.TOML:
203
+ dict_data = toml.loads(string)
204
+ elif fmt == ArtifactFormat.JSON:
205
+ dict_data = json.loads(string)
206
+ else:
207
+ raise ValueError(f"Loading many from format {fmt} is not yet supported.")
208
+
209
+ arts = []
210
+ for _, v in dict_data.items():
211
+ art = cls()
212
+ art.__setstate__(v)
213
+ arts.append(art)
214
+
215
+ return arts
216
+
217
+ #
218
+ # Public API
219
+ #
220
+
221
+ @property
222
+ def scoped_name(self) -> str:
223
+ """
224
+ Returns the name of the artifact with its scope, if it has one.
225
+ """
226
+ if hasattr(self, "name"):
227
+ if self.scope:
228
+ return f"{self.scope}::{self.name}"
229
+
230
+ return self.name
231
+ return ""
232
+
233
+ @property
234
+ def commit_msg(self) -> str:
235
+ return f"Updated {self}"
236
+
237
+ def diff(self, other, **kwargs) -> Dict:
238
+ diff_dict = {}
239
+ if not isinstance(other, self.__class__):
240
+ for k in self.slots:
241
+ if k == self.LST_CHNG_ATTR:
242
+ continue
243
+
244
+ diff_dict[k] = {
245
+ "before": getattr(self, k),
246
+ "after": None
247
+ }
248
+ return diff_dict
249
+
250
+ for k in self.slots:
251
+ self_attr, other_attr = getattr(self, k), getattr(other, k)
252
+ if self_attr != other_attr:
253
+ if k == self.LST_CHNG_ATTR:
254
+ continue
255
+
256
+ diff_dict[k] = {
257
+ "before": self_attr,
258
+ "after": other_attr
259
+ }
260
+ return diff_dict
261
+
262
+ @classmethod
263
+ def invert_diff(cls, diff_dict: Dict):
264
+ inverted_diff = {}
265
+ for k, v in diff_dict.items():
266
+ if k == "before":
267
+ inverted_diff["after"] = v
268
+ elif k == "after":
269
+ inverted_diff["before"] = v
270
+ elif isinstance(v, Dict):
271
+ inverted_diff[k] = cls.invert_diff(v)
272
+ else:
273
+ inverted_diff[k] = v
274
+
275
+ return inverted_diff
276
+
277
+ def reset_last_change(self):
278
+ """
279
+ Resets the change time of the Artifact.
280
+ In subclasses, this should also reset all artifacts with nested artifacts
281
+ """
282
+ self.last_change = None
283
+
284
+ def overwrite_merge(self, obj2: "Artifact", **kwargs):
285
+ """
286
+ This function should really be overwritten by its subclass
287
+ """
288
+ merge_obj = self.copy()
289
+ if not obj2 or merge_obj == obj2:
290
+ return merge_obj
291
+
292
+ for attr in self.slots:
293
+ a2 = getattr(obj2, attr)
294
+ if a2 is not None:
295
+ setattr(merge_obj, attr, a2)
296
+
297
+ return merge_obj
298
+
299
+ def nonconflict_merge(self, obj2: "Artifact", **kwargs):
300
+ obj1 = self.copy()
301
+ if not obj2 or obj1 == obj2:
302
+ return obj1
303
+
304
+ obj_diff = obj1.diff(obj2)
305
+ merge_obj = obj1.copy()
306
+
307
+ for attr in self.slots:
308
+ if attr in obj_diff and obj_diff[attr]["before"] is None:
309
+ setattr(merge_obj, attr, getattr(obj2, attr))
310
+
311
+ return merge_obj
@@ -0,0 +1,49 @@
1
+ import textwrap
2
+ from typing import Optional
3
+
4
+ from .artifact import Artifact
5
+
6
+
7
+ class Comment(Artifact):
8
+ __slots__ = Artifact.__slots__ + (
9
+ "addr",
10
+ "func_addr",
11
+ "comment",
12
+ "decompiled",
13
+ )
14
+
15
+ def __init__(
16
+ self,
17
+ addr: int = None,
18
+ comment: Optional[str] = None,
19
+ func_addr: int = None,
20
+ decompiled: bool = False,
21
+ **kwargs
22
+ ):
23
+ super().__init__(**kwargs)
24
+ self.addr = addr
25
+ self.comment = self.linewrap_comment(comment) if comment else None
26
+ self.func_addr = func_addr
27
+ self.decompiled = decompiled
28
+
29
+ def __str__(self):
30
+ cmt_len = len(self.comment) if self.comment else 0
31
+ return f"<Comment: @{hex(self.addr)} len={cmt_len}>"
32
+
33
+ @staticmethod
34
+ def linewrap_comment(comment: str, width=100) -> str:
35
+ # Split the comment into lines based on existing newlines
36
+ lines = comment.split('\n')
37
+ # Wrap each line individually and preserve newlines
38
+ wrapped_lines = [textwrap.fill(line, width=width) for line in lines]
39
+ # Join the wrapped lines with newline characters
40
+ wrapped_text = '\n'.join(wrapped_lines)
41
+ return wrapped_text
42
+
43
+ def nonconflict_merge(self, obj2: "Comment", **kwargs) -> "Comment":
44
+ obj1: "Comment" = self.copy()
45
+ if not obj2 or obj1 == obj2:
46
+ return obj1
47
+
48
+ merge_comment = obj1
49
+ return merge_comment
@@ -0,0 +1,61 @@
1
+ from typing import Optional
2
+
3
+ from .artifact import Artifact
4
+
5
+
6
+ class Context(Artifact):
7
+ ACT_VIEW_OPEN = "view_open"
8
+ ACT_MOUSE_CLICK = "mouse_click"
9
+ ACT_MOUSE_MOVE = "mouse_move"
10
+ ACT_UNKNOWN = "unknown"
11
+
12
+ __slots__ = Artifact.__slots__ + (
13
+ "addr",
14
+ "func_addr",
15
+ "line_number",
16
+ "col_number",
17
+ "screen_name",
18
+ "variable",
19
+ "action",
20
+ "extras",
21
+ )
22
+
23
+ def __init__(
24
+ self,
25
+ addr: Optional[int] = None,
26
+ func_addr: Optional[int] = None,
27
+ line_number: Optional[int] = None,
28
+ col_number: Optional[int] = None,
29
+ screen_name: Optional[str] = None,
30
+ variable: Optional[str] = None,
31
+ action: Optional[str] = None,
32
+ extras: Optional[dict] = None,
33
+ **kwargs
34
+ ):
35
+ self.addr = addr
36
+ self.func_addr = func_addr
37
+ self.line_number = line_number
38
+ self.col_number = col_number
39
+ self.screen_name = screen_name
40
+ self.variable = variable
41
+ self.action: str = action or self.ACT_UNKNOWN
42
+ self.extras = extras or {}
43
+ super().__init__(**kwargs)
44
+
45
+ def __str__(self):
46
+ post_text = f" screen={self.screen_name}" if self.screen_name else ""
47
+ post_text += f" var={self.variable}" if self.variable else ""
48
+ if self.func_addr is not None:
49
+ post_text = f"@{hex(self.func_addr)}" + post_text
50
+ if self.addr is not None:
51
+ post_text = hex(self.addr) + post_text
52
+ if self.line_number is not None:
53
+ post_text += f" line={self.line_number}"
54
+ if self.col_number is not None:
55
+ post_text += f" col={self.col_number}"
56
+ if self.action != self.ACT_UNKNOWN:
57
+ post_text += f" action={self.action}"
58
+ if self.extras:
59
+ post_text += f" extras={self.extras}"
60
+
61
+ return f"<Context {post_text}>"
@@ -0,0 +1,35 @@
1
+ import toml
2
+
3
+ from .artifact import Artifact
4
+
5
+
6
+ class Decompilation(Artifact):
7
+ __slots__ = Artifact.__slots__ + (
8
+ "addr",
9
+ "text",
10
+ "line_map",
11
+ "decompiler",
12
+ "bs_func",
13
+ )
14
+
15
+ def __init__(
16
+ self,
17
+ addr: int = None,
18
+ text: str = None,
19
+ line_map: dict = None,
20
+ decompiler: str = None,
21
+ bs_func = None,
22
+ **kwargs
23
+ ):
24
+ super().__init__(**kwargs)
25
+ self.addr = addr
26
+ self.text = text
27
+ self.line_map = line_map or {}
28
+ self.decompiler = decompiler
29
+ self.bs_func = bs_func
30
+
31
+ def __str__(self):
32
+ return f"//ADDR: {hex(self.addr)}\n// SOURCE: {self.decompiler}\n{self.text}"
33
+
34
+ def __repr__(self):
35
+ return f"<Decompilation: {self.decompiler}@{hex(self.addr)} len={len(self.text)}>"
@@ -0,0 +1,53 @@
1
+ from collections import OrderedDict
2
+ from typing import Dict
3
+
4
+ from .artifact import Artifact
5
+
6
+
7
+ class Enum(Artifact):
8
+ __slots__ = Artifact.__slots__ + (
9
+ "name",
10
+ "members",
11
+ )
12
+
13
+ def __init__(
14
+ self,
15
+ name: str = None,
16
+ members: Dict[str, int] = None,
17
+ **kwargs,
18
+ ):
19
+ super().__init__(**kwargs)
20
+ self.name = name
21
+ # sorts map by the int value
22
+ self.members = self._order_members(members) if members else {}
23
+
24
+ def __str__(self):
25
+ scope_str = f" scope={self.scope}" if self.scope else ""
26
+ return f"<Enum: {self.name} member_count={len(self.members)}{scope_str}>"
27
+
28
+ @staticmethod
29
+ def _order_members(members):
30
+ return OrderedDict(sorted(members.items(), key=lambda kv: kv[1]))
31
+
32
+ def nonconflict_merge(self, enum2: "Enum", **kwargs):
33
+ enum1: Enum = self.copy()
34
+ if not enum2 or enum1 == enum2:
35
+ return enum1.copy()
36
+
37
+ master_state = kwargs.get("master_state", None)
38
+ local_names = {mem for mem in enum1.members}
39
+ if master_state:
40
+ for _, enum in master_state.get_enums().items():
41
+ local_names.union(set(enum.members.keys()))
42
+ else:
43
+ local_names = enum1.members
44
+
45
+ constants = {
46
+ value for value in enum1.members.values()
47
+ }
48
+
49
+ for name, constant in enum2.members.items():
50
+ if name in local_names or constant in constants:
51
+ continue
52
+ enum1.members[name] = constant
53
+ return enum1
@@ -0,0 +1,27 @@
1
+ import typing
2
+
3
+ from toml import TomlEncoder
4
+
5
+ if typing.TYPE_CHECKING:
6
+ from ..api import CTypeParser, CType
7
+
8
+
9
+ class ArtifactFormat:
10
+ TOML = "toml"
11
+ JSON = "json"
12
+ C_LANG = "c"
13
+
14
+
15
+ class TomlHexEncoder(TomlEncoder):
16
+ def __init__(self, _dict=dict, preserve=False):
17
+ super(TomlHexEncoder, self).__init__(_dict, preserve=preserve)
18
+ self.dump_funcs[int] = lambda v: hex(v) if v >= 0 else v
19
+
20
+
21
+ def ctype_from_size(size, type_parser: typing.Optional["CTypeParser"] = None) -> "CType":
22
+ if type_parser is None:
23
+ from ..api.type_parser import CTypeParser
24
+ type_parser = CTypeParser()
25
+
26
+ ctype = type_parser.size_to_type(size)
27
+ return ctype