angr 9.2.122__py3-none-manylinux2014_aarch64.whl → 9.2.124__py3-none-manylinux2014_aarch64.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 (95) hide show
  1. angr/__init__.py +1 -1
  2. angr/analyses/calling_convention.py +6 -1
  3. angr/analyses/cfg/indirect_jump_resolvers/mips_elf_fast.py +11 -8
  4. angr/analyses/cfg/indirect_jump_resolvers/mips_elf_got.py +2 -2
  5. angr/analyses/decompiler/ail_simplifier.py +38 -342
  6. angr/analyses/decompiler/callsite_maker.py +8 -7
  7. angr/analyses/decompiler/ccall_rewriters/amd64_ccalls.py +24 -2
  8. angr/analyses/decompiler/clinic.py +30 -3
  9. angr/analyses/decompiler/condition_processor.py +10 -3
  10. angr/analyses/decompiler/decompilation_cache.py +2 -0
  11. angr/analyses/decompiler/decompiler.py +50 -8
  12. angr/analyses/decompiler/dephication/graph_vvar_mapping.py +10 -2
  13. angr/analyses/decompiler/dephication/rewriting_engine.py +65 -2
  14. angr/analyses/decompiler/expression_narrower.py +206 -6
  15. angr/analyses/decompiler/optimization_passes/div_simplifier.py +4 -1
  16. angr/analyses/decompiler/optimization_passes/inlined_string_transformation_simplifier.py +7 -0
  17. angr/analyses/decompiler/optimization_passes/ite_region_converter.py +34 -11
  18. angr/analyses/decompiler/optimization_passes/lowered_switch_simplifier.py +10 -1
  19. angr/analyses/decompiler/optimization_passes/optimization_pass.py +3 -1
  20. angr/analyses/decompiler/optimization_passes/return_duplicator_base.py +8 -5
  21. angr/analyses/decompiler/optimization_passes/return_duplicator_high.py +10 -5
  22. angr/analyses/decompiler/optimization_passes/return_duplicator_low.py +18 -7
  23. angr/analyses/decompiler/optimization_passes/switch_default_case_duplicator.py +6 -0
  24. angr/analyses/decompiler/optimization_passes/win_stack_canary_simplifier.py +2 -0
  25. angr/analyses/decompiler/peephole_optimizations/const_mull_a_shift.py +75 -42
  26. angr/analyses/decompiler/peephole_optimizations/remove_cascading_conversions.py +8 -2
  27. angr/analyses/decompiler/region_identifier.py +36 -0
  28. angr/analyses/decompiler/region_simplifiers/expr_folding.py +4 -0
  29. angr/analyses/decompiler/region_simplifiers/loop.py +2 -8
  30. angr/analyses/decompiler/region_simplifiers/switch_cluster_simplifier.py +9 -3
  31. angr/analyses/decompiler/sequence_walker.py +20 -4
  32. angr/analyses/decompiler/ssailification/rewriting.py +5 -2
  33. angr/analyses/decompiler/ssailification/rewriting_engine.py +151 -25
  34. angr/analyses/decompiler/ssailification/rewriting_state.py +1 -0
  35. angr/analyses/decompiler/ssailification/ssailification.py +17 -9
  36. angr/analyses/decompiler/ssailification/traversal.py +3 -1
  37. angr/analyses/decompiler/ssailification/traversal_engine.py +35 -8
  38. angr/analyses/decompiler/ssailification/traversal_state.py +1 -0
  39. angr/analyses/decompiler/structured_codegen/c.py +42 -4
  40. angr/analyses/decompiler/structuring/phoenix.py +3 -0
  41. angr/analyses/propagator/engine_ail.py +10 -3
  42. angr/analyses/reaching_definitions/engine_ail.py +10 -15
  43. angr/analyses/s_propagator.py +26 -15
  44. angr/analyses/s_reaching_definitions/s_rda_view.py +127 -63
  45. angr/analyses/variable_recovery/engine_ail.py +14 -0
  46. angr/analyses/variable_recovery/engine_base.py +11 -0
  47. angr/calling_conventions.py +2 -2
  48. angr/engines/light/engine.py +24 -2
  49. angr/engines/soot/expressions/instanceOf.py +4 -1
  50. angr/engines/successors.py +1 -1
  51. angr/engines/vex/heavy/concretizers.py +47 -47
  52. angr/engines/vex/heavy/dirty.py +4 -4
  53. angr/knowledge_plugins/__init__.py +2 -0
  54. angr/knowledge_plugins/decompilation.py +45 -0
  55. angr/knowledge_plugins/key_definitions/atoms.py +8 -0
  56. angr/procedures/definitions/parse_win32json.py +2 -1
  57. angr/procedures/java_lang/getsimplename.py +4 -1
  58. angr/procedures/linux_kernel/iovec.py +5 -2
  59. angr/sim_type.py +3 -1
  60. angr/storage/memory_mixins/actions_mixin.py +7 -7
  61. angr/storage/memory_mixins/address_concretization_mixin.py +5 -5
  62. angr/storage/memory_mixins/bvv_conversion_mixin.py +1 -1
  63. angr/storage/memory_mixins/clouseau_mixin.py +3 -3
  64. angr/storage/memory_mixins/conditional_store_mixin.py +3 -3
  65. angr/storage/memory_mixins/default_filler_mixin.py +3 -3
  66. angr/storage/memory_mixins/memory_mixin.py +45 -34
  67. angr/storage/memory_mixins/paged_memory/page_backer_mixins.py +15 -14
  68. angr/storage/memory_mixins/paged_memory/paged_memory_mixin.py +27 -16
  69. angr/storage/memory_mixins/paged_memory/pages/cooperation.py +18 -9
  70. angr/storage/memory_mixins/paged_memory/pages/ispo_mixin.py +5 -5
  71. angr/storage/memory_mixins/paged_memory/pages/multi_values.py +89 -55
  72. angr/storage/memory_mixins/paged_memory/pages/mv_list_page.py +16 -25
  73. angr/storage/memory_mixins/paged_memory/pages/permissions_mixin.py +11 -9
  74. angr/storage/memory_mixins/paged_memory/pages/ultra_page.py +23 -7
  75. angr/storage/memory_mixins/paged_memory/privileged_mixin.py +1 -1
  76. angr/storage/memory_mixins/regioned_memory/region_meta_mixin.py +9 -7
  77. angr/storage/memory_mixins/regioned_memory/regioned_memory_mixin.py +9 -9
  78. angr/storage/memory_mixins/regioned_memory/static_find_mixin.py +1 -0
  79. angr/storage/memory_mixins/simple_interface_mixin.py +2 -2
  80. angr/storage/memory_mixins/simplification_mixin.py +2 -2
  81. angr/storage/memory_mixins/size_resolution_mixin.py +1 -1
  82. angr/storage/memory_mixins/slotted_memory.py +3 -3
  83. angr/storage/memory_mixins/smart_find_mixin.py +1 -0
  84. angr/storage/memory_mixins/underconstrained_mixin.py +5 -5
  85. angr/storage/memory_mixins/unwrapper_mixin.py +4 -4
  86. angr/storage/memory_object.py +4 -3
  87. angr/utils/constants.py +1 -1
  88. angr/utils/graph.py +15 -0
  89. angr/vaults.py +2 -2
  90. {angr-9.2.122.dist-info → angr-9.2.124.dist-info}/METADATA +7 -6
  91. {angr-9.2.122.dist-info → angr-9.2.124.dist-info}/RECORD +95 -94
  92. {angr-9.2.122.dist-info → angr-9.2.124.dist-info}/WHEEL +1 -1
  93. {angr-9.2.122.dist-info → angr-9.2.124.dist-info}/LICENSE +0 -0
  94. {angr-9.2.122.dist-info → angr-9.2.124.dist-info}/entry_points.txt +0 -0
  95. {angr-9.2.122.dist-info → angr-9.2.124.dist-info}/top_level.txt +0 -0
