angr 9.2.136__py3-none-manylinux2014_x86_64.whl → 9.2.137__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 (59) hide show
  1. angr/__init__.py +1 -1
  2. angr/analyses/calling_convention/calling_convention.py +2 -1
  3. angr/analyses/calling_convention/fact_collector.py +10 -2
  4. angr/analyses/cfg/cfg_base.py +3 -33
  5. angr/analyses/cfg/cfg_emulated.py +0 -103
  6. angr/analyses/cfg/cfg_fast.py +28 -12
  7. angr/analyses/cfg/indirect_jump_resolvers/jumptable.py +15 -0
  8. angr/analyses/class_identifier.py +1 -2
  9. angr/analyses/complete_calling_conventions.py +3 -0
  10. angr/analyses/decompiler/ail_simplifier.py +12 -1
  11. angr/analyses/decompiler/block_simplifier.py +2 -2
  12. angr/analyses/decompiler/ccall_rewriters/__init__.py +2 -0
  13. angr/analyses/decompiler/ccall_rewriters/amd64_ccalls.py +1 -1
  14. angr/analyses/decompiler/ccall_rewriters/x86_ccalls.py +69 -0
  15. angr/analyses/decompiler/clinic.py +77 -65
  16. angr/analyses/decompiler/condition_processor.py +2 -0
  17. angr/analyses/decompiler/decompiler.py +1 -0
  18. angr/analyses/decompiler/dephication/dephication_base.py +2 -0
  19. angr/analyses/decompiler/dephication/rewriting_engine.py +8 -6
  20. angr/analyses/decompiler/dephication/seqnode_dephication.py +10 -1
  21. angr/analyses/decompiler/optimization_passes/return_duplicator_base.py +1 -2
  22. angr/analyses/decompiler/sequence_walker.py +6 -2
  23. angr/analyses/decompiler/ssailification/rewriting.py +11 -1
  24. angr/analyses/decompiler/ssailification/rewriting_engine.py +56 -19
  25. angr/analyses/decompiler/ssailification/ssailification.py +13 -3
  26. angr/analyses/decompiler/ssailification/traversal.py +28 -2
  27. angr/analyses/decompiler/ssailification/traversal_state.py +6 -1
  28. angr/analyses/decompiler/structured_codegen/c.py +44 -21
  29. angr/analyses/decompiler/structuring/phoenix.py +117 -14
  30. angr/analyses/decompiler/utils.py +113 -8
  31. angr/analyses/reaching_definitions/function_handler.py +1 -1
  32. angr/analyses/s_liveness.py +5 -1
  33. angr/analyses/s_propagator.py +25 -4
  34. angr/analyses/s_reaching_definitions/s_rda_model.py +2 -1
  35. angr/analyses/s_reaching_definitions/s_rda_view.py +20 -1
  36. angr/analyses/s_reaching_definitions/s_reaching_definitions.py +11 -1
  37. angr/analyses/stack_pointer_tracker.py +26 -16
  38. angr/analyses/variable_recovery/engine_ail.py +19 -7
  39. angr/analyses/variable_recovery/engine_base.py +16 -14
  40. angr/analyses/variable_recovery/engine_vex.py +2 -2
  41. angr/analyses/variable_recovery/variable_recovery_fast.py +22 -1
  42. angr/block.py +59 -20
  43. angr/engines/pcode/emulate.py +1 -1
  44. angr/engines/pcode/lifter.py +31 -18
  45. angr/engines/soot/expressions/__init__.py +2 -4
  46. angr/engines/soot/statements/__init__.py +1 -2
  47. angr/engines/soot/values/__init__.py +1 -2
  48. angr/engines/successors.py +11 -6
  49. angr/engines/vex/lifter.py +9 -6
  50. angr/flirt/build_sig.py +8 -15
  51. angr/knowledge_plugins/functions/function.py +0 -6
  52. angr/knowledge_plugins/functions/soot_function.py +5 -8
  53. angr/knowledge_plugins/variables/variable_manager.py +16 -10
  54. {angr-9.2.136.dist-info → angr-9.2.137.dist-info}/METADATA +7 -7
  55. {angr-9.2.136.dist-info → angr-9.2.137.dist-info}/RECORD +59 -58
  56. {angr-9.2.136.dist-info → angr-9.2.137.dist-info}/WHEEL +1 -1
  57. {angr-9.2.136.dist-info → angr-9.2.137.dist-info}/LICENSE +0 -0
  58. {angr-9.2.136.dist-info → angr-9.2.137.dist-info}/entry_points.txt +0 -0
  59. {angr-9.2.136.dist-info → angr-9.2.137.dist-info}/top_level.txt +0 -0
@@ -679,14 +679,23 @@ class CStatements(CStatement):
679
679
  Represents a sequence of statements in C.
