angr 9.2.176__cp310-abi3-win_amd64.whl → 9.2.178__cp310-abi3-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 (42) hide show
  1. angr/__init__.py +1 -1
  2. angr/analyses/cfg/cfg_fast.py +15 -0
  3. angr/analyses/decompiler/ail_simplifier.py +69 -1
  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/cas_intrinsics.py +69 -12
  15. angr/analyses/decompiler/peephole_optimizations/inlined_wcscpy_consolidation.py +189 -6
  16. angr/analyses/decompiler/peephole_optimizations/remove_redundant_shifts_around_comparators.py +72 -1
  17. angr/analyses/decompiler/presets/basic.py +2 -0
  18. angr/analyses/decompiler/presets/fast.py +2 -0
  19. angr/analyses/decompiler/presets/full.py +2 -0
  20. angr/analyses/decompiler/utils.py +10 -3
  21. angr/analyses/flirt/flirt.py +5 -4
  22. angr/analyses/s_propagator.py +23 -21
  23. angr/analyses/smc.py +2 -3
  24. angr/analyses/variable_recovery/engine_ail.py +39 -0
  25. angr/emulator.py +2 -1
  26. angr/engines/hook.py +1 -1
  27. angr/engines/icicle.py +19 -3
  28. angr/knowledge_plugins/functions/function.py +2 -2
  29. angr/knowledge_plugins/labels.py +4 -4
  30. angr/procedures/definitions/__init__.py +9 -0
  31. angr/procedures/definitions/parse_win32json.py +11 -0
  32. angr/procedures/definitions/wdk/ntoskrnl.json +4 -0
  33. angr/rustylib.pyd +0 -0
  34. angr/unicornlib.dll +0 -0
  35. angr/utils/funcid.py +85 -0
  36. angr/utils/ssa/__init__.py +2 -6
  37. {angr-9.2.176.dist-info → angr-9.2.178.dist-info}/METADATA +6 -5
  38. {angr-9.2.176.dist-info → angr-9.2.178.dist-info}/RECORD +42 -38
  39. {angr-9.2.176.dist-info → angr-9.2.178.dist-info}/WHEEL +0 -0
  40. {angr-9.2.176.dist-info → angr-9.2.178.dist-info}/entry_points.txt +0 -0
  41. {angr-9.2.176.dist-info → angr-9.2.178.dist-info}/licenses/LICENSE +0 -0
  42. {angr-9.2.176.dist-info → angr-9.2.178.dist-info}/top_level.txt +0 -0
@@ -1,13 +1,47 @@
1
1
  # pylint:disable=arguments-differ
2
2
  from __future__ import annotations
3
+ from typing import TYPE_CHECKING
3
4
 
4
5
  from angr.ailment.expression import Expression, BinaryOp, Const, Register, StackBaseOffset, UnaryOp, VirtualVariable
5
- from angr.ailment.statement import Call, Store
6
+ from angr.ailment.statement import Call, Store, Assignment
6
7
 
7
8
  from angr.sim_type import SimTypePointer, SimTypeWideChar
8
9
  from .base import PeepholeOptimizationMultiStmtBase
9
10
  from .inlined_wcscpy import InlinedWcscpy
10
11
 
12
+ if TYPE_CHECKING:
13
+ from angr.ailment.statement import Statement
14
+
15
+
16
+ def match_statements(stmts: list[Statement], index: int) -> int:
17
+ ending = index
18
+ has_wcsncpy = False
19
+ for i in range(index, len(stmts)):
20
+ stmt = stmts[i]
21
+ if isinstance(stmt, Call):
22
+ if InlinedWcscpy.is_inlined_wcsncpy(stmt):
23
+ has_wcsncpy = True
24
+ else:
25
+ break
26
+ elif isinstance(stmt, Store):
27
+ if not isinstance(stmt.data, Const):
28
+ break
29
+ _, off = InlinedWcscpyConsolidation._parse_addr(stmt.addr)
30
+ if off is None:
31
+ # unsupported offset - bail
32
+ break
33
+ elif (
34
+ isinstance(stmt, Assignment)
35
+ and isinstance(stmt.dst, VirtualVariable)
36
+ and stmt.dst.was_stack
37
+ and isinstance(stmt.src, Const)
38
+ ):
39
+ pass
40
+ else:
41
+ break
42
+ ending = i + 1
43
+ return ending - index if has_wcsncpy and ending - index >= 2 else 0
44
+
11
45
 
12
46
  class InlinedWcscpyConsolidation(PeepholeOptimizationMultiStmtBase):
