angr 9.2.177__cp310-abi3-manylinux_2_28_x86_64.whl → 9.2.178__cp310-abi3-manylinux_2_28_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 (29) hide show
  1. angr/__init__.py +1 -1
  2. angr/analyses/cfg/cfg_fast.py +15 -0
  3. angr/analyses/decompiler/ail_simplifier.py +68 -0
  4. angr/analyses/decompiler/ccall_rewriters/amd64_ccalls.py +45 -7
  5. angr/analyses/decompiler/clinic.py +15 -7
  6. angr/analyses/decompiler/dirty_rewriters/__init__.py +7 -0
  7. angr/analyses/decompiler/dirty_rewriters/amd64_dirty.py +69 -0
  8. angr/analyses/decompiler/dirty_rewriters/rewriter_base.py +27 -0
  9. angr/analyses/decompiler/optimization_passes/__init__.py +3 -0
  10. angr/analyses/decompiler/optimization_passes/optimization_pass.py +10 -8
  11. angr/analyses/decompiler/optimization_passes/register_save_area_simplifier.py +44 -6
  12. angr/analyses/decompiler/optimization_passes/register_save_area_simplifier_adv.py +198 -0
  13. angr/analyses/decompiler/optimization_passes/win_stack_canary_simplifier.py +111 -55
  14. angr/analyses/decompiler/peephole_optimizations/remove_redundant_shifts_around_comparators.py +72 -1
  15. angr/analyses/decompiler/presets/basic.py +2 -0
  16. angr/analyses/decompiler/presets/fast.py +2 -0
  17. angr/analyses/decompiler/presets/full.py +2 -0
  18. angr/analyses/s_propagator.py +23 -21
  19. angr/analyses/smc.py +2 -3
  20. angr/knowledge_plugins/labels.py +4 -4
  21. angr/rustylib.abi3.so +0 -0
  22. angr/utils/funcid.py +85 -0
  23. angr/utils/ssa/__init__.py +2 -6
  24. {angr-9.2.177.dist-info → angr-9.2.178.dist-info}/METADATA +6 -5
  25. {angr-9.2.177.dist-info → angr-9.2.178.dist-info}/RECORD +29 -25
  26. {angr-9.2.177.dist-info → angr-9.2.178.dist-info}/WHEEL +0 -0
  27. {angr-9.2.177.dist-info → angr-9.2.178.dist-info}/entry_points.txt +0 -0
  28. {angr-9.2.177.dist-info → angr-9.2.178.dist-info}/licenses/LICENSE +0 -0
  29. {angr-9.2.177.dist-info → angr-9.2.178.dist-info}/top_level.txt +0 -0
angr/__init__.py CHANGED
@@ -2,7 +2,7 @@
2
2
  # pylint: disable=wrong-import-position
3
3
  from __future__ import annotations
4
4
 
5
- __version__ = "9.2.177"
5
+ __version__ = "9.2.178"
6
6
 
7
7
  if bytes is str:
