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
@@ -0,0 +1,166 @@
1
+ from __future__ import annotations
2
+ from typing import TYPE_CHECKING, cast
3
+
4
+ from collections.abc import Iterator
5
+ from dataclasses import dataclass
6
+ import logging
7
+
8
+ from angr.project import Project
9
+ from angr.knowledge_base import KnowledgeBase
10
+ from angr.knowledge_plugins.functions.function import Function
11
+ from angr.knowledge_plugins.key_definitions import DerefSize
12
+ from angr.knowledge_plugins.key_definitions.constants import ObservationPointType
13
+ from angr.knowledge_plugins.key_definitions.atoms import MemoryLocation
14
+ from angr.sim_variable import SimMemoryVariable
15
+
16
+ if TYPE_CHECKING:
17
+ from angr.analyses.reaching_definitions import (
18
+ ReachingDefinitionsAnalysis,
19
+ FunctionCallRelationships,
20
+ )
21
+
22
+
23
+ log = logging.getLogger(__name__)
24
+
25
+
26
+ @dataclass
27
+ class APIObfuscationType2:
28
+ """
29
+ API Obfuscation Type 2 result.
30
+ """
31
+
32
+ resolved_func_name: str
33
+ resolved_func_ptr: MemoryLocation
34
+ resolved_in: Function
35
+ resolved_by: Function
36
+
37
+
38
+ class APIObfuscationType2Finder:
39
+ """
40
+ Finds global function pointers initialized by calls to dlsym/GetProcAddress and names
41
+ them accordingly.
42
+ """
43
+
44
+ results: list[APIObfuscationType2]
45
+
46
+ def __init__(self, project: Project, variable_kb: KnowledgeBase | None = None):
47
+ self.project = project
48
+ self.variable_kb = variable_kb or self.project.kb
49
+ self.results = []
50
+
51
+ def analyze(self) -> list[APIObfuscationType2]:
52
+ self.results = []
53
+ for caller, callee in self._get_candidates():
54
+ rda = self.project.analyses.ReachingDefinitions(caller, observe_all=True)
55
+ for info in rda.callsites_to(callee):
56
+ self._process_callsite(caller, callee, rda, info)
57
+ self._mark_globals()
58
+ return self.results
59
+
60
+ def _get_candidates(self) -> Iterator[tuple[Function, Function]]:
61
+ """
62
+ Returns a tuple of (caller, callee) where callee is GetProcAddress/dlsym.
63
+ """
64
+ targets = ["GetProcAddress"] if self.project.simos.name == "Win32" else ["dlsym", "dlvsym"]
65
+ for callee in self.project.kb.functions.values():
66
+ if callee.name not in targets:
67
+ continue
68
+ for caller_addr in self.project.kb.callgraph.predecessors(callee.addr):
69
+ caller = self.project.kb.functions[caller_addr]
70
+ yield (caller, callee)
71
+
72
+ def _process_callsite(
73
+ self,
74
+ caller: Function,
75
+ callee: Function,
76
+ rda: ReachingDefinitionsAnalysis,
77
+ callsite_info: FunctionCallRelationships,
78
+ ) -> None:
79
+ """
80
+ Process a resolver function callsite looking for function name concrete string argument.
81
+ """
82
+ func_name_arg_idx = 1
83
+ if len(callsite_info.args_defns) <= func_name_arg_idx:
84
+ return
85
+
86
+ log.debug("Examining call to %r from %r at %r", callee, caller, callsite_info.callsite.ins_addr)
87
+ ld = rda.model.get_observation_by_insn(callsite_info.callsite, ObservationPointType.OP_BEFORE)
88
+ if ld is None:
89
+ return
90
+
91
+ # Attempt resolving static function name from callsite
92
+ string_atom = ld.deref(callsite_info.args_defns[func_name_arg_idx], DerefSize.NULL_TERMINATE)
93
+ result = ld.get_concrete_value(string_atom, cast_to=bytes)
94
+ if result is None:
95
+ log.debug("...Failed to resolve a function name")
96
+ return
97
+
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
104
+
105
+ # Examine successor definitions to find where the function pointer is written
106
+ for successor in rda.dep_graph.find_all_successors(callsite_info.ret_defns):
107
+ if not (
108
+ isinstance(successor.atom, MemoryLocation)
109
+ and isinstance(successor.atom.addr, int)
110
+ and successor.atom.size == self.project.arch.bytes
111
+ ):
112
+ continue
113
+
114
+ ptr = successor.atom
115
+ ptr_addr: int = cast(int, ptr.addr)
116
+ log.debug("...Found function pointer %r", ptr)
117
+
118
+ sym = self.project.loader.find_symbol(ptr_addr)
119
+ if sym is not None:
120
+ log.debug("...Already have pointer symbol: %r. Skipping.", sym)
121
+ continue
122
+ if ptr_addr in self.project.kb.labels:
123
+ log.debug("...Already have pointer label. Skipping.")
124
+ continue
125
+ sec = self.project.loader.find_section_containing(ptr_addr)
126
+ if not sec or not sec.is_writable:
127
+ log.debug("...Bogus section. Skipping.")
128
+ continue
129
+
130
+ self.results.append(
131
+ APIObfuscationType2(
132
+ resolved_func_name=func_name,
133
+ resolved_func_ptr=ptr,
134
+ resolved_in=caller,
135
+ resolved_by=callee,
136
+ )
137
+ )
138
+
139
+ def _mark_globals(self):
140
+ """
141
+ Create function pointer labels/variables.
142
+ """
143
+ for result in self.results:
144
+ # Create a label
145
+ lbl = self.project.kb.labels.get_unique_label(f"p_{result.resolved_func_name}")
146
+ self.project.kb.labels[result.resolved_func_ptr.addr] = lbl
147
+ log.debug("...Created label %s for address %x", lbl, result.resolved_func_ptr.addr)
148
+
149
+ # Create a variable
150
+ global_variables = self.variable_kb.variables["global"]
151
+ variables = global_variables.get_global_variables(result.resolved_func_ptr.addr)
152
+ if not variables:
153
+ ident = global_variables.next_variable_ident("global")
154
+ var = SimMemoryVariable(
155
+ result.resolved_func_ptr.addr, result.resolved_func_ptr.size, name=lbl, ident=ident
156
+ )
157
+ global_variables.set_variable("global", result.resolved_func_ptr.addr, var)
158
+ log.debug("...Created variable %r", var)
159
+ elif len(variables) == 1:
160
+ (var,) = variables
161
+ log.debug("...Renaming variable %r -> %s", var, lbl)
162
+ var.name = lbl
163
+
164
+ self.project.kb.obfuscations.type2_deobfuscated_apis[result.resolved_func_ptr.addr] = (
165
+ result.resolved_func_name
166
+ )
@@ -9,11 +9,11 @@ import networkx
9
9
 