680
680
  """
681
681
 
682
- __slots__ = ("statements",)
682
+ __slots__ = (
683
+ "addr",
684
+ "statements",
685
+ )
683
686
 
684
- def __init__(self, statements, **kwargs):
687
+ def __init__(self, statements, addr=None, **kwargs):
685
688
  super().__init__(**kwargs)
686
689
 
687
690
  self.statements = statements
691
+ self.addr = addr
688
692
 
689
693
  def c_repr_chunks(self, indent=0, asexpr=False):
694
+ indent_str = self.indent_str(indent)
695
+ if self.codegen.display_block_addrs:
696
+ yield indent_str, None
697
+ yield f"/* Block {hex(self.addr) if self.addr is not None else 'unknown'} */", None
698
+ yield "\n", None
690
699
  for stmt in self.statements:
691
700
  yield from stmt.c_repr_chunks(indent=indent, asexpr=asexpr)
692
701
  if asexpr:
@@ -1572,15 +1581,19 @@ class CVariable(CExpression):
1572
1581
  "unified_variable",
1573
1582
  "variable",
1574
1583
  "variable_type",
1584
+ "vvar_id",
1575
1585
  )
1576
1586
 
1577
- def __init__(self, variable: SimVariable, unified_variable=None, variable_type=None, tags=None, **kwargs):
1587
+ def __init__(
1588
+ self, variable: SimVariable, unified_variable=None, variable_type=None, tags=None, vvar_id=None, **kwargs
1589
+ ):
1578
1590
  super().__init__(**kwargs)
1579
1591
 
1580
1592
  self.variable: SimVariable = variable
1581
1593
  self.unified_variable: SimVariable | None = unified_variable
1582
1594
  self.variable_type: SimType = variable_type.with_arch(self.codegen.project.arch)
1583
1595
  self.tags = tags
1596
+ self.vvar_id = vvar_id
1584
1597
 
1585
1598
  @property
1586
1599
  def type(self):
@@ -1598,6 +1611,8 @@ class CVariable(CExpression):
1598
1611
 
1599
1612
  def c_repr_chunks(self, indent=0, asexpr=False):
1600
1613
  yield self.name, self
1614
+ if self.codegen.display_vvar_ids:
1615
+ yield f"<vvar_{self.vvar_id}>", self
1601
1616
 
1602
1617
 
1603
1618
  class CIndexedVariable(CExpression):
@@ -2141,7 +2156,8 @@ class CConstant(CExpression):
2141
2156
  result = self.fmt.get("neg", None)
2142
2157
  if result is None:
2143
2158
  result = False
2144
- if isinstance(self.value, int):
2159
+ # guess it
2160
+ if isinstance(self._type, (SimTypeInt, SimTypeChar)) and self._type.signed and isinstance(self.value, int):
2145
2161
  value_size = self._type.size if self._type is not None else None
2146
2162
  if (value_size == 32 and 0xF000_0000 <= self.value <= 0xFFFF_FFFF) or (
2147
2163
  value_size == 64 and 0xF000_0000_0000_0000 <= self.value <= 0xFFFF_FFFF_FFFF_FFFF
@@ -2486,6 +2502,8 @@ class CStructuredCodeGenerator(BaseStructuredCodeGenerator, Analysis):
2486
2502
  simplify_else_scope=True,
2487
2503
  cstyle_ifs=True,
2488
2504
  omit_func_header=False,
2505
+ display_block_addrs=False,
2506
+ display_vvar_ids=False,
2489
2507
  ):
2490
2508
  super().__init__(flavor=flavor)
2491
2509
 
@@ -2558,6 +2576,8 @@ class CStructuredCodeGenerator(BaseStructuredCodeGenerator, Analysis):
2558
2576
  self.simplify_else_scope = simplify_else_scope
2559
2577
  self.cstyle_ifs = cstyle_ifs
2560
2578
  self.omit_func_header = omit_func_header
2579
+ self.display_block_addrs = display_block_addrs
2580
+ self.display_vvar_ids = display_vvar_ids
2561
2581
  self.text = None
2562
2582
  self.map_pos_to_node = None
2563
2583
  self.map_pos_to_addr = None
@@ -2740,7 +2760,7 @@ class CStructuredCodeGenerator(BaseStructuredCodeGenerator, Analysis):
2740
2760
  return _mapping.get(n)(signed=signed).with_arch(self.project.arch)
2741
2761
  return SimTypeNum(n, signed=signed).with_arch(self.project.arch)
2742
2762
 
2743
- def _variable(self, variable: SimVariable, fallback_type_size: int | None) -> CVariable:
2763
+ def _variable(self, variable: SimVariable, fallback_type_size: int | None, vvar_id: int | None = None) -> CVariable:
2744
2764
  # TODO: we need to fucking make sure that variable recovery and type inference actually generates a size
2745
2765
  # TODO: for each variable it links into the fucking ail. then we can remove fallback_type_size.
2746
2766
  unified = self._variable_kb.variables[self._func.addr].unified_variable(variable)
@@ -2751,7 +2771,7 @@ class CStructuredCodeGenerator(BaseStructuredCodeGenerator, Analysis):
2751
2771
  variable_type = self.default_simtype_from_bits(
2752
2772
  (fallback_type_size or self.project.arch.bytes) * self.project.arch.byte_width
2753
2773
  )
2754
- cvar = CVariable(variable, unified_variable=unified, variable_type=variable_type, codegen=self)
2774
+ cvar = CVariable(variable, unified_variable=unified, variable_type=variable_type, codegen=self, vvar_id=vvar_id)
2755
2775
  self._variables_in_use[variable] = cvar
2756
2776
  return cvar
2757
2777
 
@@ -3106,14 +3126,18 @@ class CStructuredCodeGenerator(BaseStructuredCodeGenerator, Analysis):
3106
3126
  # Handlers
3107
3127
  #
3108
3128
 
3109
- def _handle(self, node, is_expr: bool = True, lvalue: bool = False):
3129
+ def _handle(self, node, is_expr: bool = True, lvalue: bool = False, likely_signed=False):
3110
3130
  if (node, is_expr) in self.ailexpr2cnode:
3111
3131
  return self.ailexpr2cnode[(node, is_expr)]
3112
3132
 
3113
3133
  handler: Callable | None = self._handlers.get(node.__class__, None)
3114
3134
  if handler is not None:
3115
3135
  # special case for Call
3116
- converted = handler(node, is_expr=is_expr) if isinstance(node, Stmt.Call) else handler(node, lvalue=lvalue)
3136
+ converted = (
3137
+ handler(node, is_expr=is_expr)
3138
+ if isinstance(node, Stmt.Call)
3139
+ else handler(node, lvalue=lvalue, likely_signed=likely_signed)
3140
+ )
3117
3141
  self.ailexpr2cnode[(node, is_expr)] = converted
3118
3142
  return converted
3119
3143
  raise UnsupportedNodeTypeError(f"Node type {type(node)} is not supported yet.")
@@ -3127,10 +3151,7 @@ class CStructuredCodeGenerator(BaseStructuredCodeGenerator, Analysis):
3127
3151
  for node in seq.nodes:
3128
3152
  lines.append(self._handle(node, is_expr=False))
3129
3153
 
3130
- if not lines:
3131
- return CStatements([], codegen=None)
3132
-
3133
- return CStatements(lines, codegen=self) if len(lines) > 1 else lines[0]
3154
+ return lines[0] if len(lines) == 1 else CStatements(lines, codegen=self, addr=seq.addr)
3134
3155
 
3135
3156
  def _handle_Loop(self, loop_node, **kwargs):
3136
3157
  tags = {"ins_addr": loop_node.addr}
@@ -3217,7 +3238,7 @@ class CStructuredCodeGenerator(BaseStructuredCodeGenerator, Analysis):
3217
3238
  r = self._handle(n, is_expr=False)
3218
3239
  lines.append(r)
3219
3240
 
3220
- return CStatements(lines, codegen=self) if len(lines) > 1 else lines[0]
3241
+ return lines[0] if len(lines) == 1 else CStatements(lines, codegen=self, addr=node.addr)
3221
3242
 
3222
3243
  def _handle_SwitchCase(self, node, **kwargs):
3223
3244
  """