8
8
  raise Exception(
@@ -40,6 +40,7 @@ from angr.errors import (
40
40
  from angr.utils.constants import DEFAULT_STATEMENT
41
41
  from angr.utils.funcid import (
42
42
  is_function_security_check_cookie,
43
+ is_function_security_check_cookie_strict,
43
44
  is_function_security_init_cookie,
44
45
  is_function_security_init_cookie_win8,
45
46
  is_function_likely_security_init_cookie,
@@ -2029,6 +2030,20 @@ class CFGFast(ForwardAnalysis[CFGNode, CFGNode, CFGJob, int], CFGBase): # pylin
2029
2030
  # both are found. exit from the loop
2030
2031
  break
2031
2032
 
2033
+ else:
2034
+ # security_cookie_addr is None; let's invoke the stricter version to find _security_check_cookie
2035
+ for func in self.kb.functions.values():
2036
+ if len(func.block_addrs_set) in {5, 6}:
2037
+ r, cookie_addr = is_function_security_check_cookie_strict(func, self.project)
2038
+ if r:
2039
+ security_cookie_addr = cookie_addr
2040
+ if security_cookie_addr not in self.kb.labels:
2041
+ self.kb.labels[security_cookie_addr] = "_security_cookie"
2042
+ security_check_cookie_found = True
2043
+ func.is_default_name = False
2044
+ func.name = "_security_check_cookie"
2045
+ break
2046
+
2032
2047
  # special handling: some binaries do not have SecurityCookie set, but still contain _security_init_cookie
2033
2048
  if security_init_cookie_found is False and self.functions.contains_addr(self.project.entry):
2034
2049
  start_func = self.functions.get_by_addr(self.project.entry)
@@ -57,6 +57,7 @@ from .ailgraph_walker import AILGraphWalker
57
57
  from .expression_narrower import ExprNarrowingInfo, NarrowingInfoExtractor, ExpressionNarrower
58
58
  from .block_simplifier import BlockSimplifier
59
59
  from .ccall_rewriters import CCALL_REWRITERS
60
+ from .dirty_rewriters import DIRTY_REWRITERS
60
61
  from .counters.expression_counters import SingleExpressionCounter
61
62
 
62
63
  if TYPE_CHECKING:
@@ -169,6 +170,7 @@ class AILSimplifier(Analysis):
169
170
  use_callee_saved_regs_at_return=True,
170
171
  rewrite_ccalls=True,
171
172
  rename_ccalls=True,
173
+ rewrite_dirty=True,
172
174
  removed_vvar_ids: set[int] | None = None,
173
175
  arg_vvars: dict[int, tuple[VirtualVariable, SimVariable]] | None = None,
174
176
  avoid_vvar_ids: set[int] | None = None,
@@ -190,6 +192,7 @@ class AILSimplifier(Analysis):
190
192
  self._use_callee_saved_regs_at_return = use_callee_saved_regs_at_return
191
193
  self._should_rewrite_ccalls = rewrite_ccalls
192
194
  self._should_rename_ccalls = rename_ccalls
195
+ self._should_rewrite_dirty = rewrite_dirty
193
196
  self._removed_vvar_ids = removed_vvar_ids if removed_vvar_ids is not None else set()
194
197
  self._arg_vvars = arg_vvars
195
198
  self._avoid_vvar_ids = avoid_vvar_ids if avoid_vvar_ids is not None else set()
@@ -258,6 +261,15 @@ class AILSimplifier(Analysis):
258
261
  self._rebuild_func_graph()
259
262
  self._clear_cache()
260
263
 
264
+ if self._should_rewrite_dirty:
265
+ _l.debug("Rewriting dirty expressions/statements")
266
+ dirty_rewritten = self._rewrite_dirty_calls()
267
+ self.simplified |= dirty_rewritten
268
+ if dirty_rewritten:
269
+ _l.debug("... dirty expressions/statements rewritten")
270
+ self._rebuild_func_graph()
271
+ self._clear_cache()
272
+
261
273
  if self._unify_vars:
262
274
  _l.debug("Removing dead assignments")
263
275
  r = self._iteratively_remove_dead_assignments()
@@ -841,6 +853,7 @@ class AILSimplifier(Analysis):
841
853
  if (
842
854
  isinstance(stmt, Assignment)
843
855
  and isinstance(stmt.dst, VirtualVariable)
856
+ and stmt.dst.was_reg # values of stack variables might be updated in callees or via pointers
844
857
  and isinstance(stmt.src, Const)
845
858
  and isinstance(stmt.src.value, int)
846
859
  ):
@@ -2020,6 +2033,61 @@ class AILSimplifier(Analysis):
2020
2033
 
2021
2034
  return updated
2022
2035
 
2036
+ #
2037
+ # Rewriting dirty calls
2038
+ #
2039
+
2040
+ def _rewrite_dirty_calls(self):
2041
+ rewriter_cls = DIRTY_REWRITERS.get(self.project.arch.name, None)
2042
+ if rewriter_cls is None:
2043
+ return False
2044
+
2045
+ walker = AILBlockWalker()
2046
+
2047
+ class _any_update:
2048
+ """
2049
+ Dummy class for storing if any result has been updated.
2050
+ """
2051
+
2052
+ v = False
2053
+
2054
+ def _handle_DirtyStatement( # pylint:disable=unused-argument
2055
+ stmt_idx: int, stmt: DirtyStatement, block: Block | None
2056
+ ) -> Expression | None:
2057
+ # we do not want to trigger _handle_DirtyExpression, which is why we do not call the superclass method
2058
+ rewriter = rewriter_cls(stmt, self.project.arch)
2059
+ if rewriter.result is not None:
2060
+ _any_update.v = True
2061
+ return rewriter.result # type:ignore
2062
+ return None
2063
+
2064
+ def _handle_DirtyExpression(
2065
+ expr_idx: int, expr: DirtyExpression, stmt_idx: int, stmt: Statement, block: Block | None
2066
+ ):
2067
+ r_expr = AILBlockWalker._handle_DirtyExpression(walker, expr_idx, expr, stmt_idx, stmt, block)
2068
+ if r_expr is None:
2069
+ r_expr = expr
2070
+ rewriter = rewriter_cls(r_expr, self.project.arch)
2071
+ if rewriter.result is not None:
2072
+ _any_update.v = True
2073
+ return rewriter.result
2074
+ return r_expr if r_expr is not expr else None
2075
+
2076
+ blocks_by_addr_and_idx = {(node.addr, node.idx): node for node in self.func_graph.nodes()}
2077
+ walker.expr_handlers[DirtyExpression] = _handle_DirtyExpression
2078
+ walker.stmt_handlers[DirtyStatement] = _handle_DirtyStatement
2079
+
2080
+ updated = False
2081
+ for block in blocks_by_addr_and_idx.values():
2082
+ _any_update.v = False
2083
+ old_block = block.copy()
2084
+ walker.walk(block)
2085
+ if _any_update.v:
2086
+ self.blocks[old_block] = block
2087
+ updated = True
2088
+
2089
+ return updated
2090
+
2023
2091
  #
2024
2092
  # Util functions
2025
2093
  #
@@ -26,8 +26,8 @@ class AMD64CCallRewriter(CCallRewriterBase):
26
26
  dep_1 = ccall.operands[2]
27
27
  dep_2 = ccall.operands[3]
28
28
  if isinstance(cond, Expr.Const) and isinstance(op, Expr.Const):
29
- cond_v = cond.value
30
- op_v = op.value
29
+ cond_v = cond.value_int
30
+ op_v = op.value_int
31
31
  if cond_v == AMD64_CondTypes["CondLE"]:
32
32
  if op_v in {
33
33
  AMD64_OpTypes["G_CC_OP_SUBB"],
@@ -233,7 +233,9 @@ class AMD64CCallRewriter(CCallRewriterBase):
233
233
  if op_v == AMD64_OpTypes["G_CC_OP_COPY"]:
234
234
  # dep_1 & G_CC_MASK_Z == 0 or dep_1 & G_CC_MASK_Z != 0
235
235
 
236
- flag = Expr.Const(None, None, AMD64_CondBitMasks["G_CC_MASK_Z"], dep_1.bits)
236
+ bitmask = AMD64_CondBitMasks["G_CC_MASK_Z"]
237
+ assert isinstance(bitmask, int)
238
+ flag = Expr.Const(None, None, bitmask, dep_1.bits)
237
239
  masked_dep = Expr.BinaryOp(None, "And", [dep_1, flag], False, **ccall.tags)
238
240
  zero = Expr.Const(None, None, 0, dep_1.bits)
239
241
  expr_op = "CmpEQ" if cond_v == AMD64_CondTypes["CondZ"] else "CmpNE"
@@ -372,6 +374,39 @@ class AMD64CCallRewriter(CCallRewriterBase):
372
374
  bits=ccall.bits,
373
375
  **ccall.tags,
374
376
  )
377
+ if op_v in {
378
+ AMD64_OpTypes["G_CC_OP_SUBB"],
379
+ AMD64_OpTypes["G_CC_OP_SUBW"],
380
+ AMD64_OpTypes["G_CC_OP_SUBL"],
381
+ AMD64_OpTypes["G_CC_OP_SUBQ"],
382
+ }:
383
+ # dep_1 <u dep_2
384
+
385
+ dep_1 = self._fix_size(
386
+ dep_1,
387
+ op_v,
388
+ AMD64_OpTypes["G_CC_OP_SUBB"],
389
+ AMD64_OpTypes["G_CC_OP_SUBW"],
390
+ AMD64_OpTypes["G_CC_OP_SUBL"],
391
+ ccall.tags,
392
+ )
393
+ dep_2 = self._fix_size(
394
+ dep_2,
395
+ op_v,
396
+ AMD64_OpTypes["G_CC_OP_SUBB"],
397
+ AMD64_OpTypes["G_CC_OP_SUBW"],
398
+ AMD64_OpTypes["G_CC_OP_SUBL"],
399
+ ccall.tags,
400
+ )
401
+
402
+ r = Expr.BinaryOp(
403
+ ccall.idx,
404
+ "CmpLT",
405
+ (dep_1, dep_2),
406
+ False,
407
+ **ccall.tags,
408
+ )
409
+ return Expr.Convert(None, r.bits, ccall.bits, False, r, **ccall.tags)
375
410
  elif (
376
411
  cond_v == AMD64_CondTypes["CondS"]
377
412
  and op_v
@@ -458,7 +493,7 @@ class AMD64CCallRewriter(CCallRewriterBase):
458
493
  dep_2 = ccall.operands[2]
459
494
  ndep = ccall.operands[3]
460
495
  if isinstance(op, Expr.Const):
461
- op_v = op.value
496
+ op_v = op.value_int
462
497
  if op_v in {
463
498
  AMD64_OpTypes["G_CC_OP_ADDB"],
464
499
  AMD64_OpTypes["G_CC_OP_ADDW"],
@@ -545,6 +580,9 @@ class AMD64CCallRewriter(CCallRewriterBase):
545
580
  AMD64_OpTypes["G_CC_OP_DECQ"],
546
581
  }:
547
582
  # pc_actions_DEC
583
+ bitmask = AMD64_CondBitMasks["G_CC_MASK_C"]
584
+ bitmask_1 = AMD64_CondBitOffsets["G_CC_SHIFT_C"]
585
+ assert isinstance(bitmask, int) and isinstance(bitmask_1, int)
548
586
  return Expr.BinaryOp(
549
587
  None,
550
588
  "Shr",
@@ -552,10 +590,10 @@ class AMD64CCallRewriter(CCallRewriterBase):
552
590
  Expr.BinaryOp(
553
591
  None,
554
592
  "And",
555
- [ndep, Expr.Const(None, None, AMD64_CondBitMasks["G_CC_MASK_C"], 64)],
593
+ [ndep, Expr.Const(None, None, bitmask, 64)],
556
594
  False,
557
595
  ),
558
- Expr.Const(None, None, AMD64_CondBitOffsets["G_CC_SHIFT_C"], 64),
596
+ Expr.Const(None, None, bitmask_1, 64),
559
597
  ],
560
598
  False,
561
599
  **ccall.tags,
@@ -575,6 +613,6 @@ class AMD64CCallRewriter(CCallRewriterBase):
575
613
  bits = 64
576
614
  if bits < 64:
577
615
  if isinstance(expr, Expr.Const):
578
- return Expr.Const(expr.idx, None, expr.value & ((1 << bits) - 1), bits, **tags)
616
+ return Expr.Const(expr.idx, None, expr.value_int & ((1 << bits) - 1), bits, **tags)
579
617
  return Expr.Convert(None, 64, bits, False, expr, **tags)
580
618
  return expr
@@ -46,7 +46,6 @@ from .return_maker import ReturnMaker
46
46
  from .ailgraph_walker import AILGraphWalker, RemoveNodeNotice
47
47
  from .optimization_passes import (
48
48
  OptimizationPassStage,
49
- RegisterSaveAreaSimplifier,
50
49
  StackCanarySimplifier,
51
50
  TagSlicer,
52
51
  DUPLICATING_OPTS,
@@ -598,6 +597,17 @@ class Clinic(Analysis):
598
597
  assert self.func_args is not None
599
598
  self._ail_graph = self._transform_to_ssa_level1(self._ail_graph, self.func_args)
600
599
 
600
+ # Run simplification passes
601
+ self._update_progress(49.0, text="Running simplifications 1.5")
602
+ self._ail_graph = self._run_simplification_passes(
603
+ self._ail_graph, stage=OptimizationPassStage.AFTER_SSA_LEVEL1_TRANSFORMATION
604
+ )
605
+
606
+ # register save area has been removed at this point - we should no longer use callee-saved registers in RDA
607
+ self._register_save_areas_removed = True
608
+ # clear the cached RDA result
609
+ self.reaching_definitions = None
610
+
601
611
  def _stage_pre_ssa_level1_simplifications(self) -> None:
602
612
  # Simplify blocks
603
613
  # we never remove dead memory definitions before making callsites. otherwise stack arguments may go missing
@@ -698,7 +708,10 @@ class Clinic(Analysis):
698
708
  # Run simplification passes
699
709
  self._update_progress(65.0, text="Running simplifications 3")
700
710
  self._ail_graph = self._run_simplification_passes(
701
- self._ail_graph, stack_items=self.stack_items, stage=OptimizationPassStage.AFTER_GLOBAL_SIMPLIFICATION
711
+ self._ail_graph,
712
+ stack_items=self.stack_items,
713
+ stage=OptimizationPassStage.AFTER_GLOBAL_SIMPLIFICATION,
714
+ arg_vvars=self.arg_vvars,
702
715
  )
703
716
 
704
717
  # Simplify the entire function for the third time
@@ -1579,11 +1592,6 @@ class Clinic(Analysis):
1579
1592
  if a.out_graph:
1580
1593
  # use the new graph
1581
1594
  ail_graph = a.out_graph
1582
- if isinstance(a, RegisterSaveAreaSimplifier):
1583
- # register save area has been removed - we should no longer use callee-saved registers in RDA
1584
- self._register_save_areas_removed = True
1585
- # clear the cached RDA result
1586
- self.reaching_definitions = None
1587
1595
  self.vvar_id_start = a.vvar_id_start
1588
1596
  if stack_items is not None and a.stack_items:
1589
1597
  stack_items.update(a.stack_items)
@@ -0,0 +1,7 @@
1
+ from __future__ import annotations
2
+ from .amd64_dirty import AMD64DirtyRewriter
3
+
4
+
5
+ DIRTY_REWRITERS = {
6
+ "AMD64": AMD64DirtyRewriter,
7
+ }
@@ -0,0 +1,69 @@
1
+ from __future__ import annotations
2
+
3
+ from angr.ailment import Const
4
+ from angr.ailment.statement import DirtyStatement, Statement, Call
5
+ from angr.ailment.expression import DirtyExpression, Expression
6
+ from .rewriter_base import DirtyRewriterBase
7
+
8
+
9
+ class AMD64DirtyRewriter(DirtyRewriterBase):
10
+ """
11
+ Rewrites AMD64 DirtyStatement and DirtyExpression.
12
+ """
13
+
14
+ __slots__ = ()
15
+
16
+ def _rewrite_stmt(self, dirty: DirtyStatement) -> Statement | None:
17
+ # TODO: Rewrite more dirty statements
18
+ return None
19
+
20
+ def _rewrite_expr(self, dirty: DirtyExpression) -> Expression | None:
21
+ match dirty.callee:
22
+ case "amd64g_dirtyhelper_IN":
23
+ # in
24
+ bits = (
25
+ dirty.operands[1].value * self.arch.byte_width
26
+ if len(dirty.operands) > 1 and isinstance(dirty.operands[1], Const)
27
+ else None
28
+ )
29
+ func_name = "__in"
30
+ suffix = self._inout_intrinsic_suffix(bits) if bits is not None else None
31
+ if suffix is not None:
32
+ func_name += f"{suffix}"
33
+ else:
34
+ func_name += f"_{bits}"
35
+ return Call(
36
+ dirty.idx, func_name, None, None, args=(dirty.operands[0],), ret_expr=None, bits=bits, **dirty.tags
37
+ )
38
+ case "amd64g_dirtyhelper_OUT":
39
+ # out
40
+ bits = (
41
+ dirty.operands[1].value * self.arch.byte_width
42
+ if len(dirty.operands) > 1 and isinstance(dirty.operands[1], Const)
43
+ else None
44
+ )
45
+ func_name = "__out"
46
+ suffix = self._inout_intrinsic_suffix(bits) if bits is not None else None
47
+ if suffix is not None:
48
+ func_name += f"{suffix}"
49
+ else:
50
+ func_name += f"_{bits}"
51
+ return Call(
52
+ dirty.idx, func_name, None, None, args=(dirty.operands[0],), ret_expr=None, bits=bits, **dirty.tags
53
+ )
54
+ return None
55
+
56
+ #
57
+ # in, out
58
+ #
59
+
60
+ @staticmethod
61
+ def _inout_intrinsic_suffix(bits: int) -> str | None:
62
+ match bits:
63
+ case 8:
64
+ return "byte"
65
+ case 16:
66
+ return "word"
67
+ case 32:
68
+ return "dword"
69
+ return None
@@ -0,0 +1,27 @@
1
+ from __future__ import annotations
2
+
3
+ from angr.ailment.statement import DirtyStatement, Statement
4
+ from angr.ailment.expression import DirtyExpression, Expression
5
+
6
+
7
+ class DirtyRewriterBase:
8
+ """
9
+ The base class for DirtyStatement and DirtyExpression rewriters.
10
+ """
11
+
12
+ __slots__ = (
13
+ "arch",
14
+ "result",
15
+ )
16
+
17
+ def __init__(self, dirty: DirtyExpression | DirtyStatement, arch):
18
+ self.arch = arch
19
+ self.result: Expression | Statement | None = (
20
+ self._rewrite_expr(dirty) if isinstance(dirty, DirtyExpression) else self._rewrite_stmt(dirty)
21
+ )
22
+
23
+ def _rewrite_stmt(self, dirty: DirtyStatement) -> Statement | None:
24
+ raise NotImplementedError
25
+
26
+ def _rewrite_expr(self, dirty: DirtyExpression) -> Expression | None:
27
+ raise NotImplementedError
@@ -36,6 +36,7 @@ from .condition_constprop import ConditionConstantPropagation
36
36
  from .determine_load_sizes import DetermineLoadSizes
37
37
  from .eager_std_string_concatenation import EagerStdStringConcatenationPass
38
38
  from .peephole_simplifier import PostStructuringPeepholeOptimizationPass
39
+ from .register_save_area_simplifier_adv import RegisterSaveAreaSimplifierAdvanced
39
40
 
40
41
  if TYPE_CHECKING:
41
42
  from angr.analyses.decompiler.presets import DecompilationPreset
@@ -74,6 +75,7 @@ ALL_OPTIMIZATION_PASSES = [
74
75
  DetermineLoadSizes,
75
76
  EagerStdStringConcatenationPass,
76
77
  PostStructuringPeepholeOptimizationPass,
78
+ RegisterSaveAreaSimplifierAdvanced,
77
79
  ]
78
80
 
79
81
  # these passes may duplicate code to remove gotos or improve the structure of the graph
@@ -138,6 +140,7 @@ __all__ = (
138
140
  "ModSimplifier",
139
141
  "OptimizationPassStage",
140
142
  "RegisterSaveAreaSimplifier",
143
+ "RegisterSaveAreaSimplifierAdvanced",
141
144
  "RetAddrSaveSimplifier",
142
145
  "ReturnDeduplicator",
143
146
  "ReturnDuplicatorHigh",
@@ -21,6 +21,7 @@ from angr.project import Project
21
21
 
22
22
  if TYPE_CHECKING:
23
23
  from angr.knowledge_plugins.functions import Function
24
+ from angr.sim_variable import SimVariable
24
25
  from angr.analyses.decompiler.stack_item import StackItem
25
26
 
26
27
 
@@ -54,13 +55,14 @@ class OptimizationPassStage(Enum):
54
55
  BEFORE_SSA_LEVEL0_TRANSFORMATION = 1
55
56
  AFTER_SINGLE_BLOCK_SIMPLIFICATION = 2
56
57
  BEFORE_SSA_LEVEL1_TRANSFORMATION = 3
57
- AFTER_MAKING_CALLSITES = 4
58
- AFTER_GLOBAL_SIMPLIFICATION = 5
59
- BEFORE_VARIABLE_RECOVERY = 6
60
- AFTER_VARIABLE_RECOVERY = 7
61
- BEFORE_REGION_IDENTIFICATION = 8
62
- DURING_REGION_IDENTIFICATION = 9
63
- AFTER_STRUCTURING = 10
58
+ AFTER_SSA_LEVEL1_TRANSFORMATION = 4
59
+ AFTER_MAKING_CALLSITES = 5
60
+ AFTER_GLOBAL_SIMPLIFICATION = 6
61
+ BEFORE_VARIABLE_RECOVERY = 7
62
+ AFTER_VARIABLE_RECOVERY = 8
63
+ BEFORE_REGION_IDENTIFICATION = 9
64
+ DURING_REGION_IDENTIFICATION = 10
65
+ AFTER_STRUCTURING = 11
64
66
 
65
67
 
66
68
  class BaseOptimizationPass:
@@ -138,7 +140,7 @@ class OptimizationPass(BaseOptimizationPass):
138
140
  refine_loops_with_single_successor: bool = False,
139
141
  complete_successors: bool = False,
140
142
  avoid_vvar_ids: set[int] | None = None,
141
- arg_vvars: set[int] | None = None,
143
+ arg_vvars: dict[int, tuple[ailment.Expr.VirtualVariable, SimVariable]] | None = None,
142
144
  peephole_optimizations=None,
143
145
  stack_pointer_tracker=None,
144
146
  notes: dict | None = None,
@@ -99,18 +99,39 @@ class RegisterSaveAreaSimplifier(OptimizationPass):
99
99
 
100
100
  results = []
101
101
 
102
+ # there are cases where sp is moved to another register at the beginning of the function before it is
103
+ # subtracted, then it is used for stack accesses.
104
+ # for example:
105
+ # 132B0 mov r11, rsp
106
+ # 132B3 sub rsp, 88h
107
+ # 132BA ...
108
+ # 132C1 ...
109
+ # 132C6 mov [r11-18h], r13
110
+ # 132CA mov [r11-20h], r14
111
+ # we support such cases because we will have rewritten r11-18h to SpOffset(-N) when this optimization runs.
112
+
113
+ # identify which registers have been updated in this function; they are no longer saved
114
+ ignored_regs: set[int] = set()
115
+
102
116
  for idx, stmt in enumerate(first_block.statements):
117
+ if (
118
+ isinstance(stmt, ailment.Stmt.Assignment)
119
+ and isinstance(stmt.dst, ailment.Expr.VirtualVariable)
120
+ and stmt.dst.was_reg
121
+ ):
122
+ ignored_regs.add(stmt.dst.reg_offset)
103
123
  if (
104
124
  isinstance(stmt, ailment.Stmt.Store)
105
125
  and isinstance(stmt.addr, ailment.Expr.StackBaseOffset)
106
126
  and isinstance(stmt.addr.offset, int)
107
127
  ):
108
128
  if isinstance(stmt.data, ailment.Expr.VirtualVariable) and stmt.data.was_reg:
109
- # it's storing registers to the stack!
110
- stack_offset = stmt.addr.offset
111
- reg_offset = stmt.data.reg_offset
112
- codeloc = CodeLocation(first_block.addr, idx, block_idx=first_block.idx, ins_addr=stmt.ins_addr)
113
- results.append((reg_offset, stack_offset, codeloc))
129
+ if stmt.data.reg_offset not in ignored_regs:
130
+ # it's storing registers to the stack!
131
+ stack_offset = stmt.addr.offset
132
+ reg_offset = stmt.data.reg_offset
133
+ codeloc = CodeLocation(first_block.addr, idx, block_idx=first_block.idx, ins_addr=stmt.ins_addr)
134
+ results.append((reg_offset, stack_offset, codeloc))
114
135
  elif (
115
136
  self.project.arch.name == "AMD64"
116
137
  and isinstance(stmt.data, ailment.Expr.Convert)
@@ -130,7 +151,24 @@ class RegisterSaveAreaSimplifier(OptimizationPass):
130
151
  def _find_registers_restored_from_stack(self) -> list[list[tuple[int, int, CodeLocation]]]:
131
152
  all_results = []
132
153
  for ret_site in self._func.ret_sites + self._func.jumpout_sites:
133
- for block in self._get_blocks(ret_site.addr):
154
+
155
+ ret_blocks = list(self._get_blocks(ret_site.addr))
156
+ if len(ret_blocks) == 1 and self.project.simos is not None and self.project.simos.name == "Win32":
157
+ # PE files may call __security_check_cookie (which terminates the program if the stack canary is
158
+ # corrupted) before returning.
159
+ preds = list(self._graph.predecessors(ret_blocks[0]))
160
+ if len(preds) == 1:
161
+ pred = preds[0]
162
+ if pred.statements and isinstance(pred.statements[-1], ailment.Stmt.Call):
163
+ last_stmt = pred.statements[-1]
164
+ if isinstance(last_stmt.target, ailment.Expr.Const):
165
+ callee_addr = last_stmt.target.value
166
+ if self.project.kb.functions.contains_addr(callee_addr):
167
+ callee_func = self.project.kb.functions.get_by_addr(callee_addr)
168
+ if callee_func.name == "_security_check_cookie":
169
+ ret_blocks.append(pred)
170
+
171
+ for block in ret_blocks:
134
172
  results = []
135
173
  for idx, stmt in enumerate(block.statements):
136
174
  if (