angr 9.2.143__py3-none-win_amd64.whl → 9.2.145__py3-none-win_amd64.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 (49) hide show
  1. angr/__init__.py +1 -1
  2. angr/analyses/calling_convention/calling_convention.py +13 -1
  3. angr/analyses/calling_convention/fact_collector.py +41 -5
  4. angr/analyses/cfg/cfg_base.py +7 -2
  5. angr/analyses/cfg/cfg_emulated.py +13 -4
  6. angr/analyses/cfg/cfg_fast.py +35 -61
  7. angr/analyses/cfg/indirect_jump_resolvers/__init__.py +2 -0
  8. angr/analyses/cfg/indirect_jump_resolvers/constant_value_manager.py +107 -0
  9. angr/analyses/cfg/indirect_jump_resolvers/default_resolvers.py +2 -1
  10. angr/analyses/cfg/indirect_jump_resolvers/jumptable.py +2 -101
  11. angr/analyses/cfg/indirect_jump_resolvers/syscall_resolver.py +92 -0
  12. angr/analyses/decompiler/ail_simplifier.py +5 -0
  13. angr/analyses/decompiler/clinic.py +163 -69
  14. angr/analyses/decompiler/decompiler.py +4 -4
  15. angr/analyses/decompiler/optimization_passes/base_ptr_save_simplifier.py +1 -1
  16. angr/analyses/decompiler/optimization_passes/optimization_pass.py +5 -5
  17. angr/analyses/decompiler/optimization_passes/return_duplicator_base.py +5 -0
  18. angr/analyses/decompiler/optimization_passes/win_stack_canary_simplifier.py +58 -2
  19. angr/analyses/decompiler/peephole_optimizations/__init__.py +2 -0
  20. angr/analyses/decompiler/peephole_optimizations/a_sub_a_shr_const_shr_const.py +37 -0
  21. angr/analyses/decompiler/ssailification/rewriting_engine.py +2 -0
  22. angr/analyses/decompiler/ssailification/ssailification.py +10 -2
  23. angr/analyses/decompiler/ssailification/traversal_engine.py +17 -2
  24. angr/analyses/decompiler/structured_codegen/c.py +25 -4
  25. angr/analyses/disassembly.py +3 -3
  26. angr/analyses/fcp/fcp.py +1 -4
  27. angr/analyses/s_reaching_definitions/s_reaching_definitions.py +21 -22
  28. angr/analyses/stack_pointer_tracker.py +61 -25
  29. angr/analyses/typehoon/dfa.py +13 -3
  30. angr/analyses/typehoon/typehoon.py +60 -18
  31. angr/analyses/typehoon/typevars.py +11 -7
  32. angr/analyses/variable_recovery/engine_ail.py +13 -17
  33. angr/analyses/variable_recovery/engine_base.py +26 -30
  34. angr/analyses/variable_recovery/variable_recovery_fast.py +17 -21
  35. angr/knowledge_plugins/functions/function.py +29 -15
  36. angr/knowledge_plugins/key_definitions/constants.py +2 -2
  37. angr/knowledge_plugins/key_definitions/liveness.py +4 -4
  38. angr/lib/angr_native.dll +0 -0
  39. angr/state_plugins/unicorn_engine.py +24 -8
  40. angr/storage/memory_mixins/paged_memory/page_backer_mixins.py +1 -2
  41. angr/storage/memory_mixins/paged_memory/pages/mv_list_page.py +2 -2
  42. angr/utils/funcid.py +27 -2
  43. angr/utils/graph.py +26 -20
  44. {angr-9.2.143.dist-info → angr-9.2.145.dist-info}/METADATA +11 -8
  45. {angr-9.2.143.dist-info → angr-9.2.145.dist-info}/RECORD +49 -46
  46. {angr-9.2.143.dist-info → angr-9.2.145.dist-info}/WHEEL +1 -1
  47. {angr-9.2.143.dist-info → angr-9.2.145.dist-info}/LICENSE +0 -0
  48. {angr-9.2.143.dist-info → angr-9.2.145.dist-info}/entry_points.txt +0 -0
  49. {angr-9.2.143.dist-info → angr-9.2.145.dist-info}/top_level.txt +0 -0
@@ -3,7 +3,7 @@ from __future__ import annotations
3
3
  import logging