@@ -3260,7 +3281,7 @@ class CStructuredCodeGenerator(BaseStructuredCodeGenerator, Analysis):
3260
3281
  cstmt = CUnsupportedStatement(stmt, codegen=self)
3261
3282
  cstmts.append(cstmt)
3262
3283
 
3263
- return CStatements(cstmts, codegen=self)
3284
+ return CStatements(cstmts, codegen=self, addr=node.addr)
3264
3285
 
3265
3286
  #
3266
3287
  # AIL statement handlers
@@ -3315,9 +3336,9 @@ class CStructuredCodeGenerator(BaseStructuredCodeGenerator, Analysis):
3315
3336
  if stmt.dst.variable is not None:
3316
3337
  if "struct_member_info" in stmt.dst.tags:
3317
3338
  offset, var, _ = stmt.dst.struct_member_info
3318
- cvar = self._variable(var, stmt.dst.size)
3339
+ cvar = self._variable(var, stmt.dst.size, vvar_id=stmt.dst.varid)
3319
3340
  else:
3320
- cvar = self._variable(stmt.dst.variable, stmt.dst.size)
3341
+ cvar = self._variable(stmt.dst.variable, stmt.dst.size, vvar_id=stmt.dst.varid)
3321
3342
  offset = stmt.dst.variable_offset or 0
