angr 9.2.139__py3-none-manylinux2014_x86_64.whl → 9.2.140__py3-none-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 (68) hide show
  1. angr/__init__.py +1 -1
  2. angr/analyses/calling_convention/calling_convention.py +48 -21
  3. angr/analyses/cfg/cfg_base.py +13 -0
  4. angr/analyses/cfg/cfg_fast.py +11 -0
  5. angr/analyses/decompiler/ail_simplifier.py +67 -52
  6. angr/analyses/decompiler/clinic.py +68 -43
  7. angr/analyses/decompiler/decompiler.py +17 -7
  8. angr/analyses/decompiler/expression_narrower.py +1 -1
  9. angr/analyses/decompiler/optimization_passes/const_prop_reverter.py +8 -7
  10. angr/analyses/decompiler/optimization_passes/ite_region_converter.py +21 -13
  11. angr/analyses/decompiler/optimization_passes/optimization_pass.py +16 -10
  12. angr/analyses/decompiler/optimization_passes/return_duplicator_base.py +2 -2
  13. angr/analyses/decompiler/region_simplifiers/expr_folding.py +259 -108
  14. angr/analyses/decompiler/region_simplifiers/region_simplifier.py +27 -12
  15. angr/analyses/decompiler/structuring/dream.py +21 -17
  16. angr/analyses/decompiler/structuring/phoenix.py +152 -40
  17. angr/analyses/decompiler/structuring/recursive_structurer.py +1 -0
  18. angr/analyses/decompiler/structuring/structurer_base.py +36 -10
  19. angr/analyses/decompiler/structuring/structurer_nodes.py +4 -1
  20. angr/analyses/decompiler/utils.py +60 -1
  21. angr/analyses/deobfuscator/api_obf_finder.py +8 -5
  22. angr/analyses/deobfuscator/api_obf_type2_finder.py +18 -10
  23. angr/analyses/deobfuscator/string_obf_finder.py +105 -18
  24. angr/analyses/forward_analysis/forward_analysis.py +1 -1
  25. angr/analyses/propagator/top_checker_mixin.py +6 -6
  26. angr/analyses/reaching_definitions/__init__.py +2 -1
  27. angr/analyses/reaching_definitions/dep_graph.py +1 -12
  28. angr/analyses/reaching_definitions/engine_vex.py +36 -31
  29. angr/analyses/reaching_definitions/function_handler.py +15 -2
  30. angr/analyses/reaching_definitions/rd_state.py +1 -37
  31. angr/analyses/reaching_definitions/reaching_definitions.py +13 -24
  32. angr/analyses/s_propagator.py +6 -41
  33. angr/analyses/s_reaching_definitions/s_rda_model.py +7 -1
  34. angr/analyses/stack_pointer_tracker.py +36 -22
  35. angr/analyses/typehoon/simple_solver.py +45 -7
  36. angr/analyses/typehoon/typeconsts.py +18 -5
  37. angr/analyses/variable_recovery/engine_base.py +7 -5
  38. angr/block.py +69 -107
  39. angr/callable.py +14 -7
  40. angr/calling_conventions.py +15 -1
  41. angr/distributed/__init__.py +1 -1
  42. angr/engines/__init__.py +7 -8
  43. angr/engines/engine.py +1 -120
  44. angr/engines/failure.py +2 -2
  45. angr/engines/hook.py +2 -2
  46. angr/engines/light/engine.py +2 -2
  47. angr/engines/pcode/engine.py +2 -14
  48. angr/engines/procedure.py +2 -2
  49. angr/engines/soot/engine.py +2 -2
  50. angr/engines/soot/statements/switch.py +1 -1
  51. angr/engines/successors.py +124 -11
  52. angr/engines/syscall.py +2 -2
  53. angr/engines/unicorn.py +3 -3
  54. angr/engines/vex/heavy/heavy.py +3 -15
  55. angr/factory.py +4 -19
  56. angr/knowledge_plugins/key_definitions/atoms.py +8 -4
  57. angr/knowledge_plugins/key_definitions/live_definitions.py +41 -103
  58. angr/sim_type.py +19 -17
  59. angr/state_plugins/plugin.py +19 -4
  60. angr/storage/memory_mixins/memory_mixin.py +1 -1
  61. angr/storage/memory_mixins/paged_memory/pages/multi_values.py +10 -5
  62. angr/utils/ssa/__init__.py +119 -4
  63. {angr-9.2.139.dist-info → angr-9.2.140.dist-info}/METADATA +6 -6
  64. {angr-9.2.139.dist-info → angr-9.2.140.dist-info}/RECORD +68 -68
  65. {angr-9.2.139.dist-info → angr-9.2.140.dist-info}/LICENSE +0 -0
  66. {angr-9.2.139.dist-info → angr-9.2.140.dist-info}/WHEEL +0 -0
  67. {angr-9.2.139.dist-info → angr-9.2.140.dist-info}/entry_points.txt +0 -0
  68. {angr-9.2.139.dist-info → angr-9.2.140.dist-info}/top_level.txt +0 -0
