angr 9.2.137__py3-none-win_amd64.whl → 9.2.139__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 (65) hide show
  1. angr/__init__.py +1 -1
  2. angr/analyses/calling_convention/fact_collector.py +59 -12
  3. angr/analyses/calling_convention/utils.py +2 -2
  4. angr/analyses/cfg/cfg_fast.py +15 -4
  5. angr/analyses/complete_calling_conventions.py +3 -3
  6. angr/analyses/decompiler/ail_simplifier.py +14 -3
  7. angr/analyses/decompiler/block_simplifier.py +0 -2
  8. angr/analyses/decompiler/callsite_maker.py +80 -14
  9. angr/analyses/decompiler/clinic.py +31 -37
  10. angr/analyses/decompiler/condition_processor.py +2 -2
  11. angr/analyses/decompiler/decompilation_options.py +10 -0
  12. angr/analyses/decompiler/decompiler.py +2 -0
  13. angr/analyses/decompiler/dephication/rewriting_engine.py +16 -7
  14. angr/analyses/decompiler/optimization_passes/__init__.py +3 -0
  15. angr/analyses/decompiler/optimization_passes/condition_constprop.py +149 -0
  16. angr/analyses/decompiler/optimization_passes/deadblock_remover.py +12 -3
  17. angr/analyses/decompiler/optimization_passes/flip_boolean_cmp.py +2 -2
  18. angr/analyses/decompiler/optimization_passes/inlined_string_transformation_simplifier.py +1 -1
  19. angr/analyses/decompiler/optimization_passes/optimization_pass.py +5 -2
  20. angr/analyses/decompiler/optimization_passes/return_duplicator_base.py +15 -7
  21. angr/analyses/decompiler/optimization_passes/return_duplicator_high.py +7 -10
  22. angr/analyses/decompiler/peephole_optimizations/eager_eval.py +12 -1
  23. angr/analyses/decompiler/peephole_optimizations/remove_redundant_conversions.py +61 -25
  24. angr/analyses/decompiler/peephole_optimizations/remove_redundant_nots.py +21 -3
  25. angr/analyses/decompiler/peephole_optimizations/remove_redundant_shifts.py +50 -1
  26. angr/analyses/decompiler/presets/fast.py +2 -0
  27. angr/analyses/decompiler/presets/full.py +2 -0
  28. angr/analyses/decompiler/region_simplifiers/region_simplifier.py +4 -0
  29. angr/analyses/decompiler/ssailification/rewriting_engine.py +20 -2
  30. angr/analyses/decompiler/ssailification/traversal_engine.py +4 -3
  31. angr/analyses/decompiler/structured_codegen/c.py +10 -3
  32. angr/analyses/decompiler/structuring/dream.py +7 -2
  33. angr/analyses/decompiler/structuring/phoenix.py +101 -49
  34. angr/analyses/decompiler/structuring/structurer_base.py +85 -36
  35. angr/analyses/decompiler/structuring/structurer_nodes.py +3 -1
  36. angr/analyses/deobfuscator/api_obf_finder.py +6 -1
  37. angr/analyses/deobfuscator/api_obf_type2_finder.py +158 -0
  38. angr/analyses/s_propagator.py +202 -47
  39. angr/analyses/s_reaching_definitions/s_rda_view.py +2 -2
  40. angr/analyses/s_reaching_definitions/s_reaching_definitions.py +3 -1
  41. angr/analyses/variable_recovery/engine_ail.py +1 -1
  42. angr/analyses/variable_recovery/engine_base.py +55 -62
  43. angr/analyses/variable_recovery/engine_vex.py +1 -1
  44. angr/analyses/variable_recovery/irsb_scanner.py +2 -2
  45. angr/calling_conventions.py +66 -9
  46. angr/engines/engine.py +2 -18
  47. angr/engines/light/engine.py +3 -8
  48. angr/engines/pcode/emulate.py +2 -2
  49. angr/engines/pcode/lifter.py +2 -2
  50. angr/engines/successors.py +1 -8
  51. angr/engines/vex/lifter.py +2 -2
  52. angr/engines/vex/light/light.py +2 -2
  53. angr/knowledge_plugins/cfg/cfg_model.py +3 -2
  54. angr/knowledge_plugins/labels.py +2 -2
  55. angr/knowledge_plugins/obfuscations.py +1 -0
  56. angr/knowledge_plugins/xrefs/xref_manager.py +4 -0
  57. angr/lib/angr_native.dll +0 -0
  58. angr/procedures/glibc/__libc_start_main.py +10 -3
  59. angr/utils/ssa/__init__.py +14 -1
  60. {angr-9.2.137.dist-info → angr-9.2.139.dist-info}/METADATA +6 -6
  61. {angr-9.2.137.dist-info → angr-9.2.139.dist-info}/RECORD +65 -63
  62. {angr-9.2.137.dist-info → angr-9.2.139.dist-info}/LICENSE +0 -0
  63. {angr-9.2.137.dist-info → angr-9.2.139.dist-info}/WHEEL +0 -0
  64. {angr-9.2.137.dist-info → angr-9.2.139.dist-info}/entry_points.txt +0 -0
  65. {angr-9.2.137.dist-info → angr-9.2.139.dist-info}/top_level.txt +0 -0