3322
3343
  assert type(offset) is int # I refuse to deal with the alternative
3323
3344
 
@@ -3474,7 +3495,9 @@ class CStructuredCodeGenerator(BaseStructuredCodeGenerator, Analysis):
3474
3495
  l.warning("FIXME: Leftover Tmp expressions are found.")
3475
3496
  return self._variable(SimTemporaryVariable(expr.tmp_idx, expr.bits), expr.size)
3476
3497
 
3477
- def _handle_Expr_Const(self, expr: Expr.Const, type_=None, reference_values=None, variable=None, **kwargs):
3498
+ def _handle_Expr_Const(
3499
+ self, expr: Expr.Const, type_=None, reference_values=None, variable=None, likely_signed=True, **kwargs
3500
+ ):
3478
3501
  inline_string = False
3479
3502
  function_pointer = False
3480
3503
 
@@ -3550,8 +3573,8 @@ class CStructuredCodeGenerator(BaseStructuredCodeGenerator, Analysis):
3550
3573
  inline_string = True
3551
3574
 
3552
3575
  if type_ is None:
3553
- # default to int
3554
- type_ = self.default_simtype_from_bits(expr.bits)
3576
+ # default to int or unsigned int, determined by likely_signed
3577
+ type_ = self.default_simtype_from_bits(expr.bits, signed=likely_signed)
3555
3578
 
3556
3579
  if variable is None and hasattr(expr, "reference_variable") and expr.reference_variable is not None:
3557
3580
  variable = expr.reference_variable
@@ -3583,7 +3606,7 @@ class CStructuredCodeGenerator(BaseStructuredCodeGenerator, Analysis):
3583
3606
  )
3584
3607
 
3585
3608
  lhs = self._handle(expr.operands[0])
3586
- rhs = self._handle(expr.operands[1])
3609
+ rhs = self._handle(expr.operands[1], likely_signed=expr.op not in {"And", "Or"})
3587
3610
 
3588
3611
  return CBinaryOp(
3589
3612
  expr.op,
@@ -3679,7 +3702,7 @@ class CStructuredCodeGenerator(BaseStructuredCodeGenerator, Analysis):
3679
3702
 
3680
3703
  def _handle_VirtualVariable(self, expr: Expr.VirtualVariable, **kwargs):
3681
3704
  if expr.variable:
3682
- cvar = self._variable(expr.variable, None)
3705
+ cvar = self._variable(expr.variable, None, vvar_id=expr.varid)
3683
3706
  if expr.variable.size != expr.size:
3684
3707
  l.warning(
3685
3708
  "VirtualVariable size (%d) and variable size (%d) do not match. Force a type cast.",
@@ -1,4 +1,5 @@
1
1
  # pylint:disable=line-too-long,import-outside-toplevel,import-error,multiple-statements,too-many-boolean-expressions
2
+ # ruff: noqa: SIM102
2
3
  from __future__ import annotations
3
4
  from typing import Any, TYPE_CHECKING
4
5
  from collections import defaultdict, OrderedDict
@@ -26,6 +27,7 @@ from angr.analyses.decompiler.utils import (
26
27
  is_empty_or_label_only_node,
27
28
  has_nonlabel_nonphi_statements,
28
29
  first_nonlabel_nonphi_statement,
30
+ switch_extract_bitwiseand_jumptable_info,
29
31
  )
30
32
  from angr.analyses.decompiler.counters.call_counter import AILCallCounter
31
33
  from .structurer_nodes import (
@@ -1045,6 +1047,11 @@ class PhoenixStructurer(StructurerBase):
1045
1047
  if r:
1046
1048
  return r
1047
1049
  jump_tables = self.kb.cfgs["CFGFast"].jump_tables
1050
+ r = self._match_acyclic_switch_cases_address_loaded_from_memory_no_ob_check(
1051
+ node, graph, full_graph, jump_tables
1052
+ )
1053
+ if r:
1054
+ return r
1048
1055
  r = self._match_acyclic_switch_cases_address_loaded_from_memory(node, graph, full_graph, jump_tables)
1049
1056
  if r:
1050
1057
  return r
@@ -1240,6 +1247,93 @@ class PhoenixStructurer(StructurerBase):
1240
1247
 
1241
1248
  return True
1242
1249
 
1250
+ def _match_acyclic_switch_cases_address_loaded_from_memory_no_ob_check(
1251
+ self, node, graph, full_graph, jump_tables
1252
+ ) -> bool:
1253
+ if node.addr not in jump_tables:
1254
+ return False
1255
+
1256
+ try:
1257
+ last_stmt = self.cond_proc.get_last_statement(node)
1258
+ except EmptyBlockNotice:
1259
+ return False
1260
+ if not (isinstance(last_stmt, Jump) and not isinstance(last_stmt.target, Const)):
1261
+ return False
1262
+
1263
+ jump_table = jump_tables[node.addr]
1264
+ if jump_table.type != IndirectJumpType.Jumptable_AddressLoadedFromMemory:
1265
+ return False
1266
+
1267
+ # extract the index expression, lower-, and upper-bounds from the last statement
1268
+ index = switch_extract_bitwiseand_jumptable_info(last_stmt)
1269
+ if not index:
1270
+ return False
1271
+ index_expr, cmp_lb, cmp_ub = index # pylint:disable=unused-variable
1272
+ case_count = cmp_ub - cmp_lb + 1
1273
+
1274
+ # ensure we have the same number of cases
1275
+ if case_count != len(jump_table.jumptable_entries):
1276
+ return False
1277
+
1278
+ # populate whitelist_edges
1279
+ for case_node_addr in jump_table.jumptable_entries:
1280
+ self.whitelist_edges.add((node.addr, case_node_addr))
1281
+ self.switch_case_known_heads.add(node)
1282
+
1283
+ # sanity check: case nodes are successors to node. all case nodes must have at most common one successor
1284
+ node_pred = None
1285
+ if graph.in_degree[node] == 1:
1286
+ node_pred = next(iter(graph.predecessors(node)))
1287
+
1288
+ case_nodes = list(graph.successors(node))
1289
+
1290
+ # case 1: the common successor happens to be directly reachable from node_a (usually as a result of compiler
1291
+ # optimization)
1292
+ # example: touch_touch_no_switch.o:main
1293
+ r = self.switch_case_entry_node_has_common_successor_case_1(graph, jump_table, case_nodes, node_pred)
1294
+
1295
+ # case 2: the common successor is not directly reachable from node_a. this is a more common case.
1296
+ if not r:
1297
+ r |= self.switch_case_entry_node_has_common_successor_case_2(graph, jump_table, case_nodes, node_pred)
1298
+
1299
+ if not r:
1300
+ return False
1301
+
1302
+ case_and_entry_addrs = self._find_case_and_entry_addrs(node, graph, cmp_lb, jump_table)
1303
+
1304
+ cases, node_default, to_remove = self._switch_build_cases(
1305
+ case_and_entry_addrs,
1306
+ node,
1307
+ node,
1308
+ None,
1309
+ graph,
1310
+ full_graph,
1311
+ )
1312
+
1313
+ assert node_default is None
1314
+ switch_end_addr = None
1315
+
1316
+ r = self._make_switch_cases_core(
1317
+ node,
1318
+ index_expr,
1319
+ cases,
1320
+ None,
1321
+ None,
1322
+ last_stmt.ins_addr,
1323
+ to_remove,
1324
+ graph,
1325
+ full_graph,
1326
+ node_a=None,
1327
+ )
1328
+ if not r:
1329
+ return False
1330
+
1331
+ # fully structured into a switch-case. remove node from switch_case_known_heads
1332
+ self.switch_case_known_heads.remove(node)
1333
+ self._switch_handle_gotos(cases, node_default, switch_end_addr)
1334
+
1335
+ return True
1336
+
1243
1337
  def _match_acyclic_switch_cases_address_computed(self, node, graph, full_graph, jump_tables) -> bool:
1244
1338
  if node.addr not in jump_tables:
1245
1339
  return False
@@ -1333,7 +1427,7 @@ class PhoenixStructurer(StructurerBase):
1333
1427
  case_and_entryaddrs: dict[int, int | tuple[int, int | None]],
1334
1428
  head_node,
1335
1429
  node_a: BaseNode,
1336
- node_b_addr,
1430
+ node_b_addr: int | None,
1337
1431
  graph,
1338
1432
  full_graph,
1339
1433
  ) -> tuple[OrderedDict, Any, set[Any]]:
@@ -1343,7 +1437,9 @@ class PhoenixStructurer(StructurerBase):
1343
1437
  # it is possible that the default node gets duplicated by other analyses and creates a default node (addr.a)
1344
1438
  # and a case node (addr.b). The addr.a node is a successor to the head node while the addr.b node is a
1345
1439
  # successor to node_a
1346
- default_node_candidates = [nn for nn in graph.nodes if nn.addr == node_b_addr]
1440
+ default_node_candidates = (
1441
+ [nn for nn in graph.nodes if nn.addr == node_b_addr] if node_b_addr is not None else []
1442
+ )
1347
1443
  node_default: BaseNode | None = next(
1348
1444
  iter(nn for nn in default_node_candidates if graph.has_edge(head_node, nn)), None
1349
1445
  )
@@ -1355,7 +1451,6 @@ class PhoenixStructurer(StructurerBase):
1355
1451
  self.replace_nodes(full_graph, node_default, new_node)
1356
1452
  node_default = new_node
1357
1453
 
1358
- # entry_addrs_set = set(jumptable_entries)
1359
1454
  converted_nodes: dict[tuple[int, int | None], Any] = {}
1360
1455
  entry_addr_to_ids: defaultdict[tuple[int, int | None], set[int]] = defaultdict(set)
1361
1456
 
@@ -1442,7 +1537,7 @@ class PhoenixStructurer(StructurerBase):
1442
1537
  head,
1443
1538
  cmp_expr,
1444
1539
  cases: OrderedDict,
1445
- node_default_addr: int,
1540
+ node_default_addr: int | None,
1446
1541
  node_default,
1447
1542
  addr,
1448
1543
  to_remove: set,
@@ -1491,7 +1586,7 @@ class PhoenixStructurer(StructurerBase):
1491
1586
  pass
1492
1587
  graph.remove_edge(head, node_default)
1493
1588
  full_graph.remove_edge(head, node_default)
1494
- else:
1589
+ elif node_default_addr is not None:
1495
1590
  # the default node is not in the current graph, but it might be in the full graph
1496
1591
  node_default_in_full_graph = next(iter(nn for nn in full_graph if nn.addr == node_default_addr), None)
1497
1592
  if node_default_in_full_graph is not None and full_graph.has_edge(head, node_default_in_full_graph):
@@ -1570,12 +1665,22 @@ class PhoenixStructurer(StructurerBase):
1570
1665
 
1571
1666
  return case_and_entry_addrs
1572
1667
 
1573
- def _is_switch_cases_address_loaded_from_memory_head_or_jumpnode(self, graph, node) -> bool:
1668
+ def _is_node_unstructured_switch_case_head(self, node) -> bool:
1574
1669
  jump_tables = self.kb.cfgs["CFGFast"].jump_tables
1575
1670
  if node.addr in jump_tables:
1671
+ # maybe it has been structured?
1672
+ try:
1673
+ last_stmts = self.cond_proc.get_last_statements(node)
1674
+ except EmptyBlockNotice:
1675
+ return False
1676
+ return len(last_stmts) == 1 and isinstance(last_stmts[0], Jump)
1677
+ return False
1678
+
1679
+ def _is_switch_cases_address_loaded_from_memory_head_or_jumpnode(self, graph, node) -> bool:
1680
+ if self._is_node_unstructured_switch_case_head(node):
1576
1681
  return True
1577
1682
  for succ in graph.successors(node):
1578
- if succ.addr in jump_tables:
1683
+ if self._is_node_unstructured_switch_case_head(succ):
1579
1684
  return True
1580
1685
  return node in self.switch_case_known_heads
1581
1686
 
@@ -1634,13 +1739,11 @@ class PhoenixStructurer(StructurerBase):
1634
1739
  )
1635
1740
  ):
