angr 9.2.83__py3-none-win_amd64.whl → 9.2.85__py3-none-win_amd64.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.

Potentially problematic release.


This version of angr might be problematic. Click here for more details.

Files changed (62) hide show
  1. angr/__init__.py +1 -1
  2. angr/analyses/cfg/cfg_base.py +6 -1
  3. angr/analyses/cfg/cfg_fast.py +32 -10
  4. angr/analyses/decompiler/clinic.py +204 -4
  5. angr/analyses/decompiler/condition_processor.py +8 -2
  6. angr/analyses/decompiler/decompilation_options.py +10 -0
  7. angr/analyses/decompiler/decompiler.py +19 -17
  8. angr/analyses/decompiler/goto_manager.py +34 -51
  9. angr/analyses/decompiler/optimization_passes/__init__.py +5 -5
  10. angr/analyses/decompiler/optimization_passes/div_simplifier.py +2 -0
  11. angr/analyses/decompiler/optimization_passes/lowered_switch_simplifier.py +1 -1
  12. angr/analyses/decompiler/optimization_passes/mod_simplifier.py +2 -0
  13. angr/analyses/decompiler/optimization_passes/multi_simplifier.py +2 -0
  14. angr/analyses/decompiler/optimization_passes/optimization_pass.py +131 -3
  15. angr/analyses/decompiler/optimization_passes/ret_deduplicator.py +3 -3
  16. angr/analyses/decompiler/optimization_passes/return_duplicator.py +519 -0
  17. angr/analyses/decompiler/peephole_optimizations/constant_derefs.py +14 -2
  18. angr/analyses/decompiler/region_identifier.py +8 -2
  19. angr/analyses/decompiler/region_simplifiers/goto.py +5 -4
  20. angr/analyses/decompiler/structured_codegen/c.py +66 -5
  21. angr/analyses/decompiler/structuring/phoenix.py +3 -1
  22. angr/analyses/decompiler/structuring/structurer_nodes.py +11 -5
  23. angr/analyses/decompiler/utils.py +50 -0
  24. angr/analyses/disassembly.py +10 -3
  25. angr/analyses/propagator/engine_ail.py +125 -0
  26. angr/analyses/reaching_definitions/engine_ail.py +36 -2
  27. angr/analyses/reaching_definitions/rd_initializer.py +15 -1
  28. angr/analyses/reaching_definitions/rd_state.py +9 -4
  29. angr/analyses/stack_pointer_tracker.py +10 -17
  30. angr/analyses/variable_recovery/engine_ail.py +27 -1
  31. angr/angrdb/serializers/loader.py +10 -3
  32. angr/calling_conventions.py +2 -0
  33. angr/engines/pcode/behavior.py +7 -2
  34. angr/engines/pcode/cc.py +1 -0
  35. angr/engines/pcode/emulate.py +144 -104
  36. angr/engines/pcode/lifter.py +135 -79
  37. angr/knowledge_plugins/functions/function.py +28 -0
  38. angr/knowledge_plugins/functions/function_manager.py +48 -5
  39. angr/knowledge_plugins/propagations/states.py +14 -0
  40. angr/lib/angr_native.dll +0 -0
  41. angr/procedures/cgc/deallocate.py +5 -2
  42. angr/procedures/posix/gethostbyname.py +23 -8
  43. angr/project.py +4 -0
  44. angr/simos/__init__.py +2 -0
  45. angr/simos/simos.py +1 -0
  46. angr/simos/snimmuc_nxp.py +152 -0
  47. angr/state_plugins/history.py +3 -1
  48. angr/utils/graph.py +20 -18
  49. {angr-9.2.83.dist-info → angr-9.2.85.dist-info}/METADATA +9 -8
  50. {angr-9.2.83.dist-info → angr-9.2.85.dist-info}/RECORD +61 -59
  51. tests/analyses/cfg/test_cfg_rust_got_resolution.py +2 -1
  52. tests/analyses/cfg/test_jumptables.py +2 -1
  53. tests/analyses/decompiler/test_decompiler.py +155 -103
  54. tests/engines/pcode/test_emulate.py +607 -0
  55. tests/engines/test_java.py +609 -663
  56. tests/knowledge_plugins/functions/test_function_manager.py +13 -0
  57. tests/serialization/test_db.py +30 -0
  58. angr/analyses/decompiler/optimization_passes/eager_returns.py +0 -285
  59. {angr-9.2.83.dist-info → angr-9.2.85.dist-info}/LICENSE +0 -0
  60. {angr-9.2.83.dist-info → angr-9.2.85.dist-info}/WHEEL +0 -0
  61. {angr-9.2.83.dist-info → angr-9.2.85.dist-info}/entry_points.txt +0 -0
  62. {angr-9.2.83.dist-info → angr-9.2.85.dist-info}/top_level.txt +0 -0