13
47
  """
@@ -17,12 +51,141 @@ class InlinedWcscpyConsolidation(PeepholeOptimizationMultiStmtBase):
17
51
  __slots__ = ()
18
52
 
19
53
  NAME = "Consolidate multiple inlined wcsncpy calls"
20
- stmt_classes = ((Call, Call), (Call, Store))
54
+ stmt_classes = (match_statements,)
21
55
 
22
56
  def optimize( # type:ignore
23
- self, stmts: list[Call], stmt_idx: int | None = None, block=None, **kwargs
57
+ self, stmts: list[Call | Store | Assignment], stmt_idx: int | None = None, block=None, **kwargs
24
58
  ): # pylint:disable=unused-argument
25
- last_stmt, stmt = stmts
59
+ reordered_stmts = self._reorder_stmts(stmts)
60
+ if not reordered_stmts or len(reordered_stmts) <= 1:
61
+ return None
62
+
63
+ new_stmts = []
64
+ optimized = False
65
+ stop = False
66
+ while not stop:
67
+ new_stmts = []
68
+ stop = True
69
+ for i, stmt0 in enumerate(reordered_stmts):
70
+ if i == len(reordered_stmts) - 1:
71
+ new_stmts.append(reordered_stmts[i])
72
+ break
73
+ stmt1 = reordered_stmts[i + 1]
74
+ opt_stmts = self._optimize_pair(stmt0, stmt1)
75
+ if opt_stmts is None:
76
+ new_stmts.append(stmt0)
77
+ else:
78
+ new_stmts += opt_stmts
79
+ # start again from the beginning
80
+ optimized = True
81
+ stop = False
82
+ reordered_stmts = new_stmts + reordered_stmts[i + 2 :]
83
+ break
84
+
85
+ return new_stmts if optimized and new_stmts else None
86
+
87
+ def _reorder_stmts(self, stmts: list[Call | Store | Assignment]) -> list[Call | Store] | None:
88
+ """
89
+ Order a list of statements based on ascending addresses of their destination buffers.
90
+ """
91
+
92
+ if not all(
93
+ (
94
+ InlinedWcscpy.is_inlined_wcsncpy(s)
95
+ or (isinstance(s, Store) and isinstance(s.data, Const))
96
+ or (
97
+ isinstance(s, Assignment)
98
+ and isinstance(s.dst, VirtualVariable)
99
+ and s.dst.was_stack
100
+ and isinstance(s.src, Const)
101
+ )
102
+ )
103
+ for s in stmts
104
+ ):
105
+ return None
106
+ offset_to_stmt = {}
107
+ updated_offsets: set[int] = set()
108
+ known_base = None
109
+ for stmt in stmts:
110
+ if isinstance(stmt, Call):
111
+ assert (
112
+ stmt.args is not None
113
+ and len(stmt.args) == 3
114
+ and stmt.args[0] is not None
115
+ and stmt.args[2] is not None
116
+ )
117
+ base, off = self._parse_addr(stmt.args[0])
118
+ store_size = stmt.args[2].value * 2 if isinstance(stmt.args[2], Const) else None
119
+ elif isinstance(stmt, Store):
120
+ base, off = self._parse_addr(stmt.addr)
121
+ store_size = stmt.size
122
+ elif isinstance(stmt, Assignment):
123
+ base, off = self._parse_addr(stmt.dst)
124
+ store_size = stmt.dst.size
125
+ else:
126
+ # unexpected!
127
+ return None
128
+ if off is None or store_size is None:
129
+ # bad offset or size - bail
130
+ return None
131
+ if known_base is None:
132
+ known_base = base
133
+ elif not base.likes(known_base):
134
+ # bail
135
+ return None
136
+ if off in offset_to_stmt:
137
+ # duplicate offset - bail
138
+ return None
139
+ assert isinstance(store_size, int)
140
+ for i in range(store_size):
141
+ if off + i in updated_offsets:
142
+ # overlapping store - bail
143
+ return None
144
+ updated_offsets.add(off + i)
145
+
146
+ offset_to_stmt[off] = stmt
147
+
148
+ return [offset_to_stmt[k] for k in sorted(offset_to_stmt)]
149
+
150
+ def _optimize_pair(
151
+ self, last_stmt: Call | Store | Assignment, stmt: Call | Store | Assignment
152
+ ) -> list[Call] | None:
153
+ # convert (store, wcsncpy()) to (wcsncpy(), store) if they do not overlap
154
+ if (
155
+ isinstance(stmt, Call)
156
+ and InlinedWcscpy.is_inlined_wcsncpy(stmt)
157
+ and stmt.args is not None
158
+ and len(stmt.args) == 3
159
+ and isinstance(stmt.args[2], Const)
160
+ and isinstance(stmt.args[2].value, int)
161
+ and isinstance(last_stmt, (Store, Assignment))
162
+ ):
163
+ if isinstance(last_stmt, Store) and isinstance(last_stmt.data, Const):
164
+ store_addr = last_stmt.addr
165
+ store_size = last_stmt.size
166
+ elif isinstance(last_stmt, Assignment):
167
+ store_addr = last_stmt.dst
168
+ store_size = last_stmt.dst.size
169
+ else:
170
+ return None
171
+ # check if they overlap
172
+ wcsncpy_addr = stmt.args[0]
173
+ wcsncpy_size = stmt.args[2].value * 2
174
+ delta = self._get_delta(store_addr, wcsncpy_addr)
175
+ if delta is not None:
176
+ if (0 <= delta <= store_size) or (delta < 0 and -delta <= wcsncpy_size):
177
+ # they overlap, do not switch
178
+ pass
179
+ else:
180
+ last_stmt, stmt = stmt, last_stmt
181
+
182
+ # swap two statements if they are out of order
183
+ if InlinedWcscpy.is_inlined_wcsncpy(last_stmt) and InlinedWcscpy.is_inlined_wcsncpy(stmt):
184
+ assert last_stmt.args is not None and stmt.args is not None
185
+ delta = self._get_delta(last_stmt.args[0], stmt.args[0])
186
+ if delta is not None and delta < 0:
187
+ last_stmt, stmt = stmt, last_stmt
188
+
26
189
  if InlinedWcscpy.is_inlined_wcsncpy(last_stmt):
27
190
  assert last_stmt.args is not None
28
191
  assert self.kb is not None
@@ -55,6 +218,22 @@ class InlinedWcscpyConsolidation(PeepholeOptimizationMultiStmtBase):
55
218
  )
56
219
  if r and s is not None:
57
220
  new_str = s_last + s
221
+ elif (
222
+ isinstance(stmt, Assignment)
223
+ and isinstance(stmt.dst, VirtualVariable)
224
+ and isinstance(stmt.src, Const)
225
+ and isinstance(stmt.src.value, int)
226
+ ):
227
+ # consolidating a call and an assignment, in case the assignment statement is storing the suffix of a
228
+ # string (but the suffix is too short to qualify an inlined strcpy optimization)
229
+ addr_curr = stmt.dst
230
+ delta = self._get_delta(addr_last, addr_curr)
231
+ if delta is not None and delta == len(s_last):
232
+ r, s = InlinedWcscpy.is_integer_likely_a_wide_string(
233
+ stmt.src.value, stmt.dst.size, self.project.arch.memory_endness, min_length=1 # type:ignore
234
+ )
235
+ if r and s is not None:
236
+ new_str = s_last + s
58
237
 
59
238
  if new_str is not None:
60
239
  assert self.project is not None
@@ -83,17 +262,21 @@ class InlinedWcscpyConsolidation(PeepholeOptimizationMultiStmtBase):
83
262
 
84
263
  @staticmethod
85
264
  def _parse_addr(addr: Expression) -> tuple[Expression, int]:
265
+ # we force the base to 64-bit because it does not really matter when we use it
266
+
267
+ if isinstance(addr, VirtualVariable) and addr.was_stack:
268
+ return StackBaseOffset(None, 64, 0), addr.stack_offset
86
269
  if isinstance(addr, Register):
87
270
  return addr, 0
88
271
  if isinstance(addr, StackBaseOffset):
89
- return StackBaseOffset(None, addr.bits, 0), addr.offset
272
+ return StackBaseOffset(None, 64, 0), addr.offset
90
273
  if (
91
274
  isinstance(addr, UnaryOp)
92
275
  and addr.op == "Reference"
93
276
  and isinstance(addr.operand, VirtualVariable)
94
277
  and addr.operand.was_stack
95
278
  ):
96
- return StackBaseOffset(None, addr.bits, 0), addr.operand.stack_offset
279
+ return StackBaseOffset(None, 64, 0), addr.operand.stack_offset
97
280
  if isinstance(addr, BinaryOp):
98
281
  if addr.op == "Add" and isinstance(addr.operands[1], Const) and isinstance(addr.operands[1].value, int):
99
282
  base_0, offset_0 = InlinedWcscpyConsolidation._parse_addr(addr.operands[0])
@@ -16,7 +16,9 @@ class RemoveRedundantShiftsAroundComparators(PeepholeOptimizationExprBase):
16
16
  NAME = "Remove redundant bitshifts for operands around a comparator"
17
17
  expr_classes = (BinaryOp,) # all expressions are allowed
18
18
 
19
- def optimize(self, expr: BinaryOp, **kwargs):
19
+ def optimize(
20
+ self, expr: BinaryOp, stmt_idx: int | None = None, block=None, **kwargs
21
+ ): # pylint:disable=unused-argument
20
22
  # (expr_0 << N) < (expr_1 << N) ==> expr_0 << expr_1
21
23
  # FIXME: This optimization is unsafe but seems to work for all existing case
22
24
  if expr.op in {"CmpLE", "CmpLT", "CmpEQ", "CmpNE", "CmpGE", "CmpGT"}:
@@ -41,4 +43,73 @@ class RemoveRedundantShiftsAroundComparators(PeepholeOptimizationExprBase):
41
43
  **expr.tags,
42
44
  )
43
45
 
46
+ # might have been rewritten to multiplications
47
+ if (
48
+ isinstance(op0, BinaryOp)
49
+ and op0.op == "Mul"
50
+ and isinstance(op0.operands[1], Const)
51
+ and op0.operands[1].is_int
52
+ ):
53
+ op0_op = op0.operands[0]
54
+ mul_0 = op0.operands[1].value_int
55
+ mul_1 = None
56
+ op1_op = None
57
+ if (
58
+ isinstance(op1, BinaryOp)
59
+ and op1.op == "Mul"
60
+ and isinstance(op1.operands[1], Const)
61
+ and op1.operands[1].is_int
62
+ ):
63
+ op1_op = op1.operands[0]
64
+ mul_1 = op1.operands[1].value_int
65
+ elif isinstance(op1, Const):
66
+ op1_op = None
67
+ mul_1 = op1.value_int
68
+
69
+ if mul_1 is not None:
70
+ common_shift_amount = self._get_common_shift_amount(mul_0, mul_1)
71
+ if common_shift_amount > 0:
72
+ new_mul_0 = Const(None, None, mul_0 >> common_shift_amount, expr.bits)
73
+ new_mul_1 = Const(None, None, mul_1 >> common_shift_amount, expr.bits)
74
+ new_cmp_0 = BinaryOp(op0.idx, "Mul", [op0_op, new_mul_0], op0.signed, bits=op0.bits, **op0.tags)
75
+ new_cmp_1 = (
76
+ BinaryOp(op1.idx, "Mul", [op1_op, new_mul_1], op1.signed, bits=op1.bits, **op1.tags)
77
+ if op1_op is not None
78
+ else new_mul_1
79
+ )
80
+ return BinaryOp(
81
+ expr.idx,
82
+ expr.op,
83
+ [new_cmp_0, new_cmp_1],
84
+ expr.signed,
85
+ bits=expr.bits,
86
+ floating_point=expr.floating_point,
87
+ rounding_mode=expr.rounding_mode,
88
+ **expr.tags,
89
+ )
90
+
44
91
  return None
92
+
93
+ @staticmethod
94
+ def _get_common_shift_amount(v0: int, v1: int) -> int:
95
+ if v0 == 0 or v1 == 0:
96
+ return 0
97
+ shift_amount = 0
98
+ while (v0 & 1) == 0 and (v1 & 1) == 0:
99
+ if v0 & 0xFFFF == 0 and v1 & 0xFFFF == 0:
100
+ v0 >>= 16
101
+ v1 >>= 16
102
+ shift_amount += 16
103
+ elif v0 & 0xFF == 0 and v1 & 0xFF == 0:
104
+ v0 >>= 8
105
+ v1 >>= 8
106
+ shift_amount += 8
107
+ elif v0 & 0xF == 0 and v1 & 0xF == 0:
108
+ v0 >>= 4
109
+ v1 >>= 4
110
+ shift_amount += 4
111
+ else:
112
+ v0 >>= 1
113
+ v1 >>= 1
114
+ shift_amount += 1
115
+ return shift_amount
@@ -7,6 +7,7 @@ from angr.analyses.decompiler.optimization_passes import (
7
7
  BasePointerSaveSimplifier,
8
8
  ConstantDereferencesSimplifier,
9
9
  RetAddrSaveSimplifier,
10
+ RegisterSaveAreaSimplifierAdvanced,
10
11
  X86GccGetPcSimplifier,
11
12
  CallStatementRewriter,
12
13
  SwitchReusedEntryRewriter,
@@ -23,6 +24,7 @@ preset_basic = DecompilationPreset(
23
24
  BasePointerSaveSimplifier,
24
25
  ConstantDereferencesSimplifier,
25
26
  RetAddrSaveSimplifier,
27
+ RegisterSaveAreaSimplifierAdvanced,
26
28
  X86GccGetPcSimplifier,
27
29
  CallStatementRewriter,
28
30
  SwitchReusedEntryRewriter,
@@ -22,6 +22,7 @@ from angr.analyses.decompiler.optimization_passes import (
22
22
  DeadblockRemover,
23
23
  SwitchReusedEntryRewriter,
24
24
  ConditionConstantPropagation,
25
+ RegisterSaveAreaSimplifierAdvanced,
25
26
  DetermineLoadSizes,
26
27
  PostStructuringPeepholeOptimizationPass,
27
28
  )
@@ -33,6 +34,7 @@ preset_fast = DecompilationPreset(
33
34
  RegisterSaveAreaSimplifier,
34
35
  StackCanarySimplifier,
35
36
  WinStackCanarySimplifier,
37
+ RegisterSaveAreaSimplifierAdvanced,
36
38
  BasePointerSaveSimplifier,
37
39
  ConstantDereferencesSimplifier,
38
40
  RetAddrSaveSimplifier,
@@ -4,6 +4,7 @@ from angr.analyses.decompiler.optimization_passes import (
4
4
  RegisterSaveAreaSimplifier,
5
5
  StackCanarySimplifier,
6
6
  WinStackCanarySimplifier,
7
+ RegisterSaveAreaSimplifierAdvanced,
7
8
  BasePointerSaveSimplifier,
8
9
  DivSimplifier,
9
10
  ModSimplifier,
@@ -38,6 +39,7 @@ preset_full = DecompilationPreset(
38
39
  RegisterSaveAreaSimplifier,
39
40
  StackCanarySimplifier,
40
41
  WinStackCanarySimplifier,
42
+ RegisterSaveAreaSimplifierAdvanced,
41
43
  BasePointerSaveSimplifier,
42
44
  DivSimplifier,
43
45
  ModSimplifier,
@@ -2,6 +2,7 @@
2
2
  from __future__ import annotations
3
3
  import pathlib
4
4
  import copy
5
+ from types import FunctionType
5
6
  from typing import Any
6
7
  from collections.abc import Iterable
7
8
  import logging
@@ -958,9 +959,15 @@ def peephole_optimize_multistmts(block, stmt_opts):
958
959
  for opt in stmt_opts:
959
960
  matched = False
960
961
  stmt_seq_len = None
961
- for stmt_class_seq in opt.stmt_classes:
962
- if match_stmt_classes(statements, stmt_idx, stmt_class_seq):
963
- stmt_seq_len = len(stmt_class_seq)
962
+ for stmt_class_seq_or_method in opt.stmt_classes:
963
+ if isinstance(stmt_class_seq_or_method, FunctionType):
964
+ r = stmt_class_seq_or_method(statements, stmt_idx)
965
+ if r > 0:
966
+ stmt_seq_len = r
967
+ matched = True
968
+ break
969
+ elif match_stmt_classes(statements, stmt_idx, stmt_class_seq_or_method):
970
+ stmt_seq_len = len(stmt_class_seq_or_method)
964
971
  matched = True
965
972
  break
966
973
 
@@ -35,7 +35,7 @@ class FlirtAnalysis(Analysis):
35
35
  current binary, and then match all possible signatures for the architecture.
36
36
  """