1636
1741
  # potentially ITE
1637
- jump_tables = self.kb.cfgs["CFGFast"].jump_tables
1638
-
1639
1742
  if (
1640
1743
  full_graph.in_degree[left] == 1
1641
1744
  and full_graph.in_degree[right] == 1
1642
- and left.addr not in jump_tables
1643
- and right.addr not in jump_tables
1745
+ and not self._is_node_unstructured_switch_case_head(left)
1746
+ and not self._is_node_unstructured_switch_case_head(right)
1644
1747
  ):
1645
1748
  edge_cond_left = self.cond_proc.recover_edge_condition(full_graph, start_node, left)
1646
1749
  edge_cond_right = self.cond_proc.recover_edge_condition(full_graph, start_node, right)
@@ -1681,9 +1784,9 @@ class PhoenixStructurer(StructurerBase):
1681
1784
  left_succs, right_succs = right_succs, left_succs
1682
1785
  if left in graph and not left_succs and full_graph.in_degree[left] == 1 and right in graph:
1683
1786
  # potentially If-Then
1684
- jump_tables = self.kb.cfgs["CFGFast"].jump_tables
1685
-
1686
- if left.addr not in jump_tables and right.addr not in jump_tables:
1787
+ if not self._is_node_unstructured_switch_case_head(
1788
+ left
1789
+ ) and not self._is_node_unstructured_switch_case_head(right):
1687
1790
  edge_cond_left = self.cond_proc.recover_edge_condition(full_graph, start_node, left)
1688
1791
  edge_cond_right = self.cond_proc.recover_edge_condition(full_graph, start_node, right)
1689
1792
  if claripy.is_true(claripy.Not(edge_cond_left) == edge_cond_right):
@@ -40,7 +40,7 @@ def remove_last_statement(node):
40
40
  elif type(node) is LoopNode:
41
41
  stmt = remove_last_statement(node.sequence_node)