10
10
  import claripy
11
11
 
12
- from angr import sim_options
13
12
  from angr.analyses import Analysis, AnalysesHub
14
- from angr.errors import SimMemoryMissingError, AngrCallableMultistateError, AngrCallableError
13
+ from angr.errors import SimMemoryMissingError, AngrCallableMultistateError, AngrCallableError, AngrAnalysisError
15
14
  from angr.calling_conventions import SimRegArg, default_cc
16
15
  from angr.state_plugins.sim_action import SimActionData
16
+ from angr.sim_options import ZERO_FILL_UNCONSTRAINED_REGISTERS, ZERO_FILL_UNCONSTRAINED_MEMORY, TRACK_MEMORY_ACTIONS
17
17
  from angr.sim_type import SimTypeFunction, SimTypeBottom, SimTypePointer
18
18
  from angr.analyses.reaching_definitions import ObservationPointType
19
19
  from angr.utils.graph import GraphUtils
@@ -23,12 +23,23 @@ from .irsb_reg_collector import IRSBRegisterCollector
23
23
  _l = logging.getLogger(__name__)
24
24
 
25
25
 
26
+ STEP_LIMIT_FIND = 500
27
+ STEP_LIMIT_ANALYSIS = 5000
28
+
29
+
26
30
  class StringDeobFuncDescriptor:
31
+ """
32
+ Describes a string deobfuscation function.
33
+ """
34
+
35
+ string_input_arg_idx: int
36
+ string_output_arg_idx: int
37
+ string_length_arg_idx: int | None
38
+ string_null_terminating: bool | None
39
+
27
40
  def __init__(self):
