angr 9.2.138__py3-none-manylinux2014_x86_64.whl → 9.2.140__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 (100) hide show
  1. angr/__init__.py +1 -1
  2. angr/analyses/calling_convention/calling_convention.py +48 -21
  3. angr/analyses/calling_convention/fact_collector.py +59 -12
  4. angr/analyses/calling_convention/utils.py +2 -2
  5. angr/analyses/cfg/cfg_base.py +13 -0
  6. angr/analyses/cfg/cfg_fast.py +23 -4
  7. angr/analyses/decompiler/ail_simplifier.py +79 -53
  8. angr/analyses/decompiler/block_simplifier.py +0 -2
  9. angr/analyses/decompiler/callsite_maker.py +80 -14
  10. angr/analyses/decompiler/clinic.py +99 -80
  11. angr/analyses/decompiler/condition_processor.py +2 -2
  12. angr/analyses/decompiler/decompiler.py +19 -7
  13. angr/analyses/decompiler/dephication/rewriting_engine.py +16 -7
  14. angr/analyses/decompiler/expression_narrower.py +1 -1
  15. angr/analyses/decompiler/optimization_passes/__init__.py +3 -0
  16. angr/analyses/decompiler/optimization_passes/condition_constprop.py +149 -0
  17. angr/analyses/decompiler/optimization_passes/const_prop_reverter.py +8 -7
  18. angr/analyses/decompiler/optimization_passes/deadblock_remover.py +12 -3
  19. angr/analyses/decompiler/optimization_passes/inlined_string_transformation_simplifier.py +1 -1
  20. angr/analyses/decompiler/optimization_passes/ite_region_converter.py +21 -13
  21. angr/analyses/decompiler/optimization_passes/optimization_pass.py +21 -12
  22. angr/analyses/decompiler/optimization_passes/return_duplicator_base.py +17 -9
  23. angr/analyses/decompiler/optimization_passes/return_duplicator_high.py +7 -10
  24. angr/analyses/decompiler/peephole_optimizations/eager_eval.py +12 -1
  25. angr/analyses/decompiler/peephole_optimizations/remove_redundant_conversions.py +61 -25
  26. angr/analyses/decompiler/peephole_optimizations/remove_redundant_shifts.py +50 -1
  27. angr/analyses/decompiler/presets/fast.py +2 -0
  28. angr/analyses/decompiler/presets/full.py +2 -0
  29. angr/analyses/decompiler/region_simplifiers/expr_folding.py +259 -108
  30. angr/analyses/decompiler/region_simplifiers/region_simplifier.py +28 -9
  31. angr/analyses/decompiler/ssailification/rewriting_engine.py +20 -2
  32. angr/analyses/decompiler/ssailification/traversal_engine.py +4 -3
  33. angr/analyses/decompiler/structured_codegen/c.py +10 -3
  34. angr/analyses/decompiler/structuring/dream.py +28 -19
  35. angr/analyses/decompiler/structuring/phoenix.py +253 -89
  36. angr/analyses/decompiler/structuring/recursive_structurer.py +1 -0
  37. angr/analyses/decompiler/structuring/structurer_base.py +121 -46
  38. angr/analyses/decompiler/structuring/structurer_nodes.py +6 -1
  39. angr/analyses/decompiler/utils.py +60 -1
  40. angr/analyses/deobfuscator/api_obf_finder.py +13 -5
  41. angr/analyses/deobfuscator/api_obf_type2_finder.py +166 -0
  42. angr/analyses/deobfuscator/string_obf_finder.py +105 -18
  43. angr/analyses/forward_analysis/forward_analysis.py +1 -1
  44. angr/analyses/propagator/top_checker_mixin.py +6 -6
  45. angr/analyses/reaching_definitions/__init__.py +2 -1
  46. angr/analyses/reaching_definitions/dep_graph.py +1 -12
  47. angr/analyses/reaching_definitions/engine_vex.py +36 -31
  48. angr/analyses/reaching_definitions/function_handler.py +15 -2
  49. angr/analyses/reaching_definitions/rd_state.py +1 -37
  50. angr/analyses/reaching_definitions/reaching_definitions.py +13 -24
  51. angr/analyses/s_propagator.py +129 -87
  52. angr/analyses/s_reaching_definitions/s_rda_model.py +7 -1
  53. angr/analyses/s_reaching_definitions/s_rda_view.py +2 -2
  54. angr/analyses/s_reaching_definitions/s_reaching_definitions.py +3 -1
  55. angr/analyses/stack_pointer_tracker.py +36 -22
  56. angr/analyses/typehoon/simple_solver.py +45 -7
  57. angr/analyses/typehoon/typeconsts.py +18 -5
  58. angr/analyses/variable_recovery/engine_ail.py +1 -1
  59. angr/analyses/variable_recovery/engine_base.py +62 -67
  60. angr/analyses/variable_recovery/engine_vex.py +1 -1
  61. angr/analyses/variable_recovery/irsb_scanner.py +2 -2
  62. angr/block.py +69 -107
  63. angr/callable.py +14 -7
  64. angr/calling_conventions.py +81 -10
  65. angr/distributed/__init__.py +1 -1
  66. angr/engines/__init__.py +7 -8
  67. angr/engines/engine.py +3 -138
  68. angr/engines/failure.py +2 -2
  69. angr/engines/hook.py +2 -2
  70. angr/engines/light/engine.py +5 -10
  71. angr/engines/pcode/emulate.py +2 -2
  72. angr/engines/pcode/engine.py +2 -14
  73. angr/engines/pcode/lifter.py +2 -2
  74. angr/engines/procedure.py +2 -2
  75. angr/engines/soot/engine.py +2 -2
  76. angr/engines/soot/statements/switch.py +1 -1
  77. angr/engines/successors.py +123 -17
  78. angr/engines/syscall.py +2 -2
  79. angr/engines/unicorn.py +3 -3
  80. angr/engines/vex/heavy/heavy.py +3 -15
  81. angr/engines/vex/lifter.py +2 -2
  82. angr/engines/vex/light/light.py +2 -2
  83. angr/factory.py +4 -19
  84. angr/knowledge_plugins/cfg/cfg_model.py +3 -2
  85. angr/knowledge_plugins/key_definitions/atoms.py +8 -4
  86. angr/knowledge_plugins/key_definitions/live_definitions.py +41 -103
  87. angr/knowledge_plugins/labels.py +2 -2
  88. angr/knowledge_plugins/obfuscations.py +1 -0
  89. angr/knowledge_plugins/xrefs/xref_manager.py +4 -0
  90. angr/sim_type.py +19 -17
  91. angr/state_plugins/plugin.py +19 -4
  92. angr/storage/memory_mixins/memory_mixin.py +1 -1
  93. angr/storage/memory_mixins/paged_memory/pages/multi_values.py +10 -5
  94. angr/utils/ssa/__init__.py +119 -4
  95. {angr-9.2.138.dist-info → angr-9.2.140.dist-info}/METADATA +6 -6
  96. {angr-9.2.138.dist-info → angr-9.2.140.dist-info}/RECORD +100 -98
  97. {angr-9.2.138.dist-info → angr-9.2.140.dist-info}/LICENSE +0 -0
  98. {angr-9.2.138.dist-info → angr-9.2.140.dist-info}/WHEEL +0 -0
  99. {angr-9.2.138.dist-info → angr-9.2.140.dist-info}/entry_points.txt +0 -0
  100. {angr-9.2.138.dist-info → angr-9.2.140.dist-info}/top_level.txt +0 -0
@@ -92,6 +92,7 @@ class RecursiveStructurer(Analysis):
92
92
  case_entry_to_switch_head=self._case_entry_to_switch_head,
93
93
  func=self.function,
94
94
  parent_region=parent_region,
95
+ jump_tables=self.kb.cfgs["CFGFast"].jump_tables,
95
96
  **self.structurer_options,
96
97
  )
97
98
  # replace this region with the resulting node in its parent region... if it's not an orphan
@@ -19,6 +19,8 @@ from angr.analyses.decompiler.utils import (
19
19
  has_nonlabel_nonphi_statements,
20
20
  )
21
21
  from angr.analyses.decompiler.label_collector import LabelCollector
22
+ from angr.errors import AngrDecompilationError
23
+ from angr.knowledge_plugins.cfg import IndirectJump
22
24
  from .structurer_nodes import (
23
25
  MultiNode,
24
26
  SequenceNode,
@@ -32,6 +34,7 @@ from .structurer_nodes import (
32
34
  BreakNode,
33
35
  LoopNode,
34
36
  EmptyBlockNotice,
37
+ IncompleteSwitchCaseNode,
35
38
  )
36
39
 
37
40
  if TYPE_CHECKING:
@@ -49,7 +52,7 @@ class StructurerBase(Analysis):
49
52
  longer exist due to empty node removal during structuring or prior steps.
50
53
  """
51
54
 
52
- NAME: str = None
55
+ NAME: str = "StructurerBase"
53
56
 
54
57
  def __init__(
55
58
  self,
@@ -59,6 +62,7 @@ class StructurerBase(Analysis):
59
62
  func: Function | None = None,
60
63
  case_entry_to_switch_head: dict[int, int] | None = None,
61
64
  parent_region=None,
65
+ jump_tables: dict[int, IndirectJump] | None = None,
62
66
  **kwargs,
63
67
  ):
64
68
  self._region: GraphRegion = region
@@ -66,6 +70,7 @@ class StructurerBase(Analysis):
66
70
  self.function = func
67
71
  self._case_entry_to_switch_head = case_entry_to_switch_head
68
72
  self._parent_region = parent_region
73
+ self.jump_tables = jump_tables or {}
69
74
 
70
75
  self.cond_proc = (
71
76
  condition_processor if condition_processor is not None else ConditionProcessor(self.project.arch)
@@ -132,16 +137,9 @@ class StructurerBase(Analysis):
132
137
  return seq
133
138
 
134
139
  @staticmethod
135
- def _switch_handle_gotos(cases, default, switch_end_addr):
136
- """
137
- For each case, convert the goto that goes outside of the switch-case to a break statement.
138
-
139
- :param dict cases: A dict of switch-cases.
140
- :param default: The default node.
141
- :param int|None node_b_addr: Address of the end of the switch.
142
- :return: None
143
- """
144
-
140
+ def _switch_find_switch_end_addr(
141
+ cases: dict[int, BaseNode], default: BaseNode | ailment.Block | None, region_node_addrs: set[int]
142
+ ) -> int | None:
145
143
  goto_addrs = defaultdict(int)
146
144
 
147
145
  def _find_gotos(block, **kwargs):
@@ -155,20 +153,54 @@ class StructurerBase(Analysis):
155
153
  continue
156
154
  goto_addrs[t] += 1
157
155
 
158
- if switch_end_addr is None:
159
- # we need to figure this out
160
- handlers = {ailment.Block: _find_gotos}
156
+ # we need to figure this out
157
+ handlers = {ailment.Block: _find_gotos}
161
158
 
162
- walker = SequenceWalker(handlers=handlers)
163
- for case_node in cases.values():
164
- walker.walk(case_node)
165
- if default is not None:
166
- walker.walk(default)
159
+ walker = SequenceWalker(handlers=handlers)
160
+ for case_node in cases.values():
161
+ walker.walk(case_node)
162
+ if default is not None:
163
+ walker.walk(default)
167
164
 
168
- if not goto_addrs:
169
- # there is no Goto statement - perfect
170
- return
171
- switch_end_addr = sorted(goto_addrs.items(), key=lambda x: x[1], reverse=True)[0][0]
165
+ if not goto_addrs:
166
+ # there is no Goto statement - perfect, we don't need a switch-end node
167
+ return None
168
+ if len(goto_addrs) > 1 and any(a in region_node_addrs for a in goto_addrs):
169
+ goto_addrs = {a: times for a, times in goto_addrs.items() if a in region_node_addrs}
170
+ return sorted(goto_addrs.items(), key=lambda x: x[1], reverse=True)[0][0]
171
+
172
+ def _switch_handle_gotos(self, cases: dict[int, BaseNode], default, switch_end_addr: int) -> None:
173
+ """
174
+ For each case, convert the goto that goes outside of the switch-case to a break statement.
175
+
176
+ :param cases: A dict of switch-cases.
177
+ :param default: The default node.
178
+ :param node_b_addr: Address of the end of the switch.
179
+ :return: None
180
+ """
181
+
182
+ # ensure every case node ends with a control-flow transition statement
183
+ # FIXME: The following logic only handles one case. are there other cases?
184
+ for case_addr in cases:
185
+ case_node = cases[case_addr]
186
+ if (
187
+ isinstance(case_node, SequenceNode)
188
+ and case_node.nodes
189
+ and isinstance(case_node.nodes[-1], ConditionNode)
190
+ ):
191
+ cond_node = case_node.nodes[-1]
192
+ if (cond_node.true_node is None and cond_node.false_node is not None) or (
193
+ cond_node.false_node is None and cond_node.true_node is not None
194
+ ):
195
+ # the last node is a condition node and only has one branch - we need a goto statement to ensure it
196
+ # does not fall through to the next branch
197
+ goto_stmt = ailment.Stmt.Jump(
198
+ None,
199
+ ailment.Expr.Const(None, None, switch_end_addr, self.project.arch.bits),
200
+ target_idx=None,
201
+ ins_addr=cond_node.addr,
202
+ )
203
+ case_node.nodes.append(ailment.Block(cond_node.addr, 0, statements=[goto_stmt], idx=None))
172
204
 
173
205
  # rewrite all _goto switch_end_addr_ to _break_
174
206
 
@@ -262,7 +294,7 @@ class StructurerBase(Analysis):
262
294
  and this_node.statements
263
295
  and isinstance(this_node.statements[-1], (ailment.Stmt.Jump, ailment.Stmt.ConditionalJump))
264
296
  ):
265
- jump_stmt = this_node.statements[-1]
297
+ jump_stmt = this_node.statements[-1] # type: ignore
266
298
  elif (
267
299
  isinstance(this_node, MultiNode)
268
300
  and this_node.nodes
@@ -273,9 +305,10 @@ class StructurerBase(Analysis):
273
305
  )
274
306
  ):
275
307
  this_node = this_node.nodes[-1]
276
- jump_stmt = this_node.statements[-1]
308
+ jump_stmt = this_node.statements[-1] # type: ignore
277
309
 
278
310
  if isinstance(jump_stmt, ailment.Stmt.Jump):
311
+ assert isinstance(this_node, ailment.Block)
279
312
  next_node = node.nodes[i + 1]
280
313
  if (
281
314
  isinstance(jump_stmt.target, ailment.Expr.Const)
@@ -284,6 +317,7 @@ class StructurerBase(Analysis):
284
317
  # this goto is useless
285
318
  this_node.statements = this_node.statements[:-1]
286
319
  elif isinstance(jump_stmt, ailment.Stmt.ConditionalJump):
320
+ assert isinstance(this_node, ailment.Block)
287
321
  next_node = node.nodes[i + 1]
288
322
  if (
289
323
  isinstance(jump_stmt.true_target, ailment.Expr.Const)
@@ -337,6 +371,7 @@ class StructurerBase(Analysis):
337
371
  jump_stmt = this_node.nodes[-1].statements[-1]
338
372
  this_node = this_node.nodes[-1]
339
373
 
374
+ assert isinstance(this_node, ailment.Block)
340
375
  if isinstance(jump_stmt, ailment.Stmt.Jump):
341
376
  next_node = node.nodes[i + 1]
342
377
  if (
@@ -387,7 +422,7 @@ class StructurerBase(Analysis):
387
422
  return seq
388
423
 
389
424
  def _rewrite_conditional_jumps_to_breaks(self, loop_node, successor_addrs):
390
- def _rewrite_conditional_jump_to_break(node: ailment.Block, parent=None, index=None, label=None, **kwargs):
425
+ def _rewrite_conditional_jump_to_break(node: ailment.Block, *, parent, index: int, label=None, **kwargs):
391
426
  if not node.statements:
392
427
  return
393
428
 
@@ -481,7 +516,7 @@ class StructurerBase(Analysis):
481
516
  ):
482
517
  continue_node_addr = loop_node.condition.ins_addr
483
518
 
484
- def _rewrite_jump_to_continue(node, parent=None, index=None, label=None, **kwargs):
519
+ def _rewrite_jump_to_continue(node, *, parent, index: int, label=None, **kwargs):
485
520
  if not node.statements:
486
521
  return
487
522
  stmt = node.statements[-1]
@@ -593,6 +628,7 @@ class StructurerBase(Analysis):
593
628
  if (true_target_value is not None and true_target_value in loop_successor_addrs) and (
594
629
  false_target_value is None or false_target_value not in loop_successor_addrs
595
630
  ):
631
+ assert last_stmt.true_target is not None
596
632
  cond = last_stmt.condition
597
633
  target = last_stmt.true_target.value
598
634
  new_node = ConditionalBreakNode(
@@ -601,6 +637,7 @@ class StructurerBase(Analysis):
601
637
  elif (false_target_value is not None and false_target_value in loop_successor_addrs) and (
602
638
  true_target_value is None or true_target_value not in loop_successor_addrs
603
639
  ):
640
+ assert last_stmt.false_target is not None
604
641
  cond = ailment.Expr.UnaryOp(last_stmt.condition.idx, "Not", last_stmt.condition)
605
642
  target = last_stmt.false_target.value
606
643
  new_node = ConditionalBreakNode(
@@ -611,10 +648,11 @@ class StructurerBase(Analysis):
611
648
  ):
612
649
  # both targets are pointing outside the loop
613
650
  # we should use just add a break node
651
+ assert last_stmt.false_target is not None
614
652
  new_node = BreakNode(last_stmt.ins_addr, last_stmt.false_target.value)
615
653
  else:
616
654
  _l.warning("None of the branches is jumping to outside of the loop")
617
- raise Exception
655
+ raise AngrDecompilationError("Unexpected: None of the branches is jumping to outside of the loop")
618
656
 
619
657
  return new_node
620
658
 
@@ -622,6 +660,13 @@ class StructurerBase(Analysis):
622
660
  def _merge_conditional_breaks(seq):
623
661
  # Find consecutive ConditionalBreakNodes and merge their conditions
624
662
 
663
+ class _Holder:
664
+ """
665
+ Holds values so that handlers can access them directly.
666
+ """
667
+
668
+ merged = False
669
+
625
670
  def _handle_SequenceNode(seq_node, parent=None, index=0, label=None):
626
671
  new_nodes = []
627
672
  i = 0
@@ -642,7 +687,7 @@ class StructurerBase(Analysis):
642
687
  claripy.Or(node.condition, prev_node.condition)
643
688
  )
644
689
  new_node = ConditionalBreakNode(node.addr, merged_condition, node.target)
645
- walker.merged = True
690
+ _Holder.merged = True
646
691
  else:
647
692
  walker._handle(node, parent=seq_node, index=i)
648
693
 
@@ -659,13 +704,20 @@ class StructurerBase(Analysis):
659
704
  }
660
705
 
661
706
  walker = SequenceWalker(handlers=handlers)
662
- walker.merged = False # this is just a hack
707
+ _Holder.merged = False # this is just a hack
663
708
  walker.walk(seq)
664
- return walker.merged, seq
709
+ return _Holder.merged, seq
665
710
 
666
711
  def _merge_nesting_conditionals(self, seq):
667
712
  # find if(A) { if(B) { ... ] } and simplify them to if( A && B ) { ... }
668
713
 
714
+ class _Holder:
715
+ """
716
+ Holds values so that handlers can access them directly.
717
+ """
718
+
719
+ merged = False
720
+
669
721
  def _condnode_truenode_only(node):
670
722
  if type(node) is CodeNode:
671
723
  # unpack
@@ -693,9 +745,11 @@ class StructurerBase(Analysis):
693
745
  node = seq_node.nodes[i]
694
746
  r, cond_node = _condnode_truenode_only(node)
695
747
  if r:
748
+ assert cond_node is not None
696
749
  r, cond_node_inner = _condnode_truenode_only(cond_node.true_node)
697
750
  if r:
698
751
  # amazing!
752
+ assert cond_node_inner is not None
699
753
  merged_cond = ConditionProcessor.simplify_condition(
700
754
  claripy.And(
701
755
  self.cond_proc.claripy_ast_from_ail_condition(cond_node.condition),
@@ -704,13 +758,14 @@ class StructurerBase(Analysis):
704
758
  )
705
759
  new_node = ConditionNode(cond_node.addr, None, merged_cond, cond_node_inner.true_node, None)
706
760
  seq_node.nodes[i] = new_node
707
- walker.merged = True
761
+ _Holder.merged = True
708
762
  i += 1
709
763
  continue
710
764
  # else:
711
765
  r, condbreak_node = _condbreaknode(cond_node.true_node)
712
766
  if r:
713
767
  # amazing!
768
+ assert condbreak_node is not None
714
769
  merged_cond = ConditionProcessor.simplify_condition(
715
770
  claripy.And(
716
771
  self.cond_proc.claripy_ast_from_ail_condition(cond_node.condition),
@@ -719,7 +774,7 @@ class StructurerBase(Analysis):
719
774
  )
720
775
  new_node = ConditionalBreakNode(condbreak_node.addr, merged_cond, condbreak_node.target)
721
776
  seq_node.nodes[i] = new_node
722
- walker.merged = True
777
+ _Holder.merged = True
723
778
  i += 1
724
779
  continue
725
780
 
@@ -732,14 +787,10 @@ class StructurerBase(Analysis):
732
787
  }
733
788
 
734
789
  walker = SequenceWalker(handlers=handlers)
735
- walker.merged = False # this is just a hack
790
+ _Holder.merged = False # this is just a hack
736
791
  walker.walk(seq)
737
792
 
738
- return walker.merged, seq
739
-
740
- #
741
- # Util methods
742
- #
793
+ return _Holder.merged, seq
743
794
 
744
795
  def _reorganize_switch_cases(
745
796
  self, cases: OrderedDict[int | tuple[int, ...], SequenceNode]
@@ -747,10 +798,11 @@ class StructurerBase(Analysis):
747
798
  new_cases = OrderedDict()
748
799
 
749
800
  caseid2gotoaddrs = {}
750
- addr2caseids: dict[int, list[int, tuple[int, ...]]] = defaultdict(list)
801
+ addr2caseids: dict[int, list[int | tuple[int, ...]]] = defaultdict(list)
751
802
 
752
803
  # collect goto locations
753
804
  for idx, case_node in cases.items():
805
+ assert case_node.addr is not None
754
806
  addr2caseids[case_node.addr].append(idx)
755
807
  try:
756
808
  last_stmt = self.cond_proc.get_last_statement(case_node)
@@ -842,12 +894,12 @@ class StructurerBase(Analysis):
842
894
  if isinstance(last_stmt.false_target, ailment.Expr.Const):
843
895
  jump_targets.append((last_stmt.false_target.value, last_stmt.false_target_idx))
844
896
  if any(tpl in addr_and_ids for tpl in jump_targets):
845
- return remove_last_statement(node)
897
+ return remove_last_statement(node) # type: ignore
846
898
  return None
847
899
 
848
900
  @staticmethod
849
901
  def _remove_last_statement_if_jump(
850
- node: BaseNode | ailment.Block,
902
+ node: BaseNode | ailment.Block | MultiNode,
851
903
  ) -> ailment.Stmt.Jump | ailment.Stmt.ConditionalJump | None:
852
904
  try:
853
905
  last_stmts = ConditionProcessor.get_last_statements(node)
@@ -855,7 +907,7 @@ class StructurerBase(Analysis):
855
907
  return None
856
908
 
857
909
  if len(last_stmts) == 1 and isinstance(last_stmts[0], (ailment.Stmt.Jump, ailment.Stmt.ConditionalJump)):
858
- return remove_last_statement(node)
910
+ return remove_last_statement(node) # type: ignore
859
911
  return None
860
912
 
861
913
  @staticmethod
@@ -945,8 +997,8 @@ class StructurerBase(Analysis):
945
997
  @staticmethod
946
998
  def replace_node_in_node(
947
999
  parent_node: BaseNode,
948
- old_node: BaseNode | ailment.Block,
949
- new_node: BaseNode | ailment.Block,
1000
+ old_node: BaseNode | ailment.Block | MultiNode,
1001
+ new_node: BaseNode | ailment.Block | MultiNode,
950
1002
  ) -> None:
951
1003
  if isinstance(parent_node, SequenceNode):
952
1004
  for i in range(len(parent_node.nodes)): # pylint:disable=consider-using-enumerate
@@ -969,7 +1021,9 @@ class StructurerBase(Analysis):
969
1021
  raise TypeError(f"Unsupported node type {type(parent_node)}")
970
1022
 
971
1023
  @staticmethod
972
- def is_a_jump_target(stmt: ailment.Stmt.ConditionalJump | ailment.Stmt.Jump, addr: int) -> bool:
1024
+ def is_a_jump_target(
1025
+ stmt: ailment.Stmt.ConditionalJump | ailment.Stmt.Jump | ailment.Stmt.Statement, addr: int
1026
+ ) -> bool:
973
1027
  if isinstance(stmt, ailment.Stmt.ConditionalJump):
974
1028
  if isinstance(stmt.true_target, ailment.Expr.Const) and stmt.true_target.value == addr:
975
1029
  return True
@@ -989,3 +1043,24 @@ class StructurerBase(Analysis):
989
1043
  if isinstance(node, SequenceNode):
990
1044
  return any(StructurerBase.has_nonlabel_nonphi_statements(nn) for nn in node.nodes)
991
1045
  return False
1046
+
1047
+ def _node_ending_with_jump_table_header(self, node: BaseNode) -> tuple[int | None, IndirectJump | None]:
1048
+ if isinstance(node, (ailment.Block, MultiNode, IncompleteSwitchCaseNode)):
1049
+ assert node.addr is not None
1050
+ return node.addr, self.jump_tables.get(node.addr, None)
1051
+ if isinstance(node, SequenceNode):
1052
+ return node.addr, self._node_ending_with_jump_table_header(node.nodes[-1])[1]
1053
+ return None, None
1054
+
1055
+ @staticmethod
1056
+ def _switch_find_default_node(
1057
+ graph: networkx.DiGraph, head_node: BaseNode, default_node_addr: int
1058
+ ) -> BaseNode | None:
1059
+ # it is possible that the default node gets duplicated by other analyses and creates a default node (addr.a)
1060
+ # and a case node (addr.b). The addr.a node is a successor to the head node while the addr.b node is a
1061
+ # successor to node_a
1062
+ default_node_candidates = [nn for nn in graph.nodes if nn.addr == default_node_addr]
1063
+ node_default: BaseNode | None = next(
1064
+ iter(nn for nn in default_node_candidates if graph.has_edge(head_node, nn)), None
1065
+ )
1066
+ return node_default
@@ -230,7 +230,12 @@ class CascadingConditionNode(BaseNode):
230
230
  "else_node",
231
231
  )
232
232
 
233
- def __init__(self, addr, condition_and_nodes: list[tuple[Any, BaseNode]], else_node: BaseNode = None):
233
+ def __init__(
234
+ self,
235
+ addr,
236
+ condition_and_nodes: list[tuple[Any, BaseNode | ailment.Block | MultiNode]],
237
+ else_node: BaseNode = None,
238
+ ):
234
239
  self.addr = addr
235
240
  self.condition_and_nodes = condition_and_nodes
236
241
  self.else_node = else_node
@@ -144,7 +144,9 @@ def extract_jump_targets(stmt):
144
144
  return targets
145
145
 
146
146
 
147
- def switch_extract_cmp_bounds(last_stmt: ailment.Stmt.ConditionalJump) -> tuple[Any, int, int] | None:
147
+ def switch_extract_cmp_bounds(
148
+ last_stmt: ailment.Stmt.ConditionalJump | ailment.Stmt.Statement,
149
+ ) -> tuple[Any, int, int] | None:
148
150
  """
149
151
  Check the last statement of the switch-case header node, and extract lower+upper bounds for the comparison.
150
152
 
@@ -175,6 +177,54 @@ def switch_extract_cmp_bounds(last_stmt: ailment.Stmt.ConditionalJump) -> tuple[
175
177
  return None
176
178
 
177
179
 
180
+ def switch_extract_switch_expr_from_jump_target(target: ailment.Expr.Expression) -> ailment.Expr.Expression | None:
181
+ """
182
+ Extract the switch expression from the indirect jump target expression.
183
+
184
+ :param target: The target of the indirect jump statement.
185
+ :return: The extracted expression if successful, or None otherwise.
186
+ """
187
+
188
+ # e.g.: Jump (Conv(32->64, (Load(addr=((0x140000000<64> + (vvar_229{reg 80} * 0x4<64>)) + 0x2290<64>),
189
+ # size=4,
190
+ # endness=Iend_LE
191
+ # ) + 0x140000000<32>)))
192
+
193
+ found_load = False
194
+ while True:
195
+ if isinstance(target, ailment.Expr.Convert):
196
+ if target.from_bits < target.to_bits:
197
+ target = target.operand
198
+ else:
199
+ return None
200
+ elif isinstance(target, ailment.Expr.BinaryOp):
201
+ if target.op == "Add":
202
+ # it must be adding the target expr with a constant
203
+ if isinstance(target.operands[0], ailment.Expr.Const):
204
+ target = target.operands[1]
205
+ elif isinstance(target.operands[1], ailment.Expr.Const):
206
+ target = target.operands[0]
207
+ else:
208
+ return None
209
+ elif target.op == "Mul":
210
+ # it must be multiplying the target expr with a constant
211
+ if isinstance(target.operands[0], ailment.Expr.Const):
212
+ target = target.operands[1]
213
+ elif isinstance(target.operands[1], ailment.Expr.Const):
214
+ target = target.operands[0]
215
+ else:
216
+ return None
217
+ elif isinstance(target, ailment.Expr.Load):
218
+ # we want the address!
219
+ found_load = True
220
+ target = target.addr
221
+ elif isinstance(target, ailment.Expr.VirtualVariable):
222
+ break
223
+ else:
224
+ return None
225
+ return target if found_load else None
226
+
227
+
178
228
  def switch_extract_bitwiseand_jumptable_info(last_stmt: ailment.Stmt.Jump) -> tuple[Any, int, int] | None:
179
229
  """
180
230
  Check the last statement of the switch-case header node (whose address is loaded from a jump table and computed
@@ -973,6 +1023,15 @@ def sequence_to_statements(
973
1023
  return statements
974
1024
 
975
1025
 
1026
+ def remove_edges_in_ailgraph(
1027
+ ail_graph: networkx.DiGraph, edges_to_remove: list[tuple[tuple[int, int | None], tuple[int, int | None]]]
1028
+ ) -> None:
1029
+ d = {(bb.addr, bb.idx): bb for bb in ail_graph}
1030
+ for src_addr, dst_addr in edges_to_remove:
1031
+ if src_addr in d and dst_addr in d and ail_graph.has_edge(d[src_addr], d[dst_addr]):
1032
+ ail_graph.remove_edge(d[src_addr], d[dst_addr])
1033
+
1034
+
976
1035
  # delayed import
977
1036
  from .structuring.structurer_nodes import (
978
1037
  MultiNode,
@@ -12,6 +12,7 @@ import claripy
12
12
  from angr import SIM_LIBRARIES
13
13
  from angr.calling_conventions import SimRegArg
14
14
  from angr.errors import SimMemoryMissingError
15
+ from angr.knowledge_base import KnowledgeBase
15
16
  from angr.knowledge_plugins.key_definitions.constants import ObservationPointType
16
17
  from angr.sim_type import SimTypePointer, SimTypeChar
17
18
  from angr.analyses import Analysis, AnalysesHub
@@ -25,6 +26,8 @@ from angr.analyses.decompiler.structured_codegen.c import (
25
26
  CVariable,
26
27
  )
27
28
 
29
+ from .api_obf_type2_finder import APIObfuscationType2Finder
30
+
28
31
  _l = logging.getLogger(name=__name__)
29
32
 
30
33
 
@@ -33,7 +36,7 @@ class APIObfuscationType(IntEnum):
33
36
 
34
37
 
35
38
  class APIDeobFuncDescriptor:
36
- def __init__(self, type_: APIObfuscationType, func_addr=None, libname_argidx=None, funcname_argidx=None):
39
+ def __init__(self, type_: APIObfuscationType, *, func_addr: int, libname_argidx: int, funcname_argidx: int):
37
40
  self.type = type_
38
41
  self.func_addr = func_addr
39
42
  self.libname_argidx = libname_argidx
@@ -90,11 +93,13 @@ class APIObfuscationFinder(Analysis):
90
93
 
91
94
  Currently, we support the following API "obfuscation" styles:
92
95
 
93
- - sub_A("dll_name", "api_name) where sub_a ends up calling LoadLibrary.
96
+ - Type 1: sub_A("dll_name", "api_name") where sub_A ends up calling LoadLibrary.
97
+ - Type 2: GetProcAddress(_, "api_name").
94
98
  """
95
99
 
96
- def __init__(self):
100
+ def __init__(self, variable_kb: KnowledgeBase | None = None):
97
101
  self.type1_candidates = []
102
+ self.variable_kb = variable_kb or self.project.kb
98
103
 
99
104
  self.analyze()
100
105
 
@@ -106,6 +111,8 @@ class APIObfuscationFinder(Analysis):
106
111
  type1_deobfuscated = self._analyze_type1(desc.func_addr, desc)
107
112
  self.kb.obfuscations.type1_deobfuscated_apis.update(type1_deobfuscated)
108
113
 
114
+ APIObfuscationType2Finder(self.project, self.variable_kb).analyze()
115
+
109
116
  def _find_type1(self):
110
117
  cfg = self.kb.cfgs.get_most_accurate()
111
118
  load_library_funcs = []
@@ -190,6 +197,8 @@ class APIObfuscationFinder(Analysis):
190
197
  callsite_node.instruction_addrs[-1],
191
198
  ObservationPointType.OP_BEFORE,
192
199
  )
200
+ if observ is None:
201
+ continue
193
202
  args: list[tuple[int, Any]] = []
194
203
  for arg_idx, func_arg in enumerate(func.arguments):
195
204
  # FIXME: We are ignoring all non-register function arguments until we see a test case where
@@ -227,9 +236,8 @@ class APIObfuscationFinder(Analysis):
227
236
  acceptable_args = False
228
237
  break
229
238
  arg_strs.append((idx, value.decode("utf-8")))
230
- if acceptable_args:
239
+ if acceptable_args and len(arg_strs) == 2:
231
240
  libname_arg_idx, funcname_arg_idx = None, None
232
- assert len(arg_strs) == 2
233
241
  for arg_idx, name in arg_strs:
234
242
  if self.is_libname(name):
235
243
  libname_arg_idx = arg_idx