@@ -121,6 +121,19 @@ class TestFunctionManager(unittest.TestCase):
121
121
  assert 0x400000 in self.project.kb.functions.keys()
122
122
  assert 0x400420 in self.project.kb.functions.keys()
123
123
 
124
+ def test_query(self):
125
+ bin_path = os.path.join(test_location, "x86_64", "fauxware")
126
+ proj = angr.Project(bin_path, auto_load_libs=False)
127
+ proj.analyses.CFGFast(normalize=True, data_references=True)
128
+
129
+ assert proj.kb.functions["::read"].addr == 0x400530
130
+ assert proj.kb.functions["::0x400530::read"].addr == 0x400530
131
+ assert proj.kb.functions["::libc.so.0::read"].addr == 0x700010
132
+ with self.assertRaises(KeyError):
133
+ proj.kb.functions["::0x400531::read"] # pylint:disable=pointless-statement
134
+ with self.assertRaises(KeyError):
135
+ proj.kb.functions["::bad::read"] # pylint:disable=pointless-statement
136
+
124
137
 
125
138
  if __name__ == "__main__":
126
139
  unittest.main()
@@ -4,6 +4,7 @@ __package__ = __package__ or "tests.serialization" # pylint:disable=redefined-b
4
4
 
5
5
  import os
6
6
  import tempfile
7
+ import shutil
7
8
  import unittest
8
9
 
9
10
  import angr
@@ -162,6 +163,35 @@ class TestDb(unittest.TestCase):
162
163
  proj1 = AngrDB().load(db_file)
163
164
  assert proj1.kb.comments[proj.entry] == "Comment 22222222222222222222222"
164
165
 
166
+ def test_angrdb_save_without_binary_existence(self):
167
+ bin_path = os.path.join(test_location, "x86_64", "fauxware")
168
+
169
+ with tempfile.TemporaryDirectory() as td:
170
+ db_file = os.path.join(td, "proj.adb")
171
+
172
+ with tempfile.TemporaryDirectory() as td0:
173
+ tmp_path = os.path.join(td0, os.path.basename(bin_path))
174
+ shutil.copy(bin_path, tmp_path)
175
+ proj = angr.Project(tmp_path, auto_load_libs=False)
176
+
177
+ AngrDB(proj).dump(db_file)
178
+
179
+ del proj
180
+ os.remove(tmp_path)
181
+
182
+ # now that the binary file no longer exists, we should be able to open the angr DB and save it without
183
+ # raising exceptions.
184
+
185
+ proj = AngrDB().load(db_file)
186
+ os.remove(db_file)
187
+
188
+ db_file_new = os.path.join(td, "proj_new.adb")
189
+ AngrDB(proj).dump(db_file_new)
190
+
191
+ # we should be able to load it back!
192
+ proj_new = AngrDB().load(db_file_new)
193
+ assert os.path.basename(proj_new.loader.main_object.binary) == "fauxware"
194
+
165
195
 
166
196
  if __name__ == "__main__":
167
197
  unittest.main()
