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.
- angr/__init__.py +1 -1
- angr/analyses/cfg/cfg_base.py +6 -1
- angr/analyses/cfg/cfg_fast.py +32 -10
- angr/analyses/decompiler/clinic.py +204 -4
- angr/analyses/decompiler/condition_processor.py +8 -2
- angr/analyses/decompiler/decompilation_options.py +10 -0
- angr/analyses/decompiler/decompiler.py +19 -17
- angr/analyses/decompiler/goto_manager.py +34 -51
- angr/analyses/decompiler/optimization_passes/__init__.py +5 -5
- angr/analyses/decompiler/optimization_passes/div_simplifier.py +2 -0
- angr/analyses/decompiler/optimization_passes/lowered_switch_simplifier.py +1 -1
- angr/analyses/decompiler/optimization_passes/mod_simplifier.py +2 -0
- angr/analyses/decompiler/optimization_passes/multi_simplifier.py +2 -0
- angr/analyses/decompiler/optimization_passes/optimization_pass.py +131 -3
- angr/analyses/decompiler/optimization_passes/ret_deduplicator.py +3 -3
- angr/analyses/decompiler/optimization_passes/return_duplicator.py +519 -0
- angr/analyses/decompiler/peephole_optimizations/constant_derefs.py +14 -2
- angr/analyses/decompiler/region_identifier.py +8 -2
- angr/analyses/decompiler/region_simplifiers/goto.py +5 -4
- angr/analyses/decompiler/structured_codegen/c.py +66 -5
- angr/analyses/decompiler/structuring/phoenix.py +3 -1
- angr/analyses/decompiler/structuring/structurer_nodes.py +11 -5
- angr/analyses/decompiler/utils.py +50 -0
- angr/analyses/disassembly.py +10 -3
- angr/analyses/propagator/engine_ail.py +125 -0
- angr/analyses/reaching_definitions/engine_ail.py +36 -2
- angr/analyses/reaching_definitions/rd_initializer.py +15 -1
- angr/analyses/reaching_definitions/rd_state.py +9 -4
- angr/analyses/stack_pointer_tracker.py +10 -17
- angr/analyses/variable_recovery/engine_ail.py +27 -1
- angr/angrdb/serializers/loader.py +10 -3
- angr/calling_conventions.py +2 -0
- angr/engines/pcode/behavior.py +7 -2
- angr/engines/pcode/cc.py +1 -0
- angr/engines/pcode/emulate.py +144 -104
- angr/engines/pcode/lifter.py +135 -79
- angr/knowledge_plugins/functions/function.py +28 -0
- angr/knowledge_plugins/functions/function_manager.py +48 -5
- angr/knowledge_plugins/propagations/states.py +14 -0
- angr/lib/angr_native.dll +0 -0
- angr/procedures/cgc/deallocate.py +5 -2
- angr/procedures/posix/gethostbyname.py +23 -8
- angr/project.py +4 -0
- angr/simos/__init__.py +2 -0
- angr/simos/simos.py +1 -0
- angr/simos/snimmuc_nxp.py +152 -0
- angr/state_plugins/history.py +3 -1
- angr/utils/graph.py +20 -18
- {angr-9.2.83.dist-info → angr-9.2.85.dist-info}/METADATA +9 -8
- {angr-9.2.83.dist-info → angr-9.2.85.dist-info}/RECORD +61 -59
- tests/analyses/cfg/test_cfg_rust_got_resolution.py +2 -1
- tests/analyses/cfg/test_jumptables.py +2 -1
- tests/analyses/decompiler/test_decompiler.py +155 -103
- tests/engines/pcode/test_emulate.py +607 -0
- tests/engines/test_java.py +609 -663
- tests/knowledge_plugins/functions/test_function_manager.py +13 -0
- tests/serialization/test_db.py +30 -0
- angr/analyses/decompiler/optimization_passes/eager_returns.py +0 -285
- {angr-9.2.83.dist-info → angr-9.2.85.dist-info}/LICENSE +0 -0
- {angr-9.2.83.dist-info → angr-9.2.85.dist-info}/WHEEL +0 -0
- {angr-9.2.83.dist-info → angr-9.2.85.dist-info}/entry_points.txt +0 -0
- {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()
|
tests/serialization/test_db.py
CHANGED
|
@@ -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
|
|
File without changes
|
|
File without changes
|