angr 9.2.138__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 (100) hide show
  1. angr/__init__.py +1 -1
  2. angr/analyses/calling_convention/calling_convention.py +48 -21
  3. angr/analyses/calling_convention/fact_collector.py +59 -12
  4. angr/analyses/calling_convention/utils.py +2 -2
  5. angr/analyses/cfg/cfg_base.py +13 -0
  6. angr/analyses/cfg/cfg_fast.py +23 -4
  7. angr/analyses/decompiler/ail_simplifier.py +79 -53
  8. angr/analyses/decompiler/block_simplifier.py +0 -2
  9. angr/analyses/decompiler/callsite_maker.py +80 -14
  10. angr/analyses/decompiler/clinic.py +99 -80
  11. angr/analyses/decompiler/condition_processor.py +2 -2
  12. angr/analyses/decompiler/decompiler.py +19 -7
  13. angr/analyses/decompiler/dephication/rewriting_engine.py +16 -7
  14. angr/analyses/decompiler/expression_narrower.py +1 -1
  15. angr/analyses/decompiler/optimization_passes/__init__.py +3 -0
  16. angr/analyses/decompiler/optimization_passes/condition_constprop.py +149 -0
  17. angr/analyses/decompiler/optimization_passes/const_prop_reverter.py +8 -7
  18. angr/analyses/decompiler/optimization_passes/deadblock_remover.py +12 -3
  19. angr/analyses/decompiler/optimization_passes/inlined_string_transformation_simplifier.py +1 -1
  20. angr/analyses/decompiler/optimization_passes/ite_region_converter.py +21 -13
  21. angr/analyses/decompiler/optimization_passes/optimization_pass.py +21 -12
  22. angr/analyses/decompiler/optimization_passes/return_duplicator_base.py +17 -9
  23. angr/analyses/decompiler/optimization_passes/return_duplicator_high.py +7 -10
  24. angr/analyses/decompiler/peephole_optimizations/eager_eval.py +12 -1
  25. angr/analyses/decompiler/peephole_optimizations/remove_redundant_conversions.py +61 -25
  26. angr/analyses/decompiler/peephole_optimizations/remove_redundant_shifts.py +50 -1
  27. angr/analyses/decompiler/presets/fast.py +2 -0
  28. angr/analyses/decompiler/presets/full.py +2 -0
  29. angr/analyses/decompiler/region_simplifiers/expr_folding.py +259 -108
  30. angr/analyses/decompiler/region_simplifiers/region_simplifier.py +28 -9
  31. angr/analyses/decompiler/ssailification/rewriting_engine.py +20 -2
  32. angr/analyses/decompiler/ssailification/traversal_engine.py +4 -3
  33. angr/analyses/decompiler/structured_codegen/c.py +10 -3
  34. angr/analyses/decompiler/structuring/dream.py +28 -19
  35. angr/analyses/decompiler/structuring/phoenix.py +253 -89
  36. angr/analyses/decompiler/structuring/recursive_structurer.py +1 -0
  37. angr/analyses/decompiler/structuring/structurer_base.py +121 -46
  38. angr/analyses/decompiler/structuring/structurer_nodes.py +6 -1
  39. angr/analyses/decompiler/utils.py +60 -1
  40. angr/analyses/deobfuscator/api_obf_finder.py +13 -5
  41. angr/analyses/deobfuscator/api_obf_type2_finder.py +166 -0
  42. angr/analyses/deobfuscator/string_obf_finder.py +105 -18
  43. angr/analyses/forward_analysis/forward_analysis.py +1 -1
  44. angr/analyses/propagator/top_checker_mixin.py +6 -6
  45. angr/analyses/reaching_definitions/__init__.py +2 -1
  46. angr/analyses/reaching_definitions/dep_graph.py +1 -12
  47. angr/analyses/reaching_definitions/engine_vex.py +36 -31
  48. angr/analyses/reaching_definitions/function_handler.py +15 -2
  49. angr/analyses/reaching_definitions/rd_state.py +1 -37
  50. angr/analyses/reaching_definitions/reaching_definitions.py +13 -24
  51. angr/analyses/s_propagator.py +129 -87
  52. angr/analyses/s_reaching_definitions/s_rda_model.py +7 -1
  53. angr/analyses/s_reaching_definitions/s_rda_view.py +2 -2
  54. angr/analyses/s_reaching_definitions/s_reaching_definitions.py +3 -1
  55. angr/analyses/stack_pointer_tracker.py +36 -22
  56. angr/analyses/typehoon/simple_solver.py +45 -7
  57. angr/analyses/typehoon/typeconsts.py +18 -5
  58. angr/analyses/variable_recovery/engine_ail.py +1 -1
  59. angr/analyses/variable_recovery/engine_base.py +62 -67
  60. angr/analyses/variable_recovery/engine_vex.py +1 -1
  61. angr/analyses/variable_recovery/irsb_scanner.py +2 -2
  62. angr/block.py +69 -107
  63. angr/callable.py +14 -7
  64. angr/calling_conventions.py +81 -10
  65. angr/distributed/__init__.py +1 -1
  66. angr/engines/__init__.py +7 -8
  67. angr/engines/engine.py +3 -138
  68. angr/engines/failure.py +2 -2
  69. angr/engines/hook.py +2 -2
  70. angr/engines/light/engine.py +5 -10
  71. angr/engines/pcode/emulate.py +2 -2
  72. angr/engines/pcode/engine.py +2 -14
  73. angr/engines/pcode/lifter.py +2 -2
  74. angr/engines/procedure.py +2 -2
  75. angr/engines/soot/engine.py +2 -2
  76. angr/engines/soot/statements/switch.py +1 -1
  77. angr/engines/successors.py +123 -17
  78. angr/engines/syscall.py +2 -2
  79. angr/engines/unicorn.py +3 -3
  80. angr/engines/vex/heavy/heavy.py +3 -15
  81. angr/engines/vex/lifter.py +2 -2
  82. angr/engines/vex/light/light.py +2 -2
  83. angr/factory.py +4 -19
  84. angr/knowledge_plugins/cfg/cfg_model.py +3 -2
  85. angr/knowledge_plugins/key_definitions/atoms.py +8 -4
  86. angr/knowledge_plugins/key_definitions/live_definitions.py +41 -103
  87. angr/knowledge_plugins/labels.py +2 -2
  88. angr/knowledge_plugins/obfuscations.py +1 -0
  89. angr/knowledge_plugins/xrefs/xref_manager.py +4 -0
  90. angr/sim_type.py +19 -17
  91. angr/state_plugins/plugin.py +19 -4
  92. angr/storage/memory_mixins/memory_mixin.py +1 -1
  93. angr/storage/memory_mixins/paged_memory/pages/multi_values.py +10 -5
  94. angr/utils/ssa/__init__.py +119 -4
  95. {angr-9.2.138.dist-info → angr-9.2.140.dist-info}/METADATA +6 -6
  96. {angr-9.2.138.dist-info → angr-9.2.140.dist-info}/RECORD +100 -98
  97. {angr-9.2.138.dist-info → angr-9.2.140.dist-info}/LICENSE +0 -0
  98. {angr-9.2.138.dist-info → angr-9.2.140.dist-info}/WHEEL +0 -0
  99. {angr-9.2.138.dist-info → angr-9.2.140.dist-info}/entry_points.txt +0 -0
  100. {angr-9.2.138.dist-info → angr-9.2.140.dist-info}/top_level.txt +0 -0
@@ -89,6 +89,8 @@ class Clinic(Analysis):
89
89
  A Clinic deals with AILments.
90
90
  """
91
91
 
92
+ _ail_manager: ailment.Manager
93
+
92
94
  def __init__(
93
95
  self,
94
96
  func,
@@ -109,7 +111,7 @@ class Clinic(Analysis):
109
111
  cache: DecompilationCache | None = None,
110
112
  mode: ClinicMode = ClinicMode.DECOMPILE,
111
113
  sp_shift: int = 0,
112
- inline_functions: set[Function] | None = frozenset(),
114
+ inline_functions: set[Function] | None = None,
113
115
  inlined_counts: dict[int, int] | None = None,
114
116
  inlining_parents: set[int] | None = None,
115
117
  vvar_id_start: int = 0,
@@ -117,7 +119,6 @@ class Clinic(Analysis):
117
119
  desired_variables: set[str] | None = None,
118
120
  force_loop_single_exit: bool = True,
119
121
  complete_successors: bool = False,
120
- unsound_fix_abnormal_switches: bool = True,
121
122
  ):
122
123
  if not func.normalized and mode == ClinicMode.DECOMPILE:
123
124
  raise ValueError("Decompilation must work on normalized function graphs.")
@@ -131,11 +132,10 @@ class Clinic(Analysis):
131
132
  self.arg_vvars: dict[int, tuple[ailment.Expr.VirtualVariable, SimRegArg]] | None = None
132
133
  self.variable_kb = variable_kb
133
134
  self.externs: set[SimMemoryVariable] = set()
134
- self.data_refs: dict[int, int] = {} # data address to instruction address
135
+ self.data_refs: dict[int, list[DataRefDesc]] = {} # data address to data reference description
135
136
  self.optimization_scratch = optimization_scratch if optimization_scratch is not None else {}
136
137
 
137
138
  self._func_graph: networkx.DiGraph | None = None
138
- self._ail_manager = None
139
139
  self._blocks_by_addr_and_size = {}
140
140
  self.entry_node_addr: tuple[int, int | None] = self.function.addr, None
141
141
 
@@ -149,7 +149,6 @@ class Clinic(Analysis):
149
149
  self._must_struct = must_struct
150
150
  self._reset_variable_names = reset_variable_names
151
151
  self._rewrite_ites_to_diamonds = rewrite_ites_to_diamonds
152
- self._unsound_fix_abnormal_switches = unsound_fix_abnormal_switches
153
152
  self.reaching_definitions: ReachingDefinitionsAnalysis | None = None
154
153
  self._cache = cache
155
154
  self._mode = mode
@@ -159,7 +158,7 @@ class Clinic(Analysis):
159
158
  # inlining help
160
159
  self._sp_shift = sp_shift
161
160
  self._max_stack_depth = 0
162
- self._inline_functions = inline_functions
161
+ self._inline_functions = inline_functions if inline_functions else set()
163
162
  self._inlined_counts = {} if inlined_counts is None else inlined_counts
164
163
  self._inlining_parents = inlining_parents or ()
165
164
  self._desired_variables = desired_variables
@@ -167,6 +166,7 @@ class Clinic(Analysis):
167
166
  self._complete_successors = complete_successors
168
167
 
169
168
  self._register_save_areas_removed: bool = False
169
+ self.edges_to_remove: list[tuple[tuple[int, int | None], tuple[int, int | None]]] = []
170
170
 
171
171
  self._new_block_addrs = set()
172
172
 
@@ -200,7 +200,7 @@ class Clinic(Analysis):
200
200
  """
201
201
 
202
202
  try:
203
- return self._blocks_by_addr_and_size[(addr, size)]
203
+ return self._blocks_by_addr_and_size[(addr, size)] if self._blocks_by_addr_and_size is not None else None
204
204
  except KeyError:
205
205
  return None
206
206
 
@@ -209,6 +209,7 @@ class Clinic(Analysis):
209
209
 
210
210
  :return:
211
211
  """
212
+ assert self.graph is not None
212
213
 
213
214
  s = ""
214
215
 
@@ -297,6 +298,8 @@ class Clinic(Analysis):
297
298
  return ail_graph
298
299
 
299
300
  def _slice_variables(self, ail_graph):
301
+ assert self.variable_kb is not None
302
+
300
303
  nodes_index = {(n.addr, n.idx): n for n in ail_graph.nodes()}
301
304
 
302
305
  vfm = self.variable_kb.variables.function_managers[self.function.addr]
@@ -344,7 +347,7 @@ class Clinic(Analysis):
344
347
  optimization_passes=[StackCanarySimplifier],
345
348
  sp_shift=self._max_stack_depth,
346
349
  vvar_id_start=self.vvar_id_start,
347
- fail_fast=self._fail_fast,
350
+ fail_fast=self._fail_fast, # type: ignore
348
351
  )
349
352
  self.vvar_id_start = callee_clinic.vvar_id_start + 1
350
353
  self._max_stack_depth = callee_clinic._max_stack_depth
@@ -490,9 +493,7 @@ class Clinic(Analysis):
490
493
  # we never remove dead memory definitions before making callsites. otherwise stack arguments may go missing
491
494
  # before they are recognized as stack arguments.
492
495
  self._update_progress(38.0, text="Simplifying blocks 1")
493
- ail_graph = self._simplify_blocks(
494
- ail_graph, stack_pointer_tracker=spt, remove_dead_memdefs=False, cache=block_simplification_cache
495
- )
496
+ ail_graph = self._simplify_blocks(ail_graph, stack_pointer_tracker=spt, cache=block_simplification_cache)
496
497
  self._rewrite_alloca(ail_graph)
497
498
 
498
499
  # Run simplification passes
@@ -515,9 +516,7 @@ class Clinic(Analysis):
515
516
  # Run simplification passes again. there might be more chances for peephole optimizations after function-level
516
517
  # simplification
517
518
  self._update_progress(48.0, text="Simplifying blocks 2")
518
- ail_graph = self._simplify_blocks(
519
- ail_graph, stack_pointer_tracker=spt, remove_dead_memdefs=False, cache=block_simplification_cache
520
- )
519
+ ail_graph = self._simplify_blocks(ail_graph, stack_pointer_tracker=spt, cache=block_simplification_cache)
521
520
 
522
521
  # rewrite (qualified) stack variables into SSA form
523
522
  ail_graph = self._transform_to_ssa_level1(ail_graph, func_args)
@@ -557,7 +556,6 @@ class Clinic(Analysis):
557
556
  self._update_progress(60.0, text="Simplifying blocks 3")
558
557
  ail_graph = self._simplify_blocks(
559
558
  ail_graph,
560
- remove_dead_memdefs=self._remove_dead_memdefs,
561
559
  stack_pointer_tracker=spt,
562
560
  cache=block_simplification_cache,
563
561
  )
@@ -581,7 +579,6 @@ class Clinic(Analysis):
581
579
  self._update_progress(75.0, text="Simplifying blocks 4")
582
580
  ail_graph = self._simplify_blocks(
583
581
  ail_graph,
584
- remove_dead_memdefs=self._remove_dead_memdefs,
585
582
  stack_pointer_tracker=spt,
586
583
  cache=block_simplification_cache,
587
584
  )
@@ -624,6 +621,7 @@ class Clinic(Analysis):
624
621
 
625
622
  # remove empty nodes from the graph
626
623
  ail_graph = self.remove_empty_nodes(ail_graph)
624
+ # note that there are still edges to remove before we can structure this graph!
627
625
 
628
626
  self.arg_list = arg_list
629
627
  self.arg_vvars = arg_vvars
@@ -694,9 +692,7 @@ class Clinic(Analysis):
694
692
  # we never remove dead memory definitions before making callsites. otherwise stack arguments may go missing
695
693
  # before they are recognized as stack arguments.
696
694
  self._update_progress(35.0, text="Simplifying blocks 1")
697
- ail_graph = self._simplify_blocks(
698
- ail_graph, stack_pointer_tracker=spt, remove_dead_memdefs=False, cache=block_simplification_cache
699
- )
695
+ ail_graph = self._simplify_blocks(ail_graph, stack_pointer_tracker=spt, cache=block_simplification_cache)
700
696
 
701
697
  # Simplify the entire function for the first time
702
698
  self._update_progress(45.0, text="Simplifying function 1")
@@ -808,7 +804,7 @@ class Clinic(Analysis):
808
804
 
809
805
  # case 2: the callee is a SimProcedure
810
806
  if target_func.is_simprocedure:
811
- cc = self.project.analyses.CallingConvention(target_func, fail_fast=self._fail_fast)
807
+ cc = self.project.analyses.CallingConvention(target_func, fail_fast=self._fail_fast) # type: ignore
812
808
  if cc.cc is not None and cc.prototype is not None:
813
809
  target_func.calling_convention = cc.cc
814
810
  target_func.prototype = cc.prototype
@@ -816,7 +812,7 @@ class Clinic(Analysis):
816
812
 
817
813
  # case 3: the callee is a PLT function
818
814
  if target_func.is_plt:
819
- cc = self.project.analyses.CallingConvention(target_func, fail_fast=self._fail_fast)
815
+ cc = self.project.analyses.CallingConvention(target_func, fail_fast=self._fail_fast) # type: ignore
820
816
  if cc.cc is not None and cc.prototype is not None:
821
817
  target_func.calling_convention = cc.cc
822
818
  target_func.prototype = cc.prototype
@@ -886,7 +882,7 @@ class Clinic(Analysis):
886
882
  # finally, recover the calling convention of the current function
887
883
  if self.function.prototype is None or self.function.calling_convention is None:
888
884
  self.project.analyses.CompleteCallingConventions(
889
- fail_fast=self._fail_fast,
885
+ fail_fast=self._fail_fast, # type: ignore
890
886
  recover_variables=True,
891
887
  prioritize_func_addrs=[self.function.addr],
892
888
  skip_other_funcs=True,
@@ -1101,7 +1097,6 @@ class Clinic(Analysis):
1101
1097
  def _simplify_blocks(
1102
1098
  self,
1103
1099
  ail_graph: networkx.DiGraph,
1104
- remove_dead_memdefs=False,
1105
1100
  stack_pointer_tracker=None,
1106
1101
  cache: dict[ailment.Block, NamedTuple] | None = None,
1107
1102
  ):
@@ -1120,7 +1115,6 @@ class Clinic(Analysis):
1120
1115
  for ail_block in ail_graph.nodes():
1121
1116
  simplified = self._simplify_block(
1122
1117
  ail_block,
1123
- remove_dead_memdefs=remove_dead_memdefs,
1124
1118
  stack_pointer_tracker=stack_pointer_tracker,
1125
1119
  cache=cache,
1126
1120
  )
@@ -1138,7 +1132,7 @@ class Clinic(Analysis):
1138
1132
 
1139
1133
  return ail_graph
1140
1134
 
1141
- def _simplify_block(self, ail_block, remove_dead_memdefs=False, stack_pointer_tracker=None, cache=None):
1135
+ def _simplify_block(self, ail_block, stack_pointer_tracker=None, cache=None):
1142
1136
  """