37
37
 
38
- def __init__(self, sig: FlirtSignature | str | None = None, max_mismatched_bytes: int = 0):
38
+ def __init__(self, sig: FlirtSignature | str | None = None, max_mismatched_bytes: int = 0, dry_run: bool = False):
39
39
 
40
40
  from angr.flirt import FLIRT_SIGNATURES_BY_ARCH # pylint:disable=import-outside-toplevel
41
41
 
@@ -94,9 +94,10 @@ class FlirtAnalysis(Analysis):
94
94
  sig_ = path_to_sig.get(max_suggestion_sig_path)
95
95
  assert sig_ is not None
96
96
  _l.info("Applying FLIRT signature %s for library %s.", sig_, lib)
97
- self._apply_changes(
98
- sig_.sig_name if not self._temporary_sig else None, sig_to_suggestions[max_suggestion_sig_path]
99
- )
97
+ if not dry_run:
98
+ self._apply_changes(
99
+ sig_.sig_name if not self._temporary_sig else None, sig_to_suggestions[max_suggestion_sig_path]
100
+ )
100
101
  self.matched_suggestions[lib] = (sig_, sig_to_suggestions[max_suggestion_sig_path])
101
102
 
102
103
  def _find_hits_by_strings(self, regions: list[bytes]) -> Generator[FlirtSignature]:
@@ -15,6 +15,7 @@ from angr.ailment.expression import (
15
15
  Load,
16
16
  Convert,
17
17
  Expression,
18
+ Tmp,
18
19
  )
19
20
  from angr.ailment.statement import Assignment, Store, Return, Jump, ConditionalJump
20
21
 
@@ -164,7 +165,7 @@ class SPropagatorAnalysis(Analysis):
164
165
  assert v is not None
165
166
  const_vvars[vvar_id] = v
166
167
  for vvar_at_use, useloc in vvar_uselocs[vvar_id]:
167
- replacements[useloc][vvar_at_use] = v
168
+ self.replace(replacements, useloc, vvar_at_use, v)
168
169
  continue
169
170
 
170
171
  v = phi_assignment_get_src(stmt)
@@ -185,7 +186,7 @@ class SPropagatorAnalysis(Analysis):
185
186
  const_value = const_vvars[next(iter(all_int_src_varids))]
186
187
  const_vvars[vvar.varid] = const_value
187
188
  for vvar_at_use, useloc in vvar_uselocs[vvar.varid]:
188
- replacements[useloc][vvar_at_use] = const_value
189
+ self.replace(replacements, useloc, vvar_at_use, const_value)
189
190
 
190
191
  # function mode only
191
192
  if self.mode == "function":