42
42
  else:
43
- raise NotImplementedError
43
+ raise NotImplementedError(type(node))
44
44
 
45
45
  return stmt
46
46
 
@@ -69,7 +69,7 @@ def remove_last_statements(node) -> bool:
69
69
  return r
70
70
  if type(node) is LoopNode:
71
71
  return remove_last_statements(node.sequence_node)
72
- raise NotImplementedError
72
+ raise NotImplementedError(type(node))
73
73
 
74
74
 
75
75
  def append_statement(node, stmt):
@@ -83,16 +83,16 @@ def append_statement(node, stmt):
83
83
  if node.nodes:
84
84
  append_statement(node.nodes[-1], stmt)
85
85
  else:
86
- raise NotImplementedError
86
+ raise NotImplementedError("MultiNode without nodes")
87
87
  return
88
88
  if type(node) is SequenceNode:
89
89
  if node.nodes:
90
90
  append_statement(node.nodes[-1], stmt)
91
91
  else:
92
- raise NotImplementedError
92
+ raise NotImplementedError("SequenceNode without nodes")
93
93
  return
94
94
 
95
- raise NotImplementedError
95
+ raise NotImplementedError(type(node))
96
96
 
97
97
 
98
98
  def replace_last_statement(node, old_stmt, new_stmt):
@@ -118,7 +118,7 @@ def replace_last_statement(node, old_stmt, new_stmt):
118
118
  replace_last_statement(node.false_node, old_stmt, new_stmt)
119
119
  return
120
120
 
121
- raise NotImplementedError
121
+ raise NotImplementedError(type(node))
122
122
 
123
123
 
124
124
  def extract_jump_targets(stmt):
