angr 9.2.177__cp310-abi3-win_amd64.whl → 9.2.179__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 (41) hide show
  1. angr/__init__.py +1 -1
  2. angr/analyses/cfg/cfb.py +11 -0
  3. angr/analyses/cfg/cfg_fast.py +15 -0
  4. angr/analyses/decompiler/ail_simplifier.py +69 -1
  5. angr/analyses/decompiler/ccall_rewriters/amd64_ccalls.py +45 -7
  6. angr/analyses/decompiler/clinic.py +24 -10
  7. angr/analyses/decompiler/dirty_rewriters/__init__.py +7 -0
  8. angr/analyses/decompiler/dirty_rewriters/amd64_dirty.py +69 -0
  9. angr/analyses/decompiler/dirty_rewriters/rewriter_base.py +27 -0
  10. angr/analyses/decompiler/optimization_passes/__init__.py +3 -0
  11. angr/analyses/decompiler/optimization_passes/optimization_pass.py +10 -8
  12. angr/analyses/decompiler/optimization_passes/register_save_area_simplifier.py +44 -6
  13. angr/analyses/decompiler/optimization_passes/register_save_area_simplifier_adv.py +198 -0
  14. angr/analyses/decompiler/optimization_passes/win_stack_canary_simplifier.py +111 -55
  15. angr/analyses/decompiler/peephole_optimizations/remove_redundant_shifts_around_comparators.py +72 -1
  16. angr/analyses/decompiler/presets/basic.py +2 -0
  17. angr/analyses/decompiler/presets/fast.py +2 -0
  18. angr/analyses/decompiler/presets/full.py +2 -0
  19. angr/analyses/decompiler/region_simplifiers/expr_folding.py +38 -18
  20. angr/analyses/decompiler/region_simplifiers/region_simplifier.py +10 -4
  21. angr/analyses/decompiler/structured_codegen/c.py +54 -12
  22. angr/analyses/decompiler/structuring/phoenix.py +129 -64
  23. angr/analyses/decompiler/utils.py +26 -8
  24. angr/analyses/disassembly.py +108 -52
  25. angr/analyses/proximity_graph.py +20 -19
  26. angr/analyses/s_propagator.py +23 -21
  27. angr/analyses/smc.py +2 -3
  28. angr/flirt/__init__.py +69 -42
  29. angr/knowledge_plugins/key_definitions/live_definitions.py +2 -1
  30. angr/knowledge_plugins/labels.py +4 -4
  31. angr/rustylib.pyd +0 -0
  32. angr/unicornlib.dll +0 -0
  33. angr/utils/funcid.py +85 -0
  34. angr/utils/ssa/__init__.py +2 -6
  35. angr/utils/types.py +2 -0
  36. {angr-9.2.177.dist-info → angr-9.2.179.dist-info}/METADATA +9 -8
  37. {angr-9.2.177.dist-info → angr-9.2.179.dist-info}/RECORD +41 -37
  38. {angr-9.2.177.dist-info → angr-9.2.179.dist-info}/WHEEL +0 -0
  39. {angr-9.2.177.dist-info → angr-9.2.179.dist-info}/entry_points.txt +0 -0
  40. {angr-9.2.177.dist-info → angr-9.2.179.dist-info}/licenses/LICENSE +0 -0
  41. {angr-9.2.177.dist-info → angr-9.2.179.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,198 @@
1
+ # pylint:disable=too-many-boolean-expressions
2
+ from __future__ import annotations
3
+ import logging
4
+
5
+
6
+ from angr.ailment.statement import Assignment
7
+ from angr.ailment.expression import VirtualVariable
8
+ from angr.code_location import CodeLocation, ExternalCodeLocation
9
+ from angr.analyses.decompiler.stack_item import StackItem, StackItemType
10
+ from angr.utils.ail import is_phi_assignment
11
+ from .optimization_pass import OptimizationPass, OptimizationPassStage
12
+
13
+ _l = logging.getLogger(name=__name__)
14
+
15
+
16
+ class RegisterSaveAreaSimplifierAdvanced(OptimizationPass):
17
+ """
18
+ Optimizes away registers that are stored to or restored on the stack space.
19
+
20
+ This analysis is more complex than RegisterSaveAreaSimplifier because it handles:
21
+ (1) Registers that are stored in the stack shadow space (sp+N) according to the Windows x64 calling convention.
22
+ (2) Registers that are aliases of sp.
23
+ """
24
+
25
+ ARCHES = None
26
+ PLATFORMS = None
27
+ STAGE = OptimizationPassStage.AFTER_SSA_LEVEL1_TRANSFORMATION
28
+ NAME = "Simplify register save areas (advanced)"
29
+ DESCRIPTION = __doc__.strip() # type:ignore
30
+
31
+ def __init__(self, func, **kwargs):
32
+ super().__init__(func, **kwargs)
33
+ self._srda = None
34
+
35
+ self.analyze()
36
+
37
+ def _check(self):
38
+
39
+ self._srda = self.project.analyses.SReachingDefinitions(
40
+ subject=self._func, func_graph=self._graph, func_args=self._arg_vvars
41
+ )
42
+ info = self._find_reg_store_and_restore_locations()
43
+ if not info:
44
+ return False, None
45
+
46
+ return True, {"info": info}
47
+
48
+ @staticmethod
49
+ def _modify_statement(
50
+ old_block, stmt_idx_: int, updated_blocks_, stack_offset: int | None = None
51
+ ): # pylint:disable=unused-argument
52
+ if old_block not in updated_blocks_:
53
+ block = old_block.copy()
54
+ updated_blocks_[old_block] = block
55
+ else:
56
+ block = updated_blocks_[old_block]
57
+ block.statements[stmt_idx_] = None
58
+
59
+ def _analyze(self, cache=None):
60
+
61
+ if cache is None:
62
+ return
63
+
64
+ info: list[tuple[int, CodeLocation, int, CodeLocation, int]] = cache["info"]
65
+ updated_blocks = {}
66
+
67
+ for _regvar, regvar_loc, _stackvar, stackvar_loc, _ in info:
68
+ # remove storing statements
69
+ old_block = self._get_block(regvar_loc.block_addr, idx=regvar_loc.block_idx)
70
+ assert regvar_loc.stmt_idx is not None
71
+ self._modify_statement(old_block, regvar_loc.stmt_idx, updated_blocks)
72
+ old_block = self._get_block(stackvar_loc.block_addr, idx=stackvar_loc.block_idx)
73
+ assert stackvar_loc.stmt_idx is not None
74
+ self._modify_statement(old_block, stackvar_loc.stmt_idx, updated_blocks)
75
+
76
+ for old_block, new_block in updated_blocks.items():
77
+ # remove all statements that are None
78
+ new_block.statements = [stmt for stmt in new_block.statements if stmt is not None]
79
+ # update it
80
+ self._update_block(old_block, new_block)
81
+
82
+ if updated_blocks:
83
+ # update stack_items
84
+ for _, _, _, _, stack_offset in info:
85
+ self.stack_items[stack_offset] = StackItem(
86
+ stack_offset, self.project.arch.bytes, "regs", StackItemType.SAVED_REGS
87
+ )
88
+
89
+ def _find_reg_store_and_restore_locations(self) -> list[tuple[int, CodeLocation, int, CodeLocation, int]]:
90
+ results = []
91
+
92
+ assert self._srda is not None
93
+ srda_model = self._srda.model
94
+ # find all registers that are defined externally and used exactly once
95
+ saved_vvars: set[tuple[int, CodeLocation]] = set()
96
+ for vvar_id, loc in srda_model.all_vvar_definitions.items():
97
+ if isinstance(loc, ExternalCodeLocation):
98
+ uses = srda_model.all_vvar_uses.get(vvar_id, [])
99
+ if len(uses) == 1:
100
+ vvar, used_loc = next(iter(uses))
101
+ if vvar is not None and vvar.was_reg:
102
+ saved_vvars.add((vvar_id, used_loc))
103
+
104
+ if not saved_vvars:
105
+ return results
106
+
107
+ # for each candidate, we check to ensure:
108
+ # - it is stored onto the stack (into a stack virtual variable)
109
+ # - the stack virtual variable is only used once and restores the value to the same register
110
+ # - the restore location is in the dominance frontier of the store location
111
+ for vvar_id, used_loc in saved_vvars:
112
+ def_block = self._get_block(used_loc.block_addr, idx=used_loc.block_idx)
113
+ assert def_block is not None and used_loc.stmt_idx is not None
114
+ stmt = def_block.statements[used_loc.stmt_idx]
115
+ if not (
116
+ isinstance(stmt, Assignment)
117
+ and isinstance(stmt.dst, VirtualVariable)
118
+ and stmt.dst.was_stack
119
+ and isinstance(stmt.src, VirtualVariable)
120
+ and stmt.src.was_reg
121
+ and stmt.src.varid == vvar_id
122
+ ):
123
+ continue
124
+ stack_vvar = stmt.dst
125
+ all_stack_vvar_uses = srda_model.all_vvar_uses.get(stack_vvar.varid, [])
126
+ # eliminate the use location if it's a phi statement
127
+ stack_vvar_uses = set()
128
+ for vvar_, loc_ in all_stack_vvar_uses:
129
+ use_block = self._get_block(loc_.block_addr, idx=loc_.block_idx)
130
+ if use_block is None or loc_.stmt_idx is None:
131
+ continue
132
+ use_stmt = use_block.statements[loc_.stmt_idx]
133
+ if is_phi_assignment(use_stmt):
134
+ continue
135
+ stack_vvar_uses.add((vvar_, loc_))
136
+ if len(stack_vvar_uses) != 1:
137
+ continue
138
+ _, stack_vvar_use_loc = next(iter(stack_vvar_uses))
139
+ restore_block = self._get_block(stack_vvar_use_loc.block_addr, idx=stack_vvar_use_loc.block_idx)
140
+ assert restore_block is not None
141
+ restore_stmt = restore_block.statements[stack_vvar_use_loc.stmt_idx]
142
+
143
+ if not (
144
+ isinstance(restore_stmt, Assignment)
145
+ and isinstance(restore_stmt.src, VirtualVariable)
146
+ and restore_stmt.src.varid == stack_vvar.varid
147
+ and isinstance(restore_stmt.dst, VirtualVariable)
148
+ and restore_stmt.dst.was_reg
149
+ and restore_stmt.dst.reg_offset == stmt.src.reg_offset
150
+ ):
151
+ continue
152
+ # this is the dumb version of the dominance frontier check
153
+ if self._within_dominance_frontier(def_block, restore_block, True, True):
154
+ results.append(
155
+ (stmt.src.varid, used_loc, stack_vvar.varid, stack_vvar_use_loc, stack_vvar.stack_offset)
156
+ )
157
+
158
+ return results
159
+
160
+ def _within_dominance_frontier(self, dom_node, node, use_preds: bool, use_succs: bool) -> bool:
161
+ if use_succs:
162
+ # scan forward
163
+ succs = [succ for succ in self._graph.successors(dom_node) if succ is not dom_node]
164
+ if len(succs) == 1:
165
+ succ = succs[0]
166
+ succ_preds = [pred for pred in self._graph.predecessors(succ) if pred is not succ]
167
+ if len(succ_preds) == 0:
168
+ # the successor has no other predecessors
169
+ r = self._within_dominance_frontier(succ, node, False, True)
170
+ if r:
171
+ return True
172
+
173
+ else:
174
+ # the successor has other predecessors; gotta step back
175
+ preds = [pred for pred in self._graph.predecessors(node) if pred is not node]
176
+ if len(preds) == 1 and preds[0] is node:
177
+ return True
178
+ elif len(succs) == 2:
179
+ return any(succ is node for succ in succs)
180
+
181
+ if use_preds:
182
+ # scan backward
183
+ preds = [pred for pred in self._graph.predecessors(dom_node) if pred is not dom_node]
184
+ if len(preds) == 1:
185
+ pred = preds[0]
186
+ pred_succs = [succ for succ in self._graph.successors(pred) if succ is not pred]
187
+ if len(pred_succs) == 0:
188
+ # the predecessor has no other successors
189
+ return self._within_dominance_frontier(pred, node, True, False)
190
+
191
+ # the predecessor has other successors; gotta step forward
192
+ succs = [succ for succ in self._graph.successors(node) if succ is not node]
193
+ if len(succs) == 1:
194
+ return self._graph.has_edge(node, succs[0])
195
+ elif len(preds) == 2:
196
+ return False
197
+
198
+ return False
@@ -33,9 +33,12 @@ class WinStackCanarySimplifier(OptimizationPass):
33
33
 
34
34
  def __init__(self, func, **kwargs):
35
35
  super().__init__(func, **kwargs)
36
- self._security_cookie_addr = None
36
+ self._security_cookie_addr: int | None = None
37
37
  if isinstance(self.project.loader.main_object, cle.PE):
38
38
  self._security_cookie_addr = self.project.loader.main_object.load_config.get("SecurityCookie", None)
39
+ if self._security_cookie_addr is None:
40
+ # maybe it's just not set - check labels
41
+ self._security_cookie_addr = self.project.kb.labels.lookup("_security_cookie")
39
42
 
40
43
  self.analyze()
41
44
 
@@ -186,21 +189,16 @@ class WinStackCanarySimplifier(OptimizationPass):
186
189
  xor_stmt_idx = None
187
190
  xored_reg = None
188
191
 
192
+ assert self._security_cookie_addr is not None
193
+
189
194
  for idx, stmt in enumerate(block.statements):
190
195
  # if we are lucky and things get folded into one statement:
191
196
  if (
192
197
  isinstance(stmt, ailment.Stmt.Store)
193
198
  and isinstance(stmt.addr, ailment.Expr.StackBaseOffset)
194
- and isinstance(stmt.data, ailment.Expr.BinaryOp)
195
- and stmt.data.op == "Xor"
196
- and isinstance(stmt.data.operands[1], ailment.Expr.StackBaseOffset)
197
- and isinstance(stmt.data.operands[0], ailment.Expr.Load)
198
- and isinstance(stmt.data.operands[0].addr, ailment.Expr.Const)
199
+ and self._is_expr_loading_stack_cookie(stmt.data, self._security_cookie_addr)
199
200
  ):
200
- # Check addr: must be __security_cookie
201
- load_addr = stmt.data.operands[0].addr.value
202
- if load_addr == self._security_cookie_addr:
203
- return [idx]
201
+ return [idx]
204
202
  # or if we are unlucky and the load and the xor are two different statements
205
203
  if (
206
204
  isinstance(stmt, ailment.Stmt.Assignment)
@@ -213,25 +211,14 @@ class WinStackCanarySimplifier(OptimizationPass):
213
211
  if load_addr == self._security_cookie_addr:
214
212
  load_stmt_idx = idx
215
213
  load_reg = stmt.dst.reg_offset
216
- if load_stmt_idx is not None and xor_stmt_idx is None and idx >= load_stmt_idx + 1: # noqa:SIM102
214
+ if load_stmt_idx is not None and load_reg is not None and xor_stmt_idx is None and idx >= load_stmt_idx + 1:
215
+ assert self.project.arch.bp_offset is not None
217
216
  if (
218
217
  isinstance(stmt, ailment.Stmt.Assignment)
219
218
  and isinstance(stmt.dst, ailment.Expr.VirtualVariable)
220
219
  and stmt.dst.was_reg
221
220
  and not self.project.arch.is_artificial_register(stmt.dst.reg_offset, stmt.dst.size)
222
- and isinstance(stmt.src, ailment.Expr.BinaryOp)
223
- and stmt.src.op == "Xor"
224
- and isinstance(stmt.src.operands[0], ailment.Expr.VirtualVariable)
225
- and stmt.src.operands[0].was_reg
226
- and stmt.src.operands[0].reg_offset == load_reg
227
- and (
228
- isinstance(stmt.src.operands[1], ailment.Expr.StackBaseOffset)
229
- or (
230
- isinstance(stmt.src.operands[1], ailment.Expr.VirtualVariable)
231
- and stmt.src.operands[1].was_reg
232
- and stmt.src.operands[1].reg_offset == self.project.arch.registers["ebp"][0]
233
- )
234
- )
221
+ and self._is_expr_xoring_stack_cookie_reg(stmt.src, load_reg, self.project.arch.bp_offset)
235
222
  ):
236
223
  xor_stmt_idx = idx
237
224
  xored_reg = stmt.dst.reg_offset
@@ -255,6 +242,25 @@ class WinStackCanarySimplifier(OptimizationPass):
255
242
  ):
256
243
  return [load_stmt_idx, xor_stmt_idx, idx]
257
244
  break
245
+ if load_stmt_idx is not None and xor_stmt_idx is None and idx >= load_stmt_idx + 1: # noqa:SIM102
246
+ if isinstance(stmt, ailment.Stmt.Store) and (
247
+ isinstance(stmt.addr, ailment.Expr.StackBaseOffset)
248
+ or (
249
+ isinstance(stmt.addr, ailment.Expr.BinaryOp)
250
+ and stmt.addr.op == "Sub"
251
+ and isinstance(stmt.addr.operands[0], ailment.Expr.VirtualVariable)
252
+ and stmt.addr.operands[0].was_reg
253
+ and stmt.addr.operands[0].reg_offset == self.project.arch.registers["ebp"][0]
254
+ and isinstance(stmt.addr.operands[1], ailment.Expr.Const)
255
+ )
256
+ ):
257
+ if (
258
+ isinstance(stmt.data, ailment.Expr.VirtualVariable)
259
+ and stmt.data.was_reg
260
+ and stmt.data.reg_offset == load_reg
261
+ ):
262
+ return [load_stmt_idx, idx]
263
+ break
258
264
  return None
259
265
 
260
266
  def _find_amd64_canary_storing_stmt(self, block, canary_value_stack_offset):
@@ -263,23 +269,27 @@ class WinStackCanarySimplifier(OptimizationPass):
263
269
  for idx, stmt in enumerate(block.statements):
264
270
  # when we are lucky, we have one instruction
265
271
  if (
266
- (
267
- isinstance(stmt, ailment.Stmt.Assignment)
268
- and isinstance(stmt.dst, ailment.Expr.VirtualVariable)
269
- and stmt.dst.was_reg
270
- and stmt.dst.reg_offset == self.project.arch.registers["rcx"][0]
271
- )
272
- and isinstance(stmt.src, ailment.Expr.BinaryOp)
273
- and stmt.src.op == "Xor"
272
+ isinstance(stmt, ailment.Stmt.Assignment)
273
+ and isinstance(stmt.dst, ailment.Expr.VirtualVariable)
274
+ and stmt.dst.was_reg
275
+ and stmt.dst.reg_offset == self.project.arch.registers["rcx"][0]
274
276
  ):
275
- op0, op1 = stmt.src.operands
276
- if (
277
- isinstance(op0, ailment.Expr.Load)
278
- and isinstance(op0.addr, ailment.Expr.StackBaseOffset)
279
- and op0.addr.offset == canary_value_stack_offset
280
- ) and isinstance(op1, ailment.Expr.StackBaseOffset):
281
- # found it
282
- return idx
277
+ if isinstance(stmt.src, ailment.Expr.BinaryOp) and stmt.src.op == "Xor":
278
+ op0, op1 = stmt.src.operands
279
+ if (
280
+ isinstance(op0, ailment.Expr.Load)
281
+ and isinstance(op0.addr, ailment.Expr.StackBaseOffset)
282
+ and op0.addr.offset == canary_value_stack_offset
283
+ ) and isinstance(op1, ailment.Expr.StackBaseOffset):
284
+ # found it
285
+ return idx
286
+ elif isinstance(stmt.src, ailment.Expr.Load):
287
+ if (
288
+ isinstance(stmt.src.addr, ailment.Expr.StackBaseOffset)
289
+ and stmt.src.addr.offset == canary_value_stack_offset
290
+ ):
291
+ # found it
292
+ return idx
283
293
  # or when we are unlucky, we have two instructions...
284
294
  if (
285
295
  isinstance(stmt, ailment.Stmt.Assignment)
@@ -317,22 +327,26 @@ class WinStackCanarySimplifier(OptimizationPass):
317
327
  for idx, stmt in enumerate(block.statements):
318
328
  # when we are lucky, we have one instruction
319
329
  if (
320
- (
321
- isinstance(stmt, ailment.Stmt.Assignment)
322
- and isinstance(stmt.dst, ailment.Expr.VirtualVariable)
323
- and stmt.dst.was_reg
324
- and not self.project.arch.is_artificial_register(stmt.dst.reg_offset, stmt.dst.size)
325
- )
326
- and isinstance(stmt.src, ailment.Expr.BinaryOp)
327
- and stmt.src.op == "Xor"
330
+ isinstance(stmt, ailment.Stmt.Assignment)
331
+ and isinstance(stmt.dst, ailment.Expr.VirtualVariable)
332
+ and stmt.dst.was_reg
333
+ and not self.project.arch.is_artificial_register(stmt.dst.reg_offset, stmt.dst.size)
328
334
  ):
329
- op0, op1 = stmt.src.operands
330
- if (
331
- isinstance(op0, ailment.Expr.Load)
332
- and self._get_bp_offset(op0.addr, stmt.ins_addr) == canary_value_stack_offset
333
- ) and isinstance(op1, ailment.Expr.StackBaseOffset):
334
- # found it
335
- return idx
335
+ if isinstance(stmt.src, ailment.Expr.BinaryOp) and stmt.src.op == "Xor":
336
+ op0, op1 = stmt.src.operands
337
+ if (
338
+ isinstance(op0, ailment.Expr.Load)
339
+ and self._get_bp_offset(op0.addr, stmt.ins_addr) == canary_value_stack_offset
340
+ ) and isinstance(op1, ailment.Expr.StackBaseOffset):
341
+ # found it
342
+ return idx
343
+ elif isinstance(stmt.src, ailment.Expr.Load):
344
+ if (
345
+ isinstance(stmt.src.addr, ailment.Expr.StackBaseOffset)
346
+ and stmt.src.addr.offset == canary_value_stack_offset
347
+ ):
348
+ # found it
349
+ return idx
336
350
  # or when we are unlucky, we have two instructions...
337
351
  if (
338
352
  isinstance(stmt, ailment.Stmt.Assignment)
@@ -419,3 +433,45 @@ class WinStackCanarySimplifier(OptimizationPass):
419
433
  return idx
420
434
 
421
435
  return None
436
+
437
+ @staticmethod
438
+ def _is_expr_loading_stack_cookie(expr: ailment.Expr.Expression, security_cookie_addr: int) -> bool:
439
+ if (
440
+ isinstance(expr, ailment.Expr.BinaryOp)
441
+ and expr.op == "Xor"
442
+ and isinstance(expr.operands[1], ailment.Expr.StackBaseOffset)
443
+ and isinstance(expr.operands[0], ailment.Expr.Load)
444
+ and isinstance(expr.operands[0].addr, ailment.Expr.Const)
445
+ ):
446
+ # Check addr: must be __security_cookie
447
+ load_addr = expr.operands[0].addr.value
448
+ if load_addr == security_cookie_addr:
449
+ return True
450
+
451
+ if isinstance(expr, ailment.Expr.Load) and isinstance(expr.addr, ailment.Expr.Const):
452
+ load_addr = expr.addr.value
453
+ # Check addr: must be __security_cookie
454
+ if load_addr == security_cookie_addr:
455
+ return True
456
+
457
+ return False
458
+
459
+ @staticmethod
460
+ def _is_expr_xoring_stack_cookie_reg(
461
+ expr: ailment.Expr.Expression, security_cookie_reg: int, bp_offset: int
462
+ ) -> bool:
463
+ return (
464
+ isinstance(expr, ailment.Expr.BinaryOp)
465
+ and expr.op == "Xor"
466
+ and isinstance(expr.operands[0], ailment.Expr.VirtualVariable)
467
+ and expr.operands[0].was_reg
468
+ and expr.operands[0].reg_offset == security_cookie_reg
469
+ and (
470
+ isinstance(expr.operands[1], ailment.Expr.StackBaseOffset)
471
+ or (
472
+ isinstance(expr.operands[1], ailment.Expr.VirtualVariable)
473
+ and expr.operands[1].was_reg
474
+ and expr.operands[1].reg_offset == bp_offset
475
+ )
476
+ )
477
+ )
@@ -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,
@@ -271,12 +271,19 @@ class ExpressionCounter(SequenceWalker):
271
271
  # the current assignment depends on, StatementLocation of the assignment statement, a Boolean variable that
272
272
  # indicates if ExpressionUseFinder has succeeded or not)
273
273
  self.assignments: defaultdict[Any, set[tuple]] = defaultdict(set)
274
- self.uses: dict[int, set[tuple[Expression, LocationBase | None]]] = {}
274
+ self.outerscope_uses: dict[int, set[tuple[Expression, LocationBase | None]]] = {}
275
+ self.all_uses: dict[int, set[tuple[Expression, LocationBase | None]]] = {}
276
+ # inner_scope indicates if we are currently within one of the inner scopes (e.g., a loop). we only collect
277
+ # assignments in the outermost level and stop collecting assignments when we enter inner scopes.
278
+ # we always collect uses, but uses in the outmost scope will be recorded in self.outerscope_uses
279
+ self._outer_scope: bool = True
275
280
 
276
281
  super().__init__(handlers)
277
282
  self.walk(node)
278
283
 
279
284
  def _handle_Statement(self, idx: int, stmt: Statement, node: ailment.Block | LoopNode):
285
+ if not self._outer_scope:
286
+ return
280
287
  if isinstance(stmt, ailment.Stmt.Assignment):
281
288
  if is_phi_assignment(stmt):
282
289
  return
@@ -312,32 +319,40 @@ class ExpressionCounter(SequenceWalker):
312
319
 
313
320
  def _handle_Block(self, node: ailment.Block, **kwargs):
314
321
  # find assignments and uses of variables
315
- use_finder = ExpressionUseFinder()
316
- for idx, stmt in enumerate(node.statements):
317
- self._handle_Statement(idx, stmt, node)
318
- use_finder.walk_statement(stmt, block=node)
319
-
320
- for varid, content in use_finder.uses.items():
321
- if varid not in self.uses:
322
- self.uses[varid] = set()
323
- self.uses[varid] |= content
322
+ self._collect_uses(node, None)
324
323
 
325
324
  def _collect_assignments(self, expr: Expression, node) -> None:
325
+ if not self._outer_scope:
326
+ return
326
327
  finder = MultiStatementExpressionAssignmentFinder(self._handle_Statement)
327
328
  finder.walk_expression(expr, None, None, node)
328
329
 
329
- def _collect_uses(self, expr: Expression | Statement, loc: LocationBase):
330
+ def _collect_uses(self, thing: Expression | Statement | ailment.Block, loc: LocationBase | None):
330
331
  use_finder = ExpressionUseFinder()
331
- if isinstance(expr, Statement):
332
- use_finder.walk_statement(expr)
332
+ if isinstance(thing, ailment.Block):
333
+ for idx, stmt in enumerate(thing.statements):
334
+ self._handle_Statement(idx, stmt, thing)
335
+ use_finder.walk_statement(stmt, block=thing)
336
+ elif isinstance(thing, Statement):
337
+ use_finder.walk_statement(thing)
333
338
  else:
334
- use_finder.walk_expression(expr, stmt_idx=-1)
339
+ use_finder.walk_expression(thing, stmt_idx=-1)
335
340
 
336
341
  for varid, uses in use_finder.uses.items():
337
342
  for use in uses:
338
- if varid not in self.uses:
339
- self.uses[varid] = set()
340
- self.uses[varid].add((use[0], loc))
343
+ # overwrite the location if loc is specified
344
+ content = (use[0], loc) if loc is not None else use
345
+
346
+ # update all_uses
347
+ if varid not in self.all_uses:
348
+ self.all_uses[varid] = set()
349
+ self.all_uses[varid].add(content)
350
+
351
+ # update outerscope_uses if we are in the outer scope
352
+ if self._outer_scope:
353
+ if varid not in self.outerscope_uses:
354
+ self.outerscope_uses[varid] = set()
355
+ self.outerscope_uses[varid].add(content)
341
356
 
342
357
  def _handle_ConditionalBreak(self, node: ConditionalBreakNode, **kwargs):
343
358
  # collect uses on the condition expression
@@ -366,7 +381,12 @@ class ExpressionCounter(SequenceWalker):
366
381
  if node.condition is not None:
367
382
  self._collect_assignments(node.condition, node)
368
383
  self._collect_uses(node.condition, ConditionLocation(node.addr))
369
- # we do not go ahead and collect into the loop body
384
+
385
+ outer_scope = self._outer_scope
386
+ self._outer_scope = False
387
+ super()._handle_Loop(node, **kwargs)
388
+ self._outer_scope = outer_scope
389
+
370
390
  return None
371
391
 
372
392
  def _handle_SwitchCase(self, node: SwitchCaseNode, **kwargs):