@@ -4,6 +4,8 @@ import contextlib
4
4
  from collections.abc import Mapping
5
5
  from collections import defaultdict
6
6
 
7
+ import networkx
8
+
7
9
  from ailment.block import Block
8
10
  from ailment.expression import (
9
11
  Const,
@@ -14,7 +16,7 @@ from ailment.expression import (
14
16
  Convert,
15
17
  Expression,
16
18
  )
17
- from ailment.statement import Assignment, Store, Return, Jump
19
+ from ailment.statement import Assignment, Store, Return, Jump, ConditionalJump
18
20
 
19
21
  from angr.knowledge_plugins.functions import Function
20
22
  from angr.code_location import CodeLocation, ExternalCodeLocation
@@ -22,6 +24,8 @@ from angr.analyses import Analysis, register_analysis
22
24
  from angr.utils.ssa import (
23
25
  get_vvar_uselocs,
24
26
  get_vvar_deflocs,
27
+ has_ite_expr,
28
+ has_ite_stmt,
25
29
  is_phi_assignment,
26
30
  is_const_assignment,
27
31
  is_const_and_vvar_assignment,
@@ -41,6 +45,8 @@ class SPropagatorModel:
41
45
 
42
46
  def __init__(self):
43
47
  self.replacements: Mapping[CodeLocation, Mapping[Expression, Expression]] = {}
48
+ # store vvars that are definitely dead (but usually not removed by default because they are stack variables)
49
+ self.dead_vvar_ids: set[int] = set()
44
50
 
45
51
 
46
52
  class SPropagatorAnalysis(Analysis):
@@ -51,7 +57,7 @@ class SPropagatorAnalysis(Analysis):
51
57
  def __init__( # pylint: disable=too-many-positional-arguments
52
58
  self,
53
59
  subject: Block | Function,
54
- func_graph=None,
60
+ func_graph: networkx.DiGraph | None = None,
55
61
  only_consts: bool = True,
56
62
  stack_pointer_tracker=None,
57
63
  func_args: set[VirtualVariable] | None = None,
@@ -86,6 +92,7 @@ class SPropagatorAnalysis(Analysis):
86
92
  bp_as_gpr = the_func.info.get("bp_as_gpr", False)
87
93
  self._bp_as_gpr = bp_as_gpr
88
94
 
95
+ # output
89
96
  self.model = SPropagatorModel()
90
97
 
91
98
  self._analyze()
@@ -94,6 +101,10 @@ class SPropagatorAnalysis(Analysis):
94
101
  def replacements(self):
95
102
  return self.model.replacements
96
103
 
104
+ @property
105
+ def dead_vvar_ids(self):
106
+ return self.model.dead_vvar_ids
107
+
97
108
  def _analyze(self):
98
109
  blocks: dict[tuple[int, int | None], Block]
99
110
  match self.mode:
@@ -128,7 +139,7 @@ class SPropagatorAnalysis(Analysis):
128
139
 
129
140
  replacements = defaultdict(dict)
130
141
 
131
- # find constant assignments
142
+ # find constant and other propagatable assignments
132
143
  vvarid_to_vvar = {}
133
144
  const_vvars: dict[int, Const] = {}
134
145
  for vvar, defloc in vvar_deflocs.items():
@@ -136,10 +147,12 @@ class SPropagatorAnalysis(Analysis):
136
147
  continue
137
148
 
138
149
  vvarid_to_vvar[vvar.varid] = vvar
139
- defloc = vvar_deflocs[vvar]
140
150
  if isinstance(defloc, ExternalCodeLocation):
141
151
  continue
142
152
 
153
+ assert defloc.block_addr is not None
154
+ assert defloc.stmt_idx is not None
155
+
143
156
  block = blocks[(defloc.block_addr, defloc.block_idx)]
144
157
  stmt = block.statements[defloc.stmt_idx]
145
158
  r, v = is_const_assignment(stmt)
@@ -155,78 +168,143 @@ class SPropagatorAnalysis(Analysis):
155
168
  if v is not None:
156
169
  src_varids = {vvar.varid if vvar is not None else None for _, vvar in v.src_and_vvars}
157
170
  if None not in src_varids and all(varid in const_vvars for varid in src_varids):
171
+ all_int_src_varids: set[int] = {varid for varid in src_varids if varid is not None}
158
172
  src_values = {
159
173
  (
160
174
  (const_vvars[varid].value, const_vvars[varid].bits)
161
175
  if isinstance(const_vvars[varid], Const)
162
176
  else const_vvars[varid]
163
177
  )
164
- for varid in src_varids
178
+ for varid in all_int_src_varids
165
179
  }
166
180
  if len(src_values) == 1:
167
181
  # replace it!
168
- const_value = const_vvars[next(iter(src_varids))]
182
+ const_value = const_vvars[next(iter(all_int_src_varids))]
169
183
  const_vvars[vvar.varid] = const_value
170
184
  for vvar_at_use, useloc in vvar_uselocs[vvar.varid]:
171
185
  replacements[useloc][vvar_at_use] = const_value
172
186
 
173
- if self.mode == "function" and vvar.varid in vvar_uselocs:
174
- if len(vvar_uselocs[vvar.varid]) == 1:
175
- vvar_used, vvar_useloc = next(iter(vvar_uselocs[vvar.varid]))
176
- if (
177
- is_const_vvar_load_assignment(stmt)
178
- and vvar_useloc.block_addr == defloc.block_addr
179
- and vvar_useloc.block_idx == defloc.block_idx
180
- and not any(
181
- isinstance(stmt_, Store)
182
- for stmt_ in block.statements[defloc.stmt_idx + 1 : vvar_useloc.stmt_idx]
183
- )
184
- ):
187
+ # function mode only
188
+ if self.mode == "function":
189
+ for vvar, defloc in vvar_deflocs.items():
190
+ if vvar.varid not in vvar_uselocs:
191
+ continue
192
+ if vvar.varid in const_vvars:
193
+ continue
194
+ if isinstance(defloc, ExternalCodeLocation):
195
+ continue
196
+
197
+ assert defloc.block_addr is not None
198
+ assert defloc.stmt_idx is not None
199
+
200
+ block = blocks[(defloc.block_addr, defloc.block_idx)]
201
+ stmt = block.statements[defloc.stmt_idx]
202
+ if (
203
+ (vvar.was_reg or vvar.was_parameter)
204
+ and len(vvar_uselocs[vvar.varid]) <= 2
205
+ and isinstance(stmt, Assignment)
206
+ and isinstance(stmt.src, Load)
207
+ ):
208
+ # do we want to propagate this Load expression if it's used for less than twice?
209
+ # it's often seen in the following pattern, where propagation will be beneficial:
210
+ # v0 = Load(...)
211
+ # if (!v0) {
212
+ # v1 = v0 + 1;
213
+ # }
214
+ can_replace = True
215
+ for _, vvar_useloc in vvar_uselocs[vvar.varid]:
216
+ if self.has_store_stmt_in_between(blocks, defloc, vvar_useloc):
217
+ can_replace = False
218
+
219
+ if can_replace:
185
220
  # we can propagate this load because there is no store between its def and use
186
- replacements[vvar_useloc][vvar_used] = stmt.src
221
+ for vvar_used, vvar_useloc in vvar_uselocs[vvar.varid]:
222
+ replacements[vvar_useloc][vvar_used] = stmt.src
187
223
  continue
188
224
 
189
- if is_const_and_vvar_assignment(stmt):
190
- # if the useloc is a phi assignment statement, ensure that stmt.src is the same as the phi
191
- # variable
192
- useloc_stmt = blocks[(vvar_useloc.block_addr, vvar_useloc.block_idx)].statements[
193
- vvar_useloc.stmt_idx
194
- ]
195
- if is_phi_assignment(useloc_stmt):
196
- if (
197
- isinstance(stmt.src, VirtualVariable)
198
- and stmt.src.oident == useloc_stmt.dst.oident
199
- and stmt.src.category == useloc_stmt.dst.category
200
- ):
201
- replacements[vvar_useloc][vvar_used] = stmt.src
202
- else:
225
+ if (
226
+ (vvar.was_reg or vvar.was_stack)
227
+ and len(vvar_uselocs[vvar.varid]) == 2
228
+ and not is_phi_assignment(stmt)
229
+ ):
230
+ # a special case: in a typical switch-case construct, a variable may be used once for comparison
231
+ # for the default case and then used again for constructing the jump target. we can propagate this
232
+ # variable for such cases.
233
+ uselocs = {loc for _, loc in vvar_uselocs[vvar.varid]}
234
+ if self.is_vvar_used_for_addr_loading_switch_case(uselocs, blocks):
235
+ for vvar_used, vvar_useloc in vvar_uselocs[vvar.varid]:
203
236
  replacements[vvar_useloc][vvar_used] = stmt.src
237
+ # mark the vvar as dead and should be removed
238
+ self.model.dead_vvar_ids.add(vvar.varid)
204
239
  continue
205
240
 
206
- elif (
207
- len(
208
- {
241
+ if vvar.was_reg or vvar.was_parameter:
242
+ if len(vvar_uselocs[vvar.varid]) == 1:
243
+ vvar_used, vvar_useloc = next(iter(vvar_uselocs[vvar.varid]))
244
+ if is_const_vvar_load_assignment(stmt) and not self.has_store_stmt_in_between(
245
+ blocks, defloc, vvar_useloc
246
+ ):
247
+ # we can propagate this load because there is no store between its def and use
248
+ replacements[vvar_useloc][vvar_used] = stmt.src
249
+ continue
250
+
251
+ if is_const_and_vvar_assignment(stmt):
252
+ # if the useloc is a phi assignment statement, ensure that stmt.src is the same as the phi
253
+ # variable
254
+ assert vvar_useloc.block_addr is not None
255
+ assert vvar_useloc.stmt_idx is not None
256
+ useloc_stmt = blocks[(vvar_useloc.block_addr, vvar_useloc.block_idx)].statements[
257
+ vvar_useloc.stmt_idx
258
+ ]
259
+ if is_phi_assignment(useloc_stmt):
260
+ if (
261
+ isinstance(stmt.src, VirtualVariable)
262
+ and stmt.src.oident == useloc_stmt.dst.oident
263
+ and stmt.src.category == useloc_stmt.dst.category
264
+ ):
265
+ replacements[vvar_useloc][vvar_used] = stmt.src
266
+ else:
267
+ replacements[vvar_useloc][vvar_used] = stmt.src
268
+ continue
269
+
270
+ else:
271
+ non_exitsite_uselocs = [
209
272
  loc
210
273
  for _, loc in vvar_uselocs[vvar.varid]
211
274
  if (loc.block_addr, loc.block_idx, loc.stmt_idx) not in (retsites | jumpsites)
212
- }
213
- )
214
- == 1
215
- ):
216
- if is_const_and_vvar_assignment(stmt):
217
- # this vvar is used once if we exclude its uses at ret sites or jump sites. we can propagate it
218
- for vvar_used, vvar_useloc in vvar_uselocs[vvar.varid]:
219
- replacements[vvar_useloc][vvar_used] = stmt.src
220
- continue
275
+ ]
276
+ if is_const_and_vvar_assignment(stmt):
277
+ if len(non_exitsite_uselocs) == 1:
278
+ # this vvar is used once if we exclude its uses at ret sites or jump sites. we can
279
+ # propagate it
280
+ for vvar_used, vvar_useloc in vvar_uselocs[vvar.varid]:
281
+ replacements[vvar_useloc][vvar_used] = stmt.src
282
+ continue
283
+
284
+ if len(set(non_exitsite_uselocs)) == 1 and not has_ite_expr(stmt.src):
285
+ useloc = non_exitsite_uselocs[0]
286
+ assert useloc.block_addr is not None
287
+ assert useloc.stmt_idx is not None
288
+ useloc_stmt = blocks[(useloc.block_addr, useloc.block_idx)].statements[useloc.stmt_idx]
289
+ if stmt.src.depth <= 3 and not has_ite_stmt(useloc_stmt):
290
+ # remove duplicate use locs (e.g., if the variable is used multiple times by the
291
+ # same statement) - but ensure stmt is simple enough
292
+ for vvar_used, vvar_useloc in vvar_uselocs[vvar.varid]:
293
+ replacements[vvar_useloc][vvar_used] = stmt.src
294
+ continue
221
295
 
222
296
  # special logic for global variables: if it's used once or multiple times, and the variable is never
223
297
  # updated before it's used, we will propagate the load
224
- if isinstance(stmt, Assignment):
298
+ if (vvar.was_reg or vvar.was_parameter) and isinstance(stmt, Assignment):
225
299
  stmt_src = stmt.src
226
300
  # unpack conversions
227
301
  while isinstance(stmt_src, Convert):
228
302
  stmt_src = stmt_src.operand
229
- if isinstance(stmt_src, Load) and isinstance(stmt_src.addr, Const):
303
+ if (
304
+ isinstance(stmt_src, Load)
305
+ and isinstance(stmt_src.addr, Const)
306
+ and isinstance(stmt_src.addr.value, int)
307
+ ):
230
308
  gv_updated = False
231
309
  for _vvar_used, vvar_useloc in vvar_uselocs[vvar.varid]:
232
310
  gv_updated |= self.is_global_variable_updated(
@@ -286,6 +364,8 @@ class SPropagatorAnalysis(Analysis):
286
364
  for block_loc, tmp_and_uses in tmp_uselocs.items():
287
365
  for tmp_atom, tmp_uses in tmp_and_uses.items():
288
366
  # take a look at the definition and propagate the definition if supported
367
+ assert block_loc.block_addr is not None
368
+
289
369
  block = blocks[(block_loc.block_addr, block_loc.block_idx)]
290
370
  tmp_def_stmtidx = tmp_deflocs[block_loc][tmp_atom]
291
371
 
@@ -350,6 +430,8 @@ class SPropagatorAnalysis(Analysis):
350
430
 
351
431
  start_stmt_idx = defloc.stmt_idx if block is defblock else 0 # inclusive
352
432
  end_stmt_idx = useloc.stmt_idx if block is useblock else len(block.statements) # exclusive
433
+ assert start_stmt_idx is not None
434
+ assert end_stmt_idx is not None
353
435
 
354
436
  for idx in range(start_stmt_idx, end_stmt_idx):
355
437
  stmt = block.statements[idx]
@@ -380,5 +462,78 @@ class SPropagatorAnalysis(Analysis):
380
462
 
381
463
  return False
382
464
 
465
+ def has_store_stmt_in_between(
466
+ self, blocks: dict[tuple[int, int | None], Block], defloc: CodeLocation, useloc: CodeLocation
467
+ ) -> bool:
468
+ assert defloc.block_addr is not None
469
+ assert defloc.stmt_idx is not None
470
+ assert useloc.block_addr is not None
471
+ assert useloc.stmt_idx is not None
472
+ assert self.func_graph is not None
473
+
474
+ use_block = blocks[(useloc.block_addr, useloc.block_idx)]
475
+ def_block = blocks[(defloc.block_addr, defloc.block_idx)]
476
+
477
+ # traverse the graph, go from use_block until we reach def_block, and look for Store statements
478
+ seen = {use_block}
479
+ queue = [use_block]
480
+ while queue:
481
+ block = queue.pop(0)
482
+
483
+ starting_stmt_idx, ending_stmt_idx = 0, len(block.statements)
484
+ if block is def_block:
485
+ starting_stmt_idx = defloc.stmt_idx + 1
486
+ if block is use_block:
487
+ ending_stmt_idx = useloc.stmt_idx
488
+
489
+ for i in range(starting_stmt_idx, ending_stmt_idx):
490
+ if isinstance(block.statements[i], Store):
491
+ return True
492
+
493
+ if block is def_block:
494
+ continue
495
+
496
+ for pred in self.func_graph.predecessors(block):
497
+ if pred not in seen:
498
+ seen.add(pred)
499
+ queue.append(pred)
500
+
501
+ return False
502
+
503
+ @staticmethod
504
+ def is_vvar_used_for_addr_loading_switch_case(uselocs: set[CodeLocation], blocks) -> bool:
505
+ """
506
+ Check if a virtual variable is used for loading an address in a switch-case construct.
507
+
508
+ :param uselocs: The use locations of the virtual variable.
509
+ :param blocks: All blocks of the current function.
510
+ :return: True if the virtual variable is used for loading an address in a switch-case construct, False
511
+ otherwise.
512
+ """
513
+
514
+ if len(uselocs) != 2:
515
+ return False
516
+
517
+ useloc_0, useloc_1 = list(uselocs)
518
+ block_0 = blocks[(useloc_0.block_addr, useloc_0.block_idx)]
519
+ stmt_0 = block_0.statements[useloc_0.stmt_idx]
520
+ block_1 = blocks[(useloc_1.block_addr, useloc_1.block_idx)]
521
+ stmt_1 = block_1.statements[useloc_1.stmt_idx]
522
+
523
+ if isinstance(stmt_0, Jump):
524
+ stmt_0, stmt_1 = stmt_1, stmt_0
525
+ block_0, block_1 = block_1, block_0
526
+ if not isinstance(stmt_0, ConditionalJump) or not isinstance(stmt_1, Jump):
527
+ return False
528
+
529
+ # check if stmt_0 jumps to block_1
530
+ if not isinstance(stmt_0.true_target, Const) or not isinstance(stmt_0.false_target, Const):
531
+ return False
532
+ stmt_0_targets = {
533
+ (stmt_0.true_target.value, stmt_0.true_target_idx),
534
+ (stmt_0.false_target.value, stmt_0.false_target_idx),
535
+ }
536
+ return (block_1.addr, block_1.idx) in stmt_0_targets
537
+
383
538
 
384
539
  register_analysis(SPropagatorAnalysis, "SPropagator")
@@ -79,8 +79,8 @@ class StackVVarPredicate:
79
79
  isinstance(stmt, Assignment)
80
80
  and isinstance(stmt.dst, VirtualVariable)
81
81
  and stmt.dst.was_stack
82
- and stmt.dst.stack_offset == self.stack_offset
83
- and stmt.dst.size == self.size
82
+ and stmt.dst.stack_offset <= self.stack_offset < stmt.dst.stack_offset + stmt.dst.size
83
+ and stmt.dst.stack_offset <= self.stack_offset + self.size <= stmt.dst.stack_offset + stmt.dst.size
84
84
  ):
85
85
  self.vvars.add(stmt.dst)
86
86
  return True
@@ -143,7 +143,9 @@ class SReachingDefinitionsAnalysis(Analysis):
143
143
  cc = cc_cls(self.project.arch)
144
144
 
145
145
  codeloc = CodeLocation(block_addr, stmt_idx, block_idx=block_idx, ins_addr=stmt.ins_addr)
146
- arg_locs = cc.ARG_REGS
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]
147
149
 
148
150
  for arg_reg_name in arg_locs:
149
151
  reg_offset = self.project.arch.registers[arg_reg_name][0]
@@ -84,7 +84,7 @@ class SimEngineVRAIL(
84
84
  if vvar is not None:
85
85
  r = self._read_from_vvar(vvar, expr=stmt.src, vvar_id=self._mapped_vvarid(vvar.varid))
86
86
  if r.variable is not None:
87
- pv = self.variable_manager[self.func_addr]._phi_variables
87
+ pv = self.state.variable_manager[self.func_addr]._phi_variables
88
88
  if variable not in pv:
89
89
  pv[variable] = set()
90
90
  pv[variable].add(r.variable)