angr 9.2.130__py3-none-manylinux2014_x86_64.whl → 9.2.132__py3-none-manylinux2014_x86_64.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 (127) hide show
  1. angr/__init__.py +1 -1
  2. angr/analyses/analysis.py +6 -2
  3. angr/analyses/cfg/cfg_emulated.py +5 -5
  4. angr/analyses/cfg/cfg_fast.py +2 -2
  5. angr/analyses/cfg/indirect_jump_resolvers/jumptable.py +139 -94
  6. angr/analyses/cfg/indirect_jump_resolvers/x86_elf_pic_plt.py +1 -1
  7. angr/analyses/ddg.py +14 -11
  8. angr/analyses/decompiler/ail_simplifier.py +3 -2
  9. angr/analyses/decompiler/block_simplifier.py +10 -21
  10. angr/analyses/decompiler/clinic.py +361 -8
  11. angr/analyses/decompiler/condition_processor.py +12 -10
  12. angr/analyses/decompiler/dephication/graph_rewriting.py +1 -1
  13. angr/analyses/decompiler/dephication/rewriting_engine.py +169 -45
  14. angr/analyses/decompiler/dephication/seqnode_dephication.py +5 -4
  15. angr/analyses/decompiler/optimization_passes/__init__.py +0 -3
  16. angr/analyses/decompiler/optimization_passes/const_derefs.py +1 -0
  17. angr/analyses/decompiler/optimization_passes/div_simplifier.py +41 -16
  18. angr/analyses/decompiler/optimization_passes/engine_base.py +261 -83
  19. angr/analyses/decompiler/optimization_passes/inlined_string_transformation_simplifier.py +173 -35
  20. angr/analyses/decompiler/optimization_passes/mod_simplifier.py +5 -2
  21. angr/analyses/decompiler/optimization_passes/optimization_pass.py +39 -19
  22. angr/analyses/decompiler/peephole_optimizations/__init__.py +5 -1
  23. angr/analyses/decompiler/peephole_optimizations/a_mul_const_sub_a.py +34 -0
  24. angr/analyses/decompiler/peephole_optimizations/a_shl_const_sub_a.py +3 -1
  25. angr/analyses/decompiler/peephole_optimizations/bswap.py +10 -6
  26. angr/analyses/decompiler/peephole_optimizations/eager_eval.py +100 -19
  27. angr/analyses/decompiler/peephole_optimizations/remove_noop_conversions.py +17 -0
  28. angr/analyses/decompiler/peephole_optimizations/remove_redundant_conversions.py +42 -3
  29. angr/analyses/decompiler/peephole_optimizations/remove_redundant_shifts.py +4 -2
  30. angr/analyses/decompiler/peephole_optimizations/rol_ror.py +37 -10
  31. angr/analyses/decompiler/peephole_optimizations/shl_to_mul.py +25 -0
  32. angr/analyses/decompiler/peephole_optimizations/utils.py +18 -0
  33. angr/analyses/decompiler/presets/fast.py +0 -2
  34. angr/analyses/decompiler/presets/full.py +0 -2
  35. angr/analyses/decompiler/ssailification/rewriting.py +1 -2
  36. angr/analyses/decompiler/ssailification/rewriting_engine.py +140 -57
  37. angr/analyses/decompiler/ssailification/ssailification.py +2 -1
  38. angr/analyses/decompiler/ssailification/traversal.py +4 -6
  39. angr/analyses/decompiler/ssailification/traversal_engine.py +125 -42
  40. angr/analyses/decompiler/structured_codegen/c.py +79 -16
  41. angr/analyses/decompiler/structuring/phoenix.py +40 -14
  42. angr/analyses/decompiler/structuring/structurer_nodes.py +9 -0
  43. angr/analyses/deobfuscator/irsb_reg_collector.py +29 -60
  44. angr/analyses/deobfuscator/string_obf_finder.py +2 -2
  45. angr/analyses/init_finder.py +47 -22
  46. angr/analyses/propagator/engine_base.py +21 -14
  47. angr/analyses/propagator/engine_vex.py +149 -179
  48. angr/analyses/propagator/propagator.py +10 -28
  49. angr/analyses/propagator/top_checker_mixin.py +211 -5
  50. angr/analyses/propagator/vex_vars.py +1 -1
  51. angr/analyses/reaching_definitions/dep_graph.py +1 -1
  52. angr/analyses/reaching_definitions/engine_ail.py +304 -329
  53. angr/analyses/reaching_definitions/engine_vex.py +243 -229
  54. angr/analyses/reaching_definitions/function_handler.py +3 -3
  55. angr/analyses/reaching_definitions/rd_state.py +37 -32
  56. angr/analyses/s_propagator.py +38 -5
  57. angr/analyses/s_reaching_definitions/s_reaching_definitions.py +9 -5
  58. angr/analyses/typehoon/simple_solver.py +16 -7
  59. angr/analyses/typehoon/translator.py +8 -0
  60. angr/analyses/typehoon/typeconsts.py +10 -2
  61. angr/analyses/typehoon/typehoon.py +4 -1
  62. angr/analyses/typehoon/typevars.py +9 -7
  63. angr/analyses/variable_recovery/engine_ail.py +296 -256
  64. angr/analyses/variable_recovery/engine_base.py +137 -116
  65. angr/analyses/variable_recovery/engine_vex.py +175 -185
  66. angr/analyses/variable_recovery/irsb_scanner.py +49 -38
  67. angr/analyses/variable_recovery/variable_recovery.py +28 -5
  68. angr/analyses/variable_recovery/variable_recovery_base.py +32 -33
  69. angr/analyses/variable_recovery/variable_recovery_fast.py +2 -2
  70. angr/analyses/xrefs.py +46 -19
  71. angr/annocfg.py +19 -14
  72. angr/block.py +4 -9
  73. angr/calling_conventions.py +1 -1
  74. angr/engines/engine.py +30 -14
  75. angr/engines/light/__init__.py +11 -3
  76. angr/engines/light/engine.py +1003 -1185
  77. angr/engines/pcode/cc.py +2 -0
  78. angr/engines/successors.py +13 -9
  79. angr/engines/vex/claripy/datalayer.py +1 -1
  80. angr/engines/vex/claripy/irop.py +14 -3
  81. angr/engines/vex/light/slicing.py +2 -2
  82. angr/exploration_techniques/__init__.py +1 -124
  83. angr/exploration_techniques/base.py +126 -0
  84. angr/exploration_techniques/bucketizer.py +1 -1
  85. angr/exploration_techniques/dfs.py +3 -1
  86. angr/exploration_techniques/director.py +2 -3
  87. angr/exploration_techniques/driller_core.py +1 -1
  88. angr/exploration_techniques/explorer.py +4 -2
  89. angr/exploration_techniques/lengthlimiter.py +2 -1
  90. angr/exploration_techniques/local_loop_seer.py +2 -1
  91. angr/exploration_techniques/loop_seer.py +5 -5
  92. angr/exploration_techniques/manual_mergepoint.py +2 -1
  93. angr/exploration_techniques/memory_watcher.py +3 -1
  94. angr/exploration_techniques/oppologist.py +4 -5
  95. angr/exploration_techniques/slicecutor.py +4 -2
  96. angr/exploration_techniques/spiller.py +1 -1
  97. angr/exploration_techniques/stochastic.py +2 -1
  98. angr/exploration_techniques/stub_stasher.py +2 -1
  99. angr/exploration_techniques/suggestions.py +3 -1
  100. angr/exploration_techniques/symbion.py +3 -1
  101. angr/exploration_techniques/tech_builder.py +2 -1
  102. angr/exploration_techniques/threading.py +4 -7
  103. angr/exploration_techniques/timeout.py +4 -2
  104. angr/exploration_techniques/tracer.py +4 -3
  105. angr/exploration_techniques/unique.py +3 -2
  106. angr/exploration_techniques/veritesting.py +1 -1
  107. angr/knowledge_plugins/key_definitions/atoms.py +2 -2
  108. angr/knowledge_plugins/key_definitions/live_definitions.py +16 -13
  109. angr/knowledge_plugins/propagations/states.py +13 -8
  110. angr/knowledge_plugins/variables/variable_manager.py +23 -9
  111. angr/sim_manager.py +1 -3
  112. angr/sim_state.py +39 -41
  113. angr/sim_type.py +5 -0
  114. angr/sim_variable.py +29 -28
  115. angr/utils/bits.py +17 -0
  116. angr/utils/formatting.py +4 -1
  117. angr/utils/orderedset.py +4 -1
  118. angr/utils/ssa/__init__.py +21 -3
  119. {angr-9.2.130.dist-info → angr-9.2.132.dist-info}/METADATA +6 -6
  120. {angr-9.2.130.dist-info → angr-9.2.132.dist-info}/RECORD +124 -123
  121. angr/analyses/decompiler/optimization_passes/multi_simplifier.py +0 -223
  122. angr/analyses/propagator/engine_ail.py +0 -1562
  123. angr/storage/memory_mixins/__init__.pyi +0 -48
  124. {angr-9.2.130.dist-info → angr-9.2.132.dist-info}/LICENSE +0 -0
  125. {angr-9.2.130.dist-info → angr-9.2.132.dist-info}/WHEEL +0 -0
  126. {angr-9.2.130.dist-info → angr-9.2.132.dist-info}/entry_points.txt +0 -0
  127. {angr-9.2.130.dist-info → angr-9.2.132.dist-info}/top_level.txt +0 -0
angr/analyses/ddg.py CHANGED
@@ -2,8 +2,10 @@ from __future__ import annotations
2
2
  import logging
3
3
  from collections import defaultdict
4
4
 
5
+ import claripy
5
6
  import networkx
6
7
  import pyvex
8
+
7
9
  from . import Analysis
8
10
 
9
11
  from angr.code_location import CodeLocation
@@ -1030,9 +1032,9 @@ class DDG(Analysis):
1030
1032
 
1031
1033
  if not action.data.reg_deps and not action.data.tmp_deps:
1032
1034
  # might be a constant assignment
1033
- v = action.data.ast
1035
+ v: claripy.ast.BV = action.data.ast
1034
1036
  if not v.symbolic:
1035
- const_var = SimConstantVariable(v.concrete_value)
1037
+ const_var = SimConstantVariable(value=v.concrete_value, size=v.size())
1036
1038
  const_progvar = ProgramVariable(const_var, prog_var.location)
1037
1039
  self._data_graph_add_edge(const_progvar, prog_var, type="mem_data")
1038
1040
 
@@ -1109,7 +1111,8 @@ class DDG(Analysis):
1109
1111
  elif isinstance(statement.data, pyvex.IRExpr.Const):
1110
1112
  # assignment
1111
1113
  const = statement.data.con.value
1112
- self._ast_graph.add_edge(ProgramVariable(SimConstantVariable(const), location), pv)
1114
+ size = statement.data.con.size
1115
+ self._ast_graph.add_edge(ProgramVariable(SimConstantVariable(value=const, size=size), location), pv)
1113
1116
 
1114
1117
  def _handle_reg_read(self, action, location, state, statement): # pylint:disable=unused-argument
1115
1118
  reg_offset = action.offset
@@ -1140,7 +1143,7 @@ class DDG(Analysis):
1140
1143
  elif reg_offset == self.project.arch.bp_offset:
1141
1144
  self._custom_data_per_statement = ("bp", 0)
1142
1145
 
1143
- def _handle_reg_write(self, action, location, state, statement): # pylint:disable=unused-argument
1146
+ def _handle_reg_write(self, action, location, state, statement: pyvex.stmt.Put): # pylint:disable=unused-argument
1144
1147
  reg_offset = action.offset
1145
1148
  variable = SimRegisterVariable(reg_offset, action.data.ast.size() // 8)
1146
1149
 
@@ -1157,9 +1160,9 @@ class DDG(Analysis):
1157
1160
  if not action.reg_deps and not action.tmp_deps:
1158
1161
  # moving a constant into the register
1159
1162
  # try to parse out the constant from statement
1160
- const_variable = SimConstantVariable()
1163
+ const_variable = SimConstantVariable(size=1)
1161
1164
  if statement is not None and isinstance(statement.data, pyvex.IRExpr.Const):
1162
- const_variable = SimConstantVariable(value=statement.data.con.value)
1165
+ const_variable = SimConstantVariable(value=statement.data.con.value, size=statement.data.con.size)
1163
1166
  const_pv = ProgramVariable(const_variable, location, arch=self.project.arch)
1164
1167
  self._data_graph_add_edge(const_pv, pv)
1165
1168
 
@@ -1187,7 +1190,7 @@ class DDG(Analysis):
1187
1190
  ast = None
1188
1191
 
1189
1192
  tmp = action.tmp
1190
- pv = ProgramVariable(SimTemporaryVariable(tmp), location, arch=self.project.arch)
1193
+ pv = ProgramVariable(SimTemporaryVariable(tmp, len(action.data)), location, arch=self.project.arch)
1191
1194
 
1192
1195
  if ast is not None:
1193
1196
  for operand in ast.operands:
@@ -1230,12 +1233,12 @@ class DDG(Analysis):
1230
1233
  if not action.tmp_deps and not self._variables_per_statement and not ast:
1231
1234
  # read in a constant
1232
1235
  # try to parse out the constant from statement
1233
- const_variable = SimConstantVariable()
1236
+ const_variable = SimConstantVariable(size=1)
1234
1237
  if statement is not None:
1235
1238
  if isinstance(statement, pyvex.IRStmt.Dirty):
1236
1239
  l.warning("Dirty statements are not supported in DDG for now.")
1237
1240
  elif isinstance(statement.data, pyvex.IRExpr.Const):
1238
- const_variable = SimConstantVariable(value=statement.data.con.value)
1241
+ const_variable = SimConstantVariable(value=statement.data.con.value, size=statement.data.con.size)
1239
1242
  const_pv = ProgramVariable(const_variable, location, arch=self.project.arch)
1240
1243
  self._data_graph_add_edge(const_pv, pv)
1241
1244
 
@@ -1296,7 +1299,7 @@ class DDG(Analysis):
1296
1299
  const_value = expr_1.ast.args[0]
1297
1300
  tmp = next(iter(expr_0.tmp_deps))
1298
1301
 
1299
- const_def = ProgramVariable(SimConstantVariable(const_value), location)
1302
+ const_def = ProgramVariable(SimConstantVariable(value=const_value, size=len(expr_1.ast)), location)
1300
1303
  tmp_def = self._temp_variables[tmp]
1301
1304
  return AST("-", tmp_def, const_def)
1302
1305
 
@@ -1310,7 +1313,7 @@ class DDG(Analysis):
1310
1313
  const_value = expr_1.ast.args[0]
1311
1314
  tmp = next(iter(expr_0.tmp_deps))
1312
1315
 
1313
- const_def = ProgramVariable(SimConstantVariable(const_value), location)
1316
+ const_def = ProgramVariable(SimConstantVariable(value=const_value, size=len(expr_1.ast)), location)
1314
1317
  tmp_def = self._temp_variables[tmp]
1315
1318
  return AST("+", tmp_def, const_def)
1316
1319
 
@@ -24,6 +24,7 @@ from ailment.expression import (
24
24
  VirtualVariable,
25
25
  )
26
26
 
27
+ from angr.analyses.s_propagator import SPropagatorAnalysis
27
28
  from angr.analyses.s_reaching_definitions import SRDAModel
28
29
  from angr.utils.ail import is_phi_assignment, HasExprWalker
29
30
  from angr.code_location import CodeLocation, ExternalCodeLocation
@@ -213,11 +214,11 @@ class AILSimplifier(Analysis):
213
214
  self._reaching_definitions = rd
214
215
  return rd
215
216
 
216
- def _compute_propagation(self, immediate_stmt_removal: bool = False):
217
+ def _compute_propagation(self, immediate_stmt_removal: bool = False) -> SPropagatorAnalysis:
217
218
  # Propagate expressions or return the existing result
218
219
  if self._propagator is not None:
219
220
  return self._propagator
220
- prop = self.project.analyses.SPropagator(
221
+ prop = self.project.analyses[SPropagatorAnalysis].prep()(
221
222
  subject=self.func,
222
223
  func_graph=self.func_graph,
223
224
  # gp=self._gp,
@@ -2,10 +2,10 @@
2
2
  from __future__ import annotations
3
3
  import logging
4
4
  from typing import TYPE_CHECKING
5
- from collections.abc import Iterable
5
+ from collections.abc import Iterable, Mapping
6
6
 
7
7
  from ailment.statement import Statement, Assignment, Call, Store, Jump
8
- from ailment.expression import Tmp, Load, Const, Register, Convert
8
+ from ailment.expression import Tmp, Load, Const, Register, Convert, Expression
9
9
  from ailment import AILBlockWalkerBase
10
10
 
11
11
  from angr.code_location import ExternalCodeLocation, CodeLocation
@@ -139,7 +139,7 @@ class BlockSimplifier(Analysis):
139
139
 
140
140
  self.result_block = block
141
141
 
142
- def _compute_propagation(self, block):
142
+ def _compute_propagation(self, block) -> SPropagatorAnalysis:
143
143
  if self._propagator is None:
144
144
  self._propagator = self.project.analyses[SPropagatorAnalysis].prep()(
145
145
  subject=block,
@@ -155,7 +155,6 @@ class BlockSimplifier(Analysis):
155
155
  .prep()(
156
156
  subject=block,
157
157
  track_tmps=True,
158
- stack_pointer_tracker=self._stack_pointer_tracker,
159
158
  func_addr=self.func_addr,
160
159
  )
161
160
  .model
@@ -201,8 +200,8 @@ class BlockSimplifier(Analysis):
201
200
 
202
201
  @staticmethod
203
202
  def _replace_and_build(
204
- block,
205
- replacements,
203
+ block: Block,
204
+ replacements: Mapping[CodeLocation, Mapping[Expression, Expression]],
206
205
  replace_assignment_dsts: bool = False,
207
206
  replace_loads: bool = False,
208
207
  gp: int | None = None,
@@ -211,14 +210,9 @@ class BlockSimplifier(Analysis):
211
210
  new_statements = block.statements[::]
212
211
  replaced = False
213
212
 
214
- stmts_to_remove = set()
215
213
  for codeloc, repls in replacements.items():
216
214
  for old, new in repls.items():
217
- stmt_to_remove = None
218
- if isinstance(new, dict):
219
- stmt_to_remove = new["stmt_to_remove"]
220
- new = new["expr"]
221
-
215
+ assert codeloc.stmt_idx is not None
222
216
  stmt = new_statements[codeloc.stmt_idx]
223
217
  if (
224
218
  not replace_loads
@@ -229,7 +223,9 @@ class BlockSimplifier(Analysis):
229
223
  # skip memory-based replacement for non-Call and non-gp-loading statements
230
224
  continue
231
225
  if stmt == old:
232
- # replace this statement
226
+ # the replacement must be a call, since replacements can only be expressions
227
+ # and call is the only thing which is both a statement and an expression
228
+ assert isinstance(new, Call)
233
229
  r = True
234
230
  new_stmt = new
235
231
  else:
@@ -257,20 +253,13 @@ class BlockSimplifier(Analysis):
257
253
  r, new_stmt = stmt.replace(old, new)
258
254
 
259
255
  if r:
256
+ assert new_stmt is not None
260
257
  replaced = True
261
258
  new_statements[codeloc.stmt_idx] = new_stmt
262
- if stmt_to_remove is not None:
263
- stmts_to_remove.add(stmt_to_remove)
264
259
 
265
260
  if not replaced:
266
261
  return False, block
267
262
 
268
- if stmts_to_remove:
269
- stmt_ids_to_remove = {a.stmt_idx for a in stmts_to_remove}
270
- all_stmts = {idx: stmt for idx, stmt in enumerate(new_statements) if idx not in stmt_ids_to_remove}
271
- filtered_stmts = sorted(all_stmts.items(), key=lambda x: x[0])
272
- new_statements = [stmt for _, stmt in filtered_stmts]
273
-
274
263
  new_block = block.copy()
275
264
  new_block.statements = new_statements
276
265
  return True, new_block
@@ -1,3 +1,4 @@
1
+ # pylint:disable=too-many-boolean-expressions
1
2
  from __future__ import annotations
2
3
  from typing import Any, NamedTuple, TYPE_CHECKING
3
4
  import copy
@@ -12,12 +13,14 @@ import capstone
12
13
 
13
14
  import ailment
14
15
 
16
+ from angr.analyses.decompiler.ssailification.ssailification import Ssailification
15
17
  from angr.errors import AngrDecompilationError
16
18
  from angr.knowledge_base import KnowledgeBase
17
19
  from angr.knowledge_plugins.functions import Function
18
20
  from angr.knowledge_plugins.cfg.memory_data import MemoryDataSort
19
21
  from angr.codenode import BlockNode
20
22
  from angr.utils import timethis
23
+ from angr.utils.graph import GraphUtils
21
24
  from angr.calling_conventions import SimRegArg, SimStackArg, SimFunctionArgument
22
25
  from angr.sim_type import (
23
26
  SimTypeChar,
@@ -115,6 +118,7 @@ class Clinic(Analysis):
115
118
  desired_variables: set[str] | None = None,
116
119
  force_loop_single_exit: bool = True,
117
120
  complete_successors: bool = False,
121
+ unsound_fix_abnormal_switches: bool = True,
118
122
  ):
119
123
  if not func.normalized and mode == ClinicMode.DECOMPILE:
120
124
  raise ValueError("Decompilation must work on normalized function graphs.")
@@ -146,6 +150,7 @@ class Clinic(Analysis):
146
150
  self._must_struct = must_struct
147
151
  self._reset_variable_names = reset_variable_names
148
152
  self._rewrite_ites_to_diamonds = rewrite_ites_to_diamonds
153
+ self._unsound_fix_abnormal_switches = unsound_fix_abnormal_switches
149
154
  self.reaching_definitions: ReachingDefinitionsAnalysis | None = None
150
155
  self._cache = cache
151
156
  self._mode = mode
@@ -265,6 +270,9 @@ class Clinic(Analysis):
265
270
  def _decompilation_fixups(self, ail_graph):
266
271
  is_pcode_arch = ":" in self.project.arch.name
267
272
 
273
+ self._remove_redundant_jump_blocks(ail_graph)
274
+ # _fix_abnormal_switch_case_heads may re-lift from VEX blocks, so it should be placed as high up as possible
275
+ self._fix_abnormal_switch_case_heads(ail_graph)
268
276
  if self._rewrite_ites_to_diamonds:
269
277
  self._rewrite_ite_expressions(ail_graph)
270
278
  self._remove_redundant_jump_blocks(ail_graph)
@@ -941,7 +949,7 @@ class Clinic(Analysis):
941
949
 
942
950
  def _convert(self, block_node):
943
951
  """
944
- Convert a VEX block to an AIL block.
952
+ Convert a BlockNode to an AIL block.
945
953
 
946
954
  :param block_node: A BlockNode instance.
947
955
  :return: A converted AIL block.
@@ -955,13 +963,16 @@ class Clinic(Analysis):
955
963
  return ailment.Block(block_node.addr, 0, statements=[])
956
964
 
957
965
  block = self.project.factory.block(block_node.addr, block_node.size, cross_insn_opt=False)
966
+ return self._convert_vex(block)
967
+
968
+ def _convert_vex(self, block):
958
969
  if block.vex.jumpkind not in {"Ijk_Call", "Ijk_Boring", "Ijk_Ret"} and not block.vex.jumpkind.startswith(
959
970
  "Ijk_Sys"
960
971
  ):
961
972
  # we don't support lifting this block. use a dummy block instead
962
973
  dirty_expr = ailment.Expr.DirtyExpression(
963
974
  self._ail_manager.next_atom,
964
- f"Unsupported jumpkind {block.vex.jumpkind} at address {block_node.addr}",
975
+ f"Unsupported jumpkind {block.vex.jumpkind} at address {block.addr}",
965
976
  [],
966
977
  bits=0,
967
978
  )
@@ -969,10 +980,10 @@ class Clinic(Analysis):
969
980
  ailment.Stmt.DirtyStatement(
970
981
  self._ail_manager.next_atom(),
971
982
  dirty_expr,
972
- ins_addr=block_node.addr,
983
+ ins_addr=block.addr,
973
984
  )
974
985
  ]
975
- return ailment.Block(block_node.addr, block_node.size, statements=statements)
986
+ return ailment.Block(block.addr, block.size, statements=statements)
976
987
 
977
988
  return ailment.IRSBConverter.convert(block.vex, self._ail_manager)
978
989
 
@@ -1351,10 +1362,9 @@ class Clinic(Analysis):
1351
1362
 
1352
1363
  @timethis
1353
1364
  def _transform_to_ssa_level0(self, ail_graph: networkx.DiGraph) -> networkx.DiGraph:
1354
- ssailification = self.project.analyses.Ssailification(
1365
+ ssailification = self.project.analyses[Ssailification].prep(fail_fast=self._fail_fast)(
1355
1366
  self.function,
1356
1367
  ail_graph,
1357
- fail_fast=self._fail_fast,
1358
1368
  entry=next(iter(bb for bb in ail_graph if (bb.addr, bb.idx) == self.entry_node_addr)),
1359
1369
  ail_manager=self._ail_manager,
1360
1370
  ssa_stackvars=False,
@@ -1924,10 +1934,12 @@ class Clinic(Analysis):
1924
1934
  break
1925
1935
 
1926
1936
  def _create_triangle_for_ite_expression(self, ail_graph, block_addr: int, ite_ins_addr: int):
1927
- # lift the ite instruction to get its size
1928
- ite_insn_size = self.project.factory.block(ite_ins_addr, num_inst=1).size
1937
+ ite_insn_only_block = self.project.factory.block(ite_ins_addr, num_inst=1)
1938
+ ite_insn_size = ite_insn_only_block.size
1929
1939
  if ite_insn_size <= 2: # we need an address for true_block and another address for false_block
1930
1940
  return None
1941
+ if ite_insn_only_block.vex.exit_statements:
1942
+ return None
1931
1943
 
1932
1944
  # relift the head and the ITE instruction
1933
1945
  new_head = self.project.factory.block(
@@ -2029,6 +2041,10 @@ class Clinic(Analysis):
2029
2041
  ):
2030
2042
  return None
2031
2043
 
2044
+ # corner-case: the last statement of original_block might have been patched by _remove_redundant_jump_blocks.
2045
+ # we detect such case and fix it in new_head_ail
2046
+ self._remove_redundant_jump_blocks_repatch_relifted_block(original_block, end_block_ail)
2047
+
2032
2048
  ail_graph.remove_node(original_block)
2033
2049
 
2034
2050
  if end_block_ail not in ail_graph:
@@ -2055,6 +2071,310 @@ class Clinic(Analysis):
2055
2071
 
2056
2072
  return end_block_ail.addr
2057
2073
 
2074
+ def _fix_abnormal_switch_case_heads(self, ail_graph: networkx.DiGraph) -> None:
2075
+ """
2076
+ Detect the existence of switch-case heads whose indirect jump node has more than one predecessor, and attempt
2077
+ to fix those cases by altering the graph.
2078
+ """
2079
+
2080
+ if self._cfg is None:
2081
+ return
2082
+
2083
+ if not self._cfg.jump_tables:
2084
+ return
2085
+
2086
+ node_dict: defaultdict[int, list[ailment.Block]] = defaultdict(list)
2087
+ for node in ail_graph:
2088
+ node_dict[node.addr].append(node)
2089
+
2090
+ candidates = []
2091
+ for block_addr in self._cfg.jump_tables:
2092
+ block_nodes = node_dict[block_addr]
2093
+ for block_node in block_nodes:
2094
+ if ail_graph.in_degree[block_node] > 1:
2095
+ # found it
2096
+ candidates.append(block_node)
2097
+
2098
+ if not candidates:
2099
+ return
2100
+
2101
+ sorted_nodes = GraphUtils.quasi_topological_sort_nodes(ail_graph)
2102
+ node_to_rank = {node: rank for rank, node in enumerate(sorted_nodes)}
2103
+ for candidate in candidates:
2104
+ # determine the "intended" switch-case head using topological order
2105
+ preds = list(ail_graph.predecessors(candidate))
2106
+ preds = sorted(preds, key=lambda n_: node_to_rank[n_])
2107
+ intended_head = preds[0]
2108
+ other_heads = preds[1:]
2109
+
2110
+ # now here is the tricky part. there are two cases:
2111
+ # Case 1: the intended head and the other heads share the same suffix (of instructions)
2112
+ # Example:
2113
+ # ; binary 736cb27201273f6c4f83da362c9595b50d12333362e02bc7a77dd327cc6b045a
2114
+ # 0041DA97 mov ecx, [esp+2Ch+var_18] ; this is the intended head
2115
+ # 0041DA9B mov ecx, [ecx]
2116
+ # 0041DA9D cmp ecx, 9
2117
+ # 0041DAA0 jbe loc_41D5A8
2118
+ #
2119
+ # 0041D599 mov ecx, [ecx] ; this is the other head
2120
+ # 0041D59B mov [esp+2Ch+var_10], eax
2121
+ # 0041D59F cmp ecx, 9
2122
+ # 0041D5A2 ja loc_41DAA6 ; fallthrough to 0x41d5a8
2123
+ # given the overlap of two instructions at the end of both blocks, we will alter the second block to remove
2124
+ # the overlapped instructions and add an unconditional jump so that it jumps to 0x41da9d.
2125
+ # this is the most common case created by jump threading optimization in compilers. it's easy to handle.
2126
+
2127
+ # Case 2: the intended head and the other heads do not share the same suffix of instructions. in this case,
2128
+ # we cannot reliably convert the blocks into a properly structured switch-case construct. we will alter the
2129
+ # last instruction of all other heads to jump to the cmp instruction in the intended head, but do not remove
2130
+ # any other instructions in these other heads. this is unsound, but is the best we can do in this case.
2131
+
2132
+ overlaps = [self._get_overlapping_suffix_instructions(intended_head, head) for head in other_heads]
2133
+ if overlaps and (overlap := min(overlaps)) > 0:
2134
+ # Case 1
2135
+ self._fix_abnormal_switch_case_heads_case1(ail_graph, candidate, intended_head, other_heads, overlap)
2136
+ else:
2137
+ if self._unsound_fix_abnormal_switches:
2138
+ # Case 2
2139
+ l.warning("Switch-case at %#x has multiple head nodes but cannot be fixed soundly.", candidate.addr)
2140
+ # find the comparison instruction in the intended head
2141
+ comparison_stmt = None
2142
+ if "cc_op" in self.project.arch.registers:
2143
+ comparison_stmt = next(
2144
+ iter(
2145
+ stmt
2146
+ for stmt in intended_head.statements
2147
+ if isinstance(stmt, ailment.Stmt.Assignment)
2148
+ and isinstance(stmt.dst, ailment.Expr.Register)
2149
+ and stmt.dst.reg_offset == self.project.arch.registers["cc_op"][0]
2150
+ ),
2151
+ None,
2152
+ )
2153
+ intended_head_block = self.project.factory.block(
2154
+ intended_head.addr, size=intended_head.original_size
2155
+ )
2156
+ if comparison_stmt is not None:
2157
+ cmp_rpos = len(
2158
+ intended_head_block.instruction_addrs
2159
+ ) - intended_head_block.instruction_addrs.index(comparison_stmt.ins_addr)
2160
+ else:
2161
+ cmp_rpos = min(len(intended_head_block.instruction_addrs), 2)
2162
+ self._fix_abnormal_switch_case_heads_case2(
2163
+ ail_graph,
2164
+ candidate,
2165
+ intended_head,
2166
+ other_heads,
2167
+ intended_head_split_insns=cmp_rpos,
2168
+ other_head_split_insns=0,
2169
+ )
2170
+
2171
+ def _get_overlapping_suffix_instructions(self, ailblock_0: ailment.Block, ailblock_1: ailment.Block) -> int:
2172
+ # we first compare their ending conditional jumps
2173
+ if not self._get_overlapping_suffix_instructions_compare_conditional_jumps(ailblock_0, ailblock_1):
2174
+ return 0
2175
+
2176
+ # we re-lift the blocks and compare the instructions
2177
+ block_0 = self.project.factory.block(ailblock_0.addr, size=ailblock_0.original_size)
2178
+ block_1 = self.project.factory.block(ailblock_1.addr, size=ailblock_1.original_size)
2179
+
2180
+ i0 = len(block_0.capstone.insns) - 2
2181
+ i1 = len(block_1.capstone.insns) - 2
2182
+ overlap = 1
2183
+ while i0 >= 0 and i1 >= 0:
2184
+ same = self._get_overlapping_suffix_instructions_compare_instructions(
2185
+ block_0.capstone.insns[i0], block_1.capstone.insns[i1]
2186
+ )
2187
+ if not same:
2188
+ break
2189
+ overlap += 1
2190
+ i0 -= 1
2191
+ i1 -= 1
2192
+
2193
+ return overlap
2194
+
2195
+ @staticmethod
2196
+ def _get_overlapping_suffix_instructions_compare_instructions(insn_0, insn_1) -> bool:
2197
+ return insn_0.mnemonic == insn_1.mnemonic and insn_0.op_str == insn_1.op_str
2198
+
2199
+ @staticmethod
2200
+ def _get_overlapping_suffix_instructions_compare_conditional_jumps(
2201
+ ailblock_0: ailment.Block, ailblock_1: ailment.Block
2202
+ ) -> bool:
2203
+ # TODO: The logic here is naive and highly customized to the only example I can access. Expand this method
2204
+ # later to handle more cases if needed.
2205
+ if len(ailblock_0.statements) == 0 or len(ailblock_1.statements) == 0:
2206
+ return False
2207
+
2208
+ # 12 | 0x41d5a2 | t17 = (t4 <= 0x9<32>)
2209
+ # 13 | 0x41d5a2 | t16 = Conv(1->32, t17)
2210
+ # 14 | 0x41d5a2 | t14 = t16
2211
+ # 15 | 0x41d5a2 | t18 = Conv(32->1, t14)
2212
+ # 16 | 0x41d5a2 | t9 = t18
2213
+ # 17 | 0x41d5a2 | if (t9) { Goto 0x41d5a8<32> } else { Goto 0x41daa6<32> }
2214
+
2215
+ last_stmt_0 = ailblock_0.statements[-1]
2216
+ last_stmt_1 = ailblock_1.statements[-1]
2217
+ if not (isinstance(last_stmt_0, ailment.Stmt.ConditionalJump) and last_stmt_0.likes(last_stmt_1)):
2218
+ return False
2219
+
2220
+ last_cmp_stmt_0 = next(
2221
+ iter(
2222
+ stmt
2223
+ for stmt in reversed(ailblock_0.statements)
2224
+ if isinstance(stmt, ailment.Stmt.Assignment)
2225
+ and isinstance(stmt.src, ailment.Expr.BinaryOp)
2226
+ and stmt.src.op in ailment.Expr.BinaryOp.COMPARISON_NEGATION
2227
+ and stmt.ins_addr == last_stmt_0.ins_addr
2228
+ ),
2229
+ None,
2230
+ )
2231
+ last_cmp_stmt_1 = next(
2232
+ iter(
2233
+ stmt
2234
+ for stmt in reversed(ailblock_1.statements)
2235
+ if isinstance(stmt, ailment.Stmt.Assignment)
2236
+ and isinstance(stmt.src, ailment.Expr.BinaryOp)
2237
+ and stmt.src.op in ailment.Expr.BinaryOp.COMPARISON_NEGATION
2238
+ and stmt.ins_addr == last_stmt_1.ins_addr
2239
+ ),
2240
+ None,
2241
+ )
2242
+ return (
2243
+ last_cmp_stmt_0 is not None
2244
+ and last_cmp_stmt_1 is not None
2245
+ and last_cmp_stmt_0.src.op == last_cmp_stmt_1.src.op
2246
+ and last_cmp_stmt_0.src.operands[1].likes(last_cmp_stmt_1.src.operands[1])
2247
+ )
2248
+
2249
+ def _fix_abnormal_switch_case_heads_case1(
2250
+ self,
2251
+ ail_graph: networkx.DiGraph,
2252
+ indirect_jump_node: ailment.Block,
2253
+ intended_head: ailment.Block,
2254
+ other_heads: list[ailment.Block],
2255
+ overlap: int,
2256
+ ) -> None:
2257
+ self._fix_abnormal_switch_case_heads_case2(
2258
+ ail_graph,
2259
+ indirect_jump_node,
2260
+ intended_head,
2261
+ other_heads,
2262
+ intended_head_split_insns=overlap,
2263
+ other_head_split_insns=overlap,
2264
+ )
2265
+
2266
+ def _fix_abnormal_switch_case_heads_case2(
2267
+ self,
2268
+ ail_graph: networkx.DiGraph,
2269
+ indirect_jump_node: ailment.Block,
2270
+ intended_head: ailment.Block,
2271
+ other_heads: list[ailment.Block],
2272
+ intended_head_split_insns: int = 1,
2273
+ other_head_split_insns: int = 0,
2274
+ ) -> None:
2275
+
2276
+ # split the intended head into two
2277
+ intended_head_block = self.project.factory.block(intended_head.addr, size=intended_head.original_size)
2278
+ split_ins_addr = intended_head_block.instruction_addrs[-intended_head_split_insns]
2279
+ # note that the two blocks can be fully overlapping, so block_0 will be empty...
2280
+ intended_head_block_0 = (
2281
+ self.project.factory.block(intended_head.addr, size=split_ins_addr - intended_head.addr)
2282
+ if split_ins_addr != intended_head.addr
2283
+ else None
2284
+ )
2285
+ intended_head_block_1 = self.project.factory.block(
2286
+ split_ins_addr, size=intended_head.addr + intended_head.original_size - split_ins_addr
2287
+ )
2288
+ intended_head_0 = self._convert_vex(intended_head_block_0) if intended_head_block_0 is not None else None
2289
+ intended_head_1 = self._convert_vex(intended_head_block_1)
2290
+
2291
+ # corner-case: the last statement of intended_head might have been patched by _remove_redundant_jump_blocks. we
2292
+ # detect such case and fix it in intended_head_1
2293
+ self._remove_redundant_jump_blocks_repatch_relifted_block(intended_head, intended_head_1)
2294
+
2295
+ # adjust the graph accordingly
2296
+ preds = list(ail_graph.predecessors(intended_head))
2297
+ succs = list(ail_graph.successors(intended_head))
2298
+ ail_graph.remove_node(intended_head)
2299
+
2300
+ if intended_head_0 is None:
2301
+ # perfect overlap; the first block is empty
2302
+ for pred in preds:
2303
+ if pred is intended_head:
2304
+ ail_graph.add_edge(intended_head_1, intended_head_1)
2305
+ else:
2306
+ ail_graph.add_edge(pred, intended_head_1)
2307
+ for succ in succs:
2308
+ if succ is intended_head:
2309
+ ail_graph.add_edge(intended_head_1, intended_head_1)
2310
+ else:
2311
+ ail_graph.add_edge(intended_head_1, succ)
2312
+ else:
2313
+ ail_graph.add_edge(intended_head_0, intended_head_1)
2314
+ for pred in preds:
2315
+ if pred is intended_head:
2316
+ ail_graph.add_edge(intended_head_1, intended_head_0)
2317
+ else:
2318
+ ail_graph.add_edge(pred, intended_head_0)
2319
+ for succ in succs:
2320
+ if succ is intended_head:
2321
+ ail_graph.add_edge(intended_head_1, intended_head_0)
2322
+ else:
2323
+ ail_graph.add_edge(intended_head_1, succ)
2324
+
2325
+ # split other heads
2326
+ for o in other_heads:
2327
+ if other_head_split_insns > 0:
2328
+ o_block = self.project.factory.block(o.addr, size=o.original_size)
2329
+ o_split_addr = o_block.instruction_addrs[-other_head_split_insns]
2330
+ new_o_block = (
2331
+ self.project.factory.block(o.addr, size=o_split_addr - o.addr) if o_split_addr != o.addr else None
2332
+ )
2333
+ new_head = self._convert_vex(new_o_block) if new_o_block is not None else None
2334
+ else:
2335
+ new_head = o
2336
+
2337
+ if new_head is None:
2338
+ # the head is removed - let's replace it with a jump to the target
2339
+ jump_stmt = ailment.Stmt.Jump(
2340
+ None,
2341
+ ailment.Expr.Const(None, None, intended_head_1.addr, self.project.arch.bits),
2342
+ target_idx=intended_head_1.idx,
2343
+ ins_addr=o.addr,
2344
+ )
2345
+ new_head = ailment.Block(o.addr, 1, statements=[jump_stmt], idx=o.idx)
2346
+ else:
2347
+ if (
2348
+ new_head.statements
2349
+ and isinstance(new_head.statements[-1], ailment.Stmt.Jump)
2350
+ and isinstance(new_head.statements[-1].target, ailment.Expr.Const)
2351
+ ):
2352
+ # update the jump target
2353
+ new_head.statements[-1] = ailment.Stmt.Jump(
2354
+ new_head.statements[-1].idx,
2355
+ ailment.Expr.Const(None, None, intended_head_1.addr, self.project.arch.bits),
2356
+ target_idx=intended_head_1.idx,
2357
+ **new_head.statements[-1].tags,
2358
+ )
2359
+
2360
+ # adjust the graph accordingly
2361
+ preds = list(ail_graph.predecessors(o))
2362
+ succs = list(ail_graph.successors(o))
2363
+ ail_graph.remove_node(o)
2364
+ for pred in preds:
2365
+ if pred is o:
2366
+ ail_graph.add_edge(new_head, new_head)
2367
+ else:
2368
+ ail_graph.add_edge(pred, new_head)
2369
+ for succ in succs:
2370
+ if succ is o:
2371
+ ail_graph.add_edge(new_head, new_head)
2372
+ elif succ is indirect_jump_node:
2373
+ ail_graph.add_edge(new_head, intended_head_1)
2374
+ else:
2375
+ # it should be going to the default node. ignore it
2376
+ pass
2377
+
2058
2378
  @staticmethod
2059
2379
  def _remove_redundant_jump_blocks(ail_graph):
2060
2380
  def first_conditional_jump(block: ailment.Block) -> ailment.Stmt.ConditionalJump | None:
@@ -2105,6 +2425,39 @@ class Clinic(Analysis):
2105
2425
  ail_graph.add_edge(pred, succs[0])
2106
2426
  ail_graph.remove_node(node)
2107
2427
 
2428
+ @staticmethod
2429
+ def _remove_redundant_jump_blocks_repatch_relifted_block(
2430
+ patched_block: ailment.Block, new_block: ailment.Block
2431
+ ) -> None:
2432
+ """
2433
+ The last statement of patched_block might have been patched by _remove_redundant_jump_blocks. In this case, we
2434
+ fix the last instruction for new_block, which is a newly lifted (from VEX) block that ends at the same address
2435
+ as patched_block.
2436
+
2437
+ :param patched_block: Previously patched block.
2438
+ :param new_block: Newly lifted block.
2439
+ """
2440
+
2441
+ if (
2442
+ isinstance(patched_block.statements[-1], ailment.Stmt.Jump)
2443
+ and isinstance(patched_block.statements[-1].target, ailment.Expr.Const)
2444
+ and isinstance(new_block.statements[-1], ailment.Stmt.Jump)
2445
+ and isinstance(new_block.statements[-1].target, ailment.Expr.Const)
2446
+ and not patched_block.statements[-1].likes(new_block.statements[-1])
2447
+ ):
2448
+ new_block.statements[-1].target = patched_block.statements[-1].target
2449
+ if (
2450
+ isinstance(patched_block.statements[-1], ailment.Stmt.ConditionalJump)
2451
+ and isinstance(patched_block.statements[-1].true_target, ailment.Expr.Const)
2452
+ and isinstance(patched_block.statements[-1].false_target, ailment.Expr.Const)
2453
+ and isinstance(new_block.statements[-1], ailment.Stmt.ConditionalJump)
2454
+ and isinstance(new_block.statements[-1].true_target, ailment.Expr.Const)
2455
+ and isinstance(new_block.statements[-1].false_target, ailment.Expr.Const)
2456
+ and not patched_block.statements[-1].likes(new_block.statements[-1])
2457
+ ):
2458
+ new_block.statements[-1].true_target = patched_block.statements[-1].true_target
2459
+ new_block.statements[-1].false_target = patched_block.statements[-1].false_target
2460
+
2108
2461
  @staticmethod
2109
2462
  def _insert_block_labels(ail_graph):
2110
2463
  for node in ail_graph.nodes: