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
@@ -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,
@@ -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.")
@@ -135,7 +136,6 @@ class Clinic(Analysis):
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
@@ -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
 
@@ -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
@@ -618,6 +621,7 @@ class Clinic(Analysis):
618
621
 
619
622
  # remove empty nodes from the graph
620
623
  ail_graph = self.remove_empty_nodes(ail_graph)
624
+ # note that there are still edges to remove before we can structure this graph!
621
625
 
622
626
  self.arg_list = arg_list
623
627
  self.arg_vvars = arg_vvars
@@ -800,7 +804,7 @@ class Clinic(Analysis):
800
804
 
801
805
  # case 2: the callee is a SimProcedure
802
806
  if target_func.is_simprocedure:
803
- 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
804
808
  if cc.cc is not None and cc.prototype is not None:
805
809
  target_func.calling_convention = cc.cc
806
810
  target_func.prototype = cc.prototype
@@ -808,7 +812,7 @@ class Clinic(Analysis):
808
812
 
809
813
  # case 3: the callee is a PLT function
810
814
  if target_func.is_plt:
811
- 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
812
816
  if cc.cc is not None and cc.prototype is not None:
813
817
  target_func.calling_convention = cc.cc
814
818
  target_func.prototype = cc.prototype
@@ -878,7 +882,7 @@ class Clinic(Analysis):
878
882
  # finally, recover the calling convention of the current function
879
883
  if self.function.prototype is None or self.function.calling_convention is None:
880
884
  self.project.analyses.CompleteCallingConventions(
881
- fail_fast=self._fail_fast,
885
+ fail_fast=self._fail_fast, # type: ignore
882
886
  recover_variables=True,
883
887
  prioritize_func_addrs=[self.function.addr],
884
888
  skip_other_funcs=True,
@@ -2109,49 +2113,60 @@ class Clinic(Analysis):
2109
2113
  # the overlapped instructions and add an unconditional jump so that it jumps to 0x41da9d.
2110
2114
  # this is the most common case created by jump threading optimization in compilers. it's easy to handle.
2111
2115
 
2112
- # Case 2: the intended head and the other heads do not share the same suffix of instructions. in this case,
2113
- # we cannot reliably convert the blocks into a properly structured switch-case construct. we will alter the
2114
- # last instruction of all other heads to jump to the cmp instruction in the intended head, but do not remove
2115
- # 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.
2116
2128
 
2117
2129
  overlaps = [self._get_overlapping_suffix_instructions(intended_head, head) for head in other_heads]
2118
2130
  if overlaps and (overlap := min(overlaps)) > 0:
2119
2131
  # Case 1
2120
2132
  self._fix_abnormal_switch_case_heads_case1(ail_graph, candidate, intended_head, other_heads, overlap)
2121
- else:
2122
- if self._unsound_fix_abnormal_switches:
2123
- # Case 2
2124
- l.warning("Switch-case at %#x has multiple head nodes but cannot be fixed soundly.", candidate.addr)
2125
- # find the comparison instruction in the intended head
2126
- comparison_stmt = None
2127
- if "cc_op" in self.project.arch.registers:
2128
- comparison_stmt = next(
2129
- iter(
2130
- stmt
2131
- for stmt in intended_head.statements
2132
- if isinstance(stmt, ailment.Stmt.Assignment)
2133
- and isinstance(stmt.dst, ailment.Expr.Register)
2134
- and stmt.dst.reg_offset == self.project.arch.registers["cc_op"][0]
2135
- ),
2136
- None,
2137
- )
2138
- intended_head_block = self.project.factory.block(
2139
- 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,
2140
2148
  )
2141
- if comparison_stmt is not None:
2142
- cmp_rpos = len(
2143
- intended_head_block.instruction_addrs
2144
- ) - intended_head_block.instruction_addrs.index(comparison_stmt.ins_addr)
2145
- else:
2146
- cmp_rpos = min(len(intended_head_block.instruction_addrs), 2)
2147
- self._fix_abnormal_switch_case_heads_case2(
2148
- ail_graph,
2149
- candidate,
2150
- intended_head,
2151
- other_heads,
2152
- intended_head_split_insns=cmp_rpos,
2153
- 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
2154
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
+ )
2155
2170
 
2156
2171
  def _get_overlapping_suffix_instructions(self, ailblock_0: ailment.Block, ailblock_1: ailment.Block) -> int:
2157
2172
  # we first compare their ending conditional jumps
@@ -2360,6 +2375,16 @@ class Clinic(Analysis):
2360
2375
  # it should be going to the default node. ignore it
2361
2376
  pass
2362
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
+
2363
2388
  @staticmethod
2364
2389
  def _remove_redundant_jump_blocks(ail_graph):
2365
2390
  def first_conditional_jump(block: ailment.Block) -> ailment.Stmt.ConditionalJump | None:
@@ -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
 
@@ -296,8 +299,14 @@ class Decompiler(Analysis):
296
299
  ri,
297
300
  clinic.reaching_definitions,
298
301
  ite_exprs=ite_exprs,
302
+ arg_vvars=set(clinic.arg_vvars),
303
+ edges_to_remove=clinic.edges_to_remove,
299
304
  )