28
- self.string_input_arg_idx = None
29
- self.string_output_arg_idx = None
30
41
  self.string_length_arg_idx = None
31
- self.string_null_terminating: bool | None = None
42
+ self.string_null_terminating = None
32
43
 
33
44
 
34
45
  class StringObfuscationFinder(Analysis):
@@ -89,6 +100,9 @@ class StringObfuscationFinder(Analysis):
89
100
  # Type 1 string deobfuscation functions will decrypt each string once and for good.
90
101
 
91
102
  cfg = self.kb.cfgs.get_most_accurate()
103
+ if cfg is None:
104
+ raise AngrAnalysisError("StringObfuscationFinder needs a CFG for the analysis")
105
+
92
106
  arch = self.project.arch
93
107
 
94
108
  type1_candidates: list[tuple[int, StringDeobFuncDescriptor]] = []
@@ -100,6 +114,10 @@ class StringObfuscationFinder(Analysis):
100
114
  if func.prototype is None or len(func.prototype.args) < 1:
101
115
  continue
102
116
 
117
+ if len(func.arguments) != len(func.prototype.args):
118
+ # function argument locations and function prototype arguments do not match
119
+ continue
120
+
103
121
  if self.project.kb.functions.callgraph.out_degree[func.addr] != 0:
104
122
  continue
105
123
 
@@ -123,14 +141,22 @@ class StringObfuscationFinder(Analysis):
123
141
  dec = self.project.analyses.Decompiler(func, cfg=cfg)
124
142
  except Exception: # pylint:disable=broad-exception-caught
125
143
  continue
126
- if dec.codegen is None or not self._like_type1_deobfuscation_function(dec.codegen.text):
144
+ if (
145
+ dec.codegen is None
146
+ or not dec.codegen.text
147
+ or not self._like_type1_deobfuscation_function(dec.codegen.text)
148
+ ):
149
+ continue
150
+
151
+ func_node = cfg.get_any_node(func.addr)
152
+ if func_node is None:
127
153
  continue
128
154
 
129
155
  args_list = []
130
156
  for caller in callers:
131
157
  callsite_nodes = [
132
158
  pred
133
- for pred in cfg.get_predecessors(cfg.get_any_node(func.addr))
159
+ for pred in cfg.get_predecessors(func_node)
134
160
  if pred.function_address == caller and pred.instruction_addrs
135
161
  ]
136
162
  observation_points = []
@@ -148,15 +174,21 @@ class StringObfuscationFinder(Analysis):
148
174
  callsite_node.instruction_addrs[-1],
149
175
  ObservationPointType.OP_BEFORE,
150
176
  )
177
+ if observ is None:
178
+ continue
151
179
  # load values for each function argument
152
180
  args: list[tuple[int, Any]] = []
153
181
  for arg_idx, func_arg in enumerate(func.arguments):
154
182
  # FIXME: We are ignoring all non-register function arguments until we see a test case where
155
183
  # FIXME: stack-passing arguments are used
184
+ real_arg = func.prototype.args[arg_idx]
156
185
  if isinstance(func_arg, SimRegArg):
157
186
  reg_offset, reg_size = arch.registers[func_arg.reg_name]
187
+ arg_size = (
188
+ real_arg.size if real_arg.size is not None else reg_size
189
+ ) // self.project.arch.byte_width
158
190
  try:
159
- mv = observ.registers.load(reg_offset, size=reg_size)
191
+ mv = observ.registers.load(reg_offset, size=arg_size)
160
192
  except SimMemoryMissingError:
161
193
  args.append((arg_idx, claripy.BVV(0xDEADBEEF, self.project.arch.bits)))
162
194
  continue
@@ -185,7 +217,15 @@ class StringObfuscationFinder(Analysis):
185
217
  # now that we have good arguments, let's test the function!
186
218
  for args in args_list:
187
219
  func_call = self.project.factory.callable(
188
- func.addr, concrete_only=True, cc=func.calling_convention, prototype=func.prototype
220
+ func.addr,
221
+ concrete_only=True,
222
+ cc=func.calling_convention,
223
+ prototype=func.prototype,
224
+ add_options={
225
+ ZERO_FILL_UNCONSTRAINED_MEMORY,
226
+ ZERO_FILL_UNCONSTRAINED_REGISTERS,
227
+ },
228
+ step_limit=STEP_LIMIT_FIND,
189
229
  )
190
230
 
191
231
  # before calling the function, let's record the crime scene
@@ -202,6 +242,9 @@ class StringObfuscationFinder(Analysis):
202
242
  except (AngrCallableMultistateError, AngrCallableError):
203
243
  continue
204
244
 
245
+ if func_call.result_state is None:
246
+ continue
247
+
205
248
  # let's see what this amazing function has done
206
249
  # TODO: Support cases where input and output are using different function arguments
207
250
  for arg_idx, addr, old_value in values:
@@ -240,6 +283,9 @@ class StringObfuscationFinder(Analysis):
240
283
 
241
284
  arch = self.project.arch
242
285
  cfg = self.kb.cfgs.get_most_accurate()
286
+ if cfg is None:
287
+ raise AngrAnalysisError("StringObfuscationFinder needs a CFG for the analysis")
288
+
243
289
  func = self.kb.functions.get_by_addr(func_addr)
244
290
  func_node = cfg.get_any_node(func_addr)
245
291
  assert func_node is not None
@@ -260,14 +306,20 @@ class StringObfuscationFinder(Analysis):
260
306
  callsite_node.instruction_addrs[-1],
261
307
  ObservationPointType.OP_BEFORE,
262
308
  )
309
+ if observ is None:
310
+ continue
263
311
  args = []
264
- for func_arg in func.arguments:
312
+ assert func.prototype is not None and len(func.arguments) == len(func.prototype.args)
313
+ for func_arg, real_arg in zip(func.arguments, func.prototype.args):
265
314
  # FIXME: We are ignoring all non-register function arguments until we see a test case where
266
315
  # FIXME: stack-passing arguments are used
267
316
  if isinstance(func_arg, SimRegArg):
268
317
  reg_offset, reg_size = arch.registers[func_arg.reg_name]
318
+ arg_size = (
319
+ real_arg.size if real_arg.size is not None else reg_size
320
+ ) // self.project.arch.byte_width
269
321
  try:
270
- mv = observ.registers.load(reg_offset, size=reg_size)
322
+ mv = observ.registers.load(reg_offset, size=arg_size)
271
323
  except SimMemoryMissingError:
272
324
  args.append(claripy.BVV(0xDEADBEEF, self.project.arch.bits))
273
325
  continue
@@ -286,7 +338,12 @@ class StringObfuscationFinder(Analysis):
286
338
 
287
339
  # call the function
288
340
  func_call = self.project.factory.callable(
289
- func.addr, concrete_only=True, cc=func.calling_convention, prototype=func.prototype
341
+ func.addr,
342
+ concrete_only=True,
343
+ cc=func.calling_convention,
344
+ prototype=func.prototype,
345
+ add_options={ZERO_FILL_UNCONSTRAINED_MEMORY, ZERO_FILL_UNCONSTRAINED_REGISTERS},
346
+ step_limit=STEP_LIMIT_ANALYSIS,
290
347
  )
291
348
  try:
292
349
  func_call(*args)
@@ -303,6 +360,9 @@ class StringObfuscationFinder(Analysis):
303
360
  )
304
361
  continue
305
362
 
363
+ if func_call.result_state is None:
364
+ continue
365
+
306
366
  # dump the decrypted string!
307
367
  output_addr = args[desc.string_output_arg_idx]
308
368
  length = args[desc.string_length_arg_idx].concrete_value if desc.string_length_arg_idx is not None else 256
@@ -322,6 +382,8 @@ class StringObfuscationFinder(Analysis):
322
382
  xref_set = xrefs.get_xrefs_by_dst(str_addr)
323
383
  block_addrs = {xref.block_addr for xref in xref_set}
324
384
  for block_addr in block_addrs:
385
+ if block_addr is None:
386
+ continue
325
387
  node = cfg.get_any_node(block_addr)
326
388
  if node is not None:
327
389
  callees = list(self.kb.functions.callgraph.successors(node.function_address))
@@ -340,6 +402,8 @@ class StringObfuscationFinder(Analysis):
340
402
  # Type 2 string deobfuscation functions will decrypt each string once and for good.
341
403
 
342
404
  cfg = self.kb.cfgs.get_most_accurate()
405
+ if cfg is None:
406
+ raise AngrAnalysisError("StringObfuscationFinder needs a CFG for the analysis")
343
407
 
344
408
  type2_candidates: list[tuple[int, StringDeobFuncDescriptor, list[tuple[int, int, bytes]]]] = []
345
409
 
@@ -374,7 +438,11 @@ class StringObfuscationFinder(Analysis):
374
438
  dec = self.project.analyses.Decompiler(func, cfg=cfg, expr_collapse_depth=64)
375
439
  except Exception: # pylint:disable=broad-exception-caught
376
440
  continue
377
- if dec.codegen is None or not self._like_type2_deobfuscation_function(dec.codegen.text):
441
+ if (
442
+ dec.codegen is None
443
+ or not dec.codegen.text
444
+ or not self._like_type2_deobfuscation_function(dec.codegen.text)
445
+ ):
378
446
  continue
379
447
 
380
448
  desc = StringDeobFuncDescriptor()
@@ -384,7 +452,8 @@ class StringObfuscationFinder(Analysis):
384
452
  concrete_only=True,
385
453
  cc=func.calling_convention,
386
454
  prototype=func.prototype,
387
- add_options={sim_options.TRACK_MEMORY_ACTIONS},
455
+ add_options={TRACK_MEMORY_ACTIONS, ZERO_FILL_UNCONSTRAINED_MEMORY, ZERO_FILL_UNCONSTRAINED_REGISTERS},
456
+ step_limit=STEP_LIMIT_FIND,
388
457
  )
389
458
 
390
459
  try:
@@ -392,6 +461,9 @@ class StringObfuscationFinder(Analysis):
392
461
  except (AngrCallableMultistateError, AngrCallableError):
393
462
  continue
394
463
 
464
+ if func_call.result_state is None:
465
+ continue
466
+
395
467
  # where are the reads and writes?
396
468
  all_global_reads = []
397
469
  all_global_writes = []
@@ -399,7 +471,7 @@ class StringObfuscationFinder(Analysis):
399
471
  if not isinstance(action, SimActionData):
400
472
  continue
401
473
  if not action.actual_addrs:
402
- if not action.addr.ast.concrete:
474
+ if action.addr is None or not action.addr.ast.concrete:
403
475
  continue
404
476
  actual_addrs = [action.addr.ast.concrete_value]
405
477
  else:
@@ -469,6 +541,8 @@ class StringObfuscationFinder(Analysis):
469
541
  """
470
542
 
471
543
  cfg = self.kb.cfgs.get_most_accurate()
544
+ if cfg is None:
545
+ raise AngrAnalysisError("StringObfuscationFinder needs a CFG for the analysis")
472
546
 
473
547
  # for each string table address, we find its string loader function
474
548
  # an obvious candidate function is 0x140001b20
@@ -478,6 +552,8 @@ class StringObfuscationFinder(Analysis):
478
552
  xref_set = xrefs.get_xrefs_by_dst(table_addr)
479
553
  block_addrs = {xref.block_addr for xref in xref_set}
480
554
  for block_addr in block_addrs:
555
+ if block_addr is None:
556
+ continue
481
557
  node = cfg.get_any_node(block_addr)
482
558
  if node is not None:
483
559
  callees = list(self.kb.functions.callgraph.successors(node.function_address))
@@ -496,6 +572,9 @@ class StringObfuscationFinder(Analysis):
496
572
  # not have a SimProcedure for)
497
573
 
498
574
  cfg = self.kb.cfgs.get_most_accurate()
575
+ if cfg is None:
576
+ raise AngrAnalysisError("StringObfuscationFinder needs a CFG for the analysis")
577
+
499
578
  functions = self.kb.functions
500
579
  callgraph_digraph = networkx.DiGraph(functions.callgraph)
501
580
 
@@ -554,7 +633,7 @@ class StringObfuscationFinder(Analysis):
554
633
  except Exception: # pylint:disable=broad-exception-caught
555
634
  # catch all exceptions
556
635
  continue
557
- if dec.codegen is None:
636
+ if dec.codegen is None or not dec.codegen.text:
558
637
  continue
559
638
  if not self._like_type3_deobfuscation_function(dec.codegen.text):
560
639
  continue
@@ -605,6 +684,8 @@ class StringObfuscationFinder(Analysis):
605
684
  """
606
685
 
607
686
  cfg = self.kb.cfgs.get_most_accurate()
687
+ if cfg is None:
688
+ raise AngrAnalysisError("StringObfuscationFinder needs a CFG for the analysis")
608
689
 
609
690
  call_sites = cfg.get_predecessors(cfg.get_any_node(func_addr))
610
691
  callinsn2content = {}
@@ -687,7 +768,7 @@ class StringObfuscationFinder(Analysis):
687
768
  # execute the block at the call site
688
769
  state = self.project.factory.blank_state(
689
770
  addr=call_site_addr,
690
- add_options={sim_options.ZERO_FILL_UNCONSTRAINED_REGISTERS, sim_options.ZERO_FILL_UNCONSTRAINED_MEMORY},
771
+ add_options={ZERO_FILL_UNCONSTRAINED_REGISTERS, ZERO_FILL_UNCONSTRAINED_MEMORY},
691
772
  )
692
773
  # setup sp and bp, just in case
693
774
  state.regs._sp = 0x7FFF0000
@@ -728,7 +809,13 @@ class StringObfuscationFinder(Analysis):
728
809
  self.project.arch
729
810
  )
730
811
  callable_0 = self.project.factory.callable(
731
- func_addr, concrete_only=True, base_state=in_state, cc=cc, prototype=prototype_0
812
+ func_addr,
813
+ concrete_only=True,
814
+ base_state=in_state,
815
+ cc=cc,
816
+ prototype=prototype_0,
817
+ add_options={ZERO_FILL_UNCONSTRAINED_MEMORY, ZERO_FILL_UNCONSTRAINED_REGISTERS},
818
+ step_limit=STEP_LIMIT_ANALYSIS,
732
819
  )
733
820
 
734
821
  try:
@@ -181,7 +181,7 @@ class ForwardAnalysis(Generic[AnalysisState, NodeType, JobType, JobKey]):
181
181
  """
182
182
  return node
183
183
 
184
- def _run_on_node(self, node: NodeType, state: AnalysisState) -> tuple[bool, AnalysisState]:
184
+ def _run_on_node(self, node: NodeType, state: AnalysisState) -> tuple[bool | None, AnalysisState]:
185
185
  """
186
186
  The analysis routine that runs on each node in the graph.
187
187
 
@@ -31,7 +31,7 @@ class ClaripyDataEngineMixin(
31
31
 
32
32
 
33
33
  def _vex_make_comparison(
34
- func: Callable[[claripy.ast.BV, claripy.ast.BV], claripy.ast.Bool]
34
+ func: Callable[[claripy.ast.BV, claripy.ast.BV], claripy.ast.Bool],
35
35
  ) -> Callable[[ClaripyDataEngineMixin, Binop], claripy.ast.BV]:
36
36
  @SimEngineLightVEX.binop_handler
37
37
  def inner(self, expr):
@@ -44,7 +44,7 @@ def _vex_make_comparison(
44
44
 
45
45
 
46
46
  def _vex_make_vec_comparison(
47
- func: Callable[[claripy.ast.BV, claripy.ast.BV], claripy.ast.Bool]
47
+ func: Callable[[claripy.ast.BV, claripy.ast.BV], claripy.ast.Bool],
48
48
  ) -> Callable[[ClaripyDataEngineMixin, int, int, Binop], claripy.ast.BV]:
49
49
  @SimEngineLightVEX.binopv_handler
50
50
  def inner(self, size, count, expr):
@@ -56,7 +56,7 @@ def _vex_make_vec_comparison(
56
56
 
57
57
 
58
58
  def _vex_make_operation(
59
- func: Callable[[claripy.ast.BV, claripy.ast.BV], claripy.ast.BV]
59
+ func: Callable[[claripy.ast.BV, claripy.ast.BV], claripy.ast.BV],
60
60
  ) -> Callable[[ClaripyDataEngineMixin, Binop], claripy.ast.BV]:
61
61
  @SimEngineLightVEX.binop_handler
62
62
  def inner(self, expr: Binop):
@@ -70,7 +70,7 @@ def _vex_make_operation(
70
70
 
71
71
 
72
72
  def _vex_make_unary_operation(
73
- func: Callable[[claripy.ast.BV], claripy.ast.BV]
73
+ func: Callable[[claripy.ast.BV], claripy.ast.BV],
74
74
  ) -> Callable[[ClaripyDataEngineMixin, Unop], claripy.ast.BV]:
75
75
  @SimEngineLightVEX.unop_handler
76
76
  def inner(self, expr):
@@ -84,7 +84,7 @@ def _vex_make_unary_operation(
84
84
 
85
85
 
86
86
  def _vex_make_shift_operation(
87
- func: Callable[[claripy.ast.BV, claripy.ast.BV], claripy.ast.BV]
87
+ func: Callable[[claripy.ast.BV, claripy.ast.BV], claripy.ast.BV],
88
88
  ) -> Callable[[ClaripyDataEngineMixin, Binop], claripy.ast.BV]:
89
89
  @_vex_make_operation
90
90
  def inner(a, b):
@@ -99,7 +99,7 @@ def _vex_make_shift_operation(
99
99
 
100
100
 
101
101
  def _vex_make_vec_operation(
102
- func: Callable[[claripy.ast.BV, claripy.ast.BV], claripy.ast.BV]
102
+ func: Callable[[claripy.ast.BV, claripy.ast.BV], claripy.ast.BV],
103
103
  ) -> Callable[[ClaripyDataEngineMixin, int, int, Binop], claripy.ast.BV]:
104
104
  @SimEngineLightVEX.binopv_handler
105
105
  def inner(self, size, count, expr):
@@ -15,7 +15,7 @@ from angr.knowledge_plugins.key_definitions.atoms import (
15
15
  from angr.knowledge_plugins.key_definitions.definition import Definition
16
16
  from angr.analyses import register_analysis
17
17
  from .reaching_definitions import ReachingDefinitionsAnalysis, ReachingDefinitionsModel
18
- from .function_handler import FunctionHandler, FunctionCallData
18
+ from .function_handler import FunctionHandler, FunctionCallData, FunctionCallRelationships
19
19
  from .rd_state import ReachingDefinitionsState
20
20
 
21
21
  if TYPE_CHECKING:
@@ -29,6 +29,7 @@ __all__ = (
29
29
  "ConstantSrc",
30
30
  "Definition",
31
31
  "FunctionCallData",
32
+ "FunctionCallRelationships",
32
33
  "FunctionHandler",
33
34
  "GuardUse",
34
35
  "LiveDefinitions",
@@ -6,7 +6,6 @@ from typing import (
6
6
  Any,
7
7
  )
8
8
  from collections.abc import Iterable, Iterator
9
- from dataclasses import dataclass
10
9
 
11
10
  import networkx
12
11
 
@@ -35,16 +34,6 @@ def _is_definition(node):
35
34
  return isinstance(node, Definition)
36
35
 
37
36
 
38
- @dataclass
39
- class FunctionCallRelationships: # TODO this doesn't belong in this file anymore
40
- callsite: CodeLocation
41
- target: int | None
42
- args_defns: list[set[Definition]]
43
- other_input_defns: set[Definition]
44
- ret_defns: set[Definition]
45
- other_output_defns: set[Definition]
46
-
47
-
48
37
  class DepGraph:
49
38
  """
50
39
  The representation of a dependency graph: a directed graph, where nodes are definitions, and edges represent uses.
@@ -84,7 +73,7 @@ class DepGraph:
84
73
  """
85
74
  self._graph.add_edge(source, destination, **labels)
86
75
 
87
- def nodes(self) -> networkx.classes.reportviews.NodeView[Definition]:
76
+ def nodes(self) -> Iterator[Definition]:
88
77
  return self._graph.nodes()
89
78
 
90
79
  def predecessors(self, node: Definition) -> Iterator[Definition]: