angr 9.2.161__cp310-abi3-manylinux2014_x86_64.whl → 9.2.163__cp310-abi3-manylinux2014_x86_64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


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

Files changed (50) hide show
  1. angr/__init__.py +1 -1
  2. angr/ailment/expression.py +12 -0
  3. angr/analyses/analysis.py +0 -1
  4. angr/analyses/cfg/cfg_base.py +6 -2
  5. angr/analyses/decompiler/ail_simplifier.py +20 -1
  6. angr/analyses/decompiler/block_simplifier.py +6 -3
  7. angr/analyses/decompiler/clinic.py +6 -6
  8. angr/analyses/decompiler/condition_processor.py +24 -0
  9. angr/analyses/decompiler/counters/call_counter.py +11 -1
  10. angr/analyses/decompiler/decompiler.py +3 -1
  11. angr/analyses/decompiler/graph_region.py +11 -2
  12. angr/analyses/decompiler/optimization_passes/const_prop_reverter.py +1 -1
  13. angr/analyses/decompiler/optimization_passes/lowered_switch_simplifier.py +1 -0
  14. angr/analyses/decompiler/optimization_passes/optimization_pass.py +31 -11
  15. angr/analyses/decompiler/optimization_passes/return_duplicator_low.py +2 -0
  16. angr/analyses/decompiler/region_simplifiers/goto.py +3 -3
  17. angr/analyses/decompiler/region_simplifiers/if_.py +2 -2
  18. angr/analyses/decompiler/region_simplifiers/loop.py +2 -2
  19. angr/analyses/decompiler/structured_codegen/c.py +3 -3
  20. angr/analyses/decompiler/structuring/dream.py +1 -1
  21. angr/analyses/decompiler/structuring/phoenix.py +119 -67
  22. angr/analyses/decompiler/structuring/recursive_structurer.py +3 -2
  23. angr/analyses/decompiler/structuring/sailr.py +51 -43
  24. angr/analyses/decompiler/structuring/structurer_base.py +2 -3
  25. angr/analyses/deobfuscator/string_obf_opt_passes.py +1 -1
  26. angr/analyses/disassembly.py +1 -1
  27. angr/analyses/fcp/fcp.py +11 -10
  28. angr/analyses/flirt/flirt_sig.py +5 -2
  29. angr/analyses/reaching_definitions/function_handler.py +2 -1
  30. angr/analyses/reaching_definitions/function_handler_library/stdio.py +7 -6
  31. angr/analyses/reaching_definitions/function_handler_library/stdlib.py +10 -4
  32. angr/analyses/reaching_definitions/function_handler_library/string.py +13 -2
  33. angr/analyses/reaching_definitions/function_handler_library/unistd.py +7 -0
  34. angr/analyses/s_propagator.py +2 -2
  35. angr/analyses/variable_recovery/engine_base.py +8 -4
  36. angr/knowledge_plugins/functions/function.py +1 -1
  37. angr/knowledge_plugins/functions/function_manager.py +1 -2
  38. angr/project.py +5 -2
  39. angr/rustylib.abi3.so +0 -0
  40. angr/sim_type.py +2 -2
  41. angr/simos/javavm.py +1 -1
  42. angr/utils/graph.py +28 -12
  43. angr/utils/library.py +13 -12
  44. angr/utils/ssa/__init__.py +54 -2
  45. {angr-9.2.161.dist-info → angr-9.2.163.dist-info}/METADATA +5 -5
  46. {angr-9.2.161.dist-info → angr-9.2.163.dist-info}/RECORD +50 -50
  47. {angr-9.2.161.dist-info → angr-9.2.163.dist-info}/WHEEL +0 -0
  48. {angr-9.2.161.dist-info → angr-9.2.163.dist-info}/entry_points.txt +0 -0
  49. {angr-9.2.161.dist-info → angr-9.2.163.dist-info}/licenses/LICENSE +0 -0
  50. {angr-9.2.161.dist-info → angr-9.2.163.dist-info}/top_level.txt +0 -0
@@ -126,6 +126,12 @@ class PhoenixStructurer(StructurerBase):
126
126
  self._improve_algorithm = improve_algorithm
127
127
  self._edge_virtualization_hints = []
128
128
 
129
+ # node_order keeps a dictionary of nodes and their order in a quasi-topological sort of the region full graph
130
+ # (graph_with_successors). _generate_node_order() initializes this dictionary. we then update this dictionary
131
+ # when new nodes are created. we do not populate this dictionary when working on acyclic graphs because it's
132
+ # not used for acyclic graphs.
133
+ self._node_order: dict[Any, int] | None = None
134
+
129
135
  self._use_multistmtexprs = use_multistmtexprs