@@ -226,7 +227,7 @@ class SPropagatorAnalysis(Analysis):
226
227
  if can_replace:
227
228
  # we can propagate this load because there is no store between its def and use
228
229
  for vvar_used, vvar_useloc in vvar_uselocs_set:
229
- replacements[vvar_useloc][vvar_used] = stmt.src
230
+ self.replace(replacements, vvar_useloc, vvar_used, stmt.src)
230
231
  continue
231
232
 
232
233
  if (
@@ -241,7 +242,7 @@ class SPropagatorAnalysis(Analysis):
241
242
  uselocs = {loc for _, loc in vvar_uselocs_set}
242
243
  if self.is_vvar_used_for_addr_loading_switch_case(uselocs, blocks) and not has_tmp_expr(stmt.src):
243
244
  for vvar_used, vvar_useloc in vvar_uselocs_set:
244
- replacements[vvar_useloc][vvar_used] = stmt.src
245
+ self.replace(replacements, vvar_useloc, vvar_used, stmt.src)
245
246
  # mark the vvar as dead and should be removed
246
247
  self.model.dead_vvar_ids.add(vvar.varid)
247
248
  continue
@@ -255,7 +256,7 @@ class SPropagatorAnalysis(Analysis):
255
256
  and not has_tmp_expr(stmt.src)
256
257
  ):
257
258
  # we can propagate this load because there is no store between its def and use
258
- replacements[vvar_useloc][vvar_used] = stmt.src
259
+ self.replace(replacements, vvar_useloc, vvar_used, stmt.src)
259
260
  continue
260
261
 
261
262
  if is_const_and_vvar_assignment(stmt) and not has_tmp_expr(stmt.src):
@@ -272,9 +273,9 @@ class SPropagatorAnalysis(Analysis):
272
273
  and stmt.src.oident == useloc_stmt.dst.oident
273
274
  and stmt.src.category == useloc_stmt.dst.category
274
275
  ):
275
- replacements[vvar_useloc][vvar_used] = stmt.src
276
+ self.replace(replacements, vvar_useloc, vvar_used, stmt.src)
276
277
  else:
277
- replacements[vvar_useloc][vvar_used] = stmt.src
278
+ self.replace(replacements, vvar_useloc, vvar_used, stmt.src)
278
279
  continue
279
280
 
280
281
  else:
@@ -288,7 +289,7 @@ class SPropagatorAnalysis(Analysis):
288
289
  # this vvar is used once if we exclude its uses at ret sites or jump sites. we can
289
290
  # propagate it
290
291
  for vvar_used, vvar_useloc in vvar_uselocs_set:
291
- replacements[vvar_useloc][vvar_used] = stmt.src
292
+ self.replace(replacements, vvar_useloc, vvar_used, stmt.src)
292
293
  continue
293
294
 
294
295
  if (
@@ -304,7 +305,7 @@ class SPropagatorAnalysis(Analysis):
304
305
  # remove duplicate use locs (e.g., if the variable is used multiple times by the
305
306
  # same statement) - but ensure stmt is simple enough
306
307
  for vvar_used, vvar_useloc in vvar_uselocs_set:
307
- replacements[vvar_useloc][vvar_used] = stmt.src
308
+ self.replace(replacements, vvar_useloc, vvar_used, stmt.src)
308
309
  continue
309
310
 
310
311
  # special logic for global variables: if it's used once or multiple times, and the variable is never
@@ -332,7 +333,7 @@ class SPropagatorAnalysis(Analysis):
332
333
  )
333
334
  if not gv_updated:
334
335
  for vvar_used, vvar_useloc in vvar_uselocs_set:
335
- replacements[vvar_useloc][vvar_used] = stmt.src
336
+ self.replace(replacements, vvar_useloc, vvar_used, stmt.src)
336
337
  continue
337
338
 
338
339
  for vvar_id, uselocs in vvar_uselocs.items():
@@ -353,7 +354,7 @@ class SPropagatorAnalysis(Analysis):
353
354
  if sp_bits is not None and vvar.bits < sp_bits:
354
355
  # truncation needed
355
356
  v = Convert(None, sp_bits, vvar.bits, False, v)
356
- replacements[useloc][vvar_at_use] = v
357
+ self.replace(replacements, useloc, vvar_at_use, v)
357
358
  continue
358
359
  if not self._bp_as_gpr and vvar.oident == self.project.arch.bp_offset:
359
360
  bp_bits = (
@@ -368,7 +369,7 @@ class SPropagatorAnalysis(Analysis):
368
369
  if bp_bits is not None and vvar.bits < bp_bits:
369
370
  # truncation needed
370
371
  v = Convert(None, bp_bits, vvar.bits, False, v)
371
- replacements[useloc][vvar_at_use] = v
372
+ self.replace(replacements, useloc, vvar_at_use, v)
372
373
  continue
373
374
 
374
375
  # find all tmp definitions
@@ -390,9 +391,8 @@ class SPropagatorAnalysis(Analysis):
390
391
  if r:
391
392
  # we can propagate it!
392
393
  for tmp_used, tmp_use_stmtidx in tmp_uses:
393
- replacements[
394
- CodeLocation(block_loc.block_addr, tmp_use_stmtidx, block_idx=block_loc.block_idx)
395
- ][tmp_used] = stmt.src
394
+ loc = CodeLocation(block_loc.block_addr, tmp_use_stmtidx, block_idx=block_loc.block_idx)
395
+ self.replace(replacements, loc, tmp_used, stmt.src)
396
396
  continue
397
397
 
398
398
  r = is_const_vvar_tmp_assignment(stmt)
@@ -404,9 +404,8 @@ class SPropagatorAnalysis(Analysis):
404
404
  v = stmt.src
405
405
 
406
406
  for tmp_used, tmp_use_stmtidx in tmp_uses:
407
- replacements[
408
- CodeLocation(block_loc.block_addr, tmp_use_stmtidx, block_idx=block_loc.block_idx)
409
- ][tmp_used] = v
407
+ loc = CodeLocation(block_loc.block_addr, tmp_use_stmtidx, block_idx=block_loc.block_idx)
408
+ self.replace(replacements, loc, tmp_used, v)
410
409
  continue
411
410
 
412
411
  if len(tmp_uses) <= 2 and is_const_vvar_load_dirty_assignment(stmt):
@@ -422,9 +421,8 @@ class SPropagatorAnalysis(Analysis):
422
421
  # we can propagate this load because either we do not consider memory aliasing problem
423
422
  # within the same instruction (blocks must be originally lifted with
424
423
  # CROSS_INSN_OPT=False), or there is no store between its def and use.
425
- replacements[
426
- CodeLocation(block_loc.block_addr, tmp_use_stmtidx, block_idx=block_loc.block_idx)
427
- ][tmp_used] = stmt.src
424
+ loc = CodeLocation(block_loc.block_addr, tmp_use_stmtidx, block_idx=block_loc.block_idx)
425
+ self.replace(replacements, loc, tmp_used, stmt.src)
428
426
 
429
427
  self.model.replacements = replacements
430
428
 
@@ -538,5 +536,9 @@ class SPropagatorAnalysis(Analysis):
538
536
 
539
537
  return g
540
538
 
539
+ @staticmethod
540
+ def replace(replacements: dict, loc, expr: VirtualVariable | Tmp, value: Expression) -> None:
541
+ replacements[loc][expr] = value
542
+
541
543
 
542
544
  register_analysis(SPropagatorAnalysis, "SPropagator")
angr/analyses/smc.py CHANGED
@@ -112,9 +112,8 @@ class SelfModifyingCodeAnalysis(Analysis):
112
112
  if subject is None:
113
113
  subject = self.project.entry
114
114
  if isinstance(subject, str):
115
- try:
116
- addr = self.project.kb.labels.lookup(subject)
117
- except KeyError:
115
+ addr = self.project.kb.labels.lookup(subject)
116
+ if addr is None:
118
117
  addr = self.project.kb.functions[subject].addr
119
118
  elif isinstance(subject, Function):
120
119
  addr = subject.addr
@@ -171,6 +171,45 @@ class SimEngineVRAIL(
171
171
  if isinstance(funcaddr_typevar, typevars.TypeVariable):
172
172
  load_typevar = self._create_access_typevar(funcaddr_typevar, False, self.arch.bytes, 0)
173
173
  self.state.add_type_constraint(typevars.Subtype(funcaddr_typevar, load_typevar))
174
+ elif isinstance(target, str):
175
+ # special handling for some intrinsics
176
+ match target:
177
+ case (
178
+ "InterlockedExchange8"
179
+ | "InterlockedExchange16"
180
+ | "InterlockedExchange"
181
+ | "InterlockedExchange64"
182
+ | "InterlockedCompareExchange16"
183
+ | "InterlockedCompareExchange"
184
+ | "InterlockedCompareExchange64"
185
+ | "InterlockedCompareExchange128"
186
+ | "InterlockedExchangeAdd"
187
+ | "InterlockedExchangeAdd64"
188
+ ):
189
+ arg_tv = (
190
+ args[0].typevar
191
+ if args[0].typevar is not None
192
+ else args[1].typevar if args[1].typevar is not None else None
193
+ )
194
+ if arg_tv is not None:
195
+ ret_ty = self._create_access_typevar(
196
+ arg_tv, False, args[0].data.size() // self.arch.byte_width, 0
197
+ )
198
+ return RichR(self.state.top(ret_expr_bits), typevar=ret_ty)
199
+ case (
200
+ "InterlockedIncrement"
201
+ | "InterlockedIncrement16"
202
+ | "InterlockedIncrement64"
203
+ | "InterlockedDecrement"
204
+ | "InterlockedDecrement16"
205
+ | "InterlockedDecrement64"
206
+ ):
207
+ arg_tv = args[0].typevar if args[0].typevar is not None else None
208
+ if arg_tv is not None:
209
+ ret_ty = self._create_access_typevar(
210
+ arg_tv, False, args[0].data.size() // self.arch.byte_width, 0
211
+ )
212
+ return RichR(self.state.top(ret_expr_bits), typevar=ret_ty)
174
213
 
175
214
  # discover the prototype
176
215
  prototype: SimTypeFunction | None = None
angr/emulator.py CHANGED
@@ -95,7 +95,8 @@ class Emulator:
95
95
  num_inst_executed: int = 0
96
96
  while self._state.history.jumpkind != "Ijk_Exit":
97
97
  # Check if there is a breakpoint at the current address
98
- if completed_engine_execs > 0 and self._state.addr in self._engine.get_breakpoints():
98
+ addr_with_lower_bit_cleared = self._state.addr & ~1
99
+ if completed_engine_execs > 0 and addr_with_lower_bit_cleared in self._engine.get_breakpoints():
99
100
  return EmulatorStopReason.BREAKPOINT
100
101
 
101
102
  # Check if we've already executed the requested number of instructions
angr/engines/hook.py CHANGED
@@ -54,7 +54,7 @@ class HooksMixin(SuccessorsEngine, ProcedureMixin):
54
54
  if procedure is None:
55
55
  procedure = self._lookup_hook(state, procedure)
56
56
  if procedure is None:
57
- return super().process_successors(successors, procedure=procedure, **kwargs)
57
+ return super().process_successors(successors, **kwargs)
58
58
 
59
59
  if isinstance(procedure.addr, SootAddressDescriptor):
60
60
  l.debug("Running %s (originally at %r)", repr(procedure), procedure.addr)