angr 9.2.139__py3-none-manylinux2014_x86_64.whl → 9.2.141__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 (87) hide show
  1. angr/__init__.py +1 -1
  2. angr/analyses/calling_convention/calling_convention.py +136 -53
  3. angr/analyses/calling_convention/fact_collector.py +44 -18
  4. angr/analyses/calling_convention/utils.py +3 -1
  5. angr/analyses/cfg/cfg_base.py +13 -0
  6. angr/analyses/cfg/cfg_fast.py +11 -0
  7. angr/analyses/cfg/indirect_jump_resolvers/jumptable.py +9 -8
  8. angr/analyses/decompiler/ail_simplifier.py +115 -72
  9. angr/analyses/decompiler/callsite_maker.py +24 -11
  10. angr/analyses/decompiler/clinic.py +78 -43
  11. angr/analyses/decompiler/decompiler.py +18 -7
  12. angr/analyses/decompiler/expression_narrower.py +1 -1
  13. angr/analyses/decompiler/optimization_passes/const_prop_reverter.py +8 -7
  14. angr/analyses/decompiler/optimization_passes/duplication_reverter/duplication_reverter.py +3 -1
  15. angr/analyses/decompiler/optimization_passes/flip_boolean_cmp.py +21 -2
  16. angr/analyses/decompiler/optimization_passes/ite_region_converter.py +21 -13
  17. angr/analyses/decompiler/optimization_passes/lowered_switch_simplifier.py +84 -15
  18. angr/analyses/decompiler/optimization_passes/optimization_pass.py +92 -11
  19. angr/analyses/decompiler/optimization_passes/return_duplicator_base.py +53 -9
  20. angr/analyses/decompiler/peephole_optimizations/eager_eval.py +44 -7
  21. angr/analyses/decompiler/region_identifier.py +6 -4
  22. angr/analyses/decompiler/region_simplifiers/expr_folding.py +287 -122
  23. angr/analyses/decompiler/region_simplifiers/region_simplifier.py +31 -13
  24. angr/analyses/decompiler/ssailification/rewriting.py +23 -15
  25. angr/analyses/decompiler/ssailification/rewriting_engine.py +105 -24
  26. angr/analyses/decompiler/ssailification/ssailification.py +22 -14
  27. angr/analyses/decompiler/structured_codegen/c.py +73 -137
  28. angr/analyses/decompiler/structuring/dream.py +22 -18
  29. angr/analyses/decompiler/structuring/phoenix.py +158 -41
  30. angr/analyses/decompiler/structuring/recursive_structurer.py +1 -0
  31. angr/analyses/decompiler/structuring/structurer_base.py +37 -10
  32. angr/analyses/decompiler/structuring/structurer_nodes.py +4 -1
  33. angr/analyses/decompiler/utils.py +106 -21
  34. angr/analyses/deobfuscator/api_obf_finder.py +8 -5
  35. angr/analyses/deobfuscator/api_obf_type2_finder.py +18 -10
  36. angr/analyses/deobfuscator/string_obf_finder.py +105 -18
  37. angr/analyses/forward_analysis/forward_analysis.py +1 -1
  38. angr/analyses/propagator/top_checker_mixin.py +6 -6
  39. angr/analyses/reaching_definitions/__init__.py +2 -1
  40. angr/analyses/reaching_definitions/dep_graph.py +1 -12
  41. angr/analyses/reaching_definitions/engine_vex.py +36 -31
  42. angr/analyses/reaching_definitions/function_handler.py +15 -2
  43. angr/analyses/reaching_definitions/rd_state.py +1 -37
  44. angr/analyses/reaching_definitions/reaching_definitions.py +13 -24
  45. angr/analyses/s_propagator.py +6 -41
  46. angr/analyses/s_reaching_definitions/s_rda_model.py +7 -1
  47. angr/analyses/s_reaching_definitions/s_rda_view.py +43 -25
  48. angr/analyses/stack_pointer_tracker.py +36 -22
  49. angr/analyses/typehoon/simple_solver.py +45 -7
  50. angr/analyses/typehoon/typeconsts.py +18 -5
  51. angr/analyses/variable_recovery/engine_ail.py +1 -1
  52. angr/analyses/variable_recovery/engine_base.py +7 -5
  53. angr/analyses/variable_recovery/engine_vex.py +20 -4
  54. angr/block.py +69 -107
  55. angr/callable.py +14 -7
  56. angr/calling_conventions.py +30 -11
  57. angr/distributed/__init__.py +1 -1
  58. angr/engines/__init__.py +7 -8
  59. angr/engines/engine.py +1 -120
  60. angr/engines/failure.py +2 -2
  61. angr/engines/hook.py +2 -2
  62. angr/engines/light/engine.py +2 -2
  63. angr/engines/pcode/engine.py +2 -14
  64. angr/engines/procedure.py +2 -2
  65. angr/engines/soot/engine.py +2 -2
  66. angr/engines/soot/statements/switch.py +1 -1
  67. angr/engines/successors.py +124 -11
  68. angr/engines/syscall.py +2 -2
  69. angr/engines/unicorn.py +3 -3
  70. angr/engines/vex/heavy/heavy.py +3 -15
  71. angr/factory.py +12 -22
  72. angr/knowledge_plugins/key_definitions/atoms.py +8 -4
  73. angr/knowledge_plugins/key_definitions/live_definitions.py +41 -103
  74. angr/knowledge_plugins/variables/variable_manager.py +7 -5
  75. angr/sim_type.py +19 -17
  76. angr/simos/simos.py +3 -1
  77. angr/state_plugins/plugin.py +19 -4
  78. angr/storage/memory_mixins/memory_mixin.py +1 -1
  79. angr/storage/memory_mixins/paged_memory/pages/multi_values.py +10 -5
  80. angr/utils/ssa/__init__.py +119 -4
  81. angr/utils/types.py +48 -0
  82. {angr-9.2.139.dist-info → angr-9.2.141.dist-info}/METADATA +6 -6
  83. {angr-9.2.139.dist-info → angr-9.2.141.dist-info}/RECORD +87 -86
  84. {angr-9.2.139.dist-info → angr-9.2.141.dist-info}/LICENSE +0 -0
  85. {angr-9.2.139.dist-info → angr-9.2.141.dist-info}/WHEEL +0 -0
  86. {angr-9.2.139.dist-info → angr-9.2.141.dist-info}/entry_points.txt +0 -0
  87. {angr-9.2.139.dist-info → angr-9.2.141.dist-info}/top_level.txt +0 -0
@@ -20,6 +20,7 @@ from angr.analyses.decompiler.utils import (
20
20
  )
21
21
  from angr.analyses.decompiler.label_collector import LabelCollector
22
22
  from angr.errors import AngrDecompilationError
23
+ from angr.knowledge_plugins.cfg import IndirectJump
23
24
  from .structurer_nodes import (
24
25
  MultiNode,
25
26
  SequenceNode,
@@ -33,6 +34,7 @@ from .structurer_nodes import (
33
34
  BreakNode,
34
35
  LoopNode,
35
36
  EmptyBlockNotice,
37
+ IncompleteSwitchCaseNode,
36
38
  )
37
39
 
38
40
  if TYPE_CHECKING:
@@ -60,6 +62,7 @@ class StructurerBase(Analysis):
60
62
  func: Function | None = None,
61
63
  case_entry_to_switch_head: dict[int, int] | None = None,
62
64
  parent_region=None,
65
+ jump_tables: dict[int, IndirectJump] | None = None,
63
66
  **kwargs,
64
67
  ):
65
68
  self._region: GraphRegion = region
@@ -67,6 +70,7 @@ class StructurerBase(Analysis):
67
70
  self.function = func
68
71
  self._case_entry_to_switch_head = case_entry_to_switch_head
69
72
  self._parent_region = parent_region
73
+ self.jump_tables = jump_tables or {}
70
74
 
71
75
  self.cond_proc = (
72
76
  condition_processor if condition_processor is not None else ConditionProcessor(self.project.arch)
@@ -304,6 +308,7 @@ class StructurerBase(Analysis):
304
308
  jump_stmt = this_node.statements[-1] # type: ignore
305
309
 
306
310
  if isinstance(jump_stmt, ailment.Stmt.Jump):
311
+ assert isinstance(this_node, ailment.Block)
307
312
  next_node = node.nodes[i + 1]
308
313
  if (
309
314
  isinstance(jump_stmt.target, ailment.Expr.Const)
@@ -312,6 +317,7 @@ class StructurerBase(Analysis):
312
317
  # this goto is useless
313
318
  this_node.statements = this_node.statements[:-1]
314
319
  elif isinstance(jump_stmt, ailment.Stmt.ConditionalJump):
320
+ assert isinstance(this_node, ailment.Block)
315
321
  next_node = node.nodes[i + 1]
316
322
  if (
317
323
  isinstance(jump_stmt.true_target, ailment.Expr.Const)
@@ -366,6 +372,7 @@ class StructurerBase(Analysis):
366
372
  this_node = this_node.nodes[-1]
367
373
 
368
374
  if isinstance(jump_stmt, ailment.Stmt.Jump):
375
+ assert isinstance(this_node, ailment.Block)
369
376
  next_node = node.nodes[i + 1]
370
377
  if (
371
378
  isinstance(jump_stmt.target, ailment.Expr.Const)
@@ -374,6 +381,7 @@ class StructurerBase(Analysis):
374
381
  # this goto is useless
375
382
  this_node.statements = this_node.statements[:-1]
376
383
  elif isinstance(jump_stmt, ailment.Stmt.ConditionalJump):
384
+ assert isinstance(this_node, ailment.Block)
377
385
  next_node = node.nodes[i + 1]
378
386
  if (
379
387
  isinstance(jump_stmt.true_target, ailment.Expr.Const)
@@ -785,10 +793,6 @@ class StructurerBase(Analysis):
785
793
 
786
794
  return _Holder.merged, seq
787
795
 
788
- #
789
- # Util methods
790
- #
791
-
792
796
  def _reorganize_switch_cases(
793
797
  self, cases: OrderedDict[int | tuple[int, ...], SequenceNode]
794
798
  ) -> OrderedDict[int | tuple[int, ...], SequenceNode]:
@@ -891,12 +895,12 @@ class StructurerBase(Analysis):
891
895
  if isinstance(last_stmt.false_target, ailment.Expr.Const):
892
896
  jump_targets.append((last_stmt.false_target.value, last_stmt.false_target_idx))
893
897
  if any(tpl in addr_and_ids for tpl in jump_targets):
894
- return remove_last_statement(node)
898
+ return remove_last_statement(node) # type: ignore
895
899
  return None
896
900
 
897
901
  @staticmethod
898
902
  def _remove_last_statement_if_jump(
899
- node: BaseNode | ailment.Block,
903
+ node: BaseNode | ailment.Block | MultiNode,
900
904
  ) -> ailment.Stmt.Jump | ailment.Stmt.ConditionalJump | None:
901
905
  try:
902
906
  last_stmts = ConditionProcessor.get_last_statements(node)
@@ -904,7 +908,7 @@ class StructurerBase(Analysis):
904
908
  return None
905
909
 
906
910
  if len(last_stmts) == 1 and isinstance(last_stmts[0], (ailment.Stmt.Jump, ailment.Stmt.ConditionalJump)):
907
- return remove_last_statement(node)
911
+ return remove_last_statement(node) # type: ignore
908
912
  return None
909
913
 
910
914
  @staticmethod
@@ -994,8 +998,8 @@ class StructurerBase(Analysis):
994
998
  @staticmethod
995
999
  def replace_node_in_node(
996
1000
  parent_node: BaseNode,
997
- old_node: BaseNode | ailment.Block,
998
- new_node: BaseNode | ailment.Block,
1001
+ old_node: BaseNode | ailment.Block | MultiNode,
1002
+ new_node: BaseNode | ailment.Block | MultiNode,
999
1003
  ) -> None:
1000
1004
  if isinstance(parent_node, SequenceNode):
1001
1005
  for i in range(len(parent_node.nodes)): # pylint:disable=consider-using-enumerate
@@ -1018,7 +1022,9 @@ class StructurerBase(Analysis):
1018
1022
  raise TypeError(f"Unsupported node type {type(parent_node)}")
1019
1023
 
1020
1024
  @staticmethod
1021
- def is_a_jump_target(stmt: ailment.Stmt.ConditionalJump | ailment.Stmt.Jump, addr: int) -> bool:
1025
+ def is_a_jump_target(
1026
+ stmt: ailment.Stmt.ConditionalJump | ailment.Stmt.Jump | ailment.Stmt.Statement, addr: int
1027
+ ) -> bool:
1022
1028
  if isinstance(stmt, ailment.Stmt.ConditionalJump):
1023
1029
  if isinstance(stmt.true_target, ailment.Expr.Const) and stmt.true_target.value == addr:
1024
1030
  return True
@@ -1038,3 +1044,24 @@ class StructurerBase(Analysis):
1038
1044
  if isinstance(node, SequenceNode):
1039
1045
  return any(StructurerBase.has_nonlabel_nonphi_statements(nn) for nn in node.nodes)
1040
1046
  return False
1047
+
1048
+ def _node_ending_with_jump_table_header(self, node: BaseNode) -> tuple[int | None, IndirectJump | None]:
1049
+ if isinstance(node, (ailment.Block, MultiNode, IncompleteSwitchCaseNode)):
1050
+ assert node.addr is not None
1051
+ return node.addr, self.jump_tables.get(node.addr, None)
1052
+ if isinstance(node, SequenceNode):
1053
+ return node.addr, self._node_ending_with_jump_table_header(node.nodes[-1])[1]
1054
+ return None, None
1055
+
1056
+ @staticmethod
1057
+ def _switch_find_default_node(
1058
+ graph: networkx.DiGraph, head_node: BaseNode, default_node_addr: int
1059
+ ) -> BaseNode | None:
1060
+ # it is possible that the default node gets duplicated by other analyses and creates a default node (addr.a)
1061
+ # and a case node (addr.b). The addr.a node is a successor to the head node while the addr.b node is a
1062
+ # successor to node_a
1063
+ default_node_candidates = [nn for nn in graph.nodes if nn.addr == default_node_addr]
1064
+ node_default: BaseNode | None = next(
1065
+ iter(nn for nn in default_node_candidates if graph.has_edge(head_node, nn)), None
1066
+ )
1067
+ return node_default
@@ -231,7 +231,10 @@ class CascadingConditionNode(BaseNode):
231
231
  )
232
232
 
233
233
  def __init__(
234
- self, addr, condition_and_nodes: list[tuple[Any, BaseNode | ailment.Block]], else_node: BaseNode = None
234
+ self,
235
+ addr,
236
+ condition_and_nodes: list[tuple[Any, BaseNode | ailment.Block | MultiNode]],
237
+ else_node: BaseNode = None,
235
238
  ):
236
239
  self.addr = addr
237
240
  self.condition_and_nodes = condition_and_nodes
@@ -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
@@ -575,6 +625,48 @@ def update_labels(graph: networkx.DiGraph):
575
625
  return add_labels(remove_labels(graph))
576
626
 
577
627
 
628
+ def _flatten_structured_node(packed_node: SequenceNode | MultiNode) -> list[ailment.Block]:
629
+ if not packed_node or not packed_node.nodes:
630
+ return []
631
+
632
+ blocks = []
633
+ if packed_node.nodes is not None:
634
+ for _node in packed_node.nodes:
635
+ if isinstance(_node, (SequenceNode, MultiNode)):
636
+ blocks += _flatten_structured_node(_node)
637
+ else:
638
+ blocks.append(_node)
639
+
640
+ return blocks
641
+
642
+
643
+ def _find_node_in_graph(node: ailment.Block, graph: networkx.DiGraph) -> ailment.Block | None:
644
+ for bb in graph:
645
+ if bb.addr == node.addr and bb.idx == node.idx:
646
+ return bb
647
+ return None
648
+
649
+
650
+ def structured_node_has_multi_predecessors(node: SequenceNode | MultiNode, graph: networkx.DiGraph) -> bool:
651
+ if graph is None:
652
+ return False
653
+
654
+ first_block = None
655
+ if isinstance(node, (SequenceNode, MultiNode)) and node.nodes:
656
+ flat_blocks = _flatten_structured_node(node)
657
+ node = flat_blocks[0]
658
+
659
+ if isinstance(node, ailment.Block):
660
+ first_block = node
661
+
662
+ if first_block is not None:
663
+ graph_node = _find_node_in_graph(first_block, graph)
664
+ if graph_node is not None:
665
+ return len(list(graph.predecessors(graph_node))) > 1
666
+
667
+ return False
668
+
669
+
578
670
  def structured_node_is_simple_return(
579
671
  node: SequenceNode | MultiNode, graph: networkx.DiGraph, use_packed_successors=False
580
672
  ) -> bool:
@@ -589,21 +681,6 @@ def structured_node_is_simple_return(
589
681
 
590
682
  Returns true on any block ending in linear statements and a return.
591
683
  """
592
-
593
- def _flatten_structured_node(packed_node: SequenceNode | MultiNode) -> list[ailment.Block]:
594
- if not packed_node or not packed_node.nodes:
595
- return []
596
-
597
- blocks = []
598
- if packed_node.nodes is not None:
599
- for _node in packed_node.nodes:
600
- if isinstance(_node, (SequenceNode, MultiNode)):
601
- blocks += _flatten_structured_node(_node)
602
- else:
603
- blocks.append(_node)
604
-
605
- return blocks
606
-
607
684
  # sanity check: we need a graph to understand returning blocks
608
685
  if graph is None:
609
686
  return False
@@ -626,11 +703,10 @@ def structured_node_is_simple_return(
626
703
  if valid_last_stmt:
627
704
  # note that the block may not be the same block in the AIL graph post dephication. we must find the block again
628
705
  # in the graph.
629
- for bb in graph:
630
- if bb.addr == last_block.addr and bb.idx == last_block.idx:
631
- # found it
632
- succs = list(graph.successors(bb))
633
- return not succs or succs == [bb]
706
+ last_graph_block = _find_node_in_graph(last_block, graph)
707
+ if last_graph_block is not None:
708
+ succs = list(graph.successors(last_graph_block))
709
+ return not succs or succs == [last_graph_block]
634
710
  return False
635
711
 
636
712
 
@@ -973,6 +1049,15 @@ def sequence_to_statements(
973
1049
  return statements
974
1050
 
975
1051
 
1052
+ def remove_edges_in_ailgraph(
1053
+ ail_graph: networkx.DiGraph, edges_to_remove: list[tuple[tuple[int, int | None], tuple[int, int | None]]]
1054
+ ) -> None:
1055
+ d = {(bb.addr, bb.idx): bb for bb in ail_graph}
1056
+ for src_addr, dst_addr in edges_to_remove:
1057
+ if src_addr in d and dst_addr in d and ail_graph.has_edge(d[src_addr], d[dst_addr]):
1058
+ ail_graph.remove_edge(d[src_addr], d[dst_addr])
1059
+
1060
+
976
1061
  # delayed import
977
1062
  from .structuring.structurer_nodes import (
978
1063
  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
@@ -35,7 +36,7 @@ class APIObfuscationType(IntEnum):
35
36
 
36
37
 
37
38
  class APIDeobFuncDescriptor:
38
- 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):
39
40
  self.type = type_
40
41
  self.func_addr = func_addr
41
42
  self.libname_argidx = libname_argidx
@@ -96,8 +97,9 @@ class APIObfuscationFinder(Analysis):
96
97
  - Type 2: GetProcAddress(_, "api_name").
97
98
  """
98
99
 
99
- def __init__(self):
100
+ def __init__(self, variable_kb: KnowledgeBase | None = None):
100
101
  self.type1_candidates = []
102
+ self.variable_kb = variable_kb or self.project.kb
101
103
 
102
104
  self.analyze()
103
105
 
@@ -109,7 +111,7 @@ class APIObfuscationFinder(Analysis):
109
111
  type1_deobfuscated = self._analyze_type1(desc.func_addr, desc)
110
112
  self.kb.obfuscations.type1_deobfuscated_apis.update(type1_deobfuscated)
111
113
 
112
- APIObfuscationType2Finder(self.project).analyze()
114
+ APIObfuscationType2Finder(self.project, self.variable_kb).analyze()
113
115
 
114
116
  def _find_type1(self):
115
117
  cfg = self.kb.cfgs.get_most_accurate()
@@ -195,6 +197,8 @@ class APIObfuscationFinder(Analysis):
195
197
  callsite_node.instruction_addrs[-1],
196
198
  ObservationPointType.OP_BEFORE,
197
199
  )
200
+ if observ is None:
201
+ continue
198
202
  args: list[tuple[int, Any]] = []
199
203
  for arg_idx, func_arg in enumerate(func.arguments):
200
204
  # FIXME: We are ignoring all non-register function arguments until we see a test case where
@@ -232,9 +236,8 @@ class APIObfuscationFinder(Analysis):
232
236
  acceptable_args = False
233
237
  break
234
238
  arg_strs.append((idx, value.decode("utf-8")))
235
- if acceptable_args:
239
+ if acceptable_args and len(arg_strs) == 2:
236
240
  libname_arg_idx, funcname_arg_idx = None, None
237
- assert len(arg_strs) == 2
238
241
  for arg_idx, name in arg_strs:
239
242
  if self.is_libname(name):
240
243
  libname_arg_idx = arg_idx
@@ -1,21 +1,24 @@
1
1
  from __future__ import annotations
2
- from typing import cast
2
+ from typing import TYPE_CHECKING, cast
3
3
 
4
4
  from collections.abc import Iterator
5
5
  from dataclasses import dataclass
6
6
  import logging
7
7
 
8
8
  from angr.project import Project
9
- from angr.analyses.reaching_definitions.reaching_definitions import (
10
- ReachingDefinitionsAnalysis,
11
- FunctionCallRelationships,
12
- )
9
+ from angr.knowledge_base import KnowledgeBase
13
10
  from angr.knowledge_plugins.functions.function import Function
14
11
  from angr.knowledge_plugins.key_definitions import DerefSize
15
12
  from angr.knowledge_plugins.key_definitions.constants import ObservationPointType
16
13
  from angr.knowledge_plugins.key_definitions.atoms import MemoryLocation
17
14
  from angr.sim_variable import SimMemoryVariable
18
15
 
16
+ if TYPE_CHECKING:
17
+ from angr.analyses.reaching_definitions import (
18
+ ReachingDefinitionsAnalysis,
19
+ FunctionCallRelationships,
20
+ )
21
+
19
22
 
20
23
  log = logging.getLogger(__name__)
21
24
 
@@ -40,8 +43,9 @@ class APIObfuscationType2Finder:
40
43
 
41
44
  results: list[APIObfuscationType2]
42
45
 
43
- def __init__(self, project: Project):
46
+ def __init__(self, project: Project, variable_kb: KnowledgeBase | None = None):
44
47
  self.project = project
48
+ self.variable_kb = variable_kb or self.project.kb
45
49
  self.results = []
46
50
 
47
51
  def analyze(self) -> list[APIObfuscationType2]:
@@ -91,8 +95,12 @@ class APIObfuscationType2Finder:
91
95
  log.debug("...Failed to resolve a function name")
92
96
  return
93
97
 
94
- proc_name = result.rstrip(b"\x00").decode("utf-8")
95
- log.debug("...Resolved concrete function name: %s", proc_name)
98
+ try:
99
+ func_name = result.rstrip(b"\x00").decode("utf-8")
100
+ log.debug("...Resolved concrete function name: %s", func_name)
101
+ except UnicodeDecodeError:
102
+ log.debug("...Failed to decode utf-8 function name")
103
+ return
96
104
 
97
105
  # Examine successor definitions to find where the function pointer is written
98
106
  for successor in rda.dep_graph.find_all_successors(callsite_info.ret_defns):
@@ -121,7 +129,7 @@ class APIObfuscationType2Finder:
121
129
 
122
130
  self.results.append(
123
131
  APIObfuscationType2(
124
- resolved_func_name=proc_name,
132
+ resolved_func_name=func_name,
125
133
  resolved_func_ptr=ptr,
126
134
  resolved_in=caller,
127
135
  resolved_by=callee,
@@ -139,7 +147,7 @@ class APIObfuscationType2Finder:
139
147
  log.debug("...Created label %s for address %x", lbl, result.resolved_func_ptr.addr)
140
148
 
141
149
  # Create a variable
142
- global_variables = self.project.kb.variables["global"]
150
+ global_variables = self.variable_kb.variables["global"]
143
151
  variables = global_variables.get_global_variables(result.resolved_func_ptr.addr)
144
152
  if not variables:
145
153
  ident = global_variables.next_variable_ident("global")