angr 9.2.160__cp310-abi3-macosx_11_0_arm64.whl → 9.2.162__cp310-abi3-macosx_11_0_arm64.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 +4 -1
- angr/analyses/analysis.py +0 -1
- angr/analyses/cfg/cfg_base.py +5 -1
- angr/analyses/decompiler/ail_simplifier.py +101 -2
- angr/analyses/decompiler/block_simplifier.py +13 -8
- angr/analyses/decompiler/clinic.py +1 -0
- angr/analyses/decompiler/condition_processor.py +24 -0
- angr/analyses/decompiler/counters/call_counter.py +11 -1
- angr/analyses/decompiler/decompiler.py +3 -1
- angr/analyses/decompiler/graph_region.py +11 -2
- angr/analyses/decompiler/optimization_passes/const_prop_reverter.py +1 -1
- angr/analyses/decompiler/optimization_passes/lowered_switch_simplifier.py +1 -0
- angr/analyses/decompiler/optimization_passes/optimization_pass.py +31 -11
- angr/analyses/decompiler/optimization_passes/return_duplicator_low.py +2 -0
- angr/analyses/decompiler/peephole_optimizations/__init__.py +4 -4
- angr/analyses/decompiler/peephole_optimizations/eager_eval.py +53 -0
- angr/analyses/decompiler/peephole_optimizations/modulo_simplifier.py +89 -0
- angr/analyses/decompiler/peephole_optimizations/{const_mull_a_shift.py → optimized_div_simplifier.py} +139 -25
- angr/analyses/decompiler/peephole_optimizations/remove_redundant_bitmasks.py +18 -9
- angr/analyses/decompiler/region_simplifiers/goto.py +3 -3
- angr/analyses/decompiler/region_simplifiers/if_.py +2 -2
- angr/analyses/decompiler/region_simplifiers/loop.py +2 -2
- angr/analyses/decompiler/structured_codegen/c.py +3 -3
- angr/analyses/decompiler/structuring/dream.py +1 -1
- angr/analyses/decompiler/structuring/phoenix.py +138 -99
- angr/analyses/decompiler/structuring/recursive_structurer.py +3 -2
- angr/analyses/decompiler/structuring/sailr.py +51 -43
- angr/analyses/decompiler/structuring/structurer_base.py +2 -3
- angr/analyses/deobfuscator/string_obf_opt_passes.py +1 -1
- angr/analyses/disassembly.py +1 -1
- angr/analyses/reaching_definitions/function_handler.py +1 -0
- angr/analyses/s_propagator.py +2 -2
- angr/analyses/s_reaching_definitions/s_rda_model.py +1 -0
- angr/analyses/s_reaching_definitions/s_reaching_definitions.py +5 -2
- angr/analyses/variable_recovery/engine_base.py +17 -1
- angr/analyses/variable_recovery/variable_recovery_base.py +30 -2
- angr/analyses/variable_recovery/variable_recovery_fast.py +11 -2
- angr/emulator.py +143 -0
- angr/engines/concrete.py +66 -0
- angr/engines/icicle.py +66 -30
- angr/exploration_techniques/driller_core.py +2 -2
- angr/knowledge_plugins/functions/function.py +1 -1
- angr/knowledge_plugins/functions/function_manager.py +1 -2
- angr/project.py +7 -0
- angr/rustylib.abi3.so +0 -0
- angr/sim_type.py +16 -8
- angr/simos/javavm.py +1 -1
- angr/unicornlib.dylib +0 -0
- angr/utils/graph.py +48 -13
- angr/utils/library.py +13 -12
- angr/utils/ssa/__init__.py +57 -5
- {angr-9.2.160.dist-info → angr-9.2.162.dist-info}/METADATA +5 -5
- {angr-9.2.160.dist-info → angr-9.2.162.dist-info}/RECORD +57 -55
- angr/analyses/decompiler/peephole_optimizations/a_sub_a_div_const_mul_const.py +0 -57
- {angr-9.2.160.dist-info → angr-9.2.162.dist-info}/WHEEL +0 -0
- {angr-9.2.160.dist-info → angr-9.2.162.dist-info}/entry_points.txt +0 -0
- {angr-9.2.160.dist-info → angr-9.2.162.dist-info}/licenses/LICENSE +0 -0
- {angr-9.2.160.dist-info → angr-9.2.162.dist-info}/top_level.txt +0 -0
|
@@ -26,7 +26,9 @@ class SAILRStructurer(PhoenixStructurer):
|
|
|
26
26
|
|
|
27
27
|
NAME = "sailr"
|
|
28
28
|
|
|
29
|
-
def __init__(self, region, improve_phoenix=True, **kwargs):
|
|
29
|
+
def __init__(self, region, improve_phoenix=True, postdom_max_edges=10, postdom_max_graph_size=50, **kwargs):
|
|
30
|
+
self._postdom_max_edges = postdom_max_edges
|
|
31
|
+
self._postdom_max_graph_size = postdom_max_graph_size
|
|
30
32
|
super().__init__(
|
|
31
33
|
region,
|
|
32
34
|
improve_algorithm=improve_phoenix,
|
|
@@ -46,44 +48,50 @@ class SAILRStructurer(PhoenixStructurer):
|
|
|
46
48
|
except StopIteration:
|
|
47
49
|
entry_node = None
|
|
48
50
|
|
|
51
|
+
edges = sorted(edges, key=lambda edge: (edge[0].addr, edge[1].addr))
|
|
49
52
|
best_edges = edges
|
|
50
53
|
if entry_node is not None:
|
|
51
|
-
#
|
|
52
|
-
# so we collect them for each candidate edge
|
|
53
|
-
edge_postdom_count = {}
|
|
54
|
+
# collect sibling counts for edges
|
|
54
55
|
edge_sibling_count = {}
|
|
55
56
|
for edge in edges:
|
|
56
57
|
_, dst = edge
|
|
57
|
-
|
|
58
|
-
graph_copy.remove_edge(*edge)
|
|
59
|
-
sibling_cnt = graph_copy.in_degree(dst)
|
|
58
|
+
sibling_cnt = graph.in_degree[dst] - 1
|
|
60
59
|
if sibling_cnt == 0:
|
|
61
60
|
continue
|
|
62
|
-
|
|
63
61
|
edge_sibling_count[edge] = sibling_cnt
|
|
64
|
-
post_dom_graph = PostDominators(graph_copy, entry_node).post_dom
|
|
65
|
-
post_doms = set()
|
|
66
|
-
for postdom_node, dominatee in post_dom_graph.edges():
|
|
67
|
-
if not isinstance(postdom_node, TemporaryNode) and not isinstance(dominatee, TemporaryNode):
|
|
68
|
-
post_doms.add((postdom_node, dominatee))
|
|
69
|
-
edge_postdom_count[edge] = len(post_doms)
|
|
70
|
-
|
|
71
|
-
# H1: the edge that has the least amount of sibling edges should be virtualized first
|
|
72
|
-
# this is believed to reduce the amount of virtualization needed in future rounds and increase
|
|
73
|
-
# the edges that enter a single outer-scope if-stmt
|
|
74
|
-
if edge_sibling_count:
|
|
75
|
-
min_sibling_count = min(edge_sibling_count.values())
|
|
76
|
-
best_edges = [edge for edge, cnt in edge_sibling_count.items() if cnt == min_sibling_count]
|
|
77
|
-
if len(best_edges) == 1:
|
|
78
|
-
return best_edges
|
|
79
62
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
63
|
+
# H1: the edge that has the least amount of sibling edges should be virtualized first
|
|
64
|
+
# this is believed to reduce the amount of virtualization needed in future rounds and increase
|
|
65
|
+
# the edges that enter a single outer-scope if-stmt
|
|
66
|
+
if edge_sibling_count:
|
|
67
|
+
min_sibling_count = min(edge_sibling_count.values())
|
|
68
|
+
best_edges = [edge for edge, cnt in edge_sibling_count.items() if cnt == min_sibling_count]
|
|
69
|
+
if len(best_edges) == 1:
|
|
70
|
+
return best_edges
|
|
71
|
+
|
|
72
|
+
if len(edges) <= self._postdom_max_edges and len(graph) <= self._postdom_max_graph_size:
|
|
73
|
+
# dominator analysis is expensive, so we only do it for small graphs
|
|
74
|
+
edge_postdom_count = {}
|
|
75
|
+
graph_copy = networkx.DiGraph(graph)
|
|
76
|
+
for edge in edges:
|
|
77
|
+
_, dst = edge
|
|
78
|
+
graph_copy.remove_edge(*edge)
|
|
79
|
+
post_dom_graph = PostDominators(graph_copy, entry_node).post_dom
|
|
80
|
+
post_doms = set()
|
|
81
|
+
for postdom_node, dominatee in post_dom_graph.edges():
|
|
82
|
+
if not isinstance(postdom_node, TemporaryNode) and not isinstance(dominatee, TemporaryNode):
|
|
83
|
+
post_doms.add((postdom_node, dominatee))
|
|
84
|
+
edge_postdom_count[edge] = len(post_doms)
|
|
85
|
+
# add back the edge
|
|
86
|
+
graph_copy.add_edge(*edge)
|
|
87
|
+
|
|
88
|
+
# create the next heuristic based on the best edges from the previous heuristic
|
|
89
|
+
filtered_edge_postdom_count = edge_postdom_count.copy()
|
|
90
|
+
for edge in list(edge_postdom_count.keys()):
|
|
91
|
+
if edge not in best_edges:
|
|
92
|
+
del filtered_edge_postdom_count[edge]
|
|
93
|
+
if filtered_edge_postdom_count:
|
|
94
|
+
edge_postdom_count = filtered_edge_postdom_count
|
|
87
95
|
|
|
88
96
|
# H2: the edge, when removed, that causes the most post-dominators of the graph should be virtualized
|
|
89
97
|
# first. this is believed to make the code more linear looking be reducing the amount of scopes.
|
|
@@ -94,19 +102,19 @@ class SAILRStructurer(PhoenixStructurer):
|
|
|
94
102
|
if len(best_edges) == 1:
|
|
95
103
|
return best_edges
|
|
96
104
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
105
|
+
# H3: the edge that goes directly to a return statement should be virtualized first
|
|
106
|
+
# this is believed to be good because it can be corrected in later optimization by duplicating
|
|
107
|
+
# the return
|
|
108
|
+
candidate_edges = best_edges
|
|
109
|
+
best_edges = []
|
|
110
|
+
for src, dst in candidate_edges:
|
|
111
|
+
if graph.has_node(dst) and structured_node_is_simple_return(dst, graph):
|
|
112
|
+
best_edges.append((src, dst))
|
|
113
|
+
|
|
114
|
+
if len(best_edges) == 1:
|
|
115
|
+
return best_edges
|
|
116
|
+
if not best_edges:
|
|
117
|
+
best_edges = candidate_edges
|
|
110
118
|
|
|
111
119
|
# if we have another tie, or we never used improved heuristics, then we do the default ordering.
|
|
112
120
|
return super()._order_virtualizable_edges(graph, best_edges, node_seq)
|
|
@@ -6,9 +6,9 @@ import logging
|
|
|
6
6
|
|
|
7
7
|
import networkx
|
|
8
8
|
|
|
9
|
-
import angr.ailment as ailment
|
|
10
9
|
import claripy
|
|
11
10
|
|
|
11
|
+
from angr import ailment
|
|
12
12
|
from angr.analyses import Analysis
|
|
13
13
|
from angr.analyses.decompiler.condition_processor import ConditionProcessor
|
|
14
14
|
from angr.analyses.decompiler.sequence_walker import SequenceWalker
|
|
@@ -971,8 +971,7 @@ class StructurerBase(Analysis):
|
|
|
971
971
|
new_sequences.append(new_seq_)
|
|
972
972
|
self._new_sequences = new_sequences
|
|
973
973
|
|
|
974
|
-
|
|
975
|
-
def replace_nodes(graph, old_node_0, new_node, old_node_1=None, self_loop=True):
|
|
974
|
+
def replace_nodes(self, graph, old_node_0, new_node, old_node_1=None, self_loop=True): # pylint:disable=no-self-use
|
|
976
975
|
in_edges = list(graph.in_edges(old_node_0, data=True))
|
|
977
976
|
out_edges = list(graph.out_edges(old_node_0, data=True))
|
|
978
977
|
if old_node_1 is not None:
|
|
@@ -90,7 +90,7 @@ class StringObfType3Rewriter(OptimizationPass):
|
|
|
90
90
|
else:
|
|
91
91
|
new_stmt = new_call
|
|
92
92
|
|
|
93
|
-
statements = block.statements[:-1]
|
|
93
|
+
statements = [*block.statements[:-1], new_stmt]
|
|
94
94
|
|
|
95
95
|
# remove N-2 continuous stack assignment
|
|
96
96
|
if len(deobf_content) > 2:
|
angr/analyses/disassembly.py
CHANGED
|
@@ -922,7 +922,7 @@ class Value(OperandPiece):
|
|
|
922
922
|
if func is not None and lbl == func.name and func.name != func.demangled_name:
|
|
923
923
|
# see if lbl == func.name and func.demangled_name != func.name. if so, we prioritize the
|
|
924
924
|
# demangled name
|
|
925
|
-
normalized_name = get_cpp_function_name(func.demangled_name
|
|
925
|
+
normalized_name = get_cpp_function_name(func.demangled_name)
|
|
926
926
|
return [normalized_name]
|
|
927
927
|
return [("+" if self.render_with_sign else "") + lbl]
|
|
928
928
|
if func is not None:
|
|
@@ -584,6 +584,7 @@ class FunctionHandler:
|
|
|
584
584
|
# migrate data from sub_rda to its parent
|
|
585
585
|
state.analysis.function_calls.update(sub_rda.function_calls)
|
|
586
586
|
state.analysis.model.observed_results.update(sub_rda.model.observed_results)
|
|
587
|
+
state.all_definitions |= sub_rda.all_definitions
|
|
587
588
|
|
|
588
589
|
sub_ld = get_exit_livedefinitions(data.function, sub_rda.model)
|
|
589
590
|
if sub_ld is not None:
|
angr/analyses/s_propagator.py
CHANGED
|
@@ -33,7 +33,7 @@ from angr.utils.ssa import (
|
|
|
33
33
|
is_const_vvar_load_assignment,
|
|
34
34
|
is_const_vvar_load_dirty_assignment,
|
|
35
35
|
is_const_vvar_tmp_assignment,
|
|
36
|
-
|
|
36
|
+
is_vvar_propagatable,
|
|
37
37
|
get_tmp_uselocs,
|
|
38
38
|
get_tmp_deflocs,
|
|
39
39
|
phi_assignment_get_src,
|
|
@@ -246,7 +246,7 @@ class SPropagatorAnalysis(Analysis):
|
|
|
246
246
|
self.model.dead_vvar_ids.add(vvar.varid)
|
|
247
247
|
continue
|
|
248
248
|
|
|
249
|
-
if
|
|
249
|
+
if is_vvar_propagatable(vvar, stmt):
|
|
250
250
|
if len(vvar_uselocs_set) == 1:
|
|
251
251
|
vvar_used, vvar_useloc = next(iter(vvar_uselocs_set))
|
|
252
252
|
if (
|
|
@@ -25,6 +25,7 @@ class SRDAModel:
|
|
|
25
25
|
self.all_tmp_definitions: dict[CodeLocation, dict[atoms.Tmp, int]] = defaultdict(dict)
|
|
26
26
|
self.all_tmp_uses: dict[CodeLocation, dict[atoms.Tmp, set[tuple[Tmp, int]]]] = defaultdict(dict)
|
|
27
27
|
self.phi_vvar_ids: set[int] = set()
|
|
28
|
+
self.phivarid_to_varids_with_unknown: dict[int, set[int | None]] = {}
|
|
28
29
|
self.phivarid_to_varids: dict[int, set[int]] = {}
|
|
29
30
|
self.vvar_uses_by_loc: dict[CodeLocation, list[int]] = {}
|
|
30
31
|
|
|
@@ -63,7 +63,7 @@ class SReachingDefinitionsAnalysis(Analysis):
|
|
|
63
63
|
case _:
|
|
64
64
|
raise NotImplementedError
|
|
65
65
|
|
|
66
|
-
phi_vvars: dict[int, set[int]] = {}
|
|
66
|
+
phi_vvars: dict[int, set[int | None]] = {}
|
|
67
67
|
# find all vvar definitions
|
|
68
68
|
vvar_deflocs = get_vvar_deflocs(blocks.values(), phi_vvars=phi_vvars)
|
|
69
69
|
# find all explicit vvar uses
|
|
@@ -87,7 +87,10 @@ class SReachingDefinitionsAnalysis(Analysis):
|
|
|
87
87
|
self.model.phi_vvar_ids = set(phi_vvars)
|
|
88
88
|
self.model.phivarid_to_varids = {}
|
|
89
89
|
for vvar_id, src_vvars in phi_vvars.items():
|
|
90
|
-
self.model.
|
|
90
|
+
self.model.phivarid_to_varids_with_unknown[vvar_id] = src_vvars
|
|
91
|
+
self.model.phivarid_to_varids[vvar_id] = ( # type: ignore
|
|
92
|
+
{vvar_id for vvar_id in src_vvars if vvar_id is not None} if None in src_vvars else src_vvars
|
|
93
|
+
)
|
|
91
94
|
|
|
92
95
|
if self.mode == "function":
|
|
93
96
|
|
|
@@ -178,6 +178,14 @@ class SimEngineVRBase(
|
|
|
178
178
|
for var_stack_offset, var in self.state.extract_variables(v):
|
|
179
179
|
existing_vars.append((var, var_stack_offset))
|
|
180
180
|
|
|
181
|
+
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
|
+
]
|
|
188
|
+
|
|
181
189
|
if not existing_vars:
|
|
182
190
|
# no variables exist
|
|
183
191
|
lea_size = 1
|
|
@@ -525,6 +533,10 @@ class SimEngineVRBase(
|
|
|
525
533
|
def _store_to_stack(
|
|
526
534
|
self, stack_offset, data: RichR[claripy.ast.BV | claripy.ast.FP], size, offset=0, atom=None, endness=None
|
|
527
535
|
):
|
|
536
|
+
"""
|
|
537
|
+
Store data to a stack location. We limit the size of the data to store to 256 bytes for performance reasons.
|
|
538
|
+
"""
|
|
539
|
+
|
|
528
540
|
if atom is None:
|
|
529
541
|
existing_vars = self.state.variable_manager[self.func_addr].find_variables_by_stmt(
|
|
530
542
|
self.block.addr, self.stmt_idx, "memory"
|
|
@@ -550,7 +562,11 @@ class SimEngineVRBase(
|
|
|
550
562
|
variable, variable_offset = next(iter(existing_vars))
|
|
551
563
|
|
|
552
564
|
if isinstance(stack_offset, int):
|
|
553
|
-
expr =
|
|
565
|
+
expr = data.data
|
|
566
|
+
if isinstance(expr, claripy.ast.Bits) and expr.size() > 1024:
|
|
567
|
+
# we don't write more than 256 bytes to the stack at a time for performance reasons
|
|
568
|
+
expr = expr[expr.size() - 1 : expr.size() - 1024]
|
|
569
|
+
expr = self.state.annotate_with_variables(expr, [(variable_offset, variable)])
|
|
554
570
|
stack_addr = self.state.stack_addr_from_offset(stack_offset)
|
|
555
571
|
self.state.stack_region.store(stack_addr, expr, endness=endness)
|
|
556
572
|
|
|
@@ -5,6 +5,8 @@ from collections.abc import Generator, Iterable
|
|
|
5
5
|
import logging
|
|
6
6
|
from collections import defaultdict
|
|
7
7
|
|
|
8
|
+
import networkx
|
|
9
|
+
|
|
8
10
|
import archinfo
|
|
9
11
|
import claripy
|
|
10
12
|
from claripy.annotation import Annotation
|
|
@@ -86,8 +88,18 @@ class VariableRecoveryBase(Analysis):
|
|
|
86
88
|
The base class for VariableRecovery and VariableRecoveryFast.
|
|
87
89
|
"""
|
|
88
90
|
|
|
89
|
-
def __init__(
|
|
91
|
+
def __init__(
|
|
92
|
+
self,
|
|
93
|
+
func,
|
|
94
|
+
max_iterations,
|
|
95
|
+
store_live_variables: bool,
|
|
96
|
+
vvar_to_vvar: dict[int, int] | None = None,
|
|
97
|
+
func_graph: networkx.DiGraph | None = None,
|
|
98
|
+
entry_node_addr: int | tuple[int, int | None] | None = None,
|
|
99
|
+
):
|
|
90
100
|
self.function = func
|
|
101
|
+
self.func_graph = func_graph
|
|
102
|
+
self.entry_node_addr = entry_node_addr
|
|
91
103
|
self.variable_manager = self.kb.variables
|
|
92
104
|
|
|
93
105
|
self._max_iterations = max_iterations
|
|
@@ -120,7 +132,23 @@ class VariableRecoveryBase(Analysis):
|
|
|
120
132
|
|
|
121
133
|
def initialize_dominance_frontiers(self):
|
|
122
134
|
# Computer the dominance frontier for each node in the graph
|
|
123
|
-
|
|
135
|
+
func_entry = None
|
|
136
|
+
if self.func_graph is not None:
|
|
137
|
+
entry_node_addr = self.entry_node_addr if self.entry_node_addr is not None else self.function.addr
|
|
138
|
+
assert entry_node_addr is not None
|
|
139
|
+
if isinstance(entry_node_addr, int):
|
|
140
|
+
func_entry = next(iter(node for node in self.func_graph if node.addr == entry_node_addr))
|
|
141
|
+
elif isinstance(entry_node_addr, tuple):
|
|
142
|
+
func_entry = next(
|
|
143
|
+
iter(
|
|
144
|
+
node
|
|
145
|
+
for node in self.func_graph
|
|
146
|
+
if node.addr == entry_node_addr[0] and node.idx == entry_node_addr[1]
|
|
147
|
+
)
|
|
148
|
+
)
|
|
149
|
+
else:
|
|
150
|
+
raise TypeError(f"Unsupported entry node address type: {type(entry_node_addr)}")
|
|
151
|
+
df = self.project.analyses.DominanceFrontier(self.function, func_graph=self.func_graph, entry=func_entry)
|
|
124
152
|
self._dominance_frontiers = defaultdict(set)
|
|
125
153
|
for b0, domfront in df.frontiers.items():
|
|
126
154
|
for d in domfront:
|
|
@@ -237,6 +237,7 @@ class VariableRecoveryFast(ForwardAnalysis, VariableRecoveryBase): # pylint:dis
|
|
|
237
237
|
self,
|
|
238
238
|
func: Function | str | int,
|
|
239
239
|
func_graph: networkx.DiGraph | None = None,
|
|
240
|
+
entry_node_addr: int | tuple[int, int | None] | None = None,
|
|
240
241
|
max_iterations: int = 2,
|
|
241
242
|
low_priority=False,
|
|
242
243
|
track_sp=True,
|
|
@@ -259,10 +260,18 @@ class VariableRecoveryFast(ForwardAnalysis, VariableRecoveryBase): # pylint:dis
|
|
|
259
260
|
function_graph_visitor = visitors.FunctionGraphVisitor(func, graph=func_graph)
|
|
260
261
|
|
|
261
262
|
# Make sure the function is not empty
|
|
262
|
-
if not func.block_addrs_set or func.startpoint is None:
|
|
263
|
+
if (not func.block_addrs_set or func.startpoint is None) and not func_graph:
|
|
263
264
|
raise AngrVariableRecoveryError(f"Function {func!r} is empty.")
|
|
264
265
|
|
|
265
|
-
VariableRecoveryBase.__init__(
|
|
266
|
+
VariableRecoveryBase.__init__(
|
|
267
|
+
self,
|
|
268
|
+
func,
|
|
269
|
+
max_iterations,
|
|
270
|
+
store_live_variables,
|
|
271
|
+
vvar_to_vvar=vvar_to_vvar,
|
|
272
|
+
func_graph=func_graph_with_calls,
|
|
273
|
+
entry_node_addr=entry_node_addr,
|
|
274
|
+
)
|
|
266
275
|
ForwardAnalysis.__init__(
|
|
267
276
|
self, order_jobs=True, allow_merging=True, allow_widening=False, graph_visitor=function_graph_visitor
|
|
268
277
|
)
|
angr/emulator.py
ADDED
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
from enum import Enum
|
|
5
|
+
|
|
6
|
+
from angr.engines.concrete import ConcreteEngine, HeavyConcreteState
|
|
7
|
+
from angr.errors import AngrError
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
log = logging.getLogger(name=__name__)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class EmulatorException(AngrError):
|
|
14
|
+
"""Base class for exceptions raised by the Emulator."""
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class EngineException(EmulatorException):
|
|
18
|
+
"""Exception raised when the emulator encounters an unhandlable error in the engine."""
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class StateDivergedException(EmulatorException):
|
|
22
|
+
"""Exception raised when an engine returns multiple successors."""
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class EmulatorStopReason(Enum):
|
|
26
|
+
"""
|
|
27
|
+
Enum representing the reason for stopping the emulator.
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
INSTRUCTION_LIMIT = "instruction_limit"
|
|
31
|
+
BREAKPOINT = "breakpoint"
|
|
32
|
+
NO_SUCCESSORS = "no_successors"
|
|
33
|
+
MEMORY_ERROR = "memory_error"
|
|
34
|
+
FAILURE = "failure"
|
|
35
|
+
EXIT = "exit"
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class Emulator:
|
|
39
|
+
"""
|
|
40
|
+
Emulator is a utility that adapts an angr `ConcreteEngine` to a more
|
|
41
|
+
user-friendly interface for concrete execution. It only supports concrete
|
|
42
|
+
execution and requires a ConcreteEngine.
|
|
43
|
+
|
|
44
|
+
Saftey: This class is not thread-safe. It should only be used in a
|
|
45
|
+
single-threaded context. It can be safely shared between multiple threads,
|
|
46
|
+
provided that only one thread is using it at a time.
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
_engine: ConcreteEngine
|
|
50
|
+
_state: HeavyConcreteState
|
|
51
|
+
|
|
52
|
+
def __init__(self, engine: ConcreteEngine, init_state: HeavyConcreteState):
|
|
53
|
+
"""
|
|
54
|
+
:param engine: The `ConcreteEngine` to use for emulation.
|
|
55
|
+
:param init_state: The initial state to use for emulation.
|
|
56
|
+
"""
|
|
57
|
+
self._engine = engine
|
|
58
|
+
self._state = init_state
|
|
59
|
+
|
|
60
|
+
@property
|
|
61
|
+
def state(self) -> HeavyConcreteState:
|
|
62
|
+
"""
|
|
63
|
+
The current state of the emulator.
|
|
64
|
+
"""
|
|
65
|
+
return self._state
|
|
66
|
+
|
|
67
|
+
@property
|
|
68
|
+
def breakpoints(self) -> set[int]:
|
|
69
|
+
"""
|
|
70
|
+
The set of currently set breakpoints.
|
|
71
|
+
"""
|
|
72
|
+
return self._engine.get_breakpoints()
|
|
73
|
+
|
|
74
|
+
def add_breakpoint(self, addr: int) -> None:
|
|
75
|
+
"""
|
|
76
|
+
Add a breakpoint at the given address.
|
|
77
|
+
|
|
78
|
+
:param addr: The address to set the breakpoint at.
|
|
79
|
+
"""
|
|
80
|
+
self._engine.add_breakpoint(addr)
|
|
81
|
+
|
|
82
|
+
def remove_breakpoint(self, addr: int) -> None:
|
|
83
|
+
"""
|
|
84
|
+
Remove a breakpoint at the given address, if present.
|
|
85
|
+
|
|
86
|
+
:param addr: The address to remove the breakpoint from.
|
|
87
|
+
"""
|
|
88
|
+
self._engine.remove_breakpoint(addr)
|
|
89
|
+
|
|
90
|
+
def run(self, num_inst: int | None = None) -> EmulatorStopReason:
|
|
91
|
+
"""
|
|
92
|
+
Execute the emulator.
|
|
93
|
+
"""
|
|
94
|
+
completed_engine_execs = 0
|
|
95
|
+
num_inst_executed: int = 0
|
|
96
|
+
while self._state.history.jumpkind != "Ijk_Exit":
|
|
97
|
+
# Check if there is a breakpoint at the current address
|
|
98
|
+
if completed_engine_execs > 0 and self._state.addr in self._engine.get_breakpoints():
|
|
99
|
+
return EmulatorStopReason.BREAKPOINT
|
|
100
|
+
|
|
101
|
+
# Check if we've already executed the requested number of instructions
|
|
102
|
+
if num_inst is not None and num_inst_executed >= num_inst:
|
|
103
|
+
return EmulatorStopReason.INSTRUCTION_LIMIT
|
|
104
|
+
|
|
105
|
+
# Calculate remaining instructions for this engine execution
|
|
106
|
+
remaining_inst: int | None = None
|
|
107
|
+
if num_inst is not None:
|
|
108
|
+
remaining_inst = num_inst - num_inst_executed
|
|
109
|
+
|
|
110
|
+
# Run the engine to get successors
|
|
111
|
+
try:
|
|
112
|
+
successors = self._engine.process(self._state, num_inst=remaining_inst)
|
|
113
|
+
except EngineException as e:
|
|
114
|
+
raise EngineException(f"Engine encountered an error: {e}") from e
|
|
115
|
+
|
|
116
|
+
# Handle cases with an unexpected number of successors
|
|
117
|
+
if len(successors.successors) == 0:
|
|
118
|
+
return EmulatorStopReason.NO_SUCCESSORS
|
|
119
|
+
if len(successors.successors) > 1:
|
|
120
|
+
log.warning("Concrete engine returned multiple successors")
|
|
121
|
+
|
|
122
|
+
# Set the state before raising further exceptions
|
|
123
|
+
self._state = successors.successors[0]
|
|
124
|
+
|
|
125
|
+
# Track the number of instructions executed using the state's history
|
|
126
|
+
if self._state.history.recent_instruction_count > 0:
|
|
127
|
+
num_inst_executed += self._state.history.recent_instruction_count
|
|
128
|
+
|
|
129
|
+
if successors.successors[0].history.jumpkind == "Ijk_SigSEGV":
|
|
130
|
+
return EmulatorStopReason.MEMORY_ERROR
|
|
131
|
+
|
|
132
|
+
completed_engine_execs += 1
|
|
133
|
+
|
|
134
|
+
return EmulatorStopReason.EXIT
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
__all__ = (
|
|
138
|
+
"Emulator",
|
|
139
|
+
"EmulatorException",
|
|
140
|
+
"EmulatorStopReason",
|
|
141
|
+
"EngineException",
|
|
142
|
+
"StateDivergedException",
|
|
143
|
+
)
|
angr/engines/concrete.py
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
import typing
|
|
5
|
+
from abc import abstractmethod, ABCMeta
|
|
6
|
+
from typing_extensions import override
|
|
7
|
+
|
|
8
|
+
import claripy
|
|
9
|
+
from angr.engines.successors import SimSuccessors, SuccessorsEngine
|
|
10
|
+
from angr.sim_state import SimState
|
|
11
|
+
|
|
12
|
+
log = logging.getLogger(__name__)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
HeavyConcreteState = SimState[int, int]
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class ConcreteEngine(SuccessorsEngine, metaclass=ABCMeta):
|
|
19
|
+
"""
|
|
20
|
+
ConcreteEngine extends SuccessorsEngine and adds APIs for managing breakpoints.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
@abstractmethod
|
|
24
|
+
def get_breakpoints(self) -> set[int]:
|
|
25
|
+
"""Return the set of currently set breakpoints."""
|
|
26
|
+
|
|
27
|
+
@abstractmethod
|
|
28
|
+
def add_breakpoint(self, addr: int) -> None:
|
|
29
|
+
"""Add a breakpoint at the given address."""
|
|
30
|
+
|
|
31
|
+
@abstractmethod
|
|
32
|
+
def remove_breakpoint(self, addr: int) -> None:
|
|
33
|
+
"""Remove a breakpoint at the given address, if present."""
|
|
34
|
+
|
|
35
|
+
@abstractmethod
|
|
36
|
+
def process_concrete(self, state: HeavyConcreteState, num_inst: int | None = None) -> HeavyConcreteState:
|
|
37
|
+
"""
|
|
38
|
+
Process the concrete state and return a HeavyState object.
|
|
39
|
+
|
|
40
|
+
:param state: The concrete state to process.
|
|
41
|
+
:return: A HeavyState object representing the processed state.
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
@override
|
|
45
|
+
def process_successors(
|
|
46
|
+
self, successors: SimSuccessors, *, num_inst: int | None = None, **kwargs: dict[str, typing.Any]
|
|
47
|
+
):
|
|
48
|
+
if len(kwargs) > 0:
|
|
49
|
+
log.warning("ConcreteEngine.process_successors received unknown kwargs: %s", kwargs)
|
|
50
|
+
|
|
51
|
+
# TODO: Properly error here when the state is not a HeavyConcreteState
|
|
52
|
+
# Alternatively, we could make SimSuccessors generic over the state type too
|
|
53
|
+
concrete_state = typing.cast(HeavyConcreteState, self.state)
|
|
54
|
+
|
|
55
|
+
concrete_successor = self.process_concrete(concrete_state, num_inst=num_inst)
|
|
56
|
+
successors.add_successor(
|
|
57
|
+
concrete_successor,
|
|
58
|
+
concrete_successor.ip,
|
|
59
|
+
claripy.true(),
|
|
60
|
+
concrete_successor.history.jumpkind,
|
|
61
|
+
add_guard=False,
|
|
62
|
+
)
|
|
63
|
+
successors.processed = True
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
__all__ = ["ConcreteEngine", "HeavyConcreteState"]
|