4
4
  from collections import defaultdict
5
5
  from collections.abc import Iterable
6
- from typing import Optional, Union, Any, TYPE_CHECKING
6
+ from typing import Any, TYPE_CHECKING
7
7
 
8
8
  import networkx
9
9
  from cle import SymbolType
@@ -35,9 +35,9 @@ if TYPE_CHECKING:
35
35
 
36
36
  l = logging.getLogger(name=__name__)
37
37
 
38
- _PEEPHOLE_OPTIMIZATIONS_TYPE = Optional[
39
- Iterable[Union[type["PeepholeOptimizationStmtBase"], type["PeepholeOptimizationExprBase"]]]
40
- ]
38
+ _PEEPHOLE_OPTIMIZATIONS_TYPE = (
39
+ Iterable[type["PeepholeOptimizationStmtBase"] | type["PeepholeOptimizationExprBase"]] | None
40
+ )
41
41
 
42
42
 
43
43
  class Decompiler(Analysis):
@@ -16,7 +16,7 @@ class BasePointerSaveSimplifier(OptimizationPass):
16
16
  """
17
17
 
18
18
  ARCHES = ["X86", "AMD64", "ARMEL", "ARMHF", "ARMCortexM", "MIPS32", "MIPS64"]
19
- PLATFORMS = ["cgc", "linux"]
19
+ PLATFORMS = None
20
20
  STAGE = OptimizationPassStage.AFTER_GLOBAL_SIMPLIFICATION
21
21
  NAME = "Simplify base pointer saving"
22
22
  DESCRIPTION = __doc__.strip()
@@ -443,6 +443,11 @@ class StructuringOptimizationPass(OptimizationPass):
443
443
  """
444
444
  Wrapper for _analyze() that verifies the graph is structurable before and after the optimization.
445
445
  """
446
+ # replace the normal check in OptimizationPass.analyze()
447
+ ret, cache = self._check()
448
+ if not ret:
449
+ return
450
+
446
451
  if not self._graph_is_structurable(self._graph, initial=True):
447
452
  return
448
453
 
@@ -450,11 +455,6 @@ class StructuringOptimizationPass(OptimizationPass):
450
455
  if self._require_gotos and not self._initial_gotos:
451
456
  return
452
457
 
453
- # replace the normal check in OptimizationPass.analyze()
454
- ret, cache = self._check()
455
- if not ret:
456
- return
457
-
458
458
  # setup for the very first analysis
459
459
  self.out_graph = networkx.DiGraph(self._graph)
460
460
  if self._max_opt_iters > 1:
@@ -95,6 +95,7 @@ class ReturnDuplicatorBase:
95
95
  minimize_copies_for_regions: bool = True,
96
96
  ri: RegionIdentifier | None = None,
97
97
  scratch: dict[str, Any] | None = None,
98
+ max_func_blocks: int = 1500,
98
99
  ):
99
100
  self._max_calls_in_region = max_calls_in_regions
100
101
  self._minimize_copies_for_regions = minimize_copies_for_regions
@@ -105,6 +106,7 @@ class ReturnDuplicatorBase:
105
106
  self._func = func
106
107
  self._ri: RegionIdentifier | None = ri
107
108
  self.vvar_id_start = vvar_id_start
109
+ self._max_func_blocks = max_func_blocks
108
110
 
109
111
  def next_node_idx(self) -> int:
110
112
  node_idx = self.scratch.get("returndup_node_idx", 0) + 1
@@ -123,6 +125,9 @@ class ReturnDuplicatorBase:
123
125
  #
124
126
 
125
127
  def _check(self):
128
+ # is this function too large?
129
+ if len(self._func.block_addrs_set) > self._max_func_blocks:
130
+ return False, None
126
131
  # does this function have end points?
127
132
  return bool(self._func.endpoints), None
128
133
 
@@ -110,8 +110,11 @@ class WinStackCanarySimplifier(OptimizationPass):
110
110
  _l.debug("Cannot find the statement calling _security_check_cookie() in the predecessor.")
111
111
  continue
112
112
 
113
- # TODO: Support x86
114
- canary_storing_stmt_idx = self._find_amd64_canary_storing_stmt(pred, store_offset)
113
+ canary_storing_stmt_idx = (
114
+ self._find_amd64_canary_storing_stmt(pred, store_offset)
115
+ if self.project.arch.name == "AMD64"
116
+ else self._find_x86_canary_storing_stmt(pred, store_offset)
117
+ )
115
118
  if canary_storing_stmt_idx is None:
116
119
  _l.debug("Cannot find the canary check statement in the predecessor.")
117
120
  continue
@@ -270,6 +273,59 @@ class WinStackCanarySimplifier(OptimizationPass):
270
273
  return idx
271
274
  return None
272
275
 
276
+ def _find_x86_canary_storing_stmt(self, block, canary_value_stack_offset):
277
+ load_stmt_idx = None
278
+
279
+ for idx, stmt in enumerate(block.statements):
280
+ # when we are lucky, we have one instruction
281
+ if (
282
+ (
283
+ isinstance(stmt, ailment.Stmt.Assignment)
284
+ and isinstance(stmt.dst, ailment.Expr.VirtualVariable)
285
+ and stmt.dst.was_reg
286
+ and stmt.dst.reg_offset == self.project.arch.registers["eax"][0]
287
+ )
288
+ and isinstance(stmt.src, ailment.Expr.BinaryOp)
289
+ and stmt.src.op == "Xor"
290
+ ):
291
+ op0, op1 = stmt.src.operands
292
+ if (
293
+ isinstance(op0, ailment.Expr.Load)
294
+ and isinstance(op0.addr, ailment.Expr.StackBaseOffset)
295
+ and op0.addr.offset == canary_value_stack_offset
296
+ ) and isinstance(op1, ailment.Expr.StackBaseOffset):
297
+ # found it
298
+ return idx
299
+ # or when we are unlucky, we have two instructions...
300
+ if (
301
+ isinstance(stmt, ailment.Stmt.Assignment)
302
+ and isinstance(stmt.dst, ailment.Expr.VirtualVariable)
303
+ and stmt.dst.reg_offset == self.project.arch.registers["eax"][0]
304
+ and isinstance(stmt.src, ailment.Expr.Load)
305
+ and isinstance(stmt.src.addr, ailment.Expr.StackBaseOffset)
306
+ and stmt.src.addr.offset == canary_value_stack_offset
307
+ ):
308
+ load_stmt_idx = idx
309
+ if (
310
+ load_stmt_idx is not None
311
+ and idx >= load_stmt_idx + 1
312
+ and (
313
+ isinstance(stmt, ailment.Stmt.Assignment)
314
+ and isinstance(stmt.dst, ailment.Expr.VirtualVariable)
315
+ and stmt.dst.was_reg
316
+ and isinstance(stmt.src, ailment.Expr.BinaryOp)
317
+ and stmt.src.op == "Xor"
318
+ )
319
+ and (
320
+ isinstance(stmt.src.operands[0], ailment.Expr.VirtualVariable)
321
+ and stmt.src.operands[0].was_reg
322
+ and stmt.src.operands[0].reg_offset == self.project.arch.registers["eax"][0]
323
+ and isinstance(stmt.src.operands[1], ailment.Expr.StackBaseOffset)
324
+ )
325
+ ):
326
+ return idx
327
+ return None
328
+
273
329
  @staticmethod
274
330
  def _find_return_addr_storing_stmt(block):
275
331
  for idx, stmt in enumerate(block.statements):
@@ -5,6 +5,7 @@ from .a_mul_const_div_shr_const import AMulConstDivShrConst
5
5
  from .a_shl_const_sub_a import AShlConstSubA
6
6
  from .a_sub_a_div import ASubADiv
7
7
  from .a_sub_a_div_const_mul_const import ASubADivConstMulConst
8
+ from .a_sub_a_shr_const_shr_const import ASubAShrConstShrConst
8
9
  from .arm_cmpf import ARMCmpF
9
10
  from .bswap import Bswap
10
11
  from .coalesce_same_cascading_ifs import CoalesceSameCascadingIfs
@@ -57,6 +58,7 @@ ALL_PEEPHOLE_OPTS: list[type[PeepholeOptimizationExprBase]] = [
57
58
  AMulConstSubA,
58
59
  ASubADiv,
59
60
  ASubADivConstMulConst,
61
+ ASubAShrConstShrConst,
60
62
  ARMCmpF,
61
63
  Bswap,
62
64
  CoalesceSameCascadingIfs,
@@ -0,0 +1,37 @@
1
+ # pylint:disable=no-self-use,too-many-boolean-expressions
2
+ from __future__ import annotations
3
+ from ailment.expression import BinaryOp, Const
4
+
5
+ from .base import PeepholeOptimizationExprBase
6
+
7
+
8
+ class ASubAShrConstShrConst(PeepholeOptimizationExprBase):
9
+ """
10
+ Convert `cdq; sub eax, edx; sar eax, 1` to `eax /= 2`.
11
+ """
12
+
13
+ __slots__ = ()
14
+
15
+ NAME = "(a - (a >> 31)) >> N => a / 2 ** N (signed)"
16
+ expr_classes = (BinaryOp,)
17
+
18
+ def optimize(self, expr: BinaryOp, **kwargs):
19
+ if (
20
+ expr.op == "Sar"
21
+ and len(expr.operands) == 2
22
+ and isinstance(expr.operands[1], Const)
23
+ and expr.operands[1].is_int
24
+ and isinstance(expr.operands[0], BinaryOp)
25
+ and expr.operands[0].op == "Sub"
26
+ ):
27
+ a0, a1 = expr.operands[0].operands
28
+ if (
29
+ isinstance(a1, BinaryOp)
30
+ and a1.op == "Sar"
31
+ and isinstance(a1.operands[1], Const)
32
+ and a1.operands[1].value == 31
33
+ and a0.likes(a1.operands[0])
34
+ ):
35
+ dividend = 2 ** expr.operands[1].value
36
+ return BinaryOp(a0.idx, "Div", [a0, Const(None, None, dividend, expr.bits)], True, **expr.tags)
37
+ return None
@@ -698,6 +698,8 @@ class SimEngineSSARewriting(
698
698
  raise NotImplementedError("Store expressions are not supported in _replace_use_expr.")
699
699
  if isinstance(thing, Tmp) and self.rewrite_tmps:
700
700
  return self._replace_use_tmp(self.block.addr, self.block.idx, self.stmt_idx, thing)
701
+ if isinstance(thing, Load):
702
+ return self._replace_use_load(thing)
701
703
  return None
702
704
 
703
705
  def _replace_use_reg(self, reg_expr: Register) -> VirtualVariable | Expression:
@@ -5,7 +5,15 @@ from collections import defaultdict
5
5
  from itertools import count
6
6
  from bisect import bisect_left
7
7
 
8
- from ailment.expression import Expression, Register, StackBaseOffset, Tmp, VirtualVariable, VirtualVariableCategory
8
+ from ailment.expression import (
9
+ Expression,
10
+ Register,
11
+ StackBaseOffset,
12
+ Tmp,
13
+ VirtualVariable,
14
+ VirtualVariableCategory,
15
+ Load,
16
+ )
9
17
  from ailment.statement import Statement, Store
10
18
 
11
19
  from angr.knowledge_plugins.functions import Function
@@ -151,7 +159,7 @@ class Ssailification(Analysis): # pylint:disable=abstract-method
151
159
  reg_bits = def_.size * self.project.arch.byte_width
152
160
  udef_to_defs[("reg", def_.reg_offset, reg_bits)].add(def_)
153
161
  udef_to_blockkeys[("reg", def_.reg_offset, reg_bits)].add((loc.block_addr, loc.block_idx))
154
- elif isinstance(def_, Store):
162
+ elif isinstance(def_, (Store, Load)):
155
163
  if isinstance(def_.addr, StackBaseOffset) and isinstance(def_.addr.offset, int):
156
164
  idx_begin = bisect_left(sorted_stackvar_offs, def_.addr.offset)
157
165
  for i in range(idx_begin, len(sorted_stackvar_offs)):
@@ -2,7 +2,7 @@ from __future__ import annotations
2
2
  from collections import OrderedDict
3
3
 
4
4
  from ailment.statement import Call, Store, ConditionalJump
5
- from ailment.expression import Register, BinaryOp, StackBaseOffset, ITE, VEXCCallExpression, Tmp, DirtyExpression
5
+ from ailment.expression import Register, BinaryOp, StackBaseOffset, ITE, VEXCCallExpression, Tmp, DirtyExpression, Load
6
6
 
7
7
  from angr.engines.light import SimEngineLightAIL
8
8
  from angr.project import Project
@@ -133,6 +133,22 @@ class SimEngineSSATraversal(SimEngineLightAIL[TraversalState, None, None, None])
133
133
 
134
134
  self.state.live_registers.add(base_offset)
135
135
 
136
+ def _handle_expr_Load(self, expr: Load):
137
+ self._expr(expr.addr)
138
+ if (
139
+ self.stackvars
140
+ and isinstance(expr.addr, StackBaseOffset)
141
+ and isinstance(expr.addr.offset, int)
142
+ and (expr.addr.offset, expr.size) not in self.state.live_stackvars
143
+ ):
144
+ # we must create this stack variable on the fly; we did not see its creation before it is first used
145
+ codeloc = self._codeloc()
146
+ self.def_to_loc.append((expr, codeloc))
147
+ if codeloc not in self.loc_to_defs:
148
+ self.loc_to_defs[codeloc] = OrderedSet()
149
+ self.loc_to_defs[codeloc].add(expr)
150
+ self.state.live_stackvars.add((expr.addr.offset, expr.size))
151
+
136
152
  def _handle_expr_Tmp(self, expr: Tmp):
137
153
  if self.use_tmps:
138
154
  codeloc = self._codeloc()
@@ -251,7 +267,6 @@ class SimEngineSSATraversal(SimEngineLightAIL[TraversalState, None, None, None])
251
267
 
252
268
  _handle_expr_VirtualVariable = _handle_Dummy
253
269
  _handle_expr_Phi = _handle_Dummy
254
- _handle_expr_Load = _handle_Dummy
255
270
  _handle_expr_Const = _handle_Dummy
256
271
  _handle_expr_MultiStatementExpression = _handle_Dummy
257
272
  _handle_expr_StackBaseOffset = _handle_Dummy
@@ -3426,8 +3426,13 @@ class CStructuredCodeGenerator(BaseStructuredCodeGenerator, Analysis):
3426
3426
  return old_ty
3427
3427
 
3428
3428
  if expr.variable is not None:
3429
- cvar = self._variable(expr.variable, expr.size)
3430
- offset = expr.variable_offset or 0
3429
+ if "struct_member_info" in expr.tags:
3430
+ offset, var, _ = expr.struct_member_info
3431
+ cvar = self._variable(var, var.size)
3432
+ else:
3433
+ cvar = self._variable(expr.variable, expr.size)
3434
+ offset = expr.variable_offset or 0
3435
+
3431
3436
  assert type(offset) is int # I refuse to deal with the alternative
3432
3437
  return self._access_constant_offset(CUnaryOp("Reference", cvar, codegen=self), offset, ty, False, negotiate)
3433
3438
 
@@ -3649,8 +3654,24 @@ class CStructuredCodeGenerator(BaseStructuredCodeGenerator, Analysis):
3649
3654
  return CMultiStatementExpression(cstmts, cexpr, tags=expr.tags, codegen=self)
3650
3655
 
3651
3656
  def _handle_VirtualVariable(self, expr: Expr.VirtualVariable, **kwargs):
3652
- if expr.variable:
3653
- cvar = self._variable(expr.variable, None, vvar_id=expr.varid)
3657
+ def negotiate(old_ty: SimType, proposed_ty: SimType) -> SimType:
3658
+ # we do not allow returning a struct for a primitive type
3659
+ if old_ty.size == proposed_ty.size and (
3660
+ not isinstance(proposed_ty, SimStruct) or isinstance(old_ty, SimStruct)
3661
+ ):
3662
+ return proposed_ty
3663
+ return old_ty
3664
+
3665
+ if expr.variable is not None:
3666
+ if "struct_member_info" in expr.tags:
3667
+ offset, var, _ = expr.struct_member_info
3668
+ cbasevar = self._variable(var, expr.size)
3669
+ cvar = self._access_constant_offset(
3670
+ self._get_variable_reference(cbasevar), offset, cbasevar.type, False, negotiate
3671
+ )
3672
+ else:
3673
+ cvar = self._variable(expr.variable, None, vvar_id=expr.varid)
3674
+
3654
3675
  if expr.variable.size != expr.size:
3655
3676
  l.warning(
3656
3677
  "VirtualVariable size (%d) and variable size (%d) do not match. Force a type cast.",
@@ -4,7 +4,7 @@ import contextlib
4
4
  import logging
5
5
  from collections import defaultdict
6
6
  from collections.abc import Sequence
7
- from typing import Union, Any
7
+ from typing import Any
8
8
 
9
9
  import pyvex
10
10
  import archinfo
@@ -24,8 +24,8 @@ try:
24
24
  from angr.engines import pcode
25
25
  import pypcode
26
26
 
27
- IRSBType = Union[pyvex.IRSB, pcode.lifter.IRSB]
28
- IROpObjType = Union[pyvex.stmt.IRStmt, pypcode.PcodeOp]
27
+ IRSBType = pyvex.IRSB | pcode.lifter.IRSB
28
+ IROpObjType = pyvex.stmt.IRStmt | pypcode.PcodeOp
29
29
  except ImportError:
30
30
  pcode = None
31
31
  IRSBType = pyvex.IRSB
angr/analyses/fcp/fcp.py CHANGED
@@ -407,10 +407,7 @@ class FastConstantPropagation(Analysis):
407
407
  except (TypeError, ValueError):
408
408
  arg_locs = None
409
409
 
410
- if None in arg_locs:
411
- arg_locs = None
412
-
413
- if arg_locs is not None:
410
+ if arg_locs is not None and None not in arg_locs:
414
411
  for arg_loc in arg_locs:
415
412
  for loc in arg_loc.get_footprint():
416
413
  if isinstance(loc, SimStackArg):
@@ -131,28 +131,27 @@ class SReachingDefinitionsAnalysis(Analysis):
131
131
  stmt if isinstance(stmt, Call) else stmt.src if isinstance(stmt, Assignment) else stmt.ret_exprs[0]
132
132
  )
133
133
  assert isinstance(call, Call)
134
- if call.prototype is None:
135
- # without knowing the prototype, we must conservatively add uses to all registers that are
136
- # potentially used here
137
- if call.calling_convention is not None:
138
- cc = call.calling_convention
139
- else:
140
- # just use all registers in the default calling convention because we don't know anything about
141
- # the calling convention yet
142
- cc_cls = default_cc(self.project.arch.name)
143
- assert cc_cls is not None
144
- cc = cc_cls(self.project.arch)
145
-
146
- codeloc = CodeLocation(block_addr, stmt_idx, block_idx=block_idx, ins_addr=stmt.ins_addr)
147
- arg_locs = list(cc.ARG_REGS)
148
- if cc.FP_ARG_REGS:
149
- arg_locs += [r_name for r_name in cc.FP_ARG_REGS if r_name not in arg_locs]
150
-
151
- for arg_reg_name in arg_locs:
152
- reg_offset = self.project.arch.registers[arg_reg_name][0]
153
- if reg_offset in reg_to_vvarids:
154
- vvarid = reg_to_vvarids[reg_offset]
155
- self.model.add_vvar_use(vvarid, None, codeloc)
134
+
135
+ # conservatively add uses to all registers that are potentially used here
136
+ if call.calling_convention is not None:
137
+ cc = call.calling_convention
138
+ else:
139
+ # just use all registers in the default calling convention because we don't know anything about
140
+ # the calling convention yet
141
+ cc_cls = default_cc(self.project.arch.name)
142
+ assert cc_cls is not None
143
+ cc = cc_cls(self.project.arch)
144
+
145
+ codeloc = CodeLocation(block_addr, stmt_idx, block_idx=block_idx, ins_addr=stmt.ins_addr)
146
+ arg_locs = list(cc.ARG_REGS)
147
+ if cc.FP_ARG_REGS:
148
+ arg_locs += [r_name for r_name in cc.FP_ARG_REGS if r_name not in arg_locs]
149
+
150
+ for arg_reg_name in arg_locs:
151
+ reg_offset = self.project.arch.registers[arg_reg_name][0]
152
+ if reg_offset in reg_to_vvarids:
153
+ vvarid = reg_to_vvarids[reg_offset]
154
+ self.model.add_vvar_use(vvarid, None, codeloc)
156
155
 
157
156
  if self._track_tmps:
158
157
  # track tmps
@@ -15,6 +15,7 @@ from angr.analyses import AnalysesHub
15
15
  from angr.knowledge_plugins import Function
16
16
  from angr.block import BlockNode
17
17
  from angr.errors import SimTranslationError
18
+ from angr.calling_conventions import SimStackArg
18
19
  from .analysis import Analysis
19
20
 
20
21
  try:
@@ -554,7 +555,7 @@ class StackPointerTracker(Analysis, ForwardAnalysis):
554
555
  if vex_block is not None:
555
556
  if isinstance(vex_block, pyvex.IRSB):
556
557
  curr_stmt_start_addr = self._process_vex_irsb(node, vex_block, state)
557
- elif pypcode is not None and isinstance(vex_block, pcode.lifter.IRSB):
558
+ elif pypcode is not None and isinstance(vex_block, pcode.lifter.IRSB): # type: ignore
558
559
  curr_stmt_start_addr = self._process_pcode_irsb(node, vex_block, state)
559
560
  else:
560
561
  raise NotImplementedError(f"Unsupported block type {type(vex_block)}")
@@ -587,7 +588,7 @@ class StackPointerTracker(Analysis, ForwardAnalysis):
587
588
  raise CouldNotResolveException
588
589
  if arg1_expr is BOTTOM:
589
590
  return BOTTOM
590
- return arg0_expr + arg1_expr
591
+ return arg0_expr + arg1_expr # type: ignore
591
592
  if expr.op.startswith("Iop_Sub"):
592
593
  arg0_expr = _resolve_expr(arg0)
593
594
  if arg0_expr is None:
@@ -599,7 +600,7 @@ class StackPointerTracker(Analysis, ForwardAnalysis):
599
600
  raise CouldNotResolveException
600
601
  if arg1_expr is BOTTOM:
601
602
  return BOTTOM
602
- return arg0_expr - arg1_expr
603
+ return arg0_expr - arg1_expr # type: ignore
603
604
  if expr.op.startswith("Iop_And"):
604
605
  # handle stack pointer alignments
605
606
  arg0_expr = _resolve_expr(arg0)
@@ -713,43 +714,78 @@ class StackPointerTracker(Analysis, ForwardAnalysis):
713
714
  pass
714
715
  # who are we calling?
715
716
  callees = [] if self._func is None else self._find_callees(node)
717
+ sp_adjusted = False
716
718
  if callees:
717
719
  if len(callees) == 1:
720
+
718
721
  callee = callees[0]
719
- track_rax = False
720
- if (
721
- (callee.info.get("is_rust_probestack", False) and self.project.arch.name == "AMD64")
722
- or (callee.info.get("is_alloca_probe", False) and self.project.arch.name == "AMD64")
723
- or callee.name == "__chkstk"
724
- ):
725
- # sp = sp - rax right after returning from the call
726
- track_rax = True
727
-
728
- if track_rax:
729
- for stmt in reversed(vex_block.statements):
730
- if (
731
- isinstance(stmt, pyvex.IRStmt.Put)
732
- and stmt.offset == self.project.arch.registers["rax"][0]
733
- and isinstance(stmt.data, pyvex.IRExpr.Const)
734
- ):
735
- state.put(stmt.offset, Constant(stmt.data.con.value), force=True)
736
- break
722
+ if callee.info.get("is_rust_probestack", False):
723
+ # sp = sp - rax/eax right after returning from the call
724
+ rust_probe_stack_rax_regname: str | None = None
725
+ if self.project.arch.name == "AMD64":
726
+ rust_probe_stack_rax_regname = "rax"
727
+ elif self.project.arch.name == "X86":
728
+ rust_probe_stack_rax_regname = "eax"
729
+
730
+ if rust_probe_stack_rax_regname is not None:
731
+ for stmt in reversed(vex_block.statements):
732
+ if (
733
+ isinstance(stmt, pyvex.IRStmt.Put)
734
+ and stmt.offset == self.project.arch.registers[rust_probe_stack_rax_regname][0]
735
+ and isinstance(stmt.data, pyvex.IRExpr.Const)
736
+ ):
737
+ sp_adjusted = True
738
+ state.put(stmt.offset, Constant(stmt.data.con.value), force=True)
739
+ break
740
+
741
+ if not sp_adjusted and (callee.info.get("is_alloca_probe", False) or callee.name == "__chkstk"):
742
+ # sp = sp - rax, but it's adjusted within the callee
743
+ chkstk_stack_rax_regname: str | None = None
744
+ if self.project.arch.name == "AMD64":
745
+ chkstk_stack_rax_regname = "rax"
746
+ elif self.project.arch.name == "X86":
747
+ chkstk_stack_rax_regname = "eax"
748
+
749
+ if chkstk_stack_rax_regname is not None:
750
+ for stmt in reversed(vex_block.statements):
751
+ if (
752
+ isinstance(stmt, pyvex.IRStmt.Put)
753
+ and stmt.offset == self.project.arch.registers[chkstk_stack_rax_regname][0]
754
+ and isinstance(stmt.data, pyvex.IRExpr.Const)
755
+ and self.project.arch.sp_offset in state.regs
756
+ ):
757
+ sp_adjusted = True
758
+ sp_v = state.regs[self.project.arch.sp_offset]
759
+ sp_v -= Constant(stmt.data.con.value)
760
+ state.put(self.project.arch.sp_offset, sp_v, force=True)
761
+ break
737
762
 
738
763
  callee_cleanups = [
739
764
  callee
740
765
  for callee in callees
741
- if callee.calling_convention is not None and callee.calling_convention.CALLEE_CLEANUP
766
+ if callee.calling_convention is not None
767
+ and callee.calling_convention.CALLEE_CLEANUP
768
+ and callee.prototype is not None
742
769
  ]
743
770
  if callee_cleanups:
744
771
  # found callee clean-up cases...
772
+ callee = callee_cleanups[0]
773
+ assert callee.calling_convention is not None # just to make pyright happy
745
774
  try:
746
775
  v = state.get(self.project.arch.sp_offset)
747
776
  incremented = None
748
777
  if v is BOTTOM:
749
778
  incremented = BOTTOM
750
- elif callee_cleanups[0].prototype is not None:
751
- num_args = len(callee_cleanups[0].prototype.args)
752
- incremented = v + Constant(self.project.arch.bytes * num_args)
779
+ elif callee.prototype is not None:
780
+ num_stack_args = len(
781
+ [
782
+ arg_loc
783
+ for arg_loc in callee.calling_convention.arg_locs(callee.prototype)
784
+ if isinstance(arg_loc, SimStackArg)
785
+ ]
786
+ )
787
+ if num_stack_args > 0:
788
+ incremented = v + Constant(self.project.arch.bytes * num_stack_args)
753
789
  if incremented is not None:
754
790
  state.put(self.project.arch.sp_offset, incremented)
755
791
  except CouldNotResolveException:
@@ -1,21 +1,22 @@
1
+ # pylint:disable=import-outside-toplevel
1
2
  from __future__ import annotations
2
3
  from typing import TYPE_CHECKING
3
4
 
4
5
  import networkx
5
6
 
6
7
  # FIXME: Remove the dependency on pyformlang
7
- from pyformlang.finite_automaton import Epsilon, EpsilonNFA, State, Symbol
8
8
 
9
9
  from angr.errors import AngrError
10
10
  from .typevars import BaseLabel, Subtype
11
11
  from .variance import Variance
12
12
 
13
13
  if TYPE_CHECKING:
14
+ from pyformlang.finite_automaton import EpsilonNFA
14
15
  from pyformlang.finite_automaton import DeterministicFiniteAutomaton
15
16
 
16
17
 
17
- START_STATE = State("START")
18
- END_STATE = State("END")
18
+ START_STATE = None
19
+ END_STATE = None
19
20
 
20
21
 
21
22
  class EmptyEpsilonNFAError(AngrError):
@@ -31,6 +32,15 @@ class DFAConstraintSolver:
31
32
 
32
33
  @staticmethod
33
34
  def graph_to_epsilon_nfa(graph: networkx.DiGraph, starts: set, ends: set) -> EpsilonNFA:
35
+ from pyformlang.finite_automaton import Epsilon, EpsilonNFA, State, Symbol # delayed import
36
+
37
+ global START_STATE, END_STATE # pylint:disable=global-statement
38
+
39
+ if START_STATE is None:
40
+ START_STATE = State("START")
41
+ if END_STATE is None:
42
+ END_STATE = State("END")
43
+
34
44
  enfa = EpsilonNFA()
35
45
 
36
46
  # print("Converting graph to eNFA")