@@ -13,6 +13,7 @@ from ailment.expression import (
13
13
  BinaryOp,
14
14
  VirtualVariable,
15
15
  Phi,
16
+ UnaryOp,
16
17
  VirtualVariableCategory,
17
18
  )
18
19
  from ailment.statement import ConditionalJump, Jump, Assignment
@@ -238,6 +239,12 @@ class InlinedStringTransformationAILEngine(SimEngineLightAILMixin):
238
239
  return self.state.vvar_load(vvar)
239
240
  return None
240
241
 
242
+ def _handle_Neg(self, expr: UnaryOp):
243
+ v = self._expr(expr.operand)
244
+ if isinstance(v, claripy.ast.Bits):
245
+ return ~v
246
+ return None
247
+
241
248
  def _handle_Convert(self, expr: Convert):
242
249
  v = self._expr(expr.operand)
243
250
  if isinstance(v, claripy.ast.Bits):
@@ -42,10 +42,8 @@ class ITERegionConverter(OptimizationPass):
42
42
  if not ite_assign_regions:
43
43
  break
44
44
 
45
- for region_head, region_tail, true_block, true_stmt, false_block, false_stmt in ite_assign_regions:
46
- round_update |= self._convert_region_to_ternary_expr(
47
- region_head, region_tail, true_block, true_stmt, false_block, false_stmt
48
- )
45
+ for region_head, region_tail, _, true_stmt, _, false_stmt in ite_assign_regions:
46
+ round_update |= self._convert_region_to_ternary_expr(region_head, region_tail, true_stmt, false_stmt)
49
47
 
50
48
  if not round_update:
51
49
  break
@@ -188,9 +186,7 @@ class ITERegionConverter(OptimizationPass):
188
186
  self,