130
136
  self._multistmtexpr_stmt_threshold = multistmtexpr_stmt_threshold
131
137
  self._analyze()
@@ -221,8 +227,11 @@ class PhoenixStructurer(StructurerBase):
221
227
 
222
228
  def _analyze_cyclic(self) -> bool:
223
229
  any_matches = False
224
- acyclic_graph = to_acyclic_graph(self._region.graph, loop_heads=[self._region.head])
225
- for node in list(reversed(GraphUtils.quasi_topological_sort_nodes(acyclic_graph))):
230
+
231
+ if self._node_order is None:
232
+ self._generate_node_order()
233
+ acyclic_graph = to_acyclic_graph(self._region.graph, node_order=self._node_order)
234
+ for node in list(GraphUtils.dfs_postorder_nodes_deterministic(acyclic_graph, self._region.head)):
226
235
  if node not in self._region.graph:
227
236
  continue
228
237
  matched = self._match_cyclic_schemas(
@@ -323,10 +332,9 @@ class PhoenixStructurer(StructurerBase):
323
332
  # it's a while loop if the conditional jump (or the head block) is at the beginning of node
324
333
  loop_type = "while" if head_block_idx == 0 else "do-while"
325
334
  # otherwise it's a do-while loop
326
- edge_cond_left = self.cond_proc.recover_edge_condition(full_graph, head_block, left)
327
- edge_cond_right = self.cond_proc.recover_edge_condition(full_graph, head_block, right)
328
- if claripy.is_true(claripy.Not(edge_cond_left) == edge_cond_right): # type: ignore
335
+ if self.cond_proc.have_opposite_edge_conditions(full_graph, head_block, left, right):
329
336
  # c = !c
337
+ edge_cond_left = self.cond_proc.recover_edge_condition(full_graph, head_block, left)
330
338
  if head_block_idx == 0:
331
339
  self._remove_first_statement_if_jump(head_block)
332
340
  else:
@@ -351,13 +359,12 @@ class PhoenixStructurer(StructurerBase):
351
359
  # possible candidate
352
360
  _, _, head_block = self._find_node_going_to_dst(node, left, condjump_only=True)
353
361
  if head_block is not None:
354
- edge_cond_left = self.cond_proc.recover_edge_condition(full_graph, head_block, left)
355
- edge_cond_right = self.cond_proc.recover_edge_condition(full_graph, head_block, right)
356
- if claripy.is_true(claripy.Not(edge_cond_left) == edge_cond_right): # type: ignore
362
+ if self.cond_proc.have_opposite_edge_conditions(full_graph, head_block, left, right):
357
363
  # c = !c
358
364
  if PhoenixStructurer._is_single_statement_block(node):
359
365
  # the single-statement-block check is to ensure we don't execute any code before the
360
366
  # conditional jump. this way the entire node can be dropped.
367
+ edge_cond_left = self.cond_proc.recover_edge_condition(full_graph, head_block, left)
361
368
  new_node = SequenceNode(node.addr, nodes=[left])
362
369
  loop_node = LoopNode("while", edge_cond_left, new_node, addr=node.addr)
363
370
 
@@ -372,6 +379,7 @@ class PhoenixStructurer(StructurerBase):
372
379
 
373
380
  return True, loop_node, right
374
381
  # we generate a while-true loop instead
382
+ edge_cond_right = self.cond_proc.recover_edge_condition(full_graph, head_block, right)
375
383
  last_stmt = self._remove_last_statement_if_jump(head_block)
376
384
  assert last_stmt is not None
377
385
  cond_jump = Jump(
@@ -400,10 +408,9 @@ class PhoenixStructurer(StructurerBase):
400
408
  # while (true) { ...; if (...) break; }
401
409
  _, _, head_block = self._find_node_going_to_dst(node, left, condjump_only=True)
402
410
  if head_block is not None:
403
- edge_cond_left = self.cond_proc.recover_edge_condition(full_graph, head_block, left)
404
- edge_cond_right = self.cond_proc.recover_edge_condition(full_graph, head_block, right)
405
- if claripy.is_true(claripy.Not(edge_cond_left) == edge_cond_right): # type: ignore
411
+ if self.cond_proc.have_opposite_edge_conditions(full_graph, head_block, left, right):
406
412
  # c = !c
413
+ edge_cond_right = self.cond_proc.recover_edge_condition(full_graph, head_block, right)
407
414
  self._remove_last_statement_if_jump(head_block)
408
415
  cond_break = ConditionalBreakNode(node.addr, edge_cond_right, right.addr)
409
416
  new_node = SequenceNode(node.addr, nodes=[node, cond_break, left])
@@ -501,6 +508,10 @@ class PhoenixStructurer(StructurerBase):
501
508
  self.replace_nodes(full_graph, node, loop_node, self_loop=False)
502
509
  full_graph.add_edge(loop_node, successor_node)
503
510
 
511
+ if self._node_order is not None:
512
+ self._node_order[loop_node] = self._node_order[node]
513
+ self._node_order[successor_node] = self._node_order[loop_node]
514
+
504
515
  return True, loop_node, successor_node
505
516
 
506
517
  def _cyclic_while_with_single_successor_must_return(self, successor_node: SequenceNode) -> bool:
@@ -527,10 +538,9 @@ class PhoenixStructurer(StructurerBase):
527
538
  # possible candidate
528
539
  _, _, succ_block = self._find_node_going_to_dst(succ, out_node, condjump_only=True)
529
540
  if succ_block is not None:
530
- edge_cond_succhead = self.cond_proc.recover_edge_condition(full_graph, succ_block, node)
531
- edge_cond_succout = self.cond_proc.recover_edge_condition(full_graph, succ_block, out_node)
532
- if claripy.is_true(claripy.Not(edge_cond_succhead) == edge_cond_succout): # type: ignore
541
+ if self.cond_proc.have_opposite_edge_conditions(full_graph, succ_block, node, out_node):
533
542
  # c = !c
543
+ edge_cond_succhead = self.cond_proc.recover_edge_condition(full_graph, succ_block, node)
534
544
  self._remove_last_statement_if_jump(succ)
535
545
  drop_succ = False
536
546
 
@@ -570,10 +580,9 @@ class PhoenixStructurer(StructurerBase):
570
580
  succ = succs[0]
571
581
  if not full_graph.has_edge(succ, node):
572
582
  # possible candidate
573
- edge_cond_head = self.cond_proc.recover_edge_condition(full_graph, node, node)
574
- edge_cond_head_succ = self.cond_proc.recover_edge_condition(full_graph, node, succ)
575
- if claripy.is_true(claripy.Not(edge_cond_head) == edge_cond_head_succ): # type: ignore
583
+ if self.cond_proc.have_opposite_edge_conditions(full_graph, node, node, succ):
576
584
  # c = !c
585
+ edge_cond_head = self.cond_proc.recover_edge_condition(full_graph, node, node)
577
586
  self._remove_last_statement_if_jump(node)
578
587
  seq_node = SequenceNode(node.addr, nodes=[node]) if not isinstance(node, SequenceNode) else node
579
588
  loop_node = LoopNode("do-while", edge_cond_head, seq_node, addr=seq_node.addr)
@@ -1039,7 +1048,7 @@ class PhoenixStructurer(StructurerBase):
1039
1048
 
1040
1049
  self._assert_graph_ok(acyclic_graph, "Removed wrong edges")
1041
1050
 
1042
- for node in list(reversed(GraphUtils.quasi_topological_sort_nodes(acyclic_graph))):
1051
+ for node in list(GraphUtils.dfs_postorder_nodes_deterministic(acyclic_graph, head)):
1043
1052
  if node not in graph:
1044
1053
  continue
1045
1054
  if graph.has_edge(node, head):
@@ -1145,6 +1154,8 @@ class PhoenixStructurer(StructurerBase):
1145
1154
  node_default = Block(SWITCH_MISSING_DEFAULT_NODE_ADDR, 0, statements=[jmp_to_default_node])
1146
1155
  graph.add_edge(node, node_default)
1147
1156
  full_graph.add_edge(node, node_default)
1157
+ if self._node_order is not None:
1158
+ self._node_order[node_default] = self._node_order[node]
1148
1159
  r = self._make_switch_cases_core(
1149
1160
  node,
1150
1161
  self.cond_proc.claripy_ast_from_ail_condition(last_stmt.switch_variable),
@@ -1250,6 +1261,8 @@ class PhoenixStructurer(StructurerBase):
1250
1261
  # un-structure IncompleteSwitchCaseNode
1251
1262
  if isinstance(node_a, SequenceNode) and node_a.nodes and isinstance(node_a.nodes[0], IncompleteSwitchCaseNode):
1252
1263
  _, new_seq_node = self._unpack_sequencenode_head(graph, node_a)
1264
+ if new_seq_node is not None and self._node_order is not None:
1265
+ self._node_order[new_seq_node] = self._node_order[node_a]
1253
1266
  self._unpack_sequencenode_head(full_graph, node_a, new_seq=new_seq_node)
1254
1267
  # update node_a
1255
1268
  node_a = next(iter(nn for nn in graph.nodes if nn.addr == target))
@@ -1260,6 +1273,8 @@ class PhoenixStructurer(StructurerBase):
1260
1273
  self._unpack_incompleteswitchcasenode(full_graph, node_a) # this shall not fail
1261
1274
  # update node_a
1262
1275
  node_a = next(iter(nn for nn in graph.nodes if nn.addr == target))
1276
+ if self._node_order is not None:
1277
+ self._generate_node_order()
1263
1278
 
1264
1279
  better_node_a = node_a
1265
1280
  if isinstance(node_a, SequenceNode) and is_empty_or_label_only_node(node_a.nodes[0]) and len(node_a.nodes) == 2:
@@ -1604,6 +1619,8 @@ class PhoenixStructurer(StructurerBase):
1604
1619
  self.replace_nodes(full_graph, node, new_node)
1605
1620
  if out_nodes:
1606
1621
  full_graph.add_edge(new_node, out_nodes[0])
1622
+ if self._node_order:
1623
+ self._node_order[new_node] = self._node_order[node]
1607
1624
  return True
1608
1625
  return False
1609
1626
 
@@ -1785,6 +1802,8 @@ class PhoenixStructurer(StructurerBase):
1785
1802
 
1786
1803
  graph.add_edge(head, scnode)
1787
1804
  full_graph.add_edge(head, scnode)
1805
+ if self._node_order is not None:
1806
+ self._node_order[scnode] = self._node_order[head]
1788
1807
 
1789
1808
  if out_edges:
1790
1809
  # for all out edges going to head, we ensure there is a goto at the end of each corresponding case node
@@ -1934,10 +1953,9 @@ class PhoenixStructurer(StructurerBase):
1934
1953
  and not self._is_node_unstructured_switch_case_head(left)
1935
1954
  and not self._is_node_unstructured_switch_case_head(right)
1936
1955
  ):
1937
- edge_cond_left = self.cond_proc.recover_edge_condition(full_graph, start_node, left)
1938
- edge_cond_right = self.cond_proc.recover_edge_condition(full_graph, start_node, right)
1939
- if claripy.is_true(claripy.Not(edge_cond_left) == edge_cond_right): # type: ignore
1956
+ if self.cond_proc.have_opposite_edge_conditions(full_graph, start_node, left, right):
1940
1957
  # c = !c
1958
+ edge_cond_left = self.cond_proc.recover_edge_condition(full_graph, start_node, left)
1941
1959
  last_if_jump = self._remove_last_statement_if_jump(start_node)
1942
1960
  new_cond_node = ConditionNode(
1943
1961
  last_if_jump.ins_addr if last_if_jump is not None else start_node.addr,
@@ -1976,10 +1994,9 @@ class PhoenixStructurer(StructurerBase):
1976
1994
  if not self._is_node_unstructured_switch_case_head(
1977
1995
  left
1978
1996
  ) and not self._is_node_unstructured_switch_case_head(right):
1979
- edge_cond_left = self.cond_proc.recover_edge_condition(full_graph, start_node, left)
1980
- edge_cond_right = self.cond_proc.recover_edge_condition(full_graph, start_node, right)
1981
- if claripy.is_true(claripy.Not(edge_cond_left) == edge_cond_right): # type: ignore
1997
+ if self.cond_proc.have_opposite_edge_conditions(full_graph, start_node, left, right):
1982
1998
  # c = !c
1999
+ edge_cond_left = self.cond_proc.recover_edge_condition(full_graph, start_node, left)
1983
2000
  last_if_jump = self._remove_last_statement_if_jump(start_node)
1984
2001
  new_cond_node = ConditionNode(
1985
2002
  last_if_jump.ins_addr if last_if_jump is not None else start_node.addr,
@@ -2009,10 +2026,9 @@ class PhoenixStructurer(StructurerBase):
2009
2026
  and full_graph.in_degree[left] == 1
2010
2027
  and full_graph.in_degree[right] >= 2
2011
2028
  ):
2012
- edge_cond_left = self.cond_proc.recover_edge_condition(full_graph, start_node, left)
2013
- edge_cond_right = self.cond_proc.recover_edge_condition(full_graph, start_node, right)
2014
- if claripy.is_true(claripy.Not(edge_cond_left) == edge_cond_right): # type: ignore
2029
+ if self.cond_proc.have_opposite_edge_conditions(full_graph, start_node, left, right):
2015
2030
  # c = !c
2031
+ edge_cond_left = self.cond_proc.recover_edge_condition(full_graph, start_node, left)
2016
2032
  last_if_jump = self._remove_last_statement_if_jump(start_node)
2017
2033
  new_cond_node = ConditionNode(
2018
2034
  last_if_jump.ins_addr if last_if_jump is not None else start_node.addr,
@@ -2044,10 +2060,9 @@ class PhoenixStructurer(StructurerBase):
2044
2060
  or (full_graph.in_degree[right] == 1 and not left_succs)
2045
2061
  )
2046
2062
  ):
2047
- edge_cond_left = self.cond_proc.recover_edge_condition(full_graph, start_node, left)
2048
- edge_cond_right = self.cond_proc.recover_edge_condition(full_graph, start_node, right)
2049
- if claripy.is_true(claripy.Not(edge_cond_left) == edge_cond_right): # type: ignore
2063
+ if self.cond_proc.have_opposite_edge_conditions(full_graph, start_node, left, right):
2050
2064
  # c = !c
2065
+ edge_cond_left = self.cond_proc.recover_edge_condition(full_graph, start_node, left)
2051
2066
  try:
2052
2067
  last_stmt = self.cond_proc.get_last_statement(start_node)
2053
2068
  except EmptyBlockNotice:
@@ -2169,6 +2184,23 @@ class PhoenixStructurer(StructurerBase):
2169
2184
  #
2170
2185
  # We reduce it into if (cond && next_cond) { body } else { else }
2171
2186
 
2187
+ # fast-path check to reject nodes that definitely do not work
2188
+ if full_graph.out_degree[start_node] != 2:
2189
+ return False
2190
+ next_cond_candidates = list(full_graph.successors(start_node))
2191
+ check_passed = False
2192
+ for next_cond in next_cond_candidates:
2193
+ if full_graph.out_degree[next_cond] != 2:
2194
+ continue
2195
+ for next_cond_succ in full_graph.successors(next_cond):
2196
+ if full_graph.has_edge(start_node, next_cond_succ):
2197
+ check_passed = True
2198
+ break
2199
+ if check_passed:
2200
+ break
2201
+ if not check_passed:
2202
+ return False
2203
+
2172
2204
  r = self._match_acyclic_short_circuit_conditions_type_a(graph, full_graph, start_node)
2173
2205
 
2174
2206
  if r is not None:
@@ -2336,21 +2368,17 @@ class PhoenixStructurer(StructurerBase):
2336
2368
  and full_graph.in_degree[left] == 1
2337
2369
  and full_graph.in_degree[right] >= 1
2338
2370
  ):
2339
- edge_cond_left = self.cond_proc.recover_edge_condition(full_graph, start_node, left)
2340
- edge_cond_right = self.cond_proc.recover_edge_condition(full_graph, start_node, right)
2341
- if claripy.is_true(claripy.Not(edge_cond_left) == edge_cond_right): # type: ignore
2371
+ if self.cond_proc.have_opposite_edge_conditions(full_graph, start_node, left, right):
2342
2372
  # c0 = !c0
2343
2373
  left_succs = list(full_graph.successors(left))
2344
2374
  if len(left_succs) == 2 and right in left_succs:
2345
2375
  other_succ = next(iter(succ for succ in left_succs if succ is not right))
2346
2376
  if full_graph.out_degree[right] == 1 and full_graph.has_edge(right, other_succ):
2347
2377
  # there must be an edge between right and other_succ
2348
- edge_cond_left_right = self.cond_proc.recover_edge_condition(full_graph, left, right)
2349
- edge_cond_left_other = self.cond_proc.recover_edge_condition(full_graph, left, other_succ)
2350
- if claripy.is_true(
2351
- claripy.Not(edge_cond_left_right) == edge_cond_left_other # type: ignore
2352
- ):
2378
+ if self.cond_proc.have_opposite_edge_conditions(full_graph, left, right, other_succ):
2353
2379
  # c1 = !c1
2380
+ edge_cond_left = self.cond_proc.recover_edge_condition(full_graph, start_node, left)
2381
+ edge_cond_left_right = self.cond_proc.recover_edge_condition(full_graph, left, right)
2354
2382
  return left, edge_cond_left, right, edge_cond_left_right, other_succ
2355
2383
  return None
2356
2384
 
@@ -2373,7 +2401,7 @@ class PhoenixStructurer(StructurerBase):
2373
2401
  if len(succs) == 2:
2374
2402
  left, right = succs
2375
2403
 
2376
- if full_graph.in_degree[left] == 1 and full_graph.in_degree[right] == 2:
2404
+ if full_graph.in_degree[left] == 1 and full_graph.in_degree[right] >= 2:
2377
2405
  left, right = right, left
2378
2406
 
2379
2407
  # ensure left and right nodes are not the head of a switch-case construct
@@ -2384,24 +2412,20 @@ class PhoenixStructurer(StructurerBase):
2384
2412
 
2385
2413
  if (
2386
2414
  self._is_sequential_statement_block(right)
2387
- and full_graph.in_degree[left] == 2
2415
+ and full_graph.in_degree[left] >= 2
2388
2416
  and full_graph.in_degree[right] == 1
2389
2417
  ):
2390
- edge_cond_left = self.cond_proc.recover_edge_condition(full_graph, start_node, left)
2391
- edge_cond_right = self.cond_proc.recover_edge_condition(full_graph, start_node, right)
2392
- if claripy.is_true(claripy.Not(edge_cond_left) == edge_cond_right): # type: ignore
2418
+ if self.cond_proc.have_opposite_edge_conditions(full_graph, start_node, left, right):
2393
2419
  # c0 = !c0
2394
2420
  right_succs = list(full_graph.successors(right))
2395
2421
  left_succs = list(full_graph.successors(left))
2396
2422
  if len(right_succs) == 2 and left in right_succs:
2397
2423
  else_node = next(iter(succ for succ in right_succs if succ is not left))
2398
2424
  if len([succ for succ in left_succs if succ is not else_node]) == 1:
2399
- edge_cond_right_left = self.cond_proc.recover_edge_condition(full_graph, right, left)
2400
- edge_cond_right_else = self.cond_proc.recover_edge_condition(full_graph, right, else_node)
2401
- if claripy.is_true(
2402
- claripy.Not(edge_cond_right_left) == edge_cond_right_else # type: ignore
2403
- ):
2425
+ if self.cond_proc.have_opposite_edge_conditions(full_graph, right, left, else_node):
2404
2426
  # c1 = !c1
2427
+ edge_cond_left = self.cond_proc.recover_edge_condition(full_graph, start_node, left)
2428
+ edge_cond_right_left = self.cond_proc.recover_edge_condition(full_graph, right, left)
2405
2429
  return left, edge_cond_left, right, edge_cond_right_left, else_node
2406
2430
  return None
2407
2431
 
@@ -2432,23 +2456,19 @@ class PhoenixStructurer(StructurerBase):
2432
2456
  and full_graph.in_degree[left] == 1
2433
2457
  and full_graph.in_degree[successor] >= 1
2434
2458
  ):
2435
- edge_cond_left = self.cond_proc.recover_edge_condition(full_graph, start_node, left)
2436
- edge_cond_successor = self.cond_proc.recover_edge_condition(full_graph, start_node, successor)
2437
- if claripy.is_true(claripy.Not(edge_cond_left) == edge_cond_successor): # type: ignore
2459
+ if self.cond_proc.have_opposite_edge_conditions(full_graph, start_node, left, successor):
2438
2460
  # c0 = !c0
2439
2461
  left_succs = list(full_graph.successors(left))
2440
2462
  if len(left_succs) == 2 and successor in left_succs:
2441
2463
  right = next(iter(succ for succ in left_succs if succ is not successor))
2442
2464
  if full_graph.out_degree[right] == 1 and full_graph.has_edge(right, successor):
2443
2465
  # there must be an edge from right to successor
2444
- edge_cond_left_right = self.cond_proc.recover_edge_condition(full_graph, left, right)
2445
- edge_cond_left_successor = self.cond_proc.recover_edge_condition(
2446
- full_graph, left, successor
2447
- )
2448
- if claripy.is_true(
2449
- claripy.Not(edge_cond_left_right) == edge_cond_left_successor # type: ignore
2450
- ):
2466
+ if self.cond_proc.have_opposite_edge_conditions(full_graph, left, right, successor):
2451
2467
  # c1 = !c1
2468
+ edge_cond_left = self.cond_proc.recover_edge_condition(full_graph, start_node, left)
2469
+ edge_cond_left_successor = self.cond_proc.recover_edge_condition(
2470
+ full_graph, left, successor
2471
+ )
2452
2472
  return left, edge_cond_left, successor, edge_cond_left_successor, right
2453
2473
  return None
2454
2474
 
@@ -2484,17 +2504,15 @@ class PhoenixStructurer(StructurerBase):
2484
2504
  and full_graph.in_degree[left] == 1
2485
2505
  and full_graph.in_degree[else_node] >= 1
2486
2506
  ):
2487
- edge_cond_left = self.cond_proc.recover_edge_condition(full_graph, start_node, left)
2488
- edge_cond_else = self.cond_proc.recover_edge_condition(full_graph, start_node, else_node)
2489
- if claripy.is_true(claripy.Not(edge_cond_left) == edge_cond_else): # type: ignore
2507
+ if self.cond_proc.have_opposite_edge_conditions(full_graph, start_node, left, else_node):
2490
2508
  # c0 = !c0
2491
2509
  left_succs = list(full_graph.successors(left))
2492
2510
  if len(left_succs) == 2 and else_node in left_succs:
2493
2511
  right = next(iter(succ for succ in left_succs if succ is not else_node))
2494
- edge_cond_left_right = self.cond_proc.recover_edge_condition(full_graph, left, right)
2495
- edge_cond_left_else = self.cond_proc.recover_edge_condition(full_graph, left, else_node)
2496
- if claripy.is_true(claripy.Not(edge_cond_left_right) == edge_cond_left_else): # type: ignore
2512
+ if self.cond_proc.have_opposite_edge_conditions(full_graph, left, right, else_node):
2497
2513
  # c1 = !c1
2514
+ edge_cond_left = self.cond_proc.recover_edge_condition(full_graph, start_node, left)
2515
+ edge_cond_left_right = self.cond_proc.recover_edge_condition(full_graph, left, right)
2498
2516
  return left, edge_cond_left, right, edge_cond_left_right, else_node
2499
2517
  return None
2500
2518
 
@@ -2516,7 +2534,7 @@ class PhoenixStructurer(StructurerBase):
2516
2534
  if networkx.is_directed_acyclic_graph(full_graph):
2517
2535
  acyclic_graph = full_graph
2518
2536
  else:
2519
- acyclic_graph = to_acyclic_graph(full_graph, loop_heads=[head])
2537
+ acyclic_graph = to_acyclic_graph(full_graph, node_order=self._node_order)
2520
2538
  for src, dst in acyclic_graph.edges:
2521
2539
  if src is dst:
2522
2540
  continue
@@ -2532,7 +2550,20 @@ class PhoenixStructurer(StructurerBase):
2532
2550
  if (src.addr, dst.addr) not in self.whitelist_edges:
2533
2551
  other_edges.append((src, dst))
2534
2552
 
2535
- ordered_nodes = GraphUtils.quasi_topological_sort_nodes(acyclic_graph, loop_heads=[head])
2553
+ # acyclic graph may contain more than one entry node, so we may add a temporary head node to ensure all nodes
2554
+ # are accounted for in node_seq
2555
+ graph_entries = [nn for nn in acyclic_graph if acyclic_graph.in_degree[nn] == 0]
2556
+ postorder_head = head
2557
+ if len(graph_entries) > 1:
2558
+ postorder_head = Block(0, 0)
2559
+ for nn in graph_entries:
2560
+ acyclic_graph.add_edge(postorder_head, nn)
2561
+ ordered_nodes = list(
2562
+ reversed(list(GraphUtils.dfs_postorder_nodes_deterministic(acyclic_graph, postorder_head)))
2563
+ )
2564
+ if len(graph_entries) > 1:
2565
+ ordered_nodes.remove(postorder_head)
2566
+ acyclic_graph.remove_node(postorder_head)
2536
2567
  node_seq = {nn: (len(ordered_nodes) - idx) for (idx, nn) in enumerate(ordered_nodes)} # post-order
2537
2568
 
2538
2569
  if all_edges_wo_dominance:
@@ -2612,6 +2643,8 @@ class PhoenixStructurer(StructurerBase):
2612
2643
  self.virtualized_edges.add((src, dst))
2613
2644
  if new_src is not None:
2614
2645
  self.replace_nodes(graph, src, new_src)
2646
+ if self._node_order is not None:
2647
+ self._node_order[new_src] = self._node_order[src]
2615
2648
  if full_graph is not None:
2616
2649
  self.virtualized_edges.add((src, dst))
2617
2650
  full_graph.remove_edge(src, dst)
@@ -2921,10 +2954,29 @@ class PhoenixStructurer(StructurerBase):
2921
2954
  src, dst = edge_
2922
2955
  dst_in_degree = graph.in_degree[dst]
2923
2956
  src_out_degree = graph.out_degree[src]
2924
- return -node_seq.get(dst), dst_in_degree, src_out_degree, -src.addr, -dst.addr # type: ignore
2957
+ return -node_seq[dst], dst_in_degree, src_out_degree, -src.addr, -dst.addr # type: ignore
2925
2958
 
2926
2959
  return sorted(edges, key=_sort_edge, reverse=True)
2927
2960
 
2961
+ def _generate_node_order(self):
2962
+ the_graph = (
2963
+ self._region.graph_with_successors if self._region.graph_with_successors is not None else self._region.graph
2964
+ )
2965
+ the_head = self._region.head
2966
+ ordered_nodes = GraphUtils.quasi_topological_sort_nodes(
2967
+ the_graph,
2968
+ loop_heads=[the_head],
2969
+ )
2970
+ self._node_order = {n: i for i, n in enumerate(ordered_nodes)}
2971
+
2972
+ def replace_nodes(self, graph, old_node_0, new_node, old_node_1=None, self_loop=True):
2973
+ super().replace_nodes(graph, old_node_0, new_node, old_node_1=old_node_1, self_loop=self_loop)
2974
+ if self._node_order is not None and graph is self._region.graph_with_successors:
2975
+ if old_node_1 is not None:
2976
+ self._node_order[new_node] = min(self._node_order[old_node_0], self._node_order[old_node_1])
2977
+ else:
2978
+ self._node_order[new_node] = self._node_order[old_node_0]
2979
+
2928
2980
  @staticmethod
2929
2981
  def _replace_node_in_edge_list(edge_list: list[tuple], old_node, new_node) -> None:
2930
2982
  for idx in range(len(edge_list)): # pylint:disable=consider-using-enumerate
@@ -43,7 +43,7 @@ class RecursiveStructurer(Analysis):
43
43
  self.structurer_cls = structurer_cls if structurer_cls is not None else DreamStructurer
44
44
  self.structurer_options = kwargs
45
45
 
46
- self.result = None
46
+ self.result: BaseNode | None = None
47
47
  self.result_incomplete: bool = False
48
48
 
49
49
  self._analyze()
@@ -161,6 +161,7 @@ class RecursiveStructurer(Analysis):
161
161
  for jump_table_head_addr, jumptable in jump_tables.items():
162
162
  if jump_table_head_addr not in func_block_addrs:
163
163
  continue
164
+ assert jumptable.jumptable_entries is not None
164
165
  for entry_addr in jumptable.jumptable_entries:
165
166
  entries[entry_addr] = jump_table_head_addr
166
167
 
@@ -178,7 +179,7 @@ class RecursiveStructurer(Analysis):
178
179
  continue
179
180
  if node.addr == self.function.addr:
180
181
  return node
181
- if min_node is None or min_node.addr < node.addr:
182
+ if min_node is None or (min_node.addr is not None and node.addr is not None and min_node.addr < node.addr):
182
183
  min_node = node
183
184
 
184
185
  return min_node
@@ -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
- # the first few heuristics are based on the post-dominator count of the edge
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
- graph_copy = networkx.DiGraph(graph)
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
- # create the next heuristic based on the best edges from the previous heuristic
81
- filtered_edge_postdom_count = edge_postdom_count.copy()
82
- for edge in list(edge_postdom_count.keys()):
83
- if edge not in best_edges:
84
- del filtered_edge_postdom_count[edge]
85
- if filtered_edge_postdom_count:
86
- edge_postdom_count = filtered_edge_postdom_count
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
- # H3: the edge that goes directly to a return statement should be virtualized first
98
- # this is believed to be good because it can be corrected in later optimization by duplicating
99
- # the return
100
- candidate_edges = best_edges
101
- best_edges = []
102
- for src, dst in candidate_edges:
103
- if graph.has_node(dst) and structured_node_is_simple_return(dst, graph):
104
- best_edges.append((src, dst))
105
-
106
- if len(best_edges) == 1:
107
- return best_edges
108
- if not best_edges:
109
- best_edges = candidate_edges
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
- @staticmethod
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] + [new_stmt]
93
+ statements = [*block.statements[:-1], new_stmt]
94
94
 
95
95
  # remove N-2 continuous stack assignment
96
96
  if len(deobf_content) > 2:
@@ -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, specialized=False, qualified=True)
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:
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
- succs = list(func_graph_with_callees.successors(node))
347
- if any(isinstance(succ, (Function, HookNode)) for succ in succs):
348
- callee = next(succ for succ in succs if isinstance(succ, (Function, HookNode)))
349
- if isinstance(callee, HookNode):
350
- # attempt to convert it into a function
351
- if self.kb.functions.contains_addr(callee.addr):
352
- callee = self.kb.functions.get_by_addr(callee.addr)
353
- else:
354
- callee = None
355
- state = self._handle_function(state, callee)
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