angr 9.2.162__cp310-abi3-manylinux2014_aarch64.whl → 9.2.164__cp310-abi3-manylinux2014_aarch64.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/ailment/converter_vex.py +1 -1
- angr/ailment/expression.py +17 -1
- angr/analyses/cfg/cfg_base.py +17 -14
- angr/analyses/cfg/cfg_emulated.py +5 -1
- angr/analyses/cfg/cfg_fast.py +27 -4
- angr/analyses/cfg/indirect_jump_resolvers/arm_elf_fast.py +11 -1
- angr/analyses/cfg/indirect_jump_resolvers/const_resolver.py +194 -41
- angr/analyses/decompiler/ail_simplifier.py +19 -5
- angr/analyses/decompiler/callsite_maker.py +33 -17
- angr/analyses/decompiler/clinic.py +6 -6
- angr/analyses/decompiler/graph_region.py +19 -0
- angr/analyses/decompiler/optimization_passes/deadblock_remover.py +1 -1
- angr/analyses/decompiler/region_identifier.py +22 -1
- angr/analyses/decompiler/structuring/phoenix.py +72 -20
- angr/analyses/decompiler/structuring/recursive_structurer.py +3 -4
- angr/analyses/decompiler/structuring/structurer_nodes.py +3 -0
- angr/analyses/decompiler/utils.py +17 -5
- angr/analyses/fcp/fcp.py +11 -10
- angr/analyses/flirt/flirt_sig.py +5 -2
- angr/analyses/reaching_definitions/function_handler.py +1 -1
- angr/analyses/reaching_definitions/function_handler_library/stdio.py +7 -6
- angr/analyses/reaching_definitions/function_handler_library/stdlib.py +10 -4
- angr/analyses/reaching_definitions/function_handler_library/string.py +13 -2
- angr/analyses/reaching_definitions/function_handler_library/unistd.py +7 -0
- angr/analyses/s_reaching_definitions/s_rda_view.py +2 -1
- angr/analyses/typehoon/typeconsts.py +3 -1
- angr/analyses/variable_recovery/engine_base.py +6 -10
- angr/blade.py +20 -15
- angr/engines/icicle.py +7 -2
- angr/knowledge_plugins/propagations/propagation_model.py +7 -0
- angr/project.py +5 -2
- angr/rustylib.abi3.so +0 -0
- angr/sim_type.py +18 -3
- angr/utils/constants.py +1 -1
- angr/utils/graph.py +1 -1
- angr/utils/vex.py +11 -0
- {angr-9.2.162.dist-info → angr-9.2.164.dist-info}/METADATA +5 -5
- {angr-9.2.162.dist-info → angr-9.2.164.dist-info}/RECORD +43 -42
- {angr-9.2.162.dist-info → angr-9.2.164.dist-info}/WHEEL +0 -0
- {angr-9.2.162.dist-info → angr-9.2.164.dist-info}/entry_points.txt +0 -0
- {angr-9.2.162.dist-info → angr-9.2.164.dist-info}/licenses/LICENSE +0 -0
- {angr-9.2.162.dist-info → angr-9.2.164.dist-info}/top_level.txt +0 -0
|
@@ -1216,6 +1216,7 @@ class Clinic(Analysis):
|
|
|
1216
1216
|
):
|
|
1217
1217
|
# found a single successor - replace the last statement
|
|
1218
1218
|
new_last_stmt = last_stmt.copy()
|
|
1219
|
+
assert isinstance(successors[0].addr, int)
|
|
1219
1220
|
new_last_stmt.target = ailment.Expr.Const(None, None, successors[0].addr, last_stmt.target.bits)
|
|
1220
1221
|
block.statements[-1] = new_last_stmt
|
|
1221
1222
|
|
|
@@ -1844,8 +1845,6 @@ class Clinic(Analysis):
|
|
|
1844
1845
|
if v.offset in vr.stack_offset_typevars:
|
|
1845
1846
|
tv = vr.stack_offset_typevars[v.offset]
|
|
1846
1847
|
tv_max_sizes[tv] = s
|
|
1847
|
-
# clean up existing types for this function
|
|
1848
|
-
var_manager.remove_types()
|
|
1849
1848
|
# TODO: Type inference for global variables
|
|
1850
1849
|
# run type inference
|
|
1851
1850
|
if self._must_struct:
|
|
@@ -2158,9 +2157,9 @@ class Clinic(Analysis):
|
|
|
2158
2157
|
}
|
|
2159
2158
|
else:
|
|
2160
2159
|
# global variable?
|
|
2161
|
-
global_vars = global_variables.get_global_variables(expr.
|
|
2160
|
+
global_vars = global_variables.get_global_variables(expr.value_int)
|
|
2162
2161
|
# detect if there is a related symbol
|
|
2163
|
-
if not global_vars and self.project.loader.find_object_containing(expr.
|
|
2162
|
+
if not global_vars and self.project.loader.find_object_containing(expr.value_int):
|
|
2164
2163
|
symbol = self.project.loader.find_symbol(expr.value)
|
|
2165
2164
|
if symbol is not None:
|
|
2166
2165
|
# Create a new global variable if there isn't one already
|
|
@@ -3041,12 +3040,12 @@ class Clinic(Analysis):
|
|
|
3041
3040
|
op0, op1 = addr.operands
|
|
3042
3041
|
if (
|
|
3043
3042
|
isinstance(op0, ailment.Expr.Const)
|
|
3044
|
-
and self.project.loader.find_object_containing(op0.
|
|
3043
|
+
and self.project.loader.find_object_containing(op0.value_int) is not None
|
|
3045
3044
|
):
|
|
3046
3045
|
return op0, op1
|
|
3047
3046
|
if (
|
|
3048
3047
|
isinstance(op1, ailment.Expr.Const)
|
|
3049
|
-
and self.project.loader.find_object_containing(op1.
|
|
3048
|
+
and self.project.loader.find_object_containing(op1.value_int) is not None
|
|
3050
3049
|
):
|
|
3051
3050
|
return op1, op0
|
|
3052
3051
|
return op0, op1 # best-effort guess
|
|
@@ -3279,6 +3278,7 @@ class Clinic(Analysis):
|
|
|
3279
3278
|
)
|
|
3280
3279
|
):
|
|
3281
3280
|
# found it!
|
|
3281
|
+
assert self.project.arch.sp_offset is not None
|
|
3282
3282
|
alloca_node = node
|
|
3283
3283
|
sp_equal_to = ailment.Expr.BinaryOp(
|
|
3284
3284
|
None,
|
|
@@ -271,6 +271,13 @@ class GraphRegion:
|
|
|
271
271
|
else:
|
|
272
272
|
replace_with_graph_with_successors = replace_with.graph_with_successors
|
|
273
273
|
|
|
274
|
+
# if complete_successors is True for RegionIdentifier, replace_with.graph_with_successors may include nodes
|
|
275
|
+
# and edges that are *only* reachable from immediate successors. we will want to remove these nodes and edges,
|
|
276
|
+
# otherwise we may end up structuring the same region twice!
|
|
277
|
+
replace_with_graph_with_successors = self._cleanup_graph_with_successors(
|
|
278
|
+
replace_with.graph, replace_with_graph_with_successors
|
|
279
|
+
)
|
|
280
|
+
|
|
274
281
|
self._replace_node_in_graph_with_subgraph(
|
|
275
282
|
self.graph,
|
|
276
283
|
self.successors,
|
|
@@ -289,6 +296,18 @@ class GraphRegion:
|
|
|
289
296
|
replace_with.head,
|
|
290
297
|
)
|
|
291
298
|
|
|
299
|
+
@staticmethod
|
|
300
|
+
def _cleanup_graph_with_successors(
|
|
301
|
+
graph: networkx.DiGraph, graph_with_successors: networkx.DiGraph
|
|
302
|
+
) -> networkx.DiGraph:
|
|
303
|
+
expected_nodes = set(graph)
|
|
304
|
+
for n in list(expected_nodes):
|
|
305
|
+
for succ in graph_with_successors.successors(n):
|
|
306
|
+
expected_nodes.add(succ)
|
|
307
|
+
if all(n in expected_nodes for n in graph_with_successors):
|
|
308
|
+
return graph_with_successors
|
|
309
|
+
return graph_with_successors.subgraph(expected_nodes).to_directed()
|
|
310
|
+
|
|
292
311
|
@staticmethod
|
|
293
312
|
def _replace_node_in_graph(graph: networkx.DiGraph, node, replace_with, removed_edges: set):
|
|
294
313
|
in_edges = [(src, dst) for src, dst in graph.in_edges(node) if (src, dst) not in removed_edges]
|
|
@@ -60,7 +60,7 @@ class DeadblockRemover(OptimizationPass):
|
|
|
60
60
|
blk
|
|
61
61
|
for blk in self._graph.nodes()
|
|
62
62
|
if (blk.addr != self._func.addr and self._graph.in_degree(blk) == 0)
|
|
63
|
-
or claripy.is_false(cond_proc.reaching_conditions
|
|
63
|
+
or claripy.is_false(cond_proc.reaching_conditions.get(blk, claripy.true()))
|
|
64
64
|
}
|
|
65
65
|
|
|
66
66
|
# fix up predecessors
|
|
@@ -99,7 +99,7 @@ class RegionIdentifier(Analysis):
|
|
|
99
99
|
|
|
100
100
|
def _analyze(self):
|
|
101
101
|
# make a copy of the graph
|
|
102
|
-
graph =
|
|
102
|
+
graph = self._pick_one_connected_component(self._graph, as_copy=True)
|
|
103
103
|
|
|
104
104
|
# preprocess: make it a super graph
|
|
105
105
|
self._make_supergraph(graph)
|
|
@@ -113,6 +113,27 @@ class RegionIdentifier(Analysis):
|
|
|
113
113
|
# make regions into block address lists
|
|
114
114
|
self.regions_by_block_addrs = self._make_regions_by_block_addrs()
|
|
115
115
|
|
|
116
|
+
def _pick_one_connected_component(self, digraph: networkx.DiGraph, as_copy: bool = False) -> networkx.DiGraph:
|
|
117
|
+
g = networkx.Graph(digraph)
|
|
118
|
+
components = list(networkx.connected_components(g))
|
|
119
|
+
if len(components) <= 1:
|
|
120
|
+
return networkx.DiGraph(digraph) if as_copy else digraph
|
|
121
|
+
|
|
122
|
+
the_component = None
|
|
123
|
+
largest_component = None
|
|
124
|
+
for component in components:
|
|
125
|
+
if largest_component is None or len(component) > len(largest_component):
|
|
126
|
+
largest_component = component
|
|
127
|
+
if any((block.addr, block.idx) == self.entry_node_addr for block in component):
|
|
128
|
+
the_component = component
|
|
129
|
+
break
|
|
130
|
+
|
|
131
|
+
if the_component is None:
|
|
132
|
+
the_component = largest_component
|
|
133
|
+
|
|
134
|
+
assert the_component is not None
|
|
135
|
+
return digraph.subgraph(the_component).to_directed()
|
|
136
|
+
|
|
116
137
|
@staticmethod
|
|
117
138
|
def _compute_node_order(graph: networkx.DiGraph) -> dict[Any, tuple[int, int]]:
|
|
118
139
|
sorted_nodes = GraphUtils.quasi_topological_sort_nodes(graph)
|
|
@@ -13,7 +13,7 @@ from angr.ailment.block import Block
|
|
|
13
13
|
from angr.ailment.statement import Statement, ConditionalJump, Jump, Label, Return
|
|
14
14
|
from angr.ailment.expression import Const, UnaryOp, MultiStatementExpression, BinaryOp
|
|
15
15
|
|
|
16
|
-
from angr.utils.graph import GraphUtils
|
|
16
|
+
from angr.utils.graph import GraphUtils, Dominators, compute_dominance_frontier
|
|
17
17
|
from angr.utils.ail import is_phi_assignment, is_head_controlled_loop_block
|
|
18
18
|
from angr.knowledge_plugins.cfg import IndirectJump, IndirectJumpType
|
|
19
19
|
from angr.utils.constants import SWITCH_MISSING_DEFAULT_NODE_ADDR
|
|
@@ -669,7 +669,7 @@ class PhoenixStructurer(StructurerBase):
|
|
|
669
669
|
continue_node = loop_head
|
|
670
670
|
|
|
671
671
|
is_while, result_while = self._refine_cyclic_is_while_loop(graph, fullgraph, loop_head, head_succs)
|
|
672
|
-
is_dowhile, result_dowhile = self._refine_cyclic_is_dowhile_loop(graph, fullgraph, loop_head
|
|
672
|
+
is_dowhile, result_dowhile = self._refine_cyclic_is_dowhile_loop(graph, fullgraph, loop_head)
|
|
673
673
|
|
|
674
674
|
continue_edges: list[tuple[BaseNode, BaseNode]] = []
|
|
675
675
|
outgoing_edges: list = []
|
|
@@ -702,22 +702,12 @@ class PhoenixStructurer(StructurerBase):
|
|
|
702
702
|
|
|
703
703
|
if loop_type is None:
|
|
704
704
|
# natural loop. select *any* exit edge to determine the successor
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
if loop_head is succ:
|
|
712
|
-
continue_edges.append((node, succ))
|
|
713
|
-
if successor_candidates:
|
|
714
|
-
successor_candidates = sorted(successor_candidates, key=lambda x: x.addr)
|
|
715
|
-
successor = successor_candidates[0]
|
|
716
|
-
# virtualize all other edges
|
|
717
|
-
for succ in successor_candidates:
|
|
718
|
-
for pred in fullgraph.predecessors(succ):
|
|
719
|
-
if pred in graph:
|
|
720
|
-
outgoing_edges.append((pred, succ))
|
|
705
|
+
is_natural, result_natural = self._refine_cyclic_make_natural_loop(graph, fullgraph, loop_head)
|
|
706
|
+
if not is_natural:
|
|
707
|
+
# cannot refine this loop
|
|
708
|
+
return False
|
|
709
|
+
assert result_natural is not None
|
|
710
|
+
continue_edges, outgoing_edges, successor = result_natural
|
|
721
711
|
|
|
722
712
|
if outgoing_edges:
|
|
723
713
|
# if there is a single successor, we convert all out-going edges into breaks;
|
|
@@ -963,8 +953,8 @@ class PhoenixStructurer(StructurerBase):
|
|
|
963
953
|
return True, (continue_edges, outgoing_edges, loop_head, successor)
|
|
964
954
|
return False, None
|
|
965
955
|
|
|
966
|
-
def _refine_cyclic_is_dowhile_loop(
|
|
967
|
-
self, graph, fullgraph, loop_head
|
|
956
|
+
def _refine_cyclic_is_dowhile_loop(
|
|
957
|
+
self, graph, fullgraph, loop_head
|
|
968
958
|
) -> tuple[bool, tuple[list, list, BaseNode, BaseNode] | None]:
|
|
969
959
|
# check if there is an out-going edge from the loop tail
|
|
970
960
|
head_preds = list(fullgraph.predecessors(loop_head))
|
|
@@ -996,6 +986,64 @@ class PhoenixStructurer(StructurerBase):
|
|
|
996
986
|
return True, (continue_edges, outgoing_edges, continue_node, successor)
|
|
997
987
|
return False, None
|
|
998
988
|
|
|
989
|
+
def _refine_cyclic_make_natural_loop(
|
|
990
|
+
self, graph, fullgraph, loop_head
|
|
991
|
+
) -> tuple[bool, tuple[list, list, Any] | None]:
|
|
992
|
+
continue_edges = []
|
|
993
|
+
outgoing_edges = []
|
|
994
|
+
|
|
995
|
+
# find dominance frontier
|
|
996
|
+
doms = Dominators(fullgraph, self._region.head)
|
|
997
|
+
dom_frontiers = compute_dominance_frontier(fullgraph, doms.dom)
|
|
998
|
+
|
|
999
|
+
if loop_head not in dom_frontiers:
|
|
1000
|
+
return False, None
|
|
1001
|
+
dom_frontier = dom_frontiers[loop_head]
|
|
1002
|
+
|
|
1003
|
+
# now this is a little complex
|
|
1004
|
+
dom_frontier = {node for node in dom_frontier if node is not loop_head}
|
|
1005
|
+
if len(dom_frontier) == 0:
|
|
1006
|
+
# the dominance frontier is empty (the loop head dominates all nodes in the full graph). however, this does
|
|
1007
|
+
# not mean that the loop head must dominate all the nodes, because we only have a limited view of the full
|
|
1008
|
+
# graph (e.g., some predecessors of the successor may not be in this full graph). as such, successors are
|
|
1009
|
+
# the ones that are in the fullgraph but not in the graph.
|
|
1010
|
+
successor_candidates = set()
|
|
1011
|
+
for node in networkx.descendants(graph, loop_head):
|
|
1012
|
+
for succ in fullgraph.successors(node):
|
|
1013
|
+
if succ not in graph:
|
|
1014
|
+
successor_candidates.add(succ)
|
|
1015
|
+
if loop_head is succ:
|
|
1016
|
+
continue_edges.append((node, succ))
|
|
1017
|
+
|
|
1018
|
+
else:
|
|
1019
|
+
# this loop has a single successor
|
|
1020
|
+
successor_candidates = dom_frontier
|
|
1021
|
+
# traverse the loop body to find all continue edges
|
|
1022
|
+
tmp_graph = networkx.DiGraph(graph)
|
|
1023
|
+
tmp_graph.remove_nodes_from(successor_candidates)
|
|
1024
|
+
for node in networkx.descendants(tmp_graph, loop_head):
|
|
1025
|
+
if tmp_graph.has_edge(node, loop_head):
|
|
1026
|
+
continue_edges.append((node, loop_head))
|
|
1027
|
+
|
|
1028
|
+
if len(successor_candidates) == 0:
|
|
1029
|
+
successor = None
|
|
1030
|
+
else:
|
|
1031
|
+
# one or multiple successors; try to pick a successor in graph, and prioritize the one with the lowest
|
|
1032
|
+
# address
|
|
1033
|
+
successor_candidates_in_graph = {nn for nn in successor_candidates if nn in graph}
|
|
1034
|
+
if successor_candidates_in_graph:
|
|
1035
|
+
# pick the one with the lowest address
|
|
1036
|
+
successor = next(iter(sorted(successor_candidates_in_graph, key=lambda x: x.addr)))
|
|
1037
|
+
else:
|
|
1038
|
+
successor = next(iter(sorted(successor_candidates, key=lambda x: x.addr)))
|
|
1039
|
+
# mark all edges as outgoing edges so they will be virtualized if they don't lead to the successor
|
|
1040
|
+
for node in successor_candidates:
|
|
1041
|
+
for pred in fullgraph.predecessors(node):
|
|
1042
|
+
if networkx.has_path(doms.dom, loop_head, pred):
|
|
1043
|
+
outgoing_edges.append((pred, node))
|
|
1044
|
+
|
|
1045
|
+
return True, (continue_edges, outgoing_edges, successor)
|
|
1046
|
+
|
|
999
1047
|
def _analyze_acyclic(self) -> bool:
|
|
1000
1048
|
# match against known schemas
|
|
1001
1049
|
l.debug("Matching acyclic schemas for region %r.", self._region)
|
|
@@ -1219,6 +1267,10 @@ class PhoenixStructurer(StructurerBase):
|
|
|
1219
1267
|
node_a = next(iter(nn for nn in graph.nodes if nn.addr == target), None)
|
|
1220
1268
|
if node_a is None:
|
|
1221
1269
|
return False
|
|
1270
|
+
if node_a is self._region.head:
|
|
1271
|
+
# avoid structuring if node_a is the region head; this means the current node is a duplicated switch-case
|
|
1272
|
+
# head (instead of the original one), which is not something we want to structure
|
|
1273
|
+
return False
|
|
1222
1274
|
|
|
1223
1275
|
# the default case
|
|
1224
1276
|
node_b_addr = next(iter(t for t in successor_addrs if t != target), None)
|
|
@@ -3,8 +3,6 @@ import itertools
|
|
|
3
3
|
from typing import TYPE_CHECKING
|
|
4
4
|
import logging
|
|
5
5
|
|
|
6
|
-
import networkx
|
|
7
|
-
|
|
8
6
|
from angr.analyses import Analysis, register_analysis
|
|
9
7
|
from angr.analyses.decompiler.condition_processor import ConditionProcessor
|
|
10
8
|
from angr.analyses.decompiler.graph_region import GraphRegion
|
|
@@ -12,6 +10,7 @@ from angr.analyses.decompiler.jumptable_entry_condition_rewriter import JumpTabl
|
|
|
12
10
|
from angr.analyses.decompiler.empty_node_remover import EmptyNodeRemover
|
|
13
11
|
from angr.analyses.decompiler.jump_target_collector import JumpTargetCollector
|
|
14
12
|
from angr.analyses.decompiler.redundant_label_remover import RedundantLabelRemover
|
|
13
|
+
from angr.utils.graph import GraphUtils
|
|
15
14
|
from .structurer_nodes import BaseNode
|
|
16
15
|
from .structurer_base import StructurerBase
|
|
17
16
|
from .dream import DreamStructurer
|
|
@@ -61,7 +60,7 @@ class RecursiveStructurer(Analysis):
|
|
|
61
60
|
current_region = stack[-1]
|
|
62
61
|
|
|
63
62
|
has_region = False
|
|
64
|
-
for node in
|
|
63
|
+
for node in GraphUtils.dfs_postorder_nodes_deterministic(current_region.graph, current_region.head):
|
|
65
64
|
subnodes = []
|
|
66
65
|
if type(node) is GraphRegion:
|
|
67
66
|
if node.cyclic:
|
|
@@ -177,7 +176,7 @@ class RecursiveStructurer(Analysis):
|
|
|
177
176
|
for node in region.graph.nodes:
|
|
178
177
|
if not isinstance(node, BaseNode):
|
|
179
178
|
continue
|
|
180
|
-
if node.addr == self.function.addr:
|
|
179
|
+
if self.function is not None and node.addr == self.function.addr:
|
|
181
180
|
return node
|
|
182
181
|
if min_node is None or (min_node.addr is not None and node.addr is not None and min_node.addr < node.addr):
|
|
183
182
|
min_node = node
|
|
@@ -392,6 +392,9 @@ class IncompleteSwitchCaseNode(BaseNode):
|
|
|
392
392
|
self.head = head
|
|
393
393
|
self.cases: list = cases
|
|
394
394
|
|
|
395
|
+
def __repr__(self):
|
|
396
|
+
return f"<IncompleteSwitchCase {self.addr:#x} with {len(self.cases)} cases>"
|
|
397
|
+
|
|
395
398
|
|
|
396
399
|
#
|
|
397
400
|
# The following classes are custom AIL statements (not nodes, unfortunately)
|
|
@@ -158,10 +158,14 @@ def switch_extract_cmp_bounds(
|
|
|
158
158
|
return None
|
|
159
159
|
|
|
160
160
|
# TODO: Add more operations
|
|
161
|
-
if isinstance(last_stmt.condition, ailment.Expr.BinaryOp) and last_stmt.condition.op
|
|
161
|
+
if isinstance(last_stmt.condition, ailment.Expr.BinaryOp) and last_stmt.condition.op in {"CmpLE", "CmpLT"}:
|
|
162
162
|
if not isinstance(last_stmt.condition.operands[1], ailment.Expr.Const):
|
|
163
163
|
return None
|
|
164
|
-
cmp_ub =
|
|
164
|
+
cmp_ub = (
|
|
165
|
+
last_stmt.condition.operands[1].value
|
|
166
|
+
if last_stmt.condition.op == "CmpLE"
|
|
167
|
+
else last_stmt.condition.operands[1].value - 1
|
|
168
|
+
)
|
|
165
169
|
cmp_lb = 0
|
|
166
170
|
cmp = last_stmt.condition.operands[0]
|
|
167
171
|
if (
|
|
@@ -250,6 +254,10 @@ def switch_extract_bitwiseand_jumptable_info(last_stmt: ailment.Stmt.Jump) -> tu
|
|
|
250
254
|
size=4, endness=Iend_LE) + 0x4530e4<32>))
|
|
251
255
|
)
|
|
252
256
|
|
|
257
|
+
Another example:
|
|
258
|
+
|
|
259
|
+
Load(addr=(((vvar_9{reg 36} & 0x3<32>) * 0x4<32>) + 0x42cd28<32>), size=4, endness=Iend_LE)
|
|
260
|
+
|
|
253
261
|
:param last_stmt: The last statement of the switch-case header node.
|
|
254
262
|
:return: A tuple of (index expression, lower bound, upper bound), or None
|
|
255
263
|
"""
|
|
@@ -269,16 +277,20 @@ def switch_extract_bitwiseand_jumptable_info(last_stmt: ailment.Stmt.Jump) -> tu
|
|
|
269
277
|
continue
|
|
270
278
|
if isinstance(target, ailment.Expr.BinaryOp) and target.op == "Add":
|
|
271
279
|
if isinstance(target.operands[0], ailment.Expr.Const) and isinstance(target.operands[1], ailment.Expr.Load):
|
|
272
|
-
jump_addr_offset = target.operands[0]
|
|
280
|
+
jump_addr_offset = target.operands[0].value
|
|
273
281
|
jumptable_load_addr = target.operands[1].addr
|
|
274
282
|
break
|
|
275
283
|
if isinstance(target.operands[1], ailment.Expr.Const) and isinstance(target.operands[0], ailment.Expr.Load):
|
|
276
|
-
jump_addr_offset = target.operands[1]
|
|
284
|
+
jump_addr_offset = target.operands[1].value
|
|
277
285
|
jumptable_load_addr = target.operands[0].addr
|
|
278
286
|
break
|
|
279
287
|
return None
|
|
280
288
|
if isinstance(target, ailment.Expr.Const):
|
|
281
289
|
return None
|
|
290
|
+
if isinstance(target, ailment.Expr.Load):
|
|
291
|
+
jumptable_load_addr = target.addr
|
|
292
|
+
jump_addr_offset = 0
|
|
293
|
+
break
|
|
282
294
|
break
|
|
283
295
|
|
|
284
296
|
if jump_addr_offset is None or jumptable_load_addr is None:
|
|
@@ -655,7 +667,7 @@ def _flatten_structured_node(packed_node: SequenceNode | MultiNode) -> list[ailm
|
|
|
655
667
|
|
|
656
668
|
def _find_node_in_graph(node: ailment.Block, graph: networkx.DiGraph) -> ailment.Block | None:
|
|
657
669
|
for bb in graph:
|
|
658
|
-
if bb.addr == node.addr and bb.idx == node.idx:
|
|
670
|
+
if isinstance(bb, ailment.Block) and bb.addr == node.addr and bb.idx == node.idx:
|
|
659
671
|
return bb
|
|
660
672
|
return None
|
|
661
673
|
|
angr/analyses/fcp/fcp.py
CHANGED
|
@@ -343,16 +343,17 @@ class FastConstantPropagation(Analysis):
|
|
|
343
343
|
engine.process(state, block=block)
|
|
344
344
|
|
|
345
345
|
# if the node ends with a function call, call _handle_function
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
346
|
+
if node in func_graph_with_callees:
|
|
347
|
+
succs = list(func_graph_with_callees.successors(node))
|
|
348
|
+
if any(isinstance(succ, (Function, HookNode)) for succ in succs):
|
|
349
|
+
callee = next(succ for succ in succs if isinstance(succ, (Function, HookNode)))
|
|
350
|
+
if isinstance(callee, HookNode):
|
|
351
|
+
# attempt to convert it into a function
|
|
352
|
+
if self.kb.functions.contains_addr(callee.addr):
|
|
353
|
+
callee = self.kb.functions.get_by_addr(callee.addr)
|
|
354
|
+
else:
|
|
355
|
+
callee = None
|
|
356
|
+
state = self._handle_function(state, callee)
|
|
356
357
|
|
|
357
358
|
states[node] = state
|
|
358
359
|
|
angr/analyses/flirt/flirt_sig.py
CHANGED
|
@@ -146,7 +146,7 @@ class FlirtSignatureParsed:
|
|
|
146
146
|
name_lst = []
|
|
147
147
|
name_end = False # in case the function name is too long...
|
|
148
148
|
for _ in range(1024): # max length of a function name
|
|
149
|
-
if next_byte < 0x20:
|
|
149
|
+
if next_byte < 0x20 or next_byte >= 0x80:
|
|
150
150
|
name_end = True
|
|
151
151
|
break
|
|
152
152
|
name_lst.append(next_byte)
|
|
@@ -347,7 +347,10 @@ class FlirtSignatureParsed:
|
|
|
347
347
|
# is it compressed?
|
|
348
348
|
if obj.features & FlirtFeatureFlag.FEATURE_COMPRESSED:
|
|
349
349
|
data = file_obj.read()
|
|
350
|
-
|
|
350
|
+
try:
|
|
351
|
+
decompressed = BytesIO(zlib.decompress(data))
|
|
352
|
+
except zlib.error as ex:
|
|
353
|
+
raise FlirtSignatureError(f"Failed to decompress FLIRT signature: {ex}") from ex
|
|
351
354
|
file_obj = decompressed
|
|
352
355
|
|
|
353
356
|
root = obj.parse_tree(file_obj, root=True)
|
|
@@ -575,7 +575,7 @@ class FunctionHandler:
|
|
|
575
575
|
sub_rda = state.analysis.project.analyses.ReachingDefinitions(
|
|
576
576
|
data.function,
|
|
577
577
|
observe_all=state.analysis._observe_all,
|
|
578
|
-
observation_points=list(state.analysis._observation_points or [])
|
|
578
|
+
observation_points=list(state.analysis._observation_points or []) + return_observation_points,
|
|
579
579
|
observe_callback=state.analysis._observe_callback,
|
|
580
580
|
dep_graph=state.dep_graph,
|
|
581
581
|
function_handler=self,
|
|
@@ -156,14 +156,14 @@ class LibcStdioHandlers(FunctionHandler):
|
|
|
156
156
|
@FunctionCallDataUnwrapped.decorate
|
|
157
157
|
def handle_impl_fread(self, state: ReachingDefinitionsState, data: FunctionCallDataUnwrapped):
|
|
158
158
|
size = state.get_concrete_value(data.args_atoms[1]) or 1
|
|
159
|
-
nmemb = state.get_concrete_value(data.args_atoms[
|
|
159
|
+
nmemb = state.get_concrete_value(data.args_atoms[2]) or 2
|
|
160
160
|
dst_atom = state.deref(data.args_atoms[0], size * nmemb)
|
|
161
161
|
data.depends(dst_atom, StdinAtom("fread", size * nmemb))
|
|
162
162
|
|
|
163
163
|
@FunctionCallDataUnwrapped.decorate
|
|
164
164
|
def handle_impl_fwrite(self, state: ReachingDefinitionsState, data: FunctionCallDataUnwrapped):
|
|
165
165
|
size = state.get_concrete_value(data.args_atoms[1]) or 1
|
|
166
|
-
nmemb = state.get_concrete_value(data.args_atoms[
|
|
166
|
+
nmemb = state.get_concrete_value(data.args_atoms[2]) or 2
|
|
167
167
|
src_atom = state.deref(data.args_atoms[0], size * nmemb)
|
|
168
168
|
data.depends(StdoutAtom("fwrite", size * nmemb), src_atom, value=state.get_values(src_atom))
|
|
169
169
|
|
|
@@ -206,8 +206,8 @@ def handle_printf(
|
|
|
206
206
|
elif fmt == "%u":
|
|
207
207
|
buf_atoms = atom
|
|
208
208
|
buf_data = state.get_concrete_value(buf_atoms)
|
|
209
|
-
if buf_data
|
|
210
|
-
|
|
209
|
+
buf_data = str(buf_data).encode() if buf_data else b"0"
|
|
210
|
+
|
|
211
211
|
elif fmt == "%d":
|
|
212
212
|
buf_atoms = atom
|
|
213
213
|
buf_data = state.get_concrete_value(buf_atoms)
|
|
@@ -215,11 +215,12 @@ def handle_printf(
|
|
|
215
215
|
if buf_data >= 2**31:
|
|
216
216
|
buf_data -= 2**32
|
|
217
217
|
buf_data = str(buf_data).encode()
|
|
218
|
+
else:
|
|
219
|
+
buf_data = b"0"
|
|
218
220
|
elif fmt == "%c":
|
|
219
221
|
buf_atoms = atom
|
|
220
222
|
buf_data = state.get_concrete_value(atom)
|
|
221
|
-
if buf_data
|
|
222
|
-
buf_data = chr(buf_data).encode()
|
|
223
|
+
buf_data = chr(buf_data).encode() if buf_data else b"0"
|
|
223
224
|
else:
|
|
224
225
|
_l.warning("Unimplemented printf format string %s", fmt)
|
|
225
226
|
buf_atoms = set()
|
|
@@ -64,21 +64,25 @@ class LibcStdlibHandlers(FunctionHandler):
|
|
|
64
64
|
buf_value = int(buf_value.decode().strip("\0"))
|
|
65
65
|
except ValueError:
|
|
66
66
|
buf_value = 0
|
|
67
|
+
else:
|
|
68
|
+
buf_value = 0 # Make the value concrete if it cannot be parsed
|
|
67
69
|
data.depends(data.ret_atoms, buf_atoms, value=buf_value)
|
|
68
70
|
|
|
69
71
|
@FunctionCallDataUnwrapped.decorate
|
|
70
72
|
def handle_impl_malloc(self, state: ReachingDefinitionsState, data: FunctionCallDataUnwrapped):
|
|
71
73
|
malloc_size = state.get_concrete_value(data.args_atoms[0]) or 48
|
|
72
74
|
heap_ptr = state.heap_allocator.allocate(malloc_size)
|
|
73
|
-
|
|
75
|
+
heap_addr = state.heap_address(heap_ptr)
|
|
76
|
+
data.depends(data.ret_atoms, value=heap_addr)
|
|
74
77
|
|
|
75
78
|
@FunctionCallDataUnwrapped.decorate
|
|
76
79
|
def handle_impl_calloc(self, state: ReachingDefinitionsState, data: FunctionCallDataUnwrapped):
|
|
77
80
|
nmemb = state.get_concrete_value(data.args_atoms[0]) or 48
|
|
78
81
|
size = state.get_concrete_value(data.args_atoms[1]) or 1
|
|
79
|
-
heap_ptr = state.
|
|
80
|
-
|
|
81
|
-
data.depends(
|
|
82
|
+
heap_ptr = state.heap_allocator.allocate(nmemb * size)
|
|
83
|
+
heap_addr = state.heap_address(heap_ptr)
|
|
84
|
+
data.depends(state.deref(heap_addr, nmemb * size), value=0)
|
|
85
|
+
data.depends(data.ret_atoms, value=heap_addr)
|
|
82
86
|
|
|
83
87
|
@FunctionCallDataUnwrapped.decorate
|
|
84
88
|
def handle_impl_getenv(self, state: ReachingDefinitionsState, data: FunctionCallDataUnwrapped):
|
|
@@ -156,6 +160,8 @@ class LibcStdlibHandlers(FunctionHandler):
|
|
|
156
160
|
def handle_impl_execve(self, state: ReachingDefinitionsState, data: FunctionCallDataUnwrapped):
|
|
157
161
|
argv_value = state.get_one_value(data.args_atoms[1])
|
|
158
162
|
if argv_value is None:
|
|
163
|
+
# Model execve with unknown argv - still has system effects
|
|
164
|
+
data.depends(SystemAtom(1), data.args_atoms[0], value=state.get_values(data.args_atoms[0]))
|
|
159
165
|
return
|
|
160
166
|
|
|
161
167
|
nonce = random.randint(1, 999999999)
|
|
@@ -30,7 +30,7 @@ class LibcStringHandlers(FunctionHandler):
|
|
|
30
30
|
dest_value = src0_value.concat(MultiValues(top_val))
|
|
31
31
|
dest_atom = state.deref(data.args_atoms[0], len(dest_value) // 8, endness=archinfo.Endness.BE)
|
|
32
32
|
else:
|
|
33
|
-
dest_value =
|
|
33
|
+
dest_value = state.top(state.arch.bits)
|
|
34
34
|
dest_atom = src0_atom
|
|
35
35
|
if src0_atom is not None and src1_atom is not None:
|
|
36
36
|
data.depends(dest_atom, src0_atom, src1_atom, value=dest_value)
|
|
@@ -109,7 +109,8 @@ class LibcStringHandlers(FunctionHandler):
|
|
|
109
109
|
src_str = state.get_values(src_atom)
|
|
110
110
|
malloc_size = len(src_str) // 8 if src_str is not None else 1
|
|
111
111
|
heap_ptr = state.heap_allocator.allocate(malloc_size)
|
|
112
|
-
|
|
112
|
+
heap_addr = state.heap_address(heap_ptr)
|
|
113
|
+
dst_atom = state.deref(heap_addr, malloc_size)
|
|
113
114
|
data.depends(dst_atom, src_atom, value=src_str)
|
|
114
115
|
data.depends(data.ret_atoms, data.args_atoms[0], value=state.get_values(data.args_atoms[0]))
|
|
115
116
|
|
|
@@ -121,6 +122,16 @@ class LibcStringHandlers(FunctionHandler):
|
|
|
121
122
|
dst_atom = state.deref(data.args_atoms[0], size)
|
|
122
123
|
if src_atom is not None:
|
|
123
124
|
data.depends(dst_atom, src_atom, value=state.get_values(src_atom))
|
|
125
|
+
else:
|
|
126
|
+
# When size is unknown, use default size and create TOP value
|
|
127
|
+
default_size = state.arch.bytes
|
|
128
|
+
src_atom = state.deref(data.args_atoms[1], default_size)
|
|
129
|
+
dst_atom = state.deref(data.args_atoms[0], default_size)
|
|
130
|
+
if src_atom is not None:
|
|
131
|
+
top_val = state.top(default_size * 8)
|
|
132
|
+
for defn in state.get_definitions(src_atom):
|
|
133
|
+
top_val = state.annotate_with_def(top_val, defn)
|
|
134
|
+
data.depends(dst_atom, src_atom, value=MultiValues(top_val))
|
|
124
135
|
data.depends(data.ret_atoms, data.args_atoms[0], value=state.get_values(data.args_atoms[0]))
|
|
125
136
|
|
|
126
137
|
@FunctionCallDataUnwrapped.decorate
|
|
@@ -32,6 +32,9 @@ class LibcUnistdHandlers(FunctionHandler):
|
|
|
32
32
|
buf_data = state.top(size * 8) if size is not None else state.top(state.arch.bits)
|
|
33
33
|
|
|
34
34
|
data.depends(dst_atom, fd_atom, value=buf_data)
|
|
35
|
+
# Model return value - number of bytes read (could be 0 to size, or -1 for error)
|
|
36
|
+
ret_val = state.top(state.arch.bits) # Unknown number of bytes read
|
|
37
|
+
data.depends(data.ret_atoms, fd_atom, value=ret_val)
|
|
35
38
|
|
|
36
39
|
handle_impl_recv = handle_impl_recvfrom = handle_impl_read
|
|
37
40
|
|
|
@@ -41,4 +44,8 @@ class LibcUnistdHandlers(FunctionHandler):
|
|
|
41
44
|
src_atom = state.deref(data.args_atoms[1], size)
|
|
42
45
|
data.depends(StdoutAtom(data.function.name, size), src_atom, value=state.get_values(src_atom))
|
|
43
46
|
|
|
47
|
+
# Model return value - number of bytes written (could be 0 to size, or -1 for error)
|
|
48
|
+
ret_val = state.top(state.arch.bits) # Unknown number of bytes written
|
|
49
|
+
data.depends(data.ret_atoms, value=ret_val)
|
|
50
|
+
|
|
44
51
|
handle_impl_send = handle_impl_write
|
|
@@ -37,7 +37,8 @@ class RegVVarPredicate:
|
|
|
37
37
|
if cc is not None:
|
|
38
38
|
reg_list = cc.CALLER_SAVED_REGS
|
|
39
39
|
if isinstance(cc.RETURN_VAL, SimRegArg):
|
|
40
|
-
reg_list
|
|
40
|
+
# do not update reg_list directly, otherwise you may update cc.CALLER_SAVED_REGS!
|
|
41
|
+
reg_list = [*reg_list, cc.RETURN_VAL.reg_name]
|
|
41
42
|
return {self.arch.registers[reg_name][0] for reg_name in reg_list}
|
|
42
43
|
log.warning("Cannot determine registers that are clobbered by call statement %r.", stmt)
|
|
43
44
|
return set()
|
|
@@ -245,7 +245,9 @@ class Struct(TypeConstant):
|
|
|
245
245
|
if not self.fields:
|
|
246
246
|
return 0
|
|
247
247
|
max_field_off = max(self.fields.keys())
|
|
248
|
-
return max_field_off +
|
|
248
|
+
return max_field_off + (
|
|
249
|
+
self.fields[max_field_off].size if not isinstance(self.fields[max_field_off], BottomType) else 1
|
|
250
|
+
)
|
|
249
251
|
|
|
250
252
|
@memoize
|
|
251
253
|
def __repr__(self, memo=None):
|
|
@@ -142,6 +142,8 @@ class SimEngineVRBase(
|
|
|
142
142
|
) -> list[tuple[SimVariable, int]]:
|
|
143
143
|
data = richr_addr.data
|
|
144
144
|
|
|
145
|
+
variable: SimVariable | None = None
|
|
146
|
+
|
|
145
147
|
if self.state.is_stack_address(data):
|
|
146
148
|
# this is a stack address
|
|
147
149
|
# extract stack offset
|
|
@@ -157,7 +159,6 @@ class SimEngineVRBase(
|
|
|
157
159
|
for candidate, offset in var_candidates:
|
|
158
160
|
if isinstance(candidate, SimStackVariable) and candidate.offset == stack_offset:
|
|
159
161
|
existing_vars.append((candidate, offset))
|
|
160
|
-
variable = None
|
|
161
162
|
if existing_vars:
|
|
162
163
|
variable, _ = existing_vars[0]
|
|
163
164
|
|
|
@@ -179,12 +180,7 @@ class SimEngineVRBase(
|
|
|
179
180
|
existing_vars.append((var, var_stack_offset))
|
|
180
181
|
|
|
181
182
|
if not existing_vars:
|
|
182
|
-
existing_vars = [
|
|
183
|
-
(v, 0)
|
|
184
|
-
for v in self.state.variable_manager[self.func_addr].find_variables_by_stack_offset(
|
|
185
|
-
stack_offset
|
|
186
|
-
)
|
|
187
|
-
]
|
|
183
|
+
existing_vars = [(v, 0) for v in variable_manager.find_variables_by_stack_offset(stack_offset)]
|
|
188
184
|
|
|
189
185
|
if not existing_vars:
|
|
190
186
|
# no variables exist
|
|
@@ -193,10 +189,10 @@ class SimEngineVRBase(
|
|
|
193
189
|
stack_offset,
|
|
194
190
|
lea_size,
|
|
195
191
|
base="bp",
|
|
196
|
-
ident=
|
|
192
|
+
ident=variable_manager.next_variable_ident("stack"),
|
|
197
193
|
region=self.func_addr,
|
|
198
194
|
)
|
|
199
|
-
|
|
195
|
+
variable_manager.add_variable("stack", stack_offset, variable)
|
|
200
196
|
l.debug("Identified a new stack variable %s at %#x.", variable, self.ins_addr)
|
|
201
197
|
existing_vars.append((variable, 0))
|
|
202
198
|
|
|
@@ -563,7 +559,7 @@ class SimEngineVRBase(
|
|
|
563
559
|
|
|
564
560
|
if isinstance(stack_offset, int):
|
|
565
561
|
expr = data.data
|
|
566
|
-
if isinstance(expr, claripy.ast.
|
|
562
|
+
if isinstance(expr, claripy.ast.BV) and expr.size() > 1024:
|
|
567
563
|
# we don't write more than 256 bytes to the stack at a time for performance reasons
|
|
568
564
|
expr = expr[expr.size() - 1 : expr.size() - 1024]
|
|
569
565
|
expr = self.state.annotate_with_variables(expr, [(variable_offset, variable)])
|