@@ -10,7 +10,7 @@ import claripy
10
10
  import ailment
11
11
 
12
12
  from angr.utils.graph import GraphUtils
13
- from angr.knowledge_plugins.cfg import IndirectJump, IndirectJumpType
13
+ from angr.knowledge_plugins.cfg import IndirectJumpType
14
14
  from angr.analyses.decompiler.graph_region import GraphRegion
15
15
  from angr.analyses.decompiler.empty_node_remover import EmptyNodeRemover
16
16
  from angr.analyses.decompiler.jumptable_entry_condition_rewriter import JumpTableEntryConditionRewriter
@@ -112,6 +112,7 @@ class DreamStructurer(StructurerBase):
112
112
 
113
113
  loop_subgraph = self._region.graph
114
114
  successors = self._region.successors
115
+ assert successors is not None
115
116
 
116
117
  assert len(successors) <= 1
117
118
 
@@ -267,6 +268,7 @@ class DreamStructurer(StructurerBase):
267
268
 
268
269
  def _to_loop_body_sequence(self, loop_head, loop_subgraph, loop_successors):
269
270
  graph = self._region.graph_with_successors
271
+ assert graph is not None
270
272
  loop_region_graph = networkx.DiGraph()
271
273
 
272
274
  # TODO: Make sure the loop body has been structured
@@ -372,7 +374,7 @@ class DreamStructurer(StructurerBase):
372
374
  node.condition_and_nodes = new_cond_and_nodes
373
375
 
374
376
  if node.else_node is not None:
375
- node.else_node = walker._handle(node.else_node)
377
+ node.else_node = walker._handle(node.else_node) # type: ignore
376
378
  return node
377
379
 
378
380
  def _handle_SwitchCaseNode(node, **kwargs): # pylint:disable=unused-argument
@@ -480,8 +482,6 @@ class DreamStructurer(StructurerBase):
480
482
  :return: None
481
483
  """
482
484
 
483
- jump_tables = self.kb.cfgs["CFGFast"].jump_tables
484
-
485
485
  addr2nodes: dict[int, set[CodeNode]] = defaultdict(set)
486
486
  for node in seq.nodes:
487
487
  addr2nodes[node.addr].add(node)
@@ -491,14 +491,14 @@ class DreamStructurer(StructurerBase):
491
491
  node = seq.nodes[i]
492
492
 
493
493
  # Jumptable_AddressLoadedFromMemory
494
- r = self._make_switch_cases_address_loaded_from_memory(seq, i, node, addr2nodes, jump_tables)
494
+ r = self._make_switch_cases_address_loaded_from_memory(seq, i, node, addr2nodes)
495
495
  if r:
496
496
  # we found a node that looks like a switch-case. seq.nodes are changed. resume to find the next such
497
497
  # case
498
498
  break
499
499
 
500
500
  # Jumptable_AddressComputed
501
- r = self._make_switch_cases_address_computed(seq, i, node, addr2nodes, jump_tables)
501
+ r = self._make_switch_cases_address_computed(seq, i, node, addr2nodes)
502
502
  if r:
503
503
  break
504
504
 
@@ -506,9 +506,7 @@ class DreamStructurer(StructurerBase):
506
506
  # we did not find any node that looks like a switch-case. exit.
507
507
  break
508
508
 
509
- def _make_switch_cases_address_loaded_from_memory(
510
- self, seq, i, node, addr2nodes: dict[int, set[CodeNode]], jump_tables: dict[int, IndirectJump]
511
- ) -> bool:
509
+ def _make_switch_cases_address_loaded_from_memory(self, seq, i, node, addr2nodes: dict[int, set[CodeNode]]) -> bool:
512
510
  """
513
511
  A typical jump table involves multiple nodes, which look like the following:
514
512
 
@@ -526,6 +524,8 @@ class DreamStructurer(StructurerBase):
526
524
 
527
525
  try:
528
526
  last_stmt = self.cond_proc.get_last_statement(node)
527
+ if not isinstance(last_stmt, ailment.Stmt.ConditionalJump):
528
+ return False
529
529
  except EmptyBlockNotice:
530
530
  return False
531
531
  successor_addrs = extract_jump_targets(last_stmt)
@@ -533,14 +533,14 @@ class DreamStructurer(StructurerBase):
533
533
  return False
534
534
 
535
535
  for t in successor_addrs:
536
- if t in addr2nodes and t in jump_tables:
536
+ if t in addr2nodes and t in self.jump_tables:
537
537
  # this is a candidate!
538
538
  target = t
539
539
  break
540
540
  else:
541
541
  return False
542
542
 
543
- jump_table = jump_tables[target]
543
+ jump_table = self.jump_tables[target]
544
544
  if jump_table.type != IndirectJumpType.Jumptable_AddressLoadedFromMemory:
545
545
  return False
546
546
 
@@ -563,6 +563,7 @@ class DreamStructurer(StructurerBase):
563
563
  return False
564
564
 
565
565
  # build switch-cases
566
+ assert jump_table.jumptable_entries is not None
566
567
  cases, node_default, to_remove = self._switch_build_cases(
567
568
  seq, cmp_lb, jump_table.jumptable_entries, i, node_b_addr, addr2nodes
568
569
  )
@@ -575,6 +576,7 @@ class DreamStructurer(StructurerBase):
575
576
  if switch_end_addr is not None:
576
577
  self._switch_handle_gotos(cases, node_default, switch_end_addr)
577
578
 
579
+ assert last_stmt.ins_addr is not None
578
580
  self._make_switch_cases_core(
579
581
  seq,
580
582
  i,
@@ -591,12 +593,10 @@ class DreamStructurer(StructurerBase):
591
593
 
592
594
  return True
593
595
 
594
- def _make_switch_cases_address_computed(
595
- self, seq, i, node, addr2nodes: dict[int, set[CodeNode]], jump_tables: dict[int, IndirectJump]
596
- ) -> bool:
597
- if node.addr not in jump_tables:
596
+ def _make_switch_cases_address_computed(self, seq, i, node, addr2nodes: dict[int, set[CodeNode]]) -> bool:
597
+ if node.addr not in self.jump_tables:
598
598
  return False
599
- jump_table = jump_tables[node.addr]
599
+ jump_table = self.jump_tables[node.addr]
600
600
  if jump_table.type != IndirectJumpType.Jumptable_AddressComputed:
601
601
  return False
602
602
 
@@ -622,9 +622,11 @@ class DreamStructurer(StructurerBase):
622
622
  cmp_expr, cmp_lb, cmp_ub = cmp # pylint:disable=unused-variable
623
623
 
624
624
  jumptable_entries = jump_table.jumptable_entries
625
+ assert jumptable_entries is not None
625
626
 
626
627
  if isinstance(last_stmt.false_target, ailment.Expr.Const):
627
628
  default_addr = last_stmt.false_target.value
629
+ assert isinstance(default_addr, int)
628
630
  else:
629
631
  return False
630
632
 
@@ -661,8 +663,9 @@ class DreamStructurer(StructurerBase):
661
663
  addr,
662
664
  addr2nodes,
663
665
  to_remove,
666
+ *,
667
+ jumptable_addr: int,
664
668
  node_a=None,
665
- jumptable_addr=None,
666
669
  ):
667
670
  scnode = SwitchCaseNode(cmp_expr, cases, node_default, addr=addr)
668
671
  scnode = CodeNode(scnode, node.reaching_condition)
@@ -720,6 +723,7 @@ class DreamStructurer(StructurerBase):
720
723
  ):
721
724
  # unpacking is needed
722
725
  for n in node_a.node.nodes:
726
+ assert n.addr is not None
723
727
  if isinstance(n, ConditionNode):
724
728
  unpacked = self._switch_unpack_condition_node(n, jumptable)
725
729
  if unpacked is None:
@@ -15,7 +15,7 @@ from ailment.expression import Const, UnaryOp, MultiStatementExpression
15
15
 
16
16
  from angr.utils.graph import GraphUtils
17
17
  from angr.utils.ail import is_phi_assignment
18
- from angr.knowledge_plugins.cfg import IndirectJumpType
18
+ from angr.knowledge_plugins.cfg import IndirectJump, IndirectJumpType
19
19
  from angr.utils.constants import SWITCH_MISSING_DEFAULT_NODE_ADDR
20
20
  from angr.utils.graph import dominates, to_acyclic_graph, dfs_back_edges
21
21
  from angr.analyses.decompiler.sequence_walker import SequenceWalker
@@ -28,6 +28,7 @@ from angr.analyses.decompiler.utils import (
28
28
  has_nonlabel_nonphi_statements,
29
29
  first_nonlabel_nonphi_statement,
30
30
  switch_extract_bitwiseand_jumptable_info,
31
+ switch_extract_switch_expr_from_jump_target,
31
32
  )
32
33
  from angr.analyses.decompiler.counters.call_counter import AILCallCounter
33
34
  from .structurer_nodes import (
@@ -765,6 +766,7 @@ class PhoenixStructurer(StructurerBase):
765
766
  last_src_stmt = self.cond_proc.get_last_statement(src_block)
766
767
  assert last_src_stmt is not None
767
768
  break_cond = self.cond_proc.recover_edge_condition(fullgraph, src_block, dst)
769
+ assert successor.addr is not None
768
770
  if claripy.is_true(break_cond):
769
771
  break_stmt = Jump(
770
772
  None,
@@ -869,7 +871,7 @@ class PhoenixStructurer(StructurerBase):
869
871
  if cont_block is None:
870
872
  # cont_block is not found. but it's ok. one possibility is that src is a jump table head with one
871
873
  # case being the loop head. in such cases, we can just remove the edge.
872
- if src.addr not in self.kb.cfgs["CFGFast"].jump_tables:
874
+ if src.addr not in self.jump_tables:
873
875
  l.debug(
874
876
  "_refine_cyclic_core: Cannot find the block going to loop head for edge %r -> %r. "
875
877
  "Remove the edge anyway.",
@@ -1070,25 +1072,29 @@ class PhoenixStructurer(StructurerBase):
1070
1072
  # switch cases
1071
1073
 
1072
1074
  def _match_acyclic_switch_cases(self, graph: networkx.DiGraph, full_graph: networkx.DiGraph, node) -> bool:
1073
- if isinstance(node, (SwitchCaseNode, IncompleteSwitchCaseNode)):
1075
+ if isinstance(node, SwitchCaseNode):
1076
+ return False
1077
+
1078
+ r = self._match_acyclic_switch_cases_address_loaded_from_memory_no_default_node(node, graph, full_graph)
1079
+ if r:
1080
+ return r
1081
+
1082
+ if isinstance(node, IncompleteSwitchCaseNode):
1074
1083
  return False
1075
1084
 
1076
1085
  r = self._match_acyclic_switch_cases_incomplete_switch_head(node, graph, full_graph)
1077
1086
  if r:
1078
1087
  return r
1079
- jump_tables = self.kb.cfgs["CFGFast"].jump_tables
1080
- r = self._match_acyclic_switch_cases_address_loaded_from_memory_no_ob_check(
1081
- node, graph, full_graph, jump_tables
1082
- )
1088
+ r = self._match_acyclic_switch_cases_address_loaded_from_memory_no_ob_check(node, graph, full_graph)
1083
1089
  if r:
1084
1090
  return r
1085
- r = self._match_acyclic_switch_cases_address_loaded_from_memory(node, graph, full_graph, jump_tables)
1091
+ r = self._match_acyclic_switch_cases_address_loaded_from_memory(node, graph, full_graph)
1086
1092
  if r:
1087
1093
  return r
1088
- r = self._match_acyclic_switch_cases_address_computed(node, graph, full_graph, jump_tables)
1094
+ r = self._match_acyclic_switch_cases_address_computed(node, graph, full_graph)
1089
1095
  if r:
1090
1096
  return r
1091
- return self._match_acyclic_incomplete_switch_cases(node, graph, full_graph, jump_tables)
1097
+ return self._match_acyclic_incomplete_switch_cases(node, graph, full_graph)
1092
1098
 
1093
1099
  def _match_acyclic_switch_cases_incomplete_switch_head(self, node, graph, full_graph) -> bool:
1094
1100
  try:
@@ -1162,7 +1168,7 @@ class PhoenixStructurer(StructurerBase):
1162
1168
  self._switch_handle_gotos(cases, node_default, switch_end_addr)
1163
1169
  return True
1164
1170
 
1165
- def _match_acyclic_switch_cases_address_loaded_from_memory(self, node, graph, full_graph, jump_tables) -> bool:
1171
+ def _match_acyclic_switch_cases_address_loaded_from_memory(self, node, graph, full_graph) -> bool:
1166
1172
  try:
1167
1173
  last_stmt = self.cond_proc.get_last_statement(node)
1168
1174
  except EmptyBlockNotice:
@@ -1176,14 +1182,14 @@ class PhoenixStructurer(StructurerBase):
1176
1182
  return False
1177
1183
 
1178
1184
  for t in successor_addrs:
1179
- if t in jump_tables:
1185
+ if t in self.jump_tables:
1180
1186
  # this is a candidate!
1181
1187
  target = t
1182
1188
  break
1183
1189
  else:
1184
1190
  return False
1185
1191
 
1186
- jump_table = jump_tables[target]
1192
+ jump_table = self.jump_tables[target]
1187
1193
  if jump_table.type != IndirectJumpType.Jumptable_AddressLoadedFromMemory:
1188
1194
  return False
1189
1195
 
@@ -1203,6 +1209,7 @@ class PhoenixStructurer(StructurerBase):
1203
1209
  return False
1204
1210
 
1205
1211
  # populate whitelist_edges
1212
+ assert jump_table.jumptable_entries is not None
1206
1213
  for case_node_addr in jump_table.jumptable_entries:
1207
1214
  self.whitelist_edges.add((node_a.addr, case_node_addr))
1208
1215
  self.whitelist_edges.add((node.addr, node_b_addr))
@@ -1228,6 +1235,12 @@ class PhoenixStructurer(StructurerBase):
1228
1235
  if not r:
1229
1236
  return False
1230
1237
 
1238
+ node_default = self._switch_find_default_node(graph, node, node_b_addr)
1239
+ if node_default is not None:
1240
+ # ensure we have successfully structured node_default
1241
+ if full_graph.out_degree(node_default) > 1:
1242
+ return False
1243
+
1231
1244
  # un-structure IncompleteSwitchCaseNode
1232
1245
  if isinstance(node_a, SequenceNode) and node_a.nodes and isinstance(node_a.nodes[0], IncompleteSwitchCaseNode):
1233
1246
  _, new_seq_node = self._unpack_sequencenode_head(graph, node_a)
@@ -1285,10 +1298,99 @@ class PhoenixStructurer(StructurerBase):
1285
1298
 
1286
1299
  return True
1287
1300
 
1288
- def _match_acyclic_switch_cases_address_loaded_from_memory_no_ob_check(
1289
- self, node, graph, full_graph, jump_tables
1290
- ) -> bool:
1291
- if node.addr not in jump_tables:
1301
+ def _match_acyclic_switch_cases_address_loaded_from_memory_no_default_node(self, node, graph, full_graph) -> bool:
1302
+ # sanity checks
1303
+ if not isinstance(node, IncompleteSwitchCaseNode):
1304
+ return False
1305
+ if node.addr not in self.jump_tables:
1306
+ return False
1307
+ # ensure _match_acyclic_switch_cases_address_load_from_memory cannot structure its predecessor (and this node)
1308
+ preds = list(graph.predecessors(node))
1309
+ if len(preds) != 1:
1310
+ return False
1311
+ pred = preds[0]
1312
+ if full_graph.out_degree[pred] != 1:
1313
+ return False
1314
+ jump_table: IndirectJump = self.jump_tables[node.addr]
1315
+ if jump_table.type != IndirectJumpType.Jumptable_AddressLoadedFromMemory:
1316
+ return False
1317
+
1318
+ # extract the comparison expression, lower-, and upper-bounds from the last statement
1319
+ last_stmt = self.cond_proc.get_last_statement(node.head)
1320
+ if not isinstance(last_stmt, Jump):
1321
+ return False
1322
+ cmp_expr = switch_extract_switch_expr_from_jump_target(last_stmt.target)
1323
+ if cmp_expr is None:
1324
+ return False
1325
+ cmp_lb = 0
1326
+
1327
+ # populate whitelist_edges
1328
+ assert jump_table.jumptable_entries is not None
1329
+
1330
+ # sanity check: case nodes are successors to node_a. all case nodes must have at most common one successor
1331
+ node_pred = None
1332
+ if graph.in_degree[node] == 1:
1333
+ node_pred = next(iter(graph.predecessors(node)))
1334
+
1335
+ case_nodes = list(graph.successors(node))
1336
+
1337
+ # case 1: the common successor happens to be directly reachable from node_a (usually as a result of compiler
1338
+ # optimization)
1339
+ # example: touch_touch_no_switch.o:main
1340
+ r = self.switch_case_entry_node_has_common_successor_case_1(graph, jump_table, case_nodes, node_pred)
1341
+
1342
+ # case 2: the common successor is not directly reachable from node_a. this is a more common case.
1343
+ if not r:
1344
+ r |= self.switch_case_entry_node_has_common_successor_case_2(graph, jump_table, case_nodes, node_pred)
1345
+
1346
+ if not r:
1347
+ return False
1348
+
1349
+ # un-structure IncompleteSwitchCaseNode
1350
+ if isinstance(node, IncompleteSwitchCaseNode):
1351
+ r = self._unpack_incompleteswitchcasenode(graph, node)
1352
+ if not r:
1353
+ return False
1354
+ self._unpack_incompleteswitchcasenode(full_graph, node) # this shall not fail
1355
+ # update node
1356
+ node = next(iter(nn for nn in graph.nodes if nn.addr == jump_table.addr))
1357
+
1358
+ case_and_entry_addrs = self._find_case_and_entry_addrs(node, graph, cmp_lb, jump_table)
1359
+
1360
+ cases, _, to_remove = self._switch_build_cases(
1361
+ case_and_entry_addrs,
1362
+ node,
1363
+ node,
1364
+ None,
1365
+ graph,
1366
+ full_graph,
1367
+ )
1368
+
1369
+ # we don't know what the end address of this switch-case structure is. let's figure it out
1370
+ switch_end_addr = self._switch_find_switch_end_addr(cases, None, {nn.addr for nn in self._region.graph})
1371
+ r = self._make_switch_cases_core(
1372
+ node,
1373
+ cmp_expr,
1374
+ cases,
1375
+ None,
1376
+ None,
1377
+ last_stmt.ins_addr,
1378
+ to_remove,
1379
+ graph,
1380
+ full_graph,
1381
+ node_a=None,
1382
+ )
1383
+ if not r:
1384
+ return False
1385
+
1386
+ # fully structured into a switch-case. remove node from switch_case_known_heads
1387
+ if switch_end_addr is not None:
1388
+ self._switch_handle_gotos(cases, None, switch_end_addr)
1389
+
1390
+ return True
1391
+
1392
+ def _match_acyclic_switch_cases_address_loaded_from_memory_no_ob_check(self, node, graph, full_graph) -> bool:
1393
+ if node.addr not in self.jump_tables:
1292
1394
  return False
1293
1395
 
1294
1396
  try:
@@ -1298,7 +1400,7 @@ class PhoenixStructurer(StructurerBase):
1298
1400
  if not (isinstance(last_stmt, Jump) and not isinstance(last_stmt.target, Const)):
1299
1401
  return False
1300
1402
 
1301
- jump_table = jump_tables[node.addr]
1403
+ jump_table = self.jump_tables[node.addr]
1302
1404
  if jump_table.type != IndirectJumpType.Jumptable_AddressLoadedFromMemory:
1303
1405
  return False
1304
1406
 
@@ -1310,6 +1412,7 @@ class PhoenixStructurer(StructurerBase):
1310
1412
  case_count = cmp_ub - cmp_lb + 1
1311
1413
 
1312
1414
  # ensure we have the same number of cases
1415
+ assert jump_table.jumptable_entries is not None
1313
1416
  if case_count != len(jump_table.jumptable_entries):
1314
1417
  return False
1315
1418
 
@@ -1373,10 +1476,10 @@ class PhoenixStructurer(StructurerBase):
1373
1476
 
1374
1477
  return True
1375
1478
 
1376
- def _match_acyclic_switch_cases_address_computed(self, node, graph, full_graph, jump_tables) -> bool:
1377
- if node.addr not in jump_tables:
1479
+ def _match_acyclic_switch_cases_address_computed(self, node, graph, full_graph) -> bool:
1480
+ if node.addr not in self.jump_tables:
1378
1481
  return False
1379
- jump_table = jump_tables[node.addr]
1482
+ jump_table = self.jump_tables[node.addr]
1380
1483
  if jump_table.type != IndirectJumpType.Jumptable_AddressComputed:
1381
1484
  return False
1382
1485
 
@@ -1403,9 +1506,16 @@ class PhoenixStructurer(StructurerBase):
1403
1506
 
1404
1507
  if isinstance(last_stmt.false_target, Const):
1405
1508
  default_addr = last_stmt.false_target.value
1509
+ assert isinstance(default_addr, int)
1406
1510
  else:
1407
1511
  return False
1408
1512
 
1513
+ node_default = self._switch_find_default_node(graph, node, default_addr)
1514
+ if node_default is not None:
1515
+ # ensure we have successfully structured node_default
1516
+ if full_graph.out_degree(node_default) > 1:
1517
+ return False
1518
+
1409
1519
  case_and_entry_addrs = self._find_case_and_entry_addrs(node, graph, cmp_lb, jump_table)
1410
1520
 
1411
1521
  cases, node_default, to_remove = self._switch_build_cases(
@@ -1425,10 +1535,10 @@ class PhoenixStructurer(StructurerBase):
1425
1535
  )
1426
1536
 
1427
1537
  def _match_acyclic_incomplete_switch_cases(
1428
- self, node, graph: networkx.DiGraph, full_graph: networkx.DiGraph, jump_tables: dict
1538
+ self, node, graph: networkx.DiGraph, full_graph: networkx.DiGraph
1429
1539
  ) -> bool:
1430
1540
  # sanity checks
1431
- if node.addr not in jump_tables:
1541
+ if node.addr not in self.jump_tables:
1432
1542
  return False
1433
1543
  if isinstance(node, IncompleteSwitchCaseNode):
1434
1544
  return False
@@ -1437,9 +1547,11 @@ class PhoenixStructurer(StructurerBase):
1437
1547
 
1438
1548
  successors = list(graph.successors(node))
1439
1549
 
1550
+ jump_table = self.jump_tables[node.addr]
1551
+ assert jump_table.jumptable_entries is not None
1440
1552
  if (
1441
1553
  successors
1442
- and {succ.addr for succ in successors} == set(jump_tables[node.addr].jumptable_entries)
1554
+ and {succ.addr for succ in successors} == set(jump_table.jumptable_entries)
1443
1555
  and all(graph.in_degree[succ] == 1 for succ in successors)
1444
1556
  ):
1445
1557
  out_nodes = set()
@@ -1473,16 +1585,12 @@ class PhoenixStructurer(StructurerBase):
1473
1585
  cases: OrderedDict[int | tuple[int, ...], SequenceNode] = OrderedDict()
1474
1586
  to_remove = set()
1475
1587
 
1476
- # it is possible that the default node gets duplicated by other analyses and creates a default node (addr.a)
1477
- # and a case node (addr.b). The addr.a node is a successor to the head node while the addr.b node is a
1478
- # successor to node_a
1479
1588
  default_node_candidates = (
1480
1589
  [nn for nn in graph.nodes if nn.addr == node_b_addr] if node_b_addr is not None else []
1481
1590
  )
1482
- node_default: BaseNode | None = next(
1483
- iter(nn for nn in default_node_candidates if graph.has_edge(head_node, nn)), None
1591
+ node_default = (
1592
+ self._switch_find_default_node(graph, head_node, node_b_addr) if node_b_addr is not None else None
1484
1593
  )
1485
-
1486
1594
  if node_default is not None and not isinstance(node_default, SequenceNode):
1487
1595
  # make the default node a SequenceNode so that we can insert Break and Continue nodes into it later
1488
1596
  new_node = SequenceNode(node_default.addr, nodes=[node_default])
@@ -1673,6 +1781,7 @@ class PhoenixStructurer(StructurerBase):
1673
1781
  for _out_src, out_dst in out_edges[1:]:
1674
1782
  if out_dst in full_graph and out_dst not in graph and full_graph.in_degree[out_dst] == 0:
1675
1783
  full_graph.remove_node(out_dst)
1784
+ assert self._region.successors is not None
1676
1785
  if out_dst in self._region.successors:
1677
1786
  self._region.successors.remove(out_dst)
1678
1787
 
@@ -1705,8 +1814,7 @@ class PhoenixStructurer(StructurerBase):
1705
1814
  return case_and_entry_addrs
1706
1815
 
1707
1816
  def _is_node_unstructured_switch_case_head(self, node) -> bool:
1708
- jump_tables = self.kb.cfgs["CFGFast"].jump_tables
1709
- if node.addr in jump_tables:
1817
+ if node.addr in self.jump_tables:
1710
1818
  # maybe it has been structured?
1711
1819
  try:
1712
1820
  last_stmts = self.cond_proc.get_last_statements(node)
@@ -1739,6 +1847,7 @@ class PhoenixStructurer(StructurerBase):
1739
1847
  and not self._is_switch_cases_address_loaded_from_memory_head_or_jumpnode(full_graph, end_node)
1740
1848
  and not self._is_switch_cases_address_loaded_from_memory_head_or_jumpnode(full_graph, start_node)
1741
1849
  and end_node not in self.dowhile_known_tail_nodes
1850
+ and not isinstance(end_node, IncompleteSwitchCaseNode)
1742
1851
  ):
1743
1852
  # merge two blocks
1744
1853
  new_seq = self._merge_nodes(start_node, end_node)
@@ -2299,7 +2408,7 @@ class PhoenixStructurer(StructurerBase):
2299
2408
  ):
2300
2409
  edge_cond_left = self.cond_proc.recover_edge_condition(full_graph, start_node, left)
2301
2410
  edge_cond_successor = self.cond_proc.recover_edge_condition(full_graph, start_node, successor)
2302
- if claripy.is_true(claripy.Not(edge_cond_left) == edge_cond_successor):
2411
+ if claripy.is_true(claripy.Not(edge_cond_left) == edge_cond_successor): # type: ignore
2303
2412
  # c0 = !c0
2304
2413
  left_succs = list(full_graph.successors(left))
2305
2414
  if len(left_succs) == 2 and successor in left_succs:
@@ -2310,7 +2419,9 @@ class PhoenixStructurer(StructurerBase):
2310
2419
  edge_cond_left_successor = self.cond_proc.recover_edge_condition(
2311
2420
  full_graph, left, successor
2312
2421
  )
2313
- if claripy.is_true(claripy.Not(edge_cond_left_right) == edge_cond_left_successor):
2422
+ if claripy.is_true(
2423
+ claripy.Not(edge_cond_left_right) == edge_cond_left_successor # type: ignore
2424
+ ):
2314
2425
  # c1 = !c1
2315
2426
  return left, edge_cond_left, successor, edge_cond_left_successor, right
2316
2427
  return None
@@ -2349,14 +2460,14 @@ class PhoenixStructurer(StructurerBase):
2349
2460
  ):
2350
2461
  edge_cond_left = self.cond_proc.recover_edge_condition(full_graph, start_node, left)
2351
2462
  edge_cond_else = self.cond_proc.recover_edge_condition(full_graph, start_node, else_node)
2352
- if claripy.is_true(claripy.Not(edge_cond_left) == edge_cond_else):
2463
+ if claripy.is_true(claripy.Not(edge_cond_left) == edge_cond_else): # type: ignore
2353
2464
  # c0 = !c0
2354
2465
  left_succs = list(full_graph.successors(left))
2355
2466
  if len(left_succs) == 2 and else_node in left_succs:
2356
2467
  right = next(iter(succ for succ in left_succs if succ is not else_node))
2357
2468
  edge_cond_left_right = self.cond_proc.recover_edge_condition(full_graph, left, right)
2358
2469
  edge_cond_left_else = self.cond_proc.recover_edge_condition(full_graph, left, else_node)
2359
- if claripy.is_true(claripy.Not(edge_cond_left_right) == edge_cond_left_else):
2470
+ if claripy.is_true(claripy.Not(edge_cond_left_right) == edge_cond_left_else): # type: ignore
2360
2471
  # c1 = !c1
2361
2472
  return left, edge_cond_left, right, edge_cond_left_right, else_node
2362
2473
  return None
@@ -2441,6 +2552,7 @@ class PhoenixStructurer(StructurerBase):
2441
2552
  goto1_target = None
2442
2553
 
2443
2554
  if goto0_condition is not None:
2555
+ assert goto0_target is not None and goto1_target is not None
2444
2556
  goto0 = Block(
2445
2557
  last_stmt.ins_addr,
2446
2558
  0,
@@ -2510,7 +2622,7 @@ class PhoenixStructurer(StructurerBase):
2510
2622
  dst: Block | BaseNode,
2511
2623
  last=True,
2512
2624
  condjump_only=False,
2513
- ) -> tuple[int | None, BaseNode | None, Block | None]:
2625
+ ) -> tuple[int | None, BaseNode | None, Block | MultiNode | BreakNode | None]:
2514
2626
  """
2515
2627
 
2516
2628
  :param node:
@@ -2678,7 +2790,7 @@ class PhoenixStructurer(StructurerBase):
2678
2790
  return True
2679
2791
  return all(not isinstance(stmt, (ConditionalJump, Jump)) for stmt in stmts[:-1])
2680
2792
 
2681
- def _to_statement_list(node: Block | MultiNode | SequenceNode) -> list[Statement]:
2793
+ def _to_statement_list(node: Block | MultiNode | SequenceNode | BaseNode) -> list[Statement]:
2682
2794
  if isinstance(node, Block):
2683
2795
  return node.statements
2684
2796
  if isinstance(node, MultiNode):
@@ -2733,7 +2845,7 @@ class PhoenixStructurer(StructurerBase):
2733
2845
  graph.remove_edge(src, succ)
2734
2846
 
2735
2847
  @staticmethod
2736
- def _remove_first_statement_if_jump(node: BaseNode | Block) -> Jump | ConditionalJump | None:
2848
+ def _remove_first_statement_if_jump(node: BaseNode | Block | MultiNode) -> Jump | ConditionalJump | None:
2737
2849
  if isinstance(node, Block):
2738
2850
  if node.statements:
2739
2851
  idx = 0
@@ -2781,7 +2893,7 @@ class PhoenixStructurer(StructurerBase):
2781
2893
  src, dst = edge_
2782
2894
  dst_in_degree = graph.in_degree[dst]
2783
2895
  src_out_degree = graph.out_degree[src]
2784
- return -node_seq.get(dst), dst_in_degree, src_out_degree, -src.addr, -dst.addr
2896
+ return -node_seq.get(dst), dst_in_degree, src_out_degree, -src.addr, -dst.addr # type: ignore
2785
2897
 
2786
2898
  return sorted(edges, key=_sort_edge, reverse=True)
2787
2899
 
@@ -92,6 +92,7 @@ class RecursiveStructurer(Analysis):
92
92
  case_entry_to_switch_head=self._case_entry_to_switch_head,
93
93
  func=self.function,
94
94
  parent_region=parent_region,
95
+ jump_tables=self.kb.cfgs["CFGFast"].jump_tables,
95
96
  **self.structurer_options,
96
97
  )
97
98
  # replace this region with the resulting node in its parent region... if it's not an orphan