angr 9.2.177__cp310-abi3-macosx_11_0_arm64.whl → 9.2.179__cp310-abi3-macosx_11_0_arm64.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.
- angr/__init__.py +1 -1
- angr/analyses/cfg/cfb.py +11 -0
- angr/analyses/cfg/cfg_fast.py +15 -0
- angr/analyses/decompiler/ail_simplifier.py +69 -1
- angr/analyses/decompiler/ccall_rewriters/amd64_ccalls.py +45 -7
- angr/analyses/decompiler/clinic.py +24 -10
- angr/analyses/decompiler/dirty_rewriters/__init__.py +7 -0
- angr/analyses/decompiler/dirty_rewriters/amd64_dirty.py +69 -0
- angr/analyses/decompiler/dirty_rewriters/rewriter_base.py +27 -0
- angr/analyses/decompiler/optimization_passes/__init__.py +3 -0
- angr/analyses/decompiler/optimization_passes/optimization_pass.py +10 -8
- angr/analyses/decompiler/optimization_passes/register_save_area_simplifier.py +44 -6
- angr/analyses/decompiler/optimization_passes/register_save_area_simplifier_adv.py +198 -0
- angr/analyses/decompiler/optimization_passes/win_stack_canary_simplifier.py +111 -55
- angr/analyses/decompiler/peephole_optimizations/remove_redundant_shifts_around_comparators.py +72 -1
- angr/analyses/decompiler/presets/basic.py +2 -0
- angr/analyses/decompiler/presets/fast.py +2 -0
- angr/analyses/decompiler/presets/full.py +2 -0
- angr/analyses/decompiler/region_simplifiers/expr_folding.py +38 -18
- angr/analyses/decompiler/region_simplifiers/region_simplifier.py +10 -4
- angr/analyses/decompiler/structured_codegen/c.py +54 -12
- angr/analyses/decompiler/structuring/phoenix.py +129 -64
- angr/analyses/decompiler/utils.py +26 -8
- angr/analyses/disassembly.py +108 -52
- angr/analyses/proximity_graph.py +20 -19
- angr/analyses/s_propagator.py +23 -21
- angr/analyses/smc.py +2 -3
- angr/flirt/__init__.py +69 -42
- angr/knowledge_plugins/key_definitions/live_definitions.py +2 -1
- angr/knowledge_plugins/labels.py +4 -4
- angr/rustylib.abi3.so +0 -0
- angr/unicornlib.dylib +0 -0
- angr/utils/funcid.py +85 -0
- angr/utils/ssa/__init__.py +2 -6
- angr/utils/types.py +2 -0
- {angr-9.2.177.dist-info → angr-9.2.179.dist-info}/METADATA +9 -8
- {angr-9.2.177.dist-info → angr-9.2.179.dist-info}/RECORD +41 -37
- {angr-9.2.177.dist-info → angr-9.2.179.dist-info}/WHEEL +0 -0
- {angr-9.2.177.dist-info → angr-9.2.179.dist-info}/entry_points.txt +0 -0
- {angr-9.2.177.dist-info → angr-9.2.179.dist-info}/licenses/LICENSE +0 -0
- {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
|
|
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
|
-
|
|
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:
|
|
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
|
|
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
|
-
|
|
268
|
-
|
|
269
|
-
|
|
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
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
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
|
-
|
|
322
|
-
|
|
323
|
-
|
|
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
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
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
|
+
)
|
angr/analyses/decompiler/peephole_optimizations/remove_redundant_shifts_around_comparators.py
CHANGED
|
@@ -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(
|
|
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.
|
|
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
|
-
|
|
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,
|
|
330
|
+
def _collect_uses(self, thing: Expression | Statement | ailment.Block, loc: LocationBase | None):
|
|
330
331
|
use_finder = ExpressionUseFinder()
|
|
331
|
-
if isinstance(
|
|
332
|
-
|
|
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(
|
|
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
|
|
339
|
-
|
|
340
|
-
|
|
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
|
-
|
|
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):
|