300
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
+
301
310
  # Rewrite the graph to remove phi expressions
302
311
  # this is probably optional if we do not pretty-print clinic.graph
303
312
  clinic.graph = self._transform_graph_from_ssa(clinic.graph)
@@ -325,9 +334,9 @@ class Decompiler(Analysis):
325
334
  s = self.project.analyses.RegionSimplifier(
326
335
  self.func,
327
336
  rs.result,
337
+ arg_vvars=set(self.clinic.arg_vvars),
328
338
  kb=self.kb,
329
339
  fail_fast=self._fail_fast,
330
- variable_kb=clinic.variable_kb,
331
340
  **self.options_to_params(self.options_by_class["region_simplifier"]),
332
341
  )
333
342
  seq_node = s.result
@@ -439,7 +448,7 @@ class Decompiler(Analysis):
439
448
  return ail_graph
440
449
 
441
450
  @timethis
442
- 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):
443
452
  """
444
453
  Runs optimizations that should be executed after a single region identification. This function will return
445
454
  two items: the new RegionIdentifier object and the new AIL Graph, which should probably be written
@@ -483,6 +492,7 @@ class Decompiler(Analysis):
483
492
  blocks_by_addr_and_idx=addr_and_idx_to_blocks,
484
493
  graph=ail_graph,
485
494
  variable_kb=self._variable_kb,
495
+ arg_vvars=arg_vvars,
486
496
  region_identifier=ri,
487
497
  reaching_definitions=reaching_definitions,
488
498
  vvar_id_start=self.vvar_id_start,
@@ -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
@@ -8,7 +8,7 @@ import claripy
8
8
  from ailment import Const
9
9
  from ailment.block_walker import AILBlockWalkerBase
10
10
  from ailment.statement import Call, Statement, ConditionalJump, Assignment, Store, Return
11
- from ailment.expression import Convert, Register, Expression
11
+ from ailment.expression import Convert, Register, Expression, Load
12
12
 
13
13
  from .optimization_pass import OptimizationPass, OptimizationPassStage
14
14
  from angr.analyses.decompiler.structuring import SAILRStructurer, DreamStructurer
@@ -207,16 +207,17 @@ class ConstPropOptReverter(OptimizationPass):
207
207
  continue
208
208
 
209
209
  unwrapped_sym_arg = sym_arg.operands[0] if isinstance(sym_arg, Convert) else sym_arg
210
- try:
210
+ if (
211
+ isinstance(unwrapped_sym_arg, Load)
212
+ and isinstance(unwrapped_sym_arg.addr, Const)
213
+ and isinstance(unwrapped_sym_arg.addr.value, int)
214
+ ):
211
215
  # TODO: make this support more than just Loads
212
216
  # target must be a Load of a memory location
213
217
  target_atom = MemoryLocation(unwrapped_sym_arg.addr.value, unwrapped_sym_arg.size, "Iend_LE")
214
218
  const_state = self.rd.get_reaching_definitions_by_node(blks[calls[const_arg]].addr, OP_BEFORE)
215
-
216
- state_load_vals = const_state.get_value_from_atom(target_atom)
217
- except AttributeError:
218
- continue
219
- except KeyError:
219
+ state_load_vals = const_state.get_values(target_atom)
220
+ else:
220
221
  continue
221
222
 
222
223
  if not state_load_vals:
@@ -287,19 +287,27 @@ class ITERegionConverter(OptimizationPass):
287
287
  ((region_head.addr, region_head.idx), original_vvars[0] if original_vvars else None)
288
288
  )
289
289
 
290
- new_phi = Phi(
291
- stmt.src.idx,
292
- stmt.src.bits,
293
- new_src_and_vvars,
294
- **stmt.src.tags,
295
- )
296
- new_phi_assignment = Assignment(
297
- stmt.idx,
298
- stmt.dst,
299
- new_phi,
300
- **stmt.tags,
301
- )
302
- stmts.append(new_phi_assignment)
290
+ if len(new_src_and_vvars) == 1:
291
+ new_assignment = Assignment(
292
+ stmt.idx,
293
+ stmt.dst,
294
+ new_src_and_vvars[0][1],
295
+ **stmt.tags,
296
+ )
297
+ else:
298
+ new_phi = Phi(
299
+ stmt.src.idx,
300
+ stmt.src.bits,
301
+ new_src_and_vvars,
302
+ **stmt.src.tags,
303
+ )
304
+ new_assignment = Assignment(
305
+ stmt.idx,
306
+ stmt.dst,
307
+ new_phi,
308
+ **stmt.tags,
309
+ )
310
+ stmts.append(new_assignment)
303
311
  new_region_tail = Block(region_tail.addr, region_tail.original_size, statements=stmts, idx=region_tail.idx)
304
312
 
305
313
  #
@@ -13,7 +13,7 @@ from angr.analyses.decompiler import RegionIdentifier
13
13
  from angr.analyses.decompiler.condition_processor import ConditionProcessor
14
14
  from angr.analyses.decompiler.goto_manager import Goto, GotoManager
15
15
  from angr.analyses.decompiler.structuring import RecursiveStructurer, SAILRStructurer
16
- from angr.analyses.decompiler.utils import add_labels
16
+ from angr.analyses.decompiler.utils import add_labels, remove_edges_in_ailgraph
17
17
  from angr.analyses.decompiler.counters import ControlFlowStructureCounter
18
18
  from angr.project import Project
19
19
 
@@ -129,6 +129,7 @@ class OptimizationPass(BaseOptimizationPass):
129
129
  force_loop_single_exit: bool = True,
130
130
  complete_successors: bool = False,
131
131
  avoid_vvar_ids: set[int] | None = None,
132
+ arg_vvars: set[int] | None = None,
132
133
  **kwargs,
133
134
  ):
134
135
  super().__init__(func)
@@ -141,6 +142,7 @@ class OptimizationPass(BaseOptimizationPass):
141
142
  self._rd = reaching_definitions
142
143
  self._scratch = scratch if scratch is not None else {}
143
144
  self._new_block_addrs = set()
145
+ self._arg_vvars = arg_vvars
144
146
  self.vvar_id_start = vvar_id_start
145
147
  self.entry_node_addr: tuple[int, int | None] = (
146
148
  entry_node_addr if entry_node_addr is not None else (func.addr, None)
@@ -331,14 +333,15 @@ class StructuringOptimizationPass(OptimizationPass):
331
333
  def __init__(
332
334
  self,
333
335
  func,
334
- prevent_new_gotos=True,
335
- strictly_less_gotos=False,
336
- recover_structure_fails=True,
337
- must_improve_rel_quality=True,
338
- max_opt_iters=1,
339
- simplify_ail=True,
340
- require_gotos=True,
341
- readd_labels=False,
336
+ prevent_new_gotos: bool = True,
337
+ strictly_less_gotos: bool = False,
338
+ recover_structure_fails: bool = True,
339
+ must_improve_rel_quality: bool = True,
340
+ max_opt_iters: int = 1,
341
+ simplify_ail: bool = True,
342
+ require_gotos: bool = True,
343
+ readd_labels: bool = False,
344
+ edges_to_remove: list[tuple[tuple[int, int | None], tuple[int, int | None]]] | None = None,
342
345
  **kwargs,
343
346
  ):
344
347
  super().__init__(func, **kwargs)
@@ -350,6 +353,7 @@ class StructuringOptimizationPass(OptimizationPass):
350
353
  self._require_gotos = require_gotos
351
354
  self._must_improve_rel_quality = must_improve_rel_quality
352
355
  self._readd_labels = readd_labels
356
+ self._edges_to_remove = edges_to_remove or []
353
357
 
354
358
  # relative quality metrics (excludes gotos)
355
359
  self._initial_structure_counter = None
@@ -452,6 +456,8 @@ class StructuringOptimizationPass(OptimizationPass):
452
456
  if readd_labels:
453
457
  graph = add_labels(graph)
454
458
 
459
+ remove_edges_in_ailgraph(graph, self._edges_to_remove)
460
+
455
461
  self._ri = self.project.analyses[RegionIdentifier].prep(kb=self.kb)(
456
462
  self._func,
457
463
  graph=graph,
@@ -482,7 +488,7 @@ class StructuringOptimizationPass(OptimizationPass):
482
488
  if not rs or not rs.result or not rs.result.nodes or rs.result_incomplete:
483
489
  return False
484
490
 
485
- rs = self.project.analyses.RegionSimplifier(self._func, rs.result, kb=self.kb, variable_kb=self._variable_kb)
491
+ rs = self.project.analyses.RegionSimplifier(self._func, rs.result, arg_vvars=self._arg_vvars, kb=self.kb)
486
492
  if not rs or rs.goto_manager is None or rs.result is None:
487
493
  return False
488
494
 
@@ -140,11 +140,11 @@ class ReturnDuplicatorBase:
140
140
  ):
141
141
  # every eligible pred gets a new region copy
142
142
  self._copy_region([pred_node], region_head, region, graph)
143
+ graph_changed = True
143
144
 
144
145
  if region_head in graph and graph.in_degree(region_head) == 0:
145
146
  graph.remove_nodes_from(region)
146
-
147
- graph_changed = True
147
+ graph_changed = True
148
148
 
149
149
  return graph_changed
150
150