@@ -1,285 +0,0 @@
1
- from typing import Any, Tuple, Dict, List, TYPE_CHECKING
2
- from itertools import count
3
- import copy
4
- import logging
5
- import inspect
6
-
7
- import networkx
8
-
9
- from ailment.statement import Jump, ConditionalJump
10
- from ailment.expression import Const
11
-
12
- from ..condition_processor import ConditionProcessor, EmptyBlockNotice
13
- from ..call_counter import AILCallCounter
14
- from .optimization_pass import OptimizationPass, OptimizationPassStage
15
-
16
- if TYPE_CHECKING:
17
- from ailment import Block
18
-
19
-
20
- _l = logging.getLogger(name=__name__)
21
-
22
-
23
- class EagerReturnsSimplifier(OptimizationPass):
24
- """
25
- Some compilers (if not all) generate only one returning block for a function regardless of how many returns there
26
- are in the source code. This oftentimes result in irreducible graphs and reduce the readability of the decompiled
27
- code. This optimization pass will make the function return eagerly by duplicating the return site of a function
28
- multiple times and assigning one copy of the return site to each of its sources when certain thresholds are met.
29
-
30
- Note that this simplifier may reduce the readability of the generated code in certain cases, especially if the graph
31
- is already reducible without applying this simplifier.
32
-
33
- :ivar int max_level: Number of times that we repeat the process of making returns eager.
34
- :ivar int min_indegree: The minimum in-degree of the return site to be duplicated.
35
- :ivar node_idx: The next node index. Each duplicated return site gets assigned a unique index, otherwise
36
- those duplicates will be considered as the same block in the graph because they have the
37
- same hash.
38
- """
39
-
40
- ARCHES = None
41
- PLATFORMS = None
42
- STAGE = OptimizationPassStage.AFTER_AIL_GRAPH_CREATION
43
- NAME = "Duplicate return blocks to reduce goto statements"
44
- DESCRIPTION = inspect.cleandoc(__doc__[: __doc__.index(":ivar")]) # pylint:disable=unsubscriptable-object
45
-
46
- def __init__(
47
- self,
48
- func,
49
- blocks_by_addr=None,
50
- blocks_by_addr_and_idx=None,
51
- graph=None,
52
- # internal parameters that should be used by Clinic
53
- node_idx_start=0,
54
- # settings
55
- max_level=2,
56
- min_indegree=2,
57
- max_calls_in_regions=2,
58
- reaching_definitions=None,
59
- **kwargs,
60
- ):
61
- super().__init__(
62
- func, blocks_by_addr=blocks_by_addr, blocks_by_addr_and_idx=blocks_by_addr_and_idx, graph=graph, **kwargs
63
- )
64
-
65
- self.max_level = max_level
66
- self.min_indegree = min_indegree
67
- self.node_idx = count(start=node_idx_start)
68
- self._rd = reaching_definitions
69
- self.max_calls_in_region = max_calls_in_regions
70
-
71
- self.analyze()
72
-
73
- def _check(self):
74
- # does this function have end points?
75
- if not self._func.endpoints:
76
- return False, None
77
-
78
- # TODO: More filtering
79
-
80
- return True, None
81
-
82
- def _analyze(self, cache=None):
83
- # for each block with no successors and more than 1 predecessors, make copies of this block and link it back to
84
- # the sources of incoming edges
85
- graph_copy = networkx.DiGraph(self._graph)
86
- graph_updated = False
87
-
88
- # attempt at most N levels
89
- for _ in range(self.max_level):
90
- r = self._analyze_core(graph_copy)
91
- if not r:
92
- break
93
- graph_updated = True
94
-
95
- # the output graph
96
- if graph_updated:
97
- self.out_graph = graph_copy
98
-
99
- def _analyze_core(self, graph: networkx.DiGraph):
100
- endnodes = [node for node in graph.nodes() if graph.out_degree[node] == 0]
101
- graph_changed = False
102
-
103
- # to_update is keyed by the region head.
104
- # this is because different end nodes may lead to the same region head: consider the case of the typical "fork"
105
- # region where stack canary is checked in x86-64 binaries.
106
- to_update: Dict[Any, Tuple[List[Tuple[Any, Any]], networkx.DiGraph]] = {}
107
-
108
- for end_node in endnodes:
109
- in_edges = list(graph.in_edges(end_node))
110
-
111
- if len(in_edges) > 1:
112
- region = networkx.DiGraph()
113
- region.add_node(end_node)
114
- region_head = end_node
115
- elif len(in_edges) == 1:
116
- # back-trace until it reaches a node with two predecessors
117
- region, region_head = self._single_entry_region(graph, end_node)
118
- tmp_in_edges = graph.in_edges(region_head)
119
- # remove in_edges that are coming from a node inside the region
120
- in_edges = []
121
- for src, dst in tmp_in_edges:
122
- if src not in region:
123
- in_edges.append((src, dst))
124
- else: # len(in_edges) == 0
125
- continue
126
-
127
- # region and in_edge might have been updated. re-check
128
- if not in_edges:
129
- # this is a single connected component in the graph
130
- # no need to duplicate anything
131
- continue
132
- if len(in_edges) == 1:
133
- # there is no need to duplicate it
134
- continue
135
- if len(in_edges) < self.min_indegree:
136
- # does not meet the threshold
137
- continue
138
-
139
- if any(self._is_indirect_jump_ailblock(src) for src, _ in in_edges):
140
- continue
141
-
142
- # to assure we are not copying like crazy, set a max amount of code (which is estimated in calls)
143
- # that can be copied in a region
144
- if self._number_of_calls_in(region) > self.max_calls_in_region:
145
- continue
146
-
147
- to_update[region_head] = in_edges, region
148
-
149
- for region_head, (in_edges, region) in to_update.items():
150
- # update the graph
151
- for in_edge in in_edges:
152
- pred_node = in_edge[0]
153
-
154
- # Modify the graph and then add an edge to the copy of the region
155
- copies = {}
156
- queue = [(pred_node, region_head)]
157
- while queue:
158
- pred, node = queue.pop(0)
159
- if node in copies:
160
- node_copy = copies[node]
161
- else:
162
- node_copy = copy.deepcopy(node)
163
- node_copy.idx = next(self.node_idx)
164
- copies[node] = node_copy
165
-
166
- # modify Jump.target_idx and ConditionalJump.{true,false}_target_idx accordingly
167
- graph.add_edge(pred, node_copy)
168
- try:
169
- last_stmt = ConditionProcessor.get_last_statement(pred)
170
- if isinstance(last_stmt, Jump):
171
- if isinstance(last_stmt.target, Const) and last_stmt.target.value == node_copy.addr:
172
- last_stmt.target_idx = node_copy.idx
173
- elif isinstance(last_stmt, ConditionalJump):
174
- if (
175
- isinstance(last_stmt.true_target, Const)
176
- and last_stmt.true_target.value == node_copy.addr
177
- ):
178
- last_stmt.true_target_idx = node_copy.idx
179
- elif (
180
- isinstance(last_stmt.false_target, Const)
181
- and last_stmt.false_target.value == node_copy.addr
182
- ):
183
- last_stmt.false_target_idx = node_copy.idx
184
- except EmptyBlockNotice:
185
- pass
186
-
187
- for succ in region.successors(node):
188
- queue.append((node_copy, succ))
189
-
190
- # remove all in-edges
191
- graph.remove_edges_from(in_edges)
192
- # remove the node to be copied
193
- graph.remove_nodes_from(region)
194
- graph_changed = True
195
-
196
- return graph_changed
197
-
198
- @staticmethod
199
- def _number_of_calls_in(graph: networkx.DiGraph) -> int:
200
- counter = AILCallCounter()
201
- for node in graph.nodes:
202
- counter.walk(node)
203
-
204
- return counter.calls
205
-
206
- @staticmethod
207
- def _single_entry_region(graph, end_node) -> Tuple[networkx.DiGraph, Any]:
208
- """
209
- Back track on the graph from `end_node` and find the longest chain of nodes where each node has only one
210
- predecessor and one successor (the second-to-last node may have two successors to account for the typical
211
- stack-canary-detection logic).
212
-
213
- :param end_node: A node in the graph.
214
- :return: A graph of nodes where the first node either has no predecessors or at least two
215
- predecessors.
216
- """
217
-
218
- def _is_fork_node(node_) -> bool:
219
- """
220
- Check if the node and its successors form a "fork" region. A "fork" region is a region where:
221
- - The entry node has two successors,
222
- - Each successor has only the entry node as its predecessor.
223
- - Each successor has no successors.
224
- """
225
-
226
- succs = list(graph.successors(node_))
227
- if len(succs) != 2:
228
- return False
229
- for succ in succs:
230
- if graph.in_degree[succ] != 1:
231
- return False
232
- if graph.out_degree[succ] != 0:
233
- return False
234
- return True
235
-
236
- region = networkx.DiGraph()
237
- region.add_node(end_node)
238
-
239
- traversed = {end_node}
240
- region_head = end_node
241
- while True:
242
- preds = list(graph.predecessors(region_head))
243
- if len(preds) != 1:
244
- break
245
- second_to_last_node = region_head is end_node
246
-
247
- pred_node = preds[0]
248
-
249
- if pred_node in traversed:
250
- break
251
-
252
- if second_to_last_node:
253
- if _is_fork_node(pred_node):
254
- # add the entire "fork" to the region
255
- for succ in graph.successors(pred_node):
256
- region.add_edge(pred_node, succ)
257
- elif graph.out_degree[pred_node] != 1:
258
- # the predecessor has more than one successor, and it's not a fork node
259
- break
260
-
261
- if graph.in_degree[pred_node] == 1:
262
- # continue search
263
- pass
264
- else:
265
- region.add_edge(pred_node, region_head)
266
- traversed.add(pred_node)
267
- region_head = pred_node
268
- break
269
- elif not second_to_last_node and graph.out_degree[pred_node] != 1:
270
- break
271
-
272
- region.add_edge(pred_node, region_head)
273
- traversed.add(pred_node)
274
- region_head = pred_node
275
-
276
- return region, region_head
277
-
278
- @staticmethod
279
- def _is_indirect_jump_ailblock(block: "Block") -> bool:
280
- if block.statements and isinstance(block.statements[-1], Jump):
281
- last_stmt = block.statements[-1]
282
- if not isinstance(last_stmt.target, Const):
283
- # it's an indirect jump (assuming the AIL block is properly optimized)
284
- return True
285
- return False
File without changes
File without changes