angr 9.2.165__cp310-abi3-win_amd64.whl → 9.2.167__cp310-abi3-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/indirect_jump_resolvers/jumptable.py +8 -8
- angr/analyses/decompiler/clinic.py +3 -0
- angr/analyses/decompiler/condition_processor.py +44 -1
- angr/analyses/decompiler/decompiler.py +6 -0
- angr/analyses/decompiler/node_replacer.py +42 -0
- angr/analyses/decompiler/optimization_passes/lowered_switch_simplifier.py +1 -1
- angr/analyses/decompiler/optimization_passes/optimization_pass.py +3 -0
- angr/analyses/decompiler/optimization_passes/return_duplicator_low.py +5 -76
- angr/analyses/decompiler/region_identifier.py +12 -3
- angr/analyses/decompiler/sequence_walker.py +11 -7
- angr/analyses/decompiler/structuring/phoenix.py +645 -305
- angr/analyses/decompiler/structuring/structurer_base.py +75 -1
- angr/analyses/decompiler/utils.py +71 -28
- angr/analyses/reaching_definitions/engine_vex.py +3 -2
- angr/procedures/glibc/scanf.py +8 -0
- angr/procedures/glibc/sscanf.py +4 -0
- angr/rustylib.pyd +0 -0
- angr/unicornlib.dll +0 -0
- angr/utils/graph.py +62 -24
- {angr-9.2.165.dist-info → angr-9.2.167.dist-info}/METADATA +5 -5
- {angr-9.2.165.dist-info → angr-9.2.167.dist-info}/RECORD +26 -25
- {angr-9.2.165.dist-info → angr-9.2.167.dist-info}/WHEEL +0 -0
- {angr-9.2.165.dist-info → angr-9.2.167.dist-info}/entry_points.txt +0 -0
- {angr-9.2.165.dist-info → angr-9.2.167.dist-info}/licenses/LICENSE +0 -0
- {angr-9.2.165.dist-info → angr-9.2.167.dist-info}/top_level.txt +0 -0
|
@@ -35,6 +35,7 @@ from .structurer_nodes import (
|
|
|
35
35
|
LoopNode,
|
|
36
36
|
EmptyBlockNotice,
|
|
37
37
|
IncompleteSwitchCaseNode,
|
|
38
|
+
IncompleteSwitchCaseHeadStatement,
|
|
38
39
|
)
|
|
39
40
|
|
|
40
41
|
if TYPE_CHECKING:
|
|
@@ -900,8 +901,24 @@ class StructurerBase(Analysis):
|
|
|
900
901
|
|
|
901
902
|
@staticmethod
|
|
902
903
|
def _remove_last_statement_if_jump(
|
|
903
|
-
node: BaseNode | ailment.Block | MultiNode,
|
|
904
|
+
node: BaseNode | ailment.Block | MultiNode | SequenceNode,
|
|
904
905
|
) -> ailment.Stmt.Jump | ailment.Stmt.ConditionalJump | None:
|
|
906
|
+
if isinstance(node, SequenceNode) and node.nodes and isinstance(node.nodes[-1], ConditionNode):
|
|
907
|
+
cond_node = node.nodes[-1]
|
|
908
|
+
the_stmt: ailment.Stmt.Jump | None = None
|
|
909
|
+
for block in [cond_node.true_node, cond_node.false_node]:
|
|
910
|
+
if (
|
|
911
|
+
isinstance(block, ailment.Block)
|
|
912
|
+
and block.statements
|
|
913
|
+
and isinstance(block.statements[-1], ailment.Stmt.Jump)
|
|
914
|
+
):
|
|
915
|
+
the_stmt = block.statements[-1] # type: ignore
|
|
916
|
+
break
|
|
917
|
+
|
|
918
|
+
if the_stmt is not None:
|
|
919
|
+
node.nodes = node.nodes[:-1]
|
|
920
|
+
return the_stmt
|
|
921
|
+
|
|
905
922
|
try:
|
|
906
923
|
last_stmts = ConditionProcessor.get_last_statements(node)
|
|
907
924
|
except EmptyBlockNotice:
|
|
@@ -911,6 +928,63 @@ class StructurerBase(Analysis):
|
|
|
911
928
|
return remove_last_statement(node) # type: ignore
|
|
912
929
|
return None
|
|
913
930
|
|
|
931
|
+
@staticmethod
|
|
932
|
+
def _remove_last_statement_if_jump_or_schead(
|
|
933
|
+
node: BaseNode | ailment.Block | MultiNode | SequenceNode,
|
|
934
|
+
) -> ailment.Stmt.Jump | ailment.Stmt.ConditionalJump | IncompleteSwitchCaseHeadStatement | None:
|
|
935
|
+
if isinstance(node, SequenceNode) and node.nodes and isinstance(node.nodes[-1], ConditionNode):
|
|
936
|
+
cond_node = node.nodes[-1]
|
|
937
|
+
the_stmt: ailment.Stmt.Jump | None = None
|
|
938
|
+
for block in [cond_node.true_node, cond_node.false_node]:
|
|
939
|
+
if (
|
|
940
|
+
isinstance(block, ailment.Block)
|
|
941
|
+
and block.statements
|
|
942
|
+
and isinstance(block.statements[-1], ailment.Stmt.Jump)
|
|
943
|
+
):
|
|
944
|
+
the_stmt = block.statements[-1] # type: ignore
|
|
945
|
+
break
|
|
946
|
+
|
|
947
|
+
if the_stmt is not None:
|
|
948
|
+
node.nodes = node.nodes[:-1]
|
|
949
|
+
return the_stmt
|
|
950
|
+
|
|
951
|
+
try:
|
|
952
|
+
last_stmts = ConditionProcessor.get_last_statements(node)
|
|
953
|
+
except EmptyBlockNotice:
|
|
954
|
+
return None
|
|
955
|
+
|
|
956
|
+
if len(last_stmts) == 1 and isinstance(
|
|
957
|
+
last_stmts[0], (ailment.Stmt.Jump, ailment.Stmt.ConditionalJump, IncompleteSwitchCaseHeadStatement)
|
|
958
|
+
):
|
|
959
|
+
return remove_last_statement(node) # type: ignore
|
|
960
|
+
return None
|
|
961
|
+
|
|
962
|
+
@staticmethod
|
|
963
|
+
def _copy_and_remove_last_statement_if_jump(
|
|
964
|
+
node: ailment.Block | MultiNode | SequenceNode,
|
|
965
|
+
) -> ailment.Block | MultiNode | SequenceNode:
|
|
966
|
+
if isinstance(node, SequenceNode):
|
|
967
|
+
if node.nodes and isinstance(node.nodes[-1], ConditionNode):
|
|
968
|
+
# copy the node and remove the last condition node
|
|
969
|
+
return SequenceNode(node.addr, nodes=node.nodes[:-1])
|
|
970
|
+
return node.copy()
|
|
971
|
+
|
|
972
|
+
if isinstance(node, MultiNode):
|
|
973
|
+
if node.nodes:
|
|
974
|
+
last_block = StructurerBase._copy_and_remove_last_statement_if_jump(node.nodes[-1])
|
|
975
|
+
nodes = [*node.nodes[:-1], last_block]
|
|
976
|
+
else:
|
|
977
|
+
nodes = []
|
|
978
|
+
return MultiNode(nodes, addr=node.addr, idx=node.idx)
|
|
979
|
+
|
|
980
|
+
assert isinstance(node, ailment.Block)
|
|
981
|
+
if node.statements and isinstance(node.statements[-1], (ailment.Stmt.Jump, ailment.Stmt.ConditionalJump)):
|
|
982
|
+
# copy the block and remove the last statement
|
|
983
|
+
stmts = node.statements[:-1]
|
|
984
|
+
else:
|
|
985
|
+
stmts = node.statements[::]
|
|
986
|
+
return ailment.Block(node.addr, node.original_size, statements=stmts, idx=node.idx)
|
|
987
|
+
|
|
914
988
|
@staticmethod
|
|
915
989
|
def _merge_nodes(node_0, node_1):
|
|
916
990
|
addr = node_0.addr if node_0.addr is not None else node_1.addr
|
|
@@ -156,27 +156,48 @@ def switch_extract_cmp_bounds(
|
|
|
156
156
|
|
|
157
157
|
if not isinstance(last_stmt, ailment.Stmt.ConditionalJump):
|
|
158
158
|
return None
|
|
159
|
+
return switch_extract_cmp_bounds_from_condition(last_stmt.condition)
|
|
159
160
|
|
|
161
|
+
|
|
162
|
+
def switch_extract_cmp_bounds_from_condition(cond: ailment.Expr.Expression) -> tuple[Any, int, int] | None:
|
|
160
163
|
# TODO: Add more operations
|
|
161
|
-
if isinstance(
|
|
162
|
-
if
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
cmp_lb
|
|
178
|
-
|
|
179
|
-
|
|
164
|
+
if isinstance(cond, ailment.Expr.BinaryOp):
|
|
165
|
+
if cond.op in {"CmpLE", "CmpLT"}:
|
|
166
|
+
if not (isinstance(cond.operands[1], ailment.Expr.Const) and isinstance(cond.operands[1].value, int)):
|
|
167
|
+
return None
|
|
168
|
+
cmp_ub = cond.operands[1].value if cond.op == "CmpLE" else cond.operands[1].value - 1
|
|
169
|
+
cmp_lb = 0
|
|
170
|
+
cmp = cond.operands[0]
|
|
171
|
+
if (
|
|
172
|
+
isinstance(cmp, ailment.Expr.BinaryOp)
|
|
173
|
+
and cmp.op == "Sub"
|
|
174
|
+
and isinstance(cmp.operands[1], ailment.Expr.Const)
|
|
175
|
+
and isinstance(cmp.operands[1].value, int)
|
|
176
|
+
):
|
|
177
|
+
cmp_ub += cmp.operands[1].value
|
|
178
|
+
cmp_lb += cmp.operands[1].value
|
|
179
|
+
cmp = cmp.operands[0]
|
|
180
|
+
return cmp, cmp_lb, cmp_ub
|
|
181
|
+
|
|
182
|
+
if cond.op in {"CmpGE", "CmpGT"}:
|
|
183
|
+
# We got the negated condition here
|
|
184
|
+
# CmpGE -> CmpLT
|
|
185
|
+
# CmpGT -> CmpLE
|
|
186
|
+
if not (isinstance(cond.operands[1], ailment.Expr.Const) and isinstance(cond.operands[1].value, int)):
|
|
187
|
+
return None
|
|
188
|
+
cmp_ub = cond.operands[1].value if cond.op == "CmpGT" else cond.operands[1].value - 1
|
|
189
|
+
cmp_lb = 0
|
|
190
|
+
cmp = cond.operands[0]
|
|
191
|
+
if (
|
|
192
|
+
isinstance(cmp, ailment.Expr.BinaryOp)
|
|
193
|
+
and cmp.op == "Sub"
|
|
194
|
+
and isinstance(cmp.operands[1], ailment.Expr.Const)
|
|
195
|
+
and isinstance(cmp.operands[1].value, int)
|
|
196
|
+
):
|
|
197
|
+
cmp_ub += cmp.operands[1].value
|
|
198
|
+
cmp_lb += cmp.operands[1].value
|
|
199
|
+
cmp = cmp.operands[0]
|
|
200
|
+
return cmp, cmp_lb, cmp_ub
|
|
180
201
|
|
|
181
202
|
return None
|
|
182
203
|
|
|
@@ -315,7 +336,7 @@ def switch_extract_bitwiseand_jumptable_info(last_stmt: ailment.Stmt.Jump) -> tu
|
|
|
315
336
|
coeff = None
|
|
316
337
|
index_expr = None
|
|
317
338
|
lb = None
|
|
318
|
-
ub = None
|
|
339
|
+
ub: int | None = None
|
|
319
340
|
while expr is not None:
|
|
320
341
|
if isinstance(expr, ailment.Expr.BinaryOp):
|
|
321
342
|
if expr.op == "Mul":
|
|
@@ -331,12 +352,12 @@ def switch_extract_bitwiseand_jumptable_info(last_stmt: ailment.Stmt.Jump) -> tu
|
|
|
331
352
|
masks = {0x1, 0x3, 0x7, 0xF, 0x1F, 0x3F, 0x7F, 0xFF, 0x1FF, 0x3FF}
|
|
332
353
|
if isinstance(expr.operands[1], ailment.Expr.Const) and expr.operands[1].value in masks:
|
|
333
354
|
lb = 0
|
|
334
|
-
ub = expr.operands[1].value
|
|
355
|
+
ub = expr.operands[1].value # type:ignore
|
|
335
356
|
index_expr = expr
|
|
336
357
|
break
|
|
337
358
|
if isinstance(expr.operands[0], ailment.Expr.Const) and expr.operands[1].value in masks:
|
|
338
359
|
lb = 0
|
|
339
|
-
ub = expr.operands[0].value
|
|
360
|
+
ub = expr.operands[0].value # type:ignore
|
|
340
361
|
index_expr = expr
|
|
341
362
|
break
|
|
342
363
|
return None
|
|
@@ -366,7 +387,7 @@ def get_ast_subexprs(claripy_ast):
|
|
|
366
387
|
yield ast
|
|
367
388
|
|
|
368
389
|
|
|
369
|
-
def insert_node(parent, insert_location: str, node, node_idx: int
|
|
390
|
+
def insert_node(parent, insert_location: str, node, node_idx: int, label=None):
|
|
370
391
|
if insert_location not in {"before", "after"}:
|
|
371
392
|
raise ValueError('"insert_location" must be either "before" or "after"')
|
|
372
393
|
|
|
@@ -538,12 +559,13 @@ def is_empty_or_label_only_node(node) -> bool:
|
|
|
538
559
|
|
|
539
560
|
|
|
540
561
|
def has_nonlabel_statements(block: ailment.Block) -> bool:
|
|
541
|
-
return block.statements and any(not isinstance(stmt, ailment.Stmt.Label) for stmt in block.statements)
|
|
562
|
+
return bool(block.statements and any(not isinstance(stmt, ailment.Stmt.Label) for stmt in block.statements))
|
|
542
563
|
|
|
543
564
|
|
|
544
565
|
def has_nonlabel_nonphi_statements(block: ailment.Block) -> bool:
|
|
545
|
-
return
|
|
546
|
-
|
|
566
|
+
return bool(
|
|
567
|
+
block.statements
|
|
568
|
+
and any(not (isinstance(stmt, ailment.Stmt.Label) or is_phi_assignment(stmt)) for stmt in block.statements)
|
|
547
569
|
)
|
|
548
570
|
|
|
549
571
|
|
|
@@ -589,6 +611,19 @@ def last_nonlabel_statement(block: ailment.Block) -> ailment.Stmt.Statement | No
|
|
|
589
611
|
return None
|
|
590
612
|
|
|
591
613
|
|
|
614
|
+
def last_node(node: BaseNode) -> BaseNode | ailment.Block | None:
|
|
615
|
+
"""
|
|
616
|
+
Get the last node in a sequence or code node.
|
|
617
|
+
"""
|
|
618
|
+
if isinstance(node, CodeNode):
|
|
619
|
+
return last_node(node.node)
|
|
620
|
+
if isinstance(node, SequenceNode):
|
|
621
|
+
if not node.nodes:
|
|
622
|
+
return None
|
|
623
|
+
return last_node(node.nodes[-1])
|
|
624
|
+
return node
|
|
625
|
+
|
|
626
|
+
|
|
592
627
|
def first_nonlabel_node(seq: SequenceNode) -> BaseNode | ailment.Block | None:
|
|
593
628
|
for node in seq.nodes:
|
|
594
629
|
inner_node = node.node if isinstance(node, CodeNode) else node
|
|
@@ -672,7 +707,9 @@ def _find_node_in_graph(node: ailment.Block, graph: networkx.DiGraph) -> ailment
|
|
|
672
707
|
return None
|
|
673
708
|
|
|
674
709
|
|
|
675
|
-
def structured_node_has_multi_predecessors(
|
|
710
|
+
def structured_node_has_multi_predecessors(
|
|
711
|
+
node: SequenceNode | MultiNode | ailment.Block, graph: networkx.DiGraph
|
|
712
|
+
) -> bool:
|
|
676
713
|
if graph is None:
|
|
677
714
|
return False
|
|
678
715
|
|
|
@@ -728,6 +765,7 @@ def structured_node_is_simple_return(
|
|
|
728
765
|
if valid_last_stmt:
|
|
729
766
|
# note that the block may not be the same block in the AIL graph post dephication. we must find the block again
|
|
730
767
|
# in the graph.
|
|
768
|
+
assert isinstance(last_block, ailment.Block)
|
|
731
769
|
last_graph_block = _find_node_in_graph(last_block, graph)
|
|
732
770
|
if last_graph_block is not None:
|
|
733
771
|
succs = list(graph.successors(last_graph_block))
|
|
@@ -927,6 +965,7 @@ def peephole_optimize_multistmts(block, stmt_opts):
|
|
|
927
965
|
break
|
|
928
966
|
|
|
929
967
|
if matched:
|
|
968
|
+
assert stmt_seq_len is not None
|
|
930
969
|
matched_stmts = statements[stmt_idx : stmt_idx + stmt_seq_len]
|
|
931
970
|
r = opt.optimize(matched_stmts, stmt_idx=stmt_idx, block=block)
|
|
932
971
|
if r is not None:
|
|
@@ -1036,7 +1075,11 @@ def decompile_functions(
|
|
|
1036
1075
|
_l.critical("Failed to decompile %s because %s", repr(f), exception_string)
|
|
1037
1076
|
decompilation += f"// [error: {func} | {exception_string}]\n"
|
|
1038
1077
|
else:
|
|
1039
|
-
|
|
1078
|
+
if dec is not None and dec.codegen is not None and dec.codegen.text is not None:
|
|
1079
|
+
decompilation += dec.codegen.text
|
|
1080
|
+
else:
|
|
1081
|
+
decompilation += "Invalid decompilation output"
|
|
1082
|
+
decompilation += "\n"
|
|
1040
1083
|
|
|
1041
1084
|
return decompilation
|
|
1042
1085
|
|
|
@@ -434,8 +434,9 @@ class SimEngineRDVEX(
|
|
|
434
434
|
size = bits // self.arch.byte_width
|
|
435
435
|
|
|
436
436
|
# convert addr from MultiValues to a list of valid addresses
|
|
437
|
-
if
|
|
438
|
-
|
|
437
|
+
if addr.count() == 1 and 0 in addr:
|
|
438
|
+
addrs = list(addr[0])
|
|
439
|
+
return self._load_core(addrs, size, expr.endness)
|
|
439
440
|
|
|
440
441
|
top = self.state.top(bits)
|
|
441
442
|
# annotate it
|
angr/procedures/glibc/scanf.py
CHANGED
angr/procedures/glibc/sscanf.py
CHANGED
angr/rustylib.pyd
CHANGED
|
Binary file
|
angr/unicornlib.dll
CHANGED
|
Binary file
|
angr/utils/graph.py
CHANGED
|
@@ -85,7 +85,7 @@ def to_acyclic_graph(
|
|
|
85
85
|
return acyclic_graph
|
|
86
86
|
|
|
87
87
|
|
|
88
|
-
def dfs_back_edges(graph, start_node):
|
|
88
|
+
def dfs_back_edges(graph, start_node, *, visit_all_nodes: bool = False, visited: set | None = None):
|
|
89
89
|
"""
|
|
90
90
|
Perform an iterative DFS traversal of the graph, returning back edges.
|
|
91
91
|
|
|
@@ -96,9 +96,9 @@ def dfs_back_edges(graph, start_node):
|
|
|
96
96
|
if start_node not in graph:
|
|
97
97
|
return # Ensures that the start node is in the graph
|
|
98
98
|
|
|
99
|
-
visited = set() # Tracks visited nodes
|
|
99
|
+
visited = set() if visited is None else visited # Tracks visited nodes
|
|
100
100
|
finished = set() # Tracks nodes whose descendants are fully explored
|
|
101
|
-
stack = [(start_node, iter(graph[start_node]))]
|
|
101
|
+
stack = [(start_node, iter(sorted(graph[start_node], key=GraphUtils._sort_node)))]
|
|
102
102
|
|
|
103
103
|
while stack:
|
|
104
104
|
node, children = stack[-1]
|
|
@@ -110,11 +110,17 @@ def dfs_back_edges(graph, start_node):
|
|
|
110
110
|
if child not in finished:
|
|
111
111
|
yield node, child # Found a back edge
|
|
112
112
|
elif child not in finished: # Check if the child has not been finished
|
|
113
|
-
stack.append((child, iter(graph[child])))
|
|
113
|
+
stack.append((child, iter(sorted(graph[child], key=GraphUtils._sort_node))))
|
|
114
114
|
except StopIteration:
|
|
115
115
|
stack.pop() # Done with this node's children
|
|
116
116
|
finished.add(node) # Mark this node as finished
|
|
117
117
|
|
|
118
|
+
if visit_all_nodes:
|
|
119
|
+
while len(visited) < len(graph):
|
|
120
|
+
# If we need to visit all nodes, we can start from unvisited nodes
|
|
121
|
+
node = sorted(set(graph) - visited, key=GraphUtils._sort_node)[0]
|
|
122
|
+
yield from dfs_back_edges(graph, node, visited=visited)
|
|
123
|
+
|
|
118
124
|
|
|
119
125
|
def subgraph_between_nodes(graph, source, frontier, include_frontier=False):
|
|
120
126
|
"""
|
|
@@ -594,17 +600,24 @@ class SCCPlaceholder:
|
|
|
594
600
|
Describes a placeholder for strongly-connected-components in a graph.
|
|
595
601
|
"""
|
|
596
602
|
|
|
597
|
-
__slots__ = (
|
|
603
|
+
__slots__ = (
|
|
604
|
+
"addr",
|
|
605
|
+
"scc_id",
|
|
606
|
+
)
|
|
598
607
|
|
|
599
|
-
def __init__(self, scc_id):
|
|
608
|
+
def __init__(self, scc_id, addr):
|
|
600
609
|
self.scc_id = scc_id
|
|
610
|
+
self.addr = addr
|
|
601
611
|
|
|
602
612
|
def __eq__(self, other):
|
|
603
|
-
return isinstance(other, SCCPlaceholder) and other.scc_id == self.scc_id
|
|
613
|
+
return isinstance(other, SCCPlaceholder) and other.scc_id == self.scc_id and other.addr == self.addr
|
|
604
614
|
|
|
605
615
|
def __hash__(self):
|
|
606
616
|
return hash(f"scc_placeholder_{self.scc_id}")
|
|
607
617
|
|
|
618
|
+
def __repr__(self):
|
|
619
|
+
return f"SCCPlaceholder({self.scc_id}, addr={self.addr:#x})"
|
|
620
|
+
|
|
608
621
|
|
|
609
622
|
class GraphUtils:
|
|
610
623
|
"""
|
|
@@ -676,17 +689,17 @@ class GraphUtils:
|
|
|
676
689
|
@staticmethod
|
|
677
690
|
def dfs_postorder_nodes_deterministic(graph: networkx.DiGraph, source):
|
|
678
691
|
visited = set()
|
|
679
|
-
stack = [source]
|
|
692
|
+
stack: list[tuple[Any, bool]] = [(source, True)] # NodeType, is_pre_visit
|
|
680
693
|
while stack:
|
|
681
|
-
node = stack
|
|
682
|
-
if node not in visited:
|
|
694
|
+
node, pre_visit = stack.pop()
|
|
695
|
+
if pre_visit and node not in visited:
|
|
683
696
|
visited.add(node)
|
|
697
|
+
stack.append((node, False))
|
|
684
698
|
for succ in sorted(graph.successors(node), key=GraphUtils._sort_node):
|
|
685
699
|
if succ not in visited:
|
|
686
|
-
stack.append(succ)
|
|
687
|
-
|
|
700
|
+
stack.append((succ, True))
|
|
701
|
+
elif not pre_visit:
|
|
688
702
|
yield node
|
|
689
|
-
stack.pop()
|
|
690
703
|
|
|
691
704
|
@staticmethod
|
|
692
705
|
def reverse_post_order_sort_nodes(graph, nodes=None):
|
|
@@ -759,7 +772,10 @@ class GraphUtils:
|
|
|
759
772
|
"""
|
|
760
773
|
|
|
761
774
|
# fast path for single node graphs
|
|
762
|
-
|
|
775
|
+
number_of_nodes = graph.number_of_nodes()
|
|
776
|
+
if number_of_nodes == 0:
|
|
777
|
+
return []
|
|
778
|
+
if number_of_nodes == 1:
|
|
763
779
|
if nodes is None:
|
|
764
780
|
return list(graph.nodes)
|
|
765
781
|
return [n for n in graph.nodes() if n in nodes]
|
|
@@ -768,21 +784,25 @@ class GraphUtils:
|
|
|
768
784
|
graph_copy = networkx.DiGraph()
|
|
769
785
|
|
|
770
786
|
# find all strongly connected components in the graph
|
|
771
|
-
sccs =
|
|
787
|
+
sccs = sorted(
|
|
788
|
+
(scc for scc in networkx.strongly_connected_components(graph) if len(scc) > 1),
|
|
789
|
+
key=lambda x: (len(x), min(node.addr if hasattr(node, "addr") else node for node in x)),
|
|
790
|
+
)
|
|
772
791
|
comp_indices = {}
|
|
773
792
|
for i, scc in enumerate(sccs):
|
|
793
|
+
scc_addr = min(node.addr if hasattr(node, "addr") else node for node in scc)
|
|
774
794
|
for node in scc:
|
|
775
795
|
if node not in comp_indices:
|
|
776
|
-
comp_indices[node] = i
|
|
796
|
+
comp_indices[node] = (i, scc_addr)
|
|
777
797
|
|
|
778
798
|
# collapse all strongly connected components
|
|
779
799
|
for src, dst in sorted(graph.edges(), key=GraphUtils._sort_edge):
|
|
780
|
-
scc_index = comp_indices.get(src)
|
|
800
|
+
scc_index, scc_addr = comp_indices.get(src, (None, None))
|
|
781
801
|
if scc_index is not None:
|
|
782
|
-
src = SCCPlaceholder(scc_index)
|
|
783
|
-
scc_index = comp_indices.get(dst)
|
|
802
|
+
src = SCCPlaceholder(scc_index, scc_addr)
|
|
803
|
+
scc_index, scc_addr = comp_indices.get(dst, (None, None))
|
|
784
804
|
if scc_index is not None:
|
|
785
|
-
dst = SCCPlaceholder(scc_index)
|
|
805
|
+
dst = SCCPlaceholder(scc_index, scc_addr)
|
|
786
806
|
|
|
787
807
|
if isinstance(src, SCCPlaceholder) and isinstance(dst, SCCPlaceholder) and src == dst:
|
|
788
808
|
if src not in graph_copy:
|
|
@@ -801,11 +821,28 @@ class GraphUtils:
|
|
|
801
821
|
if graph.in_degree(node) == 0:
|
|
802
822
|
graph_copy.add_node(node)
|
|
803
823
|
|
|
804
|
-
|
|
805
|
-
|
|
824
|
+
class NodeWithAddr:
|
|
825
|
+
"""
|
|
826
|
+
Temporary node class.
|
|
827
|
+
"""
|
|
806
828
|
|
|
829
|
+
def __init__(self, addr: int):
|
|
830
|
+
self.addr = addr
|
|
831
|
+
|
|
832
|
+
# topological sort on acyclic graph `graph_copy`
|
|
833
|
+
heads = [nn for nn in graph_copy if graph_copy.in_degree[nn] == 0]
|
|
834
|
+
if len(heads) > 1:
|
|
835
|
+
head = NodeWithAddr(-1)
|
|
836
|
+
for real_head in heads:
|
|
837
|
+
graph_copy.add_edge(head, real_head)
|
|
838
|
+
else:
|
|
839
|
+
assert heads
|
|
840
|
+
head = heads[0]
|
|
841
|
+
tmp_nodes = reversed(list(GraphUtils.dfs_postorder_nodes_deterministic(graph_copy, head)))
|
|
807
842
|
ordered_nodes = []
|
|
808
843
|
for n in tmp_nodes:
|
|
844
|
+
if isinstance(n, NodeWithAddr):
|
|
845
|
+
continue
|
|
809
846
|
if isinstance(n, SCCPlaceholder):
|
|
810
847
|
GraphUtils._append_scc(
|
|
811
848
|
graph,
|
|
@@ -860,9 +897,10 @@ class GraphUtils:
|
|
|
860
897
|
if len(scc_succs) > 1:
|
|
861
898
|
# calculate the distance between each pair of nodes within scc_succs, pick the one with the
|
|
862
899
|
# shortest total distance
|
|
900
|
+
sorted_scc_succs = sorted(scc_succs, key=GraphUtils._sort_node)
|
|
863
901
|
scc_node_distance = defaultdict(int)
|
|
864
|
-
for scc_succ in
|
|
865
|
-
for other_node in
|
|
902
|
+
for scc_succ in sorted_scc_succs:
|
|
903
|
+
for other_node in sorted_scc_succs:
|
|
866
904
|
if other_node is scc_succ:
|
|
867
905
|
continue
|
|
868
906
|
scc_node_distance[scc_succ] += networkx.algorithms.shortest_path_length(
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: angr
|
|
3
|
-
Version: 9.2.
|
|
3
|
+
Version: 9.2.167
|
|
4
4
|
Summary: A multi-architecture binary analysis toolkit, with the ability to perform dynamic symbolic execution and various static analyses on binaries
|
|
5
5
|
License: BSD-2-Clause
|
|
6
6
|
Project-URL: Homepage, https://angr.io/
|
|
@@ -16,12 +16,12 @@ Description-Content-Type: text/markdown
|
|
|
16
16
|
License-File: LICENSE
|
|
17
17
|
Requires-Dist: cxxheaderparser
|
|
18
18
|
Requires-Dist: GitPython
|
|
19
|
-
Requires-Dist: archinfo==9.2.
|
|
19
|
+
Requires-Dist: archinfo==9.2.167
|
|
20
20
|
Requires-Dist: cachetools
|
|
21
21
|
Requires-Dist: capstone==5.0.3
|
|
22
22
|
Requires-Dist: cffi>=1.14.0
|
|
23
|
-
Requires-Dist: claripy==9.2.
|
|
24
|
-
Requires-Dist: cle==9.2.
|
|
23
|
+
Requires-Dist: claripy==9.2.167
|
|
24
|
+
Requires-Dist: cle==9.2.167
|
|
25
25
|
Requires-Dist: mulpyplexer
|
|
26
26
|
Requires-Dist: networkx!=2.8.1,>=2.0
|
|
27
27
|
Requires-Dist: protobuf>=5.28.2
|
|
@@ -30,7 +30,7 @@ Requires-Dist: pycparser>=2.18
|
|
|
30
30
|
Requires-Dist: pydemumble
|
|
31
31
|
Requires-Dist: pyformlang
|
|
32
32
|
Requires-Dist: pypcode<4.0,>=3.2.1
|
|
33
|
-
Requires-Dist: pyvex==9.2.
|
|
33
|
+
Requires-Dist: pyvex==9.2.167
|
|
34
34
|
Requires-Dist: rich>=13.1.0
|
|
35
35
|
Requires-Dist: sortedcontainers
|
|
36
36
|
Requires-Dist: sympy
|