@@ -175,6 +175,111 @@ def switch_extract_cmp_bounds(last_stmt: ailment.Stmt.ConditionalJump) -> tuple[
175
175
  return None
176
176
 
177
177
 
178
+ def switch_extract_bitwiseand_jumptable_info(last_stmt: ailment.Stmt.Jump) -> tuple[Any, int, int] | None:
179
+ """
180
+ Check the last statement of the switch-case header node (whose address is loaded from a jump table and computed
181
+ using an index) and extract necessary information for rebuilding the switch-case construct.
182
+
183
+ An example of the statement:
184
+
185
+ Goto(Conv(32->s64, (
186
+ Load(addr=(0x4530e4<64> + (Conv(32->64, (Conv(64->32, vvar_287{reg 32}) & 0x3<32>)) * 0x4<64>)),
187
+ size=4, endness=Iend_LE) + 0x4530e4<32>))
188
+ )
189
+
190
+ :param last_stmt: The last statement of the switch-case header node.
191
+ :return: A tuple of (index expression, lower bound, upper bound), or None
192
+ """
193
+
194
+ if not isinstance(last_stmt, ailment.Stmt.Jump):
195
+ return None
196
+
197
+ # unpack the target expression
198
+ target = last_stmt.target
199
+ jump_addr_offset = None
200
+ jumptable_load_addr = None
201
+ while True:
202
+ if isinstance(target, ailment.Expr.Convert) and (
203
+ (target.from_bits == 32 and target.to_bits == 64) or (target.from_bits == 16 and target.to_bits == 32)
204
+ ):
205
+ target = target.operand
206
+ continue
207
+ if isinstance(target, ailment.Expr.BinaryOp) and target.op == "Add":
208
+ if isinstance(target.operands[0], ailment.Expr.Const) and isinstance(target.operands[1], ailment.Expr.Load):
209
+ jump_addr_offset = target.operands[0]
210
+ jumptable_load_addr = target.operands[1].addr
211
+ break
212
+ if isinstance(target.operands[1], ailment.Expr.Const) and isinstance(target.operands[0], ailment.Expr.Load):
213
+ jump_addr_offset = target.operands[1]
214
+ jumptable_load_addr = target.operands[0].addr
215
+ break
216
+ return None
217
+ if isinstance(target, ailment.Expr.Const):
218
+ return None
219
+ break
220
+
221
+ if jump_addr_offset is None or jumptable_load_addr is None:
222
+ return None
223
+
224
+ # parse jumptable_load_addr
225
+ jumptable_offset = None
226
+ jumptable_base_addr = None
227
+ if isinstance(jumptable_load_addr, ailment.Expr.BinaryOp) and jumptable_load_addr.op == "Add":
228
+ if isinstance(jumptable_load_addr.operands[0], ailment.Expr.Const):
229
+ jumptable_base_addr = jumptable_load_addr.operands[0]
230
+ jumptable_offset = jumptable_load_addr.operands[1]
231
+ elif isinstance(jumptable_load_addr.operands[1], ailment.Expr.Const):
232
+ jumptable_offset = jumptable_load_addr.operands[0]
233
+ jumptable_base_addr = jumptable_load_addr.operands[1]
234
+
235
+ if jumptable_offset is None or jumptable_base_addr is None:
236
+ return None
237
+
238
+ # parse jumptable_offset
239
+ expr = jumptable_offset
240
+ coeff = None
241
+ index_expr = None
242
+ lb = None
243
+ ub = None
244
+ while expr is not None:
245
+ if isinstance(expr, ailment.Expr.BinaryOp):
246
+ if expr.op == "Mul":
247
+ if isinstance(expr.operands[1], ailment.Expr.Const):
248
+ coeff = expr.operands[1].value
249
+ expr = expr.operands[0]
250
+ elif isinstance(expr.operands[0], ailment.Expr.Const):
251
+ coeff = expr.operands[0].value
252
+ expr = expr.operands[1]
253
+ else:
254
+ return None
255
+ elif expr.op == "And":
256
+ masks = {0x1, 0x3, 0x7, 0xF, 0x1F, 0x3F, 0x7F, 0xFF, 0x1FF, 0x3FF}
257
+ if isinstance(expr.operands[1], ailment.Expr.Const) and expr.operands[1].value in masks:
258
+ lb = 0
259
+ ub = expr.operands[1].value
260
+ index_expr = expr
261
+ break
262
+ if isinstance(expr.operands[0], ailment.Expr.Const) and expr.operands[1].value in masks:
263
+ lb = 0
264
+ ub = expr.operands[0].value
265
+ index_expr = expr
266
+ break
267
+ return None
268
+ else:
269
+ return None
270
+ elif isinstance(expr, ailment.Expr.Convert):
271
+ if expr.is_signed is False:
272
+ expr = expr.operand
273
+ else:
274
+ return None
275
+ else:
276
+ break
277
+
278
+ if coeff is not None and index_expr is not None and lb is not None and ub is not None:
279
+ return index_expr, lb, ub
280
+ return None
281
+
282
+
178
283
  def get_ast_subexprs(claripy_ast):
179
284
  queue = [claripy_ast]
180
285
  while queue:
@@ -267,9 +372,9 @@ def insert_node(parent, insert_location: str, node, node_idx: int | tuple[int] |
267
372
  parent.sequence_node = SequenceNode(parent.sequence_node.addr, nodes=[parent.sequence_node])
268
373
  insert_node(parent.sequence_node, insert_location, node, node_idx)
269
374
  else:
270
- raise NotImplementedError
375
+ raise NotImplementedError(label)
271
376
  else:
272
- raise NotImplementedError
377
+ raise NotImplementedError(type(parent))
273
378
 
274
379
 
275
380
  def _merge_ail_nodes(graph, node_a: ailment.Block, node_b: ailment.Block) -> ailment.Block:
@@ -379,7 +379,7 @@ class FunctionHandler:
379
379
  else hook_libname
380
380
  )
381
381
  type_collections = []
382
- if prototype_libname is not None:
382
+ if prototype_libname is not None and prototype_libname in SIM_LIBRARIES:
383
383
  prototype_lib = SIM_LIBRARIES[prototype_libname]
384
384
  if prototype_lib.type_collection_names:
385
385
  for typelib_name in prototype_lib.type_collection_names:
@@ -2,7 +2,7 @@ from __future__ import annotations
2
2
 
3
3
  import networkx
4
4
  from ailment.expression import VirtualVariable
5
- from ailment.statement import Assignment
5
+ from ailment.statement import Assignment, Call
6
6
 
7
7
  from angr.analyses import Analysis, register_analysis
8
8
  from angr.utils.ssa import VVarUsesCollector, phi_assignment_get_src
@@ -86,6 +86,8 @@ class SLivenessAnalysis(Analysis):
86
86
  # handle assignments: a defined vvar is not live before the assignment
87
87
  if isinstance(stmt, Assignment) and isinstance(stmt.dst, VirtualVariable):
88
88
  live.discard(stmt.dst.varid)
89
+ elif isinstance(stmt, Call) and isinstance(stmt.ret_expr, VirtualVariable):
90
+ live.discard(stmt.ret_expr.varid)
89
91
 
90
92
  phi_expr = phi_assignment_get_src(stmt)
91
93
  if phi_expr is not None:
@@ -136,6 +138,8 @@ class SLivenessAnalysis(Analysis):
136
138
  for stmt in reversed(block.statements):
137
139
  if isinstance(stmt, Assignment) and isinstance(stmt.dst, VirtualVariable):
138
140
  def_vvar = stmt.dst.varid
141
+ elif isinstance(stmt, Call) and isinstance(stmt.ret_expr, VirtualVariable):
142
+ def_vvar = stmt.ret_expr.varid
139
143
  else:
140
144
  def_vvar = None
141
145