1143
1137
  Simplify a single AIL block.
1144
1138
 
@@ -1149,8 +1143,9 @@ class Clinic(Analysis):
1149
1143
 
1150
1144
  cached_rd, cached_prop = None, None
1151
1145
  cache_item = None
1146
+ cache_key = ail_block.addr, ail_block.idx
1152
1147
  if cache:
1153
- cache_item = cache.get(ail_block, None)
1148
+ cache_item = cache.get(cache_key, None)
1154
1149
  if cache_item:
1155
1150
  # cache hit
1156
1151
  cached_rd = cache_item.rd
@@ -1160,7 +1155,6 @@ class Clinic(Analysis):
1160
1155
  ail_block,
1161
1156
  self.function.addr,
1162
1157
  fail_fast=self._fail_fast,
1163
- remove_dead_memdefs=remove_dead_memdefs,
1164
1158
  stack_pointer_tracker=stack_pointer_tracker,
1165
1159
  peephole_optimizations=self.peephole_optimizations,
1166
1160
  cached_reaching_definitions=cached_rd,
@@ -1169,8 +1163,8 @@ class Clinic(Analysis):
1169
1163
  # update the cache
1170
1164
  if cache is not None:
1171
1165
  if cache_item:
1172
- del cache[ail_block]
1173
- cache[simp.result_block] = BlockCache(simp._reaching_definitions, simp._propagator)
1166
+ del cache[cache_key]
1167
+ cache[cache_key] = BlockCache(simp._reaching_definitions, simp._propagator)
1174
1168
  return simp.result_block
1175
1169
 
1176
1170
  @timethis
@@ -1807,7 +1801,7 @@ class Clinic(Analysis):
1807
1801
  else:
1808
1802
  self._link_variables_on_expr(variable_manager, global_variables, block, stmt_idx, stmt, expr.operand)
1809
1803
 
1810
- elif type(expr) is ailment.Expr.Convert:
1804
+ elif type(expr) in {ailment.Expr.Convert, ailment.Expr.Reinterpret}:
1811
1805
  self._link_variables_on_expr(variable_manager, global_variables, block, stmt_idx, stmt, expr.operand)
1812
1806
 
1813
1807
  elif type(expr) is ailment.Expr.ITE:
@@ -1828,7 +1822,7 @@ class Clinic(Analysis):
1828
1822
  expr.variable = var
1829
1823
  expr.variable_offset = offset
1830
1824
 
1831
- elif isinstance(expr, ailment.Expr.Const):
1825
+ elif isinstance(expr, ailment.Expr.Const) and expr.is_int:
1832
1826
  # custom string?
1833
1827
  if hasattr(expr, "custom_string") and expr.custom_string is True:
1834
1828
  s = self.kb.custom_strings[expr.value]
@@ -1873,6 +1867,7 @@ class Clinic(Analysis):
1873
1867
  def _function_graph_to_ail_graph(self, func_graph, blocks_by_addr_and_size=None):
1874
1868
  if blocks_by_addr_and_size is None:
1875
1869
  blocks_by_addr_and_size = self._blocks_by_addr_and_size
1870
+ assert blocks_by_addr_and_size is not None
1876
1871
 
1877
1872
  graph = networkx.DiGraph()
1878
1873
 
@@ -1947,8 +1942,9 @@ class Clinic(Analysis):
1947
1942
  break
1948
1943
  if ite_expr_stmt_idx is None:
1949
1944
  return None
1945
+ assert ite_expr_stmt is not None
1950
1946
 
1951
- ite_expr: ailment.Expr.ITE = ite_expr_stmt.src
1947
+ ite_expr: ailment.Expr.ITE = ite_expr_stmt.src # type: ignore
1952
1948
  new_head_ail.statements = new_head_ail.statements[:ite_expr_stmt_idx]
1953
1949
  # build the conditional jump
1954
1950
  true_block_addr = ite_ins_addr + 1
@@ -1976,6 +1972,7 @@ class Clinic(Analysis):
1976
1972
  break
1977
1973
  if ite_expr_stmt_idx is None:
1978
1974
  return None
1975
+ assert ite_expr_stmt is not None
1979
1976
 
1980
1977
  true_block_ail.statements[ite_expr_stmt_idx] = ailment.Stmt.Assignment(
1981
1978
  ite_expr_stmt.idx, ite_expr_stmt.dst, ite_expr_stmt.src.iftrue, **ite_expr_stmt.tags
@@ -1995,6 +1992,7 @@ class Clinic(Analysis):
1995
1992
  break
1996
1993
  if ite_expr_stmt_idx is None:
1997
1994
  return None
1995
+ assert ite_expr_stmt is not None
1998
1996
 
1999
1997
  false_block_ail.statements[ite_expr_stmt_idx] = ailment.Stmt.Assignment(
2000
1998
  ite_expr_stmt.idx, ite_expr_stmt.dst, ite_expr_stmt.src.iffalse, **ite_expr_stmt.tags
@@ -2002,8 +2000,8 @@ class Clinic(Analysis):
2002
2000
 
2003
2001
  original_block = next(iter(b for b in ail_graph if b.addr == block_addr))
2004
2002
 
2005
- original_block_in_edges = list(ail_graph.in_edges(original_block))
2006
- original_block_out_edges = list(ail_graph.out_edges(original_block))
2003
+ original_block_in_edges = list(ail_graph.in_edges(original_block, data=True))
2004
+ original_block_out_edges = list(ail_graph.out_edges(original_block, data=True))
2007
2005
 
2008
2006
  # build the target block if the target block does not exist in the current function
2009
2007
  end_block_addr = ite_ins_addr + ite_insn_size
@@ -2040,19 +2038,19 @@ class Clinic(Analysis):
2040
2038
 
2041
2039
  if end_block_ail not in ail_graph:
2042
2040
  # newly created. add it and the necessary edges into the graph
2043
- for _, dst in original_block_out_edges:
2041
+ for _, dst, data in original_block_out_edges:
2044
2042
  if dst is original_block:
2045
- ail_graph.add_edge(end_block_ail, new_head_ail)
2043
+ ail_graph.add_edge(end_block_ail, new_head_ail, **data)
2046
2044
  else:
2047
- ail_graph.add_edge(end_block_ail, dst)
2045
+ ail_graph.add_edge(end_block_ail, dst, **data)
2048
2046
 
2049
2047
  # in edges
2050
- for src, _ in original_block_in_edges:
2048
+ for src, _, data in original_block_in_edges:
2051
2049
  if src is original_block:
2052
2050
  # loop
2053
- ail_graph.add_edge(end_block_ail, new_head_ail)
2051
+ ail_graph.add_edge(end_block_ail, new_head_ail, **data)
2054
2052
  else:
2055
- ail_graph.add_edge(src, new_head_ail)
2053
+ ail_graph.add_edge(src, new_head_ail, **data)
2056
2054
 
2057
2055
  # triangle
2058
2056
  ail_graph.add_edge(new_head_ail, true_block_ail)
@@ -2115,49 +2113,60 @@ class Clinic(Analysis):
2115
2113
  # the overlapped instructions and add an unconditional jump so that it jumps to 0x41da9d.
2116
2114
  # this is the most common case created by jump threading optimization in compilers. it's easy to handle.
2117
2115
 
2118
- # Case 2: the intended head and the other heads do not share the same suffix of instructions. in this case,
2119
- # we cannot reliably convert the blocks into a properly structured switch-case construct. we will alter the
2120
- # last instruction of all other heads to jump to the cmp instruction in the intended head, but do not remove
2121
- # any other instructions in these other heads. this is unsound, but is the best we can do in this case.
2116
+ # Case 2 & 3: the intended head and the other heads do not share the same suffix of instructions. in this
2117
+ # case, we have two choices:
2118
+ # Case 2: The intended head has two successors, but at least one unintended head has only one successor.
2119
+ # we cannot reliably convert the blocks into a properly structured switch-case construct. we will
2120
+ # last instruction of all other heads to jump to the cmp instruction in the intended head, but do
2121
+ # not remove any other instructions in these other heads. this is unsound, but is the best we can
2122
+ # do in this case.
2123
+ # Case 3: The intended head has only one successor (which is the indirect jump node). during structuring,
2124
+ # we expect it will be structured as a no-default-node switch-case construct. in this case, we
2125
+ # can simply remove the edges from all other heads to the jump node and only leave the edge from
2126
+ # the intended head to the jump node. we will see goto statements in the output, but this will
2127
+ # lead to correct structuring result.
2122
2128
 
2123
2129
  overlaps = [self._get_overlapping_suffix_instructions(intended_head, head) for head in other_heads]
2124
2130
  if overlaps and (overlap := min(overlaps)) > 0:
2125
2131
  # Case 1
2126
2132
  self._fix_abnormal_switch_case_heads_case1(ail_graph, candidate, intended_head, other_heads, overlap)
2127
- else:
2128
- if self._unsound_fix_abnormal_switches:
2129
- # Case 2
2130
- l.warning("Switch-case at %#x has multiple head nodes but cannot be fixed soundly.", candidate.addr)
2131
- # find the comparison instruction in the intended head
2132
- comparison_stmt = None
2133
- if "cc_op" in self.project.arch.registers:
2134
- comparison_stmt = next(
2135
- iter(
2136
- stmt
2137
- for stmt in intended_head.statements
2138
- if isinstance(stmt, ailment.Stmt.Assignment)
2139
- and isinstance(stmt.dst, ailment.Expr.Register)
2140
- and stmt.dst.reg_offset == self.project.arch.registers["cc_op"][0]
2141
- ),
2142
- None,
2143
- )
2144
- intended_head_block = self.project.factory.block(
2145
- intended_head.addr, size=intended_head.original_size
2133
+ elif ail_graph.out_degree[intended_head] == 2:
2134
+ # Case 2
2135
+ l.warning("Switch-case at %#x has multiple head nodes but cannot be fixed soundly.", candidate.addr)
2136
+ # find the comparison instruction in the intended head
2137
+ comparison_stmt = None
2138
+ if "cc_op" in self.project.arch.registers:
2139
+ comparison_stmt = next(
2140
+ iter(
2141
+ stmt
2142
+ for stmt in intended_head.statements
2143
+ if isinstance(stmt, ailment.Stmt.Assignment)
2144
+ and isinstance(stmt.dst, ailment.Expr.Register)
2145
+ and stmt.dst.reg_offset == self.project.arch.registers["cc_op"][0]
2146
+ ),
2147
+ None,
2146
2148
  )
2147
- if comparison_stmt is not None:
2148
- cmp_rpos = len(
2149
- intended_head_block.instruction_addrs
2150
- ) - intended_head_block.instruction_addrs.index(comparison_stmt.ins_addr)
2151
- else:
2152
- cmp_rpos = min(len(intended_head_block.instruction_addrs), 2)
2153
- self._fix_abnormal_switch_case_heads_case2(
2154
- ail_graph,
2155
- candidate,
2156
- intended_head,
2157
- other_heads,
2158
- intended_head_split_insns=cmp_rpos,
2159
- other_head_split_insns=0,
2149
+ intended_head_block = self.project.factory.block(intended_head.addr, size=intended_head.original_size)
2150
+ if comparison_stmt is not None:
2151
+ cmp_rpos = len(intended_head_block.instruction_addrs) - intended_head_block.instruction_addrs.index(
2152
+ comparison_stmt.ins_addr
2160
2153
  )
2154
+ else:
2155
+ cmp_rpos = min(len(intended_head_block.instruction_addrs), 2)
2156
+ self._fix_abnormal_switch_case_heads_case2(
2157
+ ail_graph,
2158
+ candidate,
2159
+ intended_head,
2160
+ other_heads,
2161
+ intended_head_split_insns=cmp_rpos,
2162
+ other_head_split_insns=0,
2163
+ )
2164
+ else:
2165
+ # Case 3
2166
+ self._fix_abnormal_switch_case_heads_case3(
2167
+ candidate,
2168
+ other_heads,
2169
+ )
2161
2170
 
2162
2171
  def _get_overlapping_suffix_instructions(self, ailblock_0: ailment.Block, ailblock_1: ailment.Block) -> int:
2163
2172
  # we first compare their ending conditional jumps
@@ -2366,6 +2375,16 @@ class Clinic(Analysis):
2366
2375
  # it should be going to the default node. ignore it
2367
2376
  pass
2368
2377
 
2378
+ def _fix_abnormal_switch_case_heads_case3(
2379
+ self, indirect_jump_node: ailment.Block, other_heads: list[ailment.Block]
2380
+ ) -> None:
2381
+ # remove all edges from other_heads to the indirect jump node
2382
+ for other_head in other_heads:
2383
+ # delay the edge removal so that we don't mess up the SSA analysis
2384
+ self.edges_to_remove.append(
2385
+ ((other_head.addr, other_head.idx), (indirect_jump_node.addr, indirect_jump_node.idx))
2386
+ )
2387
+
2369
2388
  @staticmethod
2370
2389
  def _remove_redundant_jump_blocks(ail_graph):
2371
2390
  def first_conditional_jump(block: ailment.Block) -> ailment.Stmt.ConditionalJump | None:
@@ -2466,7 +2485,7 @@ class Clinic(Analysis):
2466
2485
  expr_idx: int,
2467
2486
  expr: ailment.expression.Expression,
2468
2487
  stmt_idx: int,
2469
- stmt: ailment.statement.Statement,
2488
+ stmt: ailment.statement.Statement | None,
2470
2489
  block: ailment.Block | None,
2471
2490
  ):
2472
2491
  if expr is None:
@@ -2500,7 +2519,7 @@ class Clinic(Analysis):
2500
2519
  expr: ailment.expression.Const,
2501
2520
  stmt_idx: int,
2502
2521
  stmt: ailment.statement.Statement,
2503
- block: ailment.Block | None,
2522
+ block: ailment.Block,
2504
2523
  ):
2505
2524
  if isinstance(expr.value, int) and hasattr(expr, "ins_addr"):
2506
2525
  data_refs[block.addr].append(
@@ -2518,7 +2537,7 @@ class Clinic(Analysis):
2518
2537
  expr: ailment.expression.Load,
2519
2538
  stmt_idx: int,
2520
2539
  stmt: ailment.statement.Statement,
2521
- block: ailment.Block | None,
2540
+ block: ailment.Block,
2522
2541
  ):
2523
2542
  if isinstance(expr.addr, ailment.expression.Const):
2524
2543
  addr = expr.addr
@@ -2548,7 +2567,7 @@ class Clinic(Analysis):
2548
2567
 
2549
2568
  return ailment.AILBlockWalker._handle_Load(walker, expr_idx, expr, stmt_idx, stmt, block)
2550
2569
 
2551
- def handle_Store(stmt_idx: int, stmt: ailment.statement.Store, block: ailment.Block | None):
2570
+ def handle_Store(stmt_idx: int, stmt: ailment.statement.Store, block: ailment.Block):
2552
2571
  if isinstance(stmt.addr, ailment.expression.Const):
2553
2572
  addr = stmt.addr
2554
2573
  if isinstance(addr.value, int) and hasattr(addr, "ins_addr"):
@@ -18,7 +18,7 @@ from angr.utils import is_pyinstaller
18
18
  from angr.utils.graph import dominates, inverted_idoms
19
19
  from angr.block import Block, BlockNode
20
20
  from angr.errors import AngrRuntimeError
21
- from .peephole_optimizations import InvertNegatedLogicalConjunctionsAndDisjunctions
21
+ from .peephole_optimizations import InvertNegatedLogicalConjunctionsAndDisjunctions, RemoveRedundantNots
22
22
  from .structuring.structurer_nodes import (
23
23
  MultiNode,
24
24
  EmptyBlockNotice,
@@ -231,7 +231,7 @@ class ConditionProcessor:
231
231
  self._ast2annotations = {}
232
232
 
233
233
  self._peephole_expr_optimizations = [
234
- cls(None, None, None) for cls in [InvertNegatedLogicalConjunctionsAndDisjunctions]
234
+ cls(None, None, None) for cls in [InvertNegatedLogicalConjunctionsAndDisjunctions, RemoveRedundantNots]
235
235
  ]
236
236
 
237
237
  def clear(self):
@@ -2,8 +2,8 @@
2
2
  from __future__ import annotations
3
3
  import logging
4
4
  from collections import defaultdict
5
- from typing import Optional, Union, Any, TYPE_CHECKING
6
5
  from collections.abc import Iterable
6
+ from typing import Optional, Union, Any, TYPE_CHECKING
7
7
 
8
8
  import networkx
9
9
  from cle import SymbolType
@@ -15,6 +15,7 @@ from angr.knowledge_base import KnowledgeBase
15
15
  from angr.sim_variable import SimMemoryVariable, SimRegisterVariable, SimStackVariable
16
16
  from angr.utils import timethis
17
17
  from angr.analyses import Analysis, AnalysesHub
18
+ from .structured_codegen.c import CStructuredCodeGenerator
18
19
  from .structuring import RecursiveStructurer, PhoenixStructurer, DEFAULT_STRUCTURER
19
20
  from .region_identifier import RegionIdentifier
20
21
  from .optimization_passes.optimization_pass import OptimizationPassStage
@@ -22,7 +23,7 @@ from .ailgraph_walker import AILGraphWalker
22
23
  from .condition_processor import ConditionProcessor
23
24
  from .decompilation_options import DecompilationOption
24
25
  from .decompilation_cache import DecompilationCache
25
- from .utils import remove_labels
26
+ from .utils import remove_labels, remove_edges_in_ailgraph
26
27
  from .sequence_walker import SequenceWalker
27
28
  from .structuring.structurer_nodes import SequenceNode
28
29
  from .presets import DECOMPILATION_PRESETS, DecompilationPreset
@@ -30,7 +31,6 @@ from .presets import DECOMPILATION_PRESETS, DecompilationPreset
30
31
  if TYPE_CHECKING:
31
32
  from angr.knowledge_plugins.cfg.cfg_model import CFGModel
32
33
  from .peephole_optimizations import PeepholeOptimizationExprBase, PeepholeOptimizationStmtBase
33
- from .structured_codegen.c import CStructuredCodeGenerator
34
34
 
35
35
  l = logging.getLogger(name=__name__)
36
36
 
@@ -157,6 +157,8 @@ class Decompiler(Analysis):
157
157
  self.kb.decompilations[(self.func.addr, self._flavor)].errors.append(error.format())
158
158
 
159
159
  def _can_use_decompilation_cache(self, cache: DecompilationCache) -> bool:
160
+ if self._cache_parameters is None or cache.parameters is None:
161
+ return False
160
162
  a, b = self._cache_parameters, cache.parameters
161
163
  id_checks = {"cfg", "variable_kb"}
162
164
  return all(a[k] is b[k] if k in id_checks else a[k] == b[k] for k in self._cache_parameters)
@@ -201,7 +203,7 @@ class Decompiler(Analysis):
201
203
 
202
204
  variable_kb = self._variable_kb
203
205
  # fall back to old codegen
204
- if variable_kb is None and old_codegen is not None:
206
+ if variable_kb is None and old_codegen is not None and isinstance(old_codegen, CStructuredCodeGenerator):
205
207
  variable_kb = old_codegen._variable_kb
206
208
 
207
209
  if variable_kb is None:
@@ -223,7 +225,8 @@ class Decompiler(Analysis):
223
225
  fold_callexprs_into_conditions = True
224
226
 
225
227
  cache = DecompilationCache(self.func.addr)
226
- cache.parameters = self._cache_parameters
228
+ if self._cache_parameters is not None:
229
+ cache.parameters = self._cache_parameters
227
230
  cache.ite_exprs = ite_exprs
228
231
  cache.binop_operators = binop_operators
229
232
 
@@ -288,14 +291,22 @@ class Decompiler(Analysis):
288
291
  )
289
292
  ri = self._recover_regions(clinic.graph, cond_proc, update_graph=not delay_graph_updates)
290
293
 
294
+ self._update_progress(73.0, text="Running region-simplification passes")
295
+
291
296
  # run optimizations that may require re-RegionIdentification
292
297
  clinic.graph, ri = self._run_region_simplification_passes(
293
298
  clinic.graph,
294
299
  ri,
295
300
  clinic.reaching_definitions,
296
301
  ite_exprs=ite_exprs,
302
+ arg_vvars=set(clinic.arg_vvars),
303
+ edges_to_remove=clinic.edges_to_remove,
297
304
  )
298
305
 
306
+ # finally (no more graph-based simplifications will run in the future), we can remove the edges that should be
307
+ # removed!
308
+ remove_edges_in_ailgraph(clinic.graph, clinic.edges_to_remove)
309
+
299
310
  # Rewrite the graph to remove phi expressions
300
311
  # this is probably optional if we do not pretty-print clinic.graph
301
312
  clinic.graph = self._transform_graph_from_ssa(clinic.graph)
@@ -323,9 +334,9 @@ class Decompiler(Analysis):
323
334
  s = self.project.analyses.RegionSimplifier(
324
335
  self.func,
325
336
  rs.result,
337
+ arg_vvars=set(self.clinic.arg_vvars),
326
338
  kb=self.kb,
327
339
  fail_fast=self._fail_fast,
328
- variable_kb=clinic.variable_kb,
329
340
  **self.options_to_params(self.options_by_class["region_simplifier"]),
330
341
  )
331
342
  seq_node = s.result
@@ -437,7 +448,7 @@ class Decompiler(Analysis):
437
448
  return ail_graph
438
449
 
439
450
  @timethis
440
- def _run_region_simplification_passes(self, ail_graph, ri, reaching_definitions, **kwargs):
451
+ def _run_region_simplification_passes(self, ail_graph, ri, reaching_definitions, arg_vvars: set[int], **kwargs):
441
452
  """
442
453
  Runs optimizations that should be executed after a single region identification. This function will return
443
454
  two items: the new RegionIdentifier object and the new AIL Graph, which should probably be written
@@ -481,6 +492,7 @@ class Decompiler(Analysis):
481
492
  blocks_by_addr_and_idx=addr_and_idx_to_blocks,
482
493
  graph=ail_graph,
483
494
  variable_kb=self._variable_kb,
495
+ arg_vvars=arg_vvars,
484
496
  region_identifier=ri,
485
497
  reaching_definitions=reaching_definitions,
486
498
  vvar_id_start=self.vvar_id_start,
@@ -15,6 +15,7 @@ from ailment.expression import (
15
15
  ITE,
16
16
  VEXCCallExpression,
17
17
  DirtyExpression,
18
+ Reinterpret,
18
19
  )
19
20
 
20
21
  from angr.engines.light import SimEngineNostmtAIL
@@ -164,7 +165,7 @@ class SimEngineDephiRewriting(SimEngineNostmtAIL[None, Expression | None, Statem
164
165
  return Load(expr.idx, new_addr, expr.size, expr.endness, guard=expr.guard, alt=expr.alt, **expr.tags)
165
166
  return None
166
167
 
167
- def _handle_expr_Convert(self, expr):
168
+ def _handle_expr_Convert(self, expr: Convert) -> Convert | None:
168
169
  new_operand = self._expr(expr.operand)
169
170
  if new_operand is not None:
170
171
  return Convert(
@@ -180,6 +181,20 @@ class SimEngineDephiRewriting(SimEngineNostmtAIL[None, Expression | None, Statem
180
181
  )
181
182
  return None
182
183
 
184
+ def _handle_expr_Reinterpret(self, expr: Reinterpret) -> Reinterpret | None:
185
+ new_operand = self._expr(expr.operand)
186
+ if new_operand is not None:
187
+ return Reinterpret(
188
+ expr.idx,
189
+ expr.from_bits,
190
+ expr.from_type,
191
+ expr.to_bits,
192
+ expr.to_type,
193
+ new_operand,
194
+ **expr.tags,
195
+ )
196
+ return None
197
+
183
198
  def _handle_expr_Const(self, expr):
184
199
  return None
185
200
 
@@ -346,18 +361,12 @@ class SimEngineDephiRewriting(SimEngineNostmtAIL[None, Expression | None, Statem
346
361
  )
347
362
  return None
348
363
 
349
- def _handle_expr_DirtyExpression(self, expr):
350
- return None
351
-
352
364
  def _handle_expr_MultiStatementExpression(self, expr):
353
365
  return None
354
366
 
355
367
  def _handle_expr_Register(self, expr):
356
368
  return None
357
369
 
358
- def _handle_expr_Reinterpret(self, expr):
359
- return None
360
-
361
370
  def _handle_expr_Tmp(self, expr):
362
371
  return None
363
372
 
@@ -38,7 +38,7 @@ class ExprNarrowingInfo:
38
38
  narrowable: bool,
39
39
  to_size: int | None = None,
40
40
  use_exprs: list[tuple[atoms.VirtualVariable, CodeLocation, tuple[str, tuple[Expression, ...]]]] | None = None,
41
- phi_vars: set[atoms.VirtualVariable] | None = None,
41
+ phi_vars: set[VirtualVariable] | None = None,
42
42
  ):
43
43
  self.narrowable = narrowable
44
44
  self.to_size = to_size