189
187
  region_head,
190
188
  region_tail,
191
- true_block,
192
189
  true_stmt: Assignment | Call,
193
- false_block,
194
190
  false_stmt: Assignment | Call,
195
191
  ):
196
192
  if region_head not in self._graph or region_tail not in self._graph:
@@ -206,6 +202,7 @@ class ITERegionConverter(OptimizationPass):
206
202
  true_stmt_src = true_stmt.src if isinstance(true_stmt, Assignment) else true_stmt
207
203
  true_stmt_dst = true_stmt.dst if isinstance(true_stmt, Assignment) else true_stmt.ret_expr
208
204
  false_stmt_src = false_stmt.src if isinstance(false_stmt, Assignment) else false_stmt
205
+ false_stmt_dst = false_stmt.dst if isinstance(false_stmt, Assignment) else false_stmt.ret_expr
209
206
 
210
207
  addr_obj = true_stmt_src if "ins_addr" in true_stmt_src.tags else true_stmt
211
208
  ternary_expr = ITE(
@@ -213,9 +210,7 @@ class ITERegionConverter(OptimizationPass):
213
210
  conditional_jump.condition,
214
211
  false_stmt_src,
215
212
  true_stmt_src,
216
- ins_addr=addr_obj.ins_addr,
217
- vex_block_addr=addr_obj.vex_block_addr,
218
- vex_stmt_idx=addr_obj.vex_stmt_idx,
213
+ **addr_obj.tags,
219
214
  )
220
215
  dst = VirtualVariable(
221
216
  true_stmt_dst.idx,
@@ -242,6 +237,14 @@ class ITERegionConverter(OptimizationPass):
242
237
  #
243
238
 
244
239
  region_nodes = subgraph_between_nodes(self._graph, region_head, [region_tail])
240
+
241
+ # we must obtain the predecessors of the region tail instead of using true_block and false_block because
242
+ # true_block and false_block may have other successors before reaching the region tail!
243
+ region_tail_preds = [pred for pred in self._graph.predecessors(region_tail) if pred in region_nodes]
244
+ if len(region_tail_preds) != 2:
245
+ return False
246
+ region_tail_pred_srcs = {(pred.addr, pred.idx) for pred in region_tail_preds}
247
+
245
248
  for node in region_nodes:
246
249
  if node is region_head or node is region_tail:
247
250
  continue
@@ -257,12 +260,32 @@ class ITERegionConverter(OptimizationPass):
257
260
  if not is_phi_assignment(stmt):
258
261
  stmts.append(stmt)
259
262
  continue
263
+
264
+ # is this the statement that we are looking for?
265
+ found_true_src_vvar, found_false_src_vvar = False, False
266
+ for src, vvar in stmt.src.src_and_vvars:
267
+ if vvar is not None:
268
+ if vvar.varid == true_stmt_dst.varid:
269
+ found_true_src_vvar = True
270
+ elif vvar.varid == false_stmt_dst.varid:
271
+ found_false_src_vvar = True
272
+ # we should only update the vvars of this phi statement if we found both true and false source vvars
273
+ update_vars = found_true_src_vvar and found_false_src_vvar
274
+
260
275
  new_src_and_vvars = []
276
+ original_vvars = []
261
277
  for src, vvar in stmt.src.src_and_vvars:
262
- if src not in {(true_block.addr, true_block.idx), (false_block.addr, false_block.idx)}:
278
+ if src not in region_tail_pred_srcs:
263
279
  new_src_and_vvars.append((src, vvar))
280
+ else:
281
+ original_vvars.append(vvar)
264
282
  new_vvar = new_assignment.dst.copy()
265
- new_src_and_vvars.append(((region_head.addr, region_head.idx), new_vvar))
283
+ if update_vars:
284
+ new_src_and_vvars.append(((region_head.addr, region_head.idx), new_vvar))
285
+ else:
286
+ new_src_and_vvars.append(
287
+ ((region_head.addr, region_head.idx), original_vvars[0] if original_vvars else None)
288
+ )
266
289
 
267
290
  new_phi = Phi(
268
291
  stmt.src.idx,
@@ -396,7 +396,16 @@ class LoweredSwitchSimplifier(StructuringOptimizationPass):
396
396
  default_case_candidates = {}
397
397
  last_comp = None
398
398
  stack = [(head, 0, 0xFFFF_FFFF_FFFF_FFFF)]
399
- while stack:
399
+
400
+ # cursed: there is an infinite loop in the following loop that
401
+ # occurs rarely. we need to keep track of the nodes we've seen
402
+ # to break out of the loop.
403
+ # See https://github.com/angr/angr/pull/4953
404
+ #
405
+ # FIXME: the root cause should be fixed and this workaround removed
406
+ seen = set()
407
+ while stack and tuple(stack) not in seen:
408
+ seen.add(tuple(stack))
400
409
  comp, min_, max_ = stack.pop(0)
401
410
  (
402
411
  comp_type,
@@ -1,7 +1,7 @@
1
1
  # pylint:disable=unused-argument
2
2
  from __future__ import annotations
3
3
  import logging
4
- from typing import TYPE_CHECKING
4
+ from typing import Any, TYPE_CHECKING
5
5
  from collections.abc import Generator
6
6
  from enum import Enum
7
7
 
@@ -117,6 +117,7 @@ class OptimizationPass(BaseOptimizationPass):
117
117
  reaching_definitions=None,
118
118
  vvar_id_start=None,
119
119
  entry_node_addr=None,
120
+ scratch: dict[str, Any] | None = None,
120
121
  **kwargs,
121
122
  ):
122
123
  super().__init__(func)
@@ -127,6 +128,7 @@ class OptimizationPass(BaseOptimizationPass):
127
128
  self._variable_kb = variable_kb
128
129
  self._ri = region_identifier
129
130
  self._rd = reaching_definitions
131
+ self._scratch = scratch if scratch is not None else {}
130
132
  self._new_block_addrs = set()
131
133
  self.vvar_id_start = vvar_id_start
132
134
  self.entry_node_addr: tuple[int, int | None] = (
@@ -1,6 +1,5 @@
1
1
  from __future__ import annotations
2
2
  from typing import Any
3
- from itertools import count
4
3
  import copy
5
4
  import logging
6
5
 
@@ -78,23 +77,27 @@ class ReturnDuplicatorBase:
78
77
  def __init__(
79
78
  self,
80
79
  func,
81
- node_idx_start: int = 0,
82
80
  max_calls_in_regions: int = 2,
83
81
  minimize_copies_for_regions: bool = True,
84
82
  ri: RegionIdentifier | None = None,
85
83
  vvar_id_start: int | None = None,
86
- **kwargs,
84
+ scratch: dict[str, Any] | None = None,
87
85
  ):
88
- self.node_idx = count(start=node_idx_start)
89
86
  self._max_calls_in_region = max_calls_in_regions
90
87
  self._minimize_copies_for_regions = minimize_copies_for_regions
91
88
  self._supergraph = None
92
89
 
93
90
  # this should also be set by the optimization passes initer
91
+ self.scratch = scratch if scratch is not None else {}
94
92
  self._func = func
95
93
  self._ri: RegionIdentifier | None = ri
96
94
  self.vvar_id_start = vvar_id_start
97
95
 
96
+ def next_node_idx(self) -> int:
97
+ node_idx = self.scratch.get("returndup_node_idx", 0) + 1
98
+ self.scratch["returndup_node_idx"] = node_idx
99
+ return node_idx
100
+
98
101
  #
99
102
  # must implement these methods
100
103
  #
@@ -208,7 +211,7 @@ class ReturnDuplicatorBase:
208
211
  else:
209
212
  node_copy = copy.deepcopy(node)
210
213
  node_copy = self._use_fresh_virtual_variables(node_copy, vvar_mapping)
211
- node_copy.idx = next(self.node_idx)
214
+ node_copy.idx = self.next_node_idx()
212
215
  self._fix_copied_node_labels(node_copy)
213
216
  copies[node] = node_copy
214
217
 
@@ -1,5 +1,6 @@
1
1
  from __future__ import annotations
2
2
  import logging
3
+ from typing import Any
3
4
 
4
5
  import networkx
5
6
 
@@ -26,22 +27,26 @@ class ReturnDuplicatorHigh(OptimizationPass, ReturnDuplicatorBase):
26
27
  def __init__(
27
28
  self,
28
29
  func,
29
- # internal parameters that should be used by Clinic
30
- node_idx_start: int = 0,
31
30
  # settings
32
31
  max_calls_in_regions: int = 2,
33
32
  minimize_copies_for_regions: bool = True,
33
+ region_identifier=None,
34
+ vvar_id_start: int | None = None,
35
+ scratch: dict[str, Any] | None = None,
34
36
  **kwargs,
35
37
  ):
38
+ OptimizationPass.__init__(
39
+ self, func, vvar_id_start=vvar_id_start, scratch=scratch, region_identifier=region_identifier, **kwargs
40
+ )
36
41
  ReturnDuplicatorBase.__init__(
37
42
  self,
38
43
  func,
39
- node_idx_start=node_idx_start,
40
44
  max_calls_in_regions=max_calls_in_regions,
41
45
  minimize_copies_for_regions=minimize_copies_for_regions,
42
- **kwargs,
46
+ ri=region_identifier,
47
+ vvar_id_start=vvar_id_start,
48
+ scratch=scratch,
43
49
  )
44
- OptimizationPass.__init__(self, func, **kwargs)
45
50
  # since we run before the RegionIdentification pass in the decompiler, we need to collect it early here
46
51
  self._ri = self._recover_regions(self._graph)
47
52
 
@@ -1,6 +1,7 @@
1
1
  from __future__ import annotations
2
2
  import logging
3
3
  import inspect
4
+ from typing import Any
4
5
 
5
6
  import networkx
6
7
 
@@ -46,25 +47,35 @@ class ReturnDuplicatorLow(StructuringOptimizationPass, ReturnDuplicatorBase):
46
47
  def __init__(
47
48
  self,
48
49
  func,
49
- # internal parameters that should be used by Clinic
50
- node_idx_start: int = 0,
51
50
  # settings
52
51
  max_opt_iters: int = 4,
53
52
  max_calls_in_regions: int = 2,
54
53
  prevent_new_gotos: bool = True,
55
54
  minimize_copies_for_regions: bool = True,
55
+ region_identifier=None,
56
+ vvar_id_start: int | None = None,
57
+ scratch: dict[str, Any] | None = None,
56
58
  **kwargs,
57
59
  ):
60
+ StructuringOptimizationPass.__init__(
61
+ self,
62
+ func,
63
+ max_opt_iters=max_opt_iters,
64
+ prevent_new_gotos=prevent_new_gotos,
65
+ require_gotos=True,
66
+ vvar_id_start=vvar_id_start,
67
+ scratch=scratch,
68
+ region_identifier=region_identifier,
69
+ **kwargs,
70
+ )
58
71
  ReturnDuplicatorBase.__init__(
59
72
  self,
60
73
  func,
61
- node_idx_start=node_idx_start,
62
74
  max_calls_in_regions=max_calls_in_regions,
63
75
  minimize_copies_for_regions=minimize_copies_for_regions,
64
- **kwargs,
65
- )
66
- StructuringOptimizationPass.__init__(
67
- self, func, max_opt_iters=max_opt_iters, prevent_new_gotos=prevent_new_gotos, require_gotos=True, **kwargs
76
+ ri=region_identifier,
77
+ vvar_id_start=vvar_id_start,
78
+ scratch=scratch,
68
79
  )
69
80
  self.analyze()
70
81
 
@@ -75,8 +75,12 @@ class SwitchDefaultCaseDuplicator(OptimizationPass):
75
75
  default_case_node_addrs = cache["default_case_node_addrs"]
76
76
 
77
77
  out_graph = None
78
+ duplicated_default_addrs: set[int] = set()
78
79
 
79
80
  for switch_head_addr, jump_node_addr, default_addr in default_case_node_addrs:
81
+ if default_addr in duplicated_default_addrs:
82
+ continue
83
+
80
84
  default_case_node = self._func.get_node(default_addr)
81
85
  unexpected_pred_addrs = {
82
86
  pred.addr
@@ -92,6 +96,8 @@ class SwitchDefaultCaseDuplicator(OptimizationPass):
92
96
  for jump_node in jump_nodes:
93
97
  jump_node_descedents |= networkx.descendants(self._graph, jump_node)
94
98
 
99
+ duplicated_default_addrs.add(default_addr)
100
+
95
101
  # duplicate default_case_node for each unexpected predecessor
96
102
  for unexpected_pred_addr in unexpected_pred_addrs:
97
103
  for unexpected_pred in self._get_blocks(unexpected_pred_addr):
@@ -100,6 +100,8 @@ class WinStackCanarySimplifier(OptimizationPass):
100
100
  for pred_addr in pred_addr_to_endpoint_addrs:
101
101
  # the predecessor should call _security_check_cookie
102
102
  endpoint_preds = list(self._get_blocks(pred_addr))
103
+ if not endpoint_preds:
104
+ continue
103
105
  if self._find_stmt_calling_security_check_cookie(endpoint_preds[0]) is None:
104
106
  _l.debug("The predecessor does not call _security_check_cookie().")
105
107
  continue
@@ -1,7 +1,7 @@
1
1
  # pylint:disable=too-many-boolean-expressions
2
2
  from __future__ import annotations
3
3
 
4
- from ailment.expression import Convert, BinaryOp, Const
4
+ from ailment.expression import Convert, BinaryOp, Const, Expression
5
5
 
6
6
  from .base import PeepholeOptimizationExprBase
7
7
 
@@ -56,47 +56,10 @@ class ConstMullAShift(PeepholeOptimizationExprBase):
56
56
 
57
57
  elif isinstance(expr, BinaryOp) and expr.op in {"Add", "Sub"}:
58
58
  expr0, expr1 = expr.operands
59
- if (
60
- isinstance(expr0, BinaryOp)
61
- and expr0.op in {"Shr", "Sar"}
62
- and isinstance(expr0.operands[1], Const)
63
- and isinstance(expr1, BinaryOp)
64
- and expr1.op in {"Shr", "Sar"}
65
- and isinstance(expr1.operands[1], Const)
66
- ):
67
- if (
68
- isinstance(expr0.operands[0], BinaryOp)
69
- and expr0.operands[0].op in {"Mull", "Mul"}
70
- and isinstance(expr0.operands[0].operands[1], Const)
71
- ):
72
- a0 = expr0.operands[0].operands[0]
73
- a1 = expr1.operands[0]
74
- elif (
75
- isinstance(expr1.operands[0], BinaryOp)
76
- and expr1.operands[0].op in {"Mull", "Mul"}
77
- and isinstance(expr1.operands[0].operands[1], Const)
78
- ):
79
- a1 = expr0.operands[0].operands[0]
80
- a0 = expr1.operands[0]
81
- else:
82
- a0, a1 = None, None
83
- if a0 is not None and a1 is not None and a0.likes(a1):
84
- # (a * x >> M1) +/- (a >> M2) ==> a / N
85
- C = expr0.operands[0].operands[1].value
86
- X = a0
87
- V = expr0.operands[1].value
88
- ndigits = 5 if V == 32 else 6
89
- divisor = self._check_divisor(pow(2, V), C, ndigits)
90
- if divisor is not None:
91
- new_const = Const(None, None, divisor, X.bits)
92
- # we cannot drop the convert in this case
93
- return BinaryOp(
94
- expr0.operands[0].idx,
95
- "Div",
96
- [X, new_const],
97
- expr0.operands[0].signed,
98
- **expr0.operands[0].tags,
99
- )
59
+ if isinstance(expr1, Convert) and expr1.from_bits == 32 and expr1.to_bits == 64:
60
+ r = self._match_case_a(expr0, expr1)
61
+ if r is not None:
62
+ return r
100
63
 
101
64
  # with Convert in consideration
102
65
  if (
@@ -149,8 +112,78 @@ class ConstMullAShift(PeepholeOptimizationExprBase):
149
112
 
150
113
  return None
151
114
 
115
+ def _match_case_a(self, expr0: Expression, expr1: Convert) -> BinaryOp | None:
116
+ # (
117
+ # (((Conv(32->64, vvar_44{reg 32}) * 0x4325c53f<64>) >>a 0x24<8>) & 0xffffffff<64>) -
118
+ # Conv(32->s64, (vvar_44{reg 32} >>a 0x1f<8>))
119
+ # )
120
+
121
+ expr1 = expr1.operand
122
+
123
+ if (
124
+ isinstance(expr0, BinaryOp)
125
+ and expr0.op == "And"
126
+ and isinstance(expr0.operands[1], Const)
127
+ and expr0.operands[1].value == 0xFFFFFFFF
128
+ ):
129
+ expr0 = expr0.operands[0]
130
+ else:
131
+ return None
132
+
133
+ if (
134
+ isinstance(expr0, BinaryOp)
135
+ and expr0.op in {"Shr", "Sar"}
136
+ and isinstance(expr0.operands[1], Const)
137
+ and isinstance(expr1, BinaryOp)
138
+ and expr1.op in {"Shr", "Sar"}
139
+ and isinstance(expr1.operands[1], Const)
140
+ ):
141
+ if (
142
+ isinstance(expr0.operands[0], BinaryOp)
143
+ and expr0.operands[0].op in {"Mull", "Mul"}
144
+ and isinstance(expr0.operands[0].operands[1], Const)
145
+ ):
146
+ a0 = expr0.operands[0].operands[0]
147
+ a1 = expr1.operands[0]
148
+ elif (
149
+ isinstance(expr1.operands[0], BinaryOp)
150
+ and expr1.operands[0].op in {"Mull", "Mul"}
151
+ and isinstance(expr1.operands[0].operands[1], Const)
152
+ ):
153
+ a1 = expr0.operands[0].operands[0]
154
+ a0 = expr1.operands[0]
155
+ else:
156
+ a0, a1 = None, None
157
+
158
+ # a0: Conv(32->64, vvar_44{reg 32})
159
+ # a1: vvar_44{reg 32}
160
+ if isinstance(a0, Convert) and a0.from_bits == a1.bits:
161
+ a0 = a0.operand
162
+
163
+ if a0 is not None and a1 is not None and a0.likes(a1):
164
+ # (a * x >> M1) +/- (a >> M2) ==> a / N
165
+ C = expr0.operands[0].operands[1].value
166
+ X = a0
167
+ V = expr0.operands[1].value
168
+ ndigits = 5 if V == 32 else 6
169
+ divisor = self._check_divisor(pow(2, V), C, ndigits)
170
+ if divisor is not None:
171
+ new_const = Const(None, None, divisor, X.bits)
172
+ # we cannot drop the convert in this case
173
+ return BinaryOp(
174
+ expr0.operands[0].idx,
175
+ "Div",
176
+ [X, new_const],
177
+ expr0.operands[0].signed,
178
+ **expr0.operands[0].tags,
179
+ )
180
+
181
+ return None
182
+
152
183
  @staticmethod
153
184
  def _check_divisor(a, b, ndigits=6):
185
+ if b == 0:
186
+ return None
154
187
  divisor_1 = 1 + (a // b)
155
188
  divisor_2 = int(round(a / float(b), ndigits))
156
189
  return divisor_1 if divisor_1 == divisor_2 else None
@@ -11,9 +11,15 @@ class RemoveCascadingConversions(PeepholeOptimizationExprBase):
11
11
  expr_classes = (Convert,)
12
12
 
13
13
  def optimize(self, expr: Convert, **kwargs):
14
- if isinstance(expr.operand, Convert):
14
+ if (
15
+ expr.from_type == Convert.TYPE_INT
16
+ and expr.to_type == Convert.TYPE_INT
17
+ and isinstance(expr.operand, Convert)
18
+ and expr.operand.from_type == Convert.TYPE_INT
19
+ and expr.operand.to_type == Convert.TYPE_INT
20
+ ):
15
21
  inner = expr.operand
16
- if inner.from_bits == expr.to_bits:
22
+ if inner.from_bits == expr.to_bits and inner.from_type == expr.to_type:
17
23
  if inner.from_bits < inner.to_bits:
18
24
  # extension -> truncation
19
25
  return inner.operand
@@ -163,6 +163,22 @@ class RegionIdentifier(Analysis):
163
163
  raise AngrRuntimeError("Cannot find the start node from the graph!") from ex
164
164
  raise AngrRuntimeError("Cannot find the start node from the graph!")
165
165
 
166
+ def _get_entry_node(self, graph: networkx.DiGraph):
167
+ if self.entry_node_addr is None:
168
+ return None
169
+ return next(
170
+ (
171
+ n
172
+ for n in graph.nodes()
173
+ if (
174
+ (n.addr, n.idx) == self.entry_node_addr
175
+ if isinstance(n, Block)
176
+ else n.addr == self.entry_node_addr[0]
177
+ )
178
+ ),
179
+ None,
180
+ )
181
+
166
182
  def _test_reducibility(self):
167
183
  # make a copy of the graph
168
184
  graph = networkx.DiGraph(self._graph)
@@ -188,8 +204,19 @@ class RegionIdentifier(Analysis):
188
204
  return len(graph.nodes) == 1
189
205
 
190
206
  def _make_supergraph(self, graph: networkx.DiGraph):
207
+
208
+ entry_node = None
209
+ if self.entry_node_addr is not None:
210
+ entry_node = next(iter(nn for nn in graph if nn.addr == self.entry_node_addr[0]), None)
211
+
191
212
  while True:
192
213
  for src, dst, data in graph.edges(data=True):
214
+ if entry_node is not None and dst is entry_node:
215
+ # the entry node must be kept instead of merged with its predecessor (which can happen in real
216
+ # binaries! e.g., 444a401b900eb825f216e95111dcb6ef94b01a81fc7b88a48599867db8c50365, function
217
+ # 0x1802BEA28, block 0x1802BEA05 and 0x1802BEA28)
218
+ continue
219
+
193
220
  type_ = data.get("type", None)
194
221
  if type_ == "fake_return":
195
222
  if len(list(graph.successors(src))) == 1 and len(list(graph.predecessors(dst))) == 1:
@@ -452,6 +479,8 @@ class RegionIdentifier(Analysis):
452
479
  #
453
480
 
454
481
  def _make_cyclic_region(self, head, graph: networkx.DiGraph):
482
+ original_entry = self._get_entry_node(graph)
483
+
455
484
  l.debug("Found cyclic region at %#08x", head.addr)
456
485
  initial_loop_nodes = self._find_initial_loop_nodes(graph, head)
457
486
  l.debug("Initial loop nodes %s", self._dbg_block_list(initial_loop_nodes))
@@ -505,6 +534,13 @@ class RegionIdentifier(Analysis):
505
534
  # multi-successor region. refinement is required
506
535
  self._refine_loop_successors(region, graph)
507
536
 
537
+ # if the head node is in the graph and it's not the head of the graph, we will need to update the head node
538
+ # address.
539
+ if original_entry is not None and original_entry in region.graph and region.head is not original_entry:
540
+ self.entry_node_addr = (head.addr, None)
541
+ # FIXME: the identified region will probably be incorrect. we may need to add a jump block that jumps to
542
+ # original_entry.
543
+
508
544
  return region
509
545
 
510
546
  def _refine_loop_successors(self, region, graph: networkx.DiGraph):
@@ -395,6 +395,10 @@ class ExpressionReplacer(AILBlockWalker):
395
395
 
396
396
  def _handle_Assignment(self, stmt_idx: int, stmt: Assignment, block: Block | None):
397
397
  # override the base handler and make sure we do not replace .dst with a Call expression or an ITE expression
398
+
399
+ if is_phi_assignment(stmt):
400
+ return None
401
+
398
402
  changed = False
399
403
 
400
404
  dst = self._handle_expr(0, stmt.dst, stmt_idx, stmt, block)
@@ -16,6 +16,7 @@ from angr.analyses.decompiler.structuring.structurer_nodes import (
16
16
  CascadingConditionNode,
17
17
  )
18
18
  from angr.analyses.decompiler.utils import is_statement_terminating, has_nonlabel_nonphi_statements
19
+ from angr.utils.ail import is_phi_assignment
19
20
 
20
21
 
21
22
  class LoopSimplifier(SequenceWalker):
@@ -104,14 +105,7 @@ class LoopSimplifier(SequenceWalker):
104
105
  )
105
106
  and (
106
107
  all(has_nonlabel_nonphi_statements(block) for block in self.continue_preludes[node])
107
- and all(
108
- not self._control_transferring_statement(block.statements[-1])
109
- for block in self.continue_preludes[node]
110
- )
111
- and all(
112
- block.statements[-1] == self.continue_preludes[node][0].statements[-1]
113
- for block in self.continue_preludes[node]
114
- )
108
+ and all(not is_phi_assignment(block.statements[-1]) for block in self.continue_preludes[node])
115
109
  )
116
110
  ):
117
111
  node.sort = "for"
@@ -284,6 +284,10 @@ def simplify_switch_clusters(
284
284
 
285
285
  for variable in var2switches:
286
286
  switch_regions = var2switches[variable]
287
+ if len(switch_regions) <= 1:
288
+ # nothing to simplify or merge if there is only one switch region
289
+ continue
290
+
287
291
  cond_regions = list(var2condnodes[variable])
288
292
 
289
293
  if not cond_regions:
@@ -449,10 +453,10 @@ def simplify_switch_clusters(
449
453
  # build the SwitchCase node and replace old nodes in the parent node
450
454
  cases_dict = OrderedDict(cases)
451
455
  new_switchcase = SwitchCaseNode(
452
- switch_regions_default_nodes[0].node.switch_expr,
456
+ switch_regions[0].node.switch_expr,
453
457
  cases_dict,
454
458
  default_node,
455
- addr=switch_regions_default_nodes[0].node.addr,
459
+ addr=switch_regions[0].node.addr,
456
460
  )
457
461
 
458
462
  # what are we trying to replace?
@@ -525,13 +529,15 @@ def simplify_lowered_switches_core(
525
529
 
526
530
  if outermost_node is None:
527
531
  return False
532
+ if not isinstance(outermost_node, ConditionNode):
533
+ return False
528
534
  if isinstance(outermost_node.condition, UnaryOp) and outermost_node.condition.op == "Not":
529
535
  # attempt to flip any simple negated comparison for normalized operations
530
536
  outermost_node.condition = negate(outermost_node.condition.operand)
531
537
 
532
538
  caseno_to_node = {}
533
539
  default_node_candidates: list[tuple[BaseNode, BaseNode]] = [] # parent to default node candidate
534
- stack: list[(ConditionNode, int, int)] = [(outermost_node, 0, 0xFFFF_FFFF_FFFF_FFFF)]
540
+ stack: list[tuple[BaseNode, int, int]] = [(outermost_node, 0, 0xFFFF_FFFF_FFFF_FFFF)]
535
541
  while stack:
536
542
  node, min_, max_ = stack.pop(0)
537
543
  if node not in node_to_condnode:
@@ -216,11 +216,27 @@ class SequenceWalker:
216
216
  )
217
217
 
218
218
  def _handle_CascadingCondition(self, node: CascadingConditionNode, **kwargs):
219
- for index, (_, child_node) in enumerate(node.condition_and_nodes):
220
- self._handle(child_node, parent=node, index=index)
219
+ cond_nodes_changed = False
220
+ new_condition_and_nodes = []
221
+ for index, (cond, child_node) in enumerate(node.condition_and_nodes):
222
+ new_child = self._handle(child_node, parent=node, index=index)
223
+ if new_child is not None:
224
+ cond_nodes_changed = True
225
+ new_condition_and_nodes.append((cond, new_child))
226
+ else:
227
+ new_condition_and_nodes.append((cond, child_node))
228
+
229
+ new_else = None
221
230
  if node.else_node is not None:
222
- self._handle(node.else_node, parent=node, index=-1)
223
- return
231
+ new_else = self._handle(node.else_node, parent=node, index=-1)
232
+
233
+ if cond_nodes_changed or new_else is not None:
234
+ return CascadingConditionNode(
235
+ node.addr,
236
+ new_condition_and_nodes if cond_nodes_changed else node.condition_and_nodes,
237
+ else_node=new_else if new_else is not None else node.else_node,
238
+ )
239
+ return None
224
240
 
225
241
  def _handle_ConditionalBreak(self, node: ConditionalBreakNode, **kwargs): # pylint:disable=no-self-use
226
242
  return None