angr 9.2.141__py3-none-manylinux2014_x86_64.whl → 9.2.143__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 (71) hide show
  1. angr/__init__.py +1 -1
  2. angr/analyses/calling_convention/calling_convention.py +26 -12
  3. angr/analyses/calling_convention/fact_collector.py +31 -9
  4. angr/analyses/cfg/cfg_base.py +38 -4
  5. angr/analyses/cfg/cfg_fast.py +23 -7
  6. angr/analyses/cfg/indirect_jump_resolvers/const_resolver.py +12 -1
  7. angr/analyses/cfg/indirect_jump_resolvers/jumptable.py +8 -1
  8. angr/analyses/class_identifier.py +8 -7
  9. angr/analyses/complete_calling_conventions.py +19 -6
  10. angr/analyses/decompiler/ail_simplifier.py +138 -98
  11. angr/analyses/decompiler/clinic.py +73 -5
  12. angr/analyses/decompiler/condition_processor.py +7 -7
  13. angr/analyses/decompiler/decompilation_cache.py +2 -1
  14. angr/analyses/decompiler/decompiler.py +10 -2
  15. angr/analyses/decompiler/dephication/graph_vvar_mapping.py +4 -6
  16. angr/analyses/decompiler/optimization_passes/base_ptr_save_simplifier.py +8 -2
  17. angr/analyses/decompiler/optimization_passes/condition_constprop.py +110 -46
  18. angr/analyses/decompiler/optimization_passes/ite_region_converter.py +8 -0
  19. angr/analyses/decompiler/optimization_passes/lowered_switch_simplifier.py +1 -1
  20. angr/analyses/decompiler/optimization_passes/optimization_pass.py +2 -0
  21. angr/analyses/decompiler/optimization_passes/register_save_area_simplifier.py +29 -7
  22. angr/analyses/decompiler/optimization_passes/stack_canary_simplifier.py +6 -0
  23. angr/analyses/decompiler/optimization_passes/win_stack_canary_simplifier.py +9 -1
  24. angr/analyses/decompiler/peephole_optimizations/simplify_pc_relative_loads.py +15 -1
  25. angr/analyses/decompiler/region_identifier.py +70 -47
  26. angr/analyses/decompiler/sequence_walker.py +8 -0
  27. angr/analyses/decompiler/ssailification/rewriting.py +47 -17
  28. angr/analyses/decompiler/ssailification/rewriting_engine.py +13 -0
  29. angr/analyses/decompiler/stack_item.py +36 -0
  30. angr/analyses/decompiler/structured_codegen/c.py +14 -9
  31. angr/analyses/decompiler/structuring/phoenix.py +3 -3
  32. angr/analyses/decompiler/utils.py +13 -0
  33. angr/analyses/find_objects_static.py +2 -1
  34. angr/analyses/reaching_definitions/engine_vex.py +13 -0
  35. angr/analyses/reaching_definitions/function_handler.py +24 -10
  36. angr/analyses/reaching_definitions/function_handler_library/stdio.py +1 -0
  37. angr/analyses/reaching_definitions/function_handler_library/stdlib.py +45 -12
  38. angr/analyses/reaching_definitions/function_handler_library/string.py +77 -21
  39. angr/analyses/reaching_definitions/function_handler_library/unistd.py +21 -1
  40. angr/analyses/reaching_definitions/rd_state.py +11 -7
  41. angr/analyses/s_liveness.py +44 -6
  42. angr/analyses/s_propagator.py +40 -29
  43. angr/analyses/s_reaching_definitions/s_rda_model.py +48 -37
  44. angr/analyses/s_reaching_definitions/s_rda_view.py +6 -3
  45. angr/analyses/s_reaching_definitions/s_reaching_definitions.py +21 -21
  46. angr/analyses/typehoon/simple_solver.py +35 -8
  47. angr/analyses/typehoon/typehoon.py +3 -1
  48. angr/analyses/variable_recovery/engine_ail.py +6 -6
  49. angr/calling_conventions.py +20 -10
  50. angr/knowledge_plugins/functions/function.py +5 -10
  51. angr/knowledge_plugins/variables/variable_manager.py +27 -0
  52. angr/procedures/definitions/__init__.py +3 -10
  53. angr/procedures/definitions/linux_kernel.py +5 -0
  54. angr/procedures/definitions/wdk_ntoskrnl.py +2 -0
  55. angr/procedures/win32_kernel/__fastfail.py +15 -0
  56. angr/sim_procedure.py +2 -2
  57. angr/simos/simos.py +14 -10
  58. angr/simos/windows.py +42 -1
  59. angr/utils/ail.py +41 -1
  60. angr/utils/cpp.py +17 -0
  61. angr/utils/doms.py +149 -0
  62. angr/utils/library.py +1 -1
  63. angr/utils/ssa/__init__.py +21 -14
  64. angr/utils/ssa/vvar_uses_collector.py +2 -2
  65. angr/utils/types.py +12 -1
  66. {angr-9.2.141.dist-info → angr-9.2.143.dist-info}/METADATA +7 -7
  67. {angr-9.2.141.dist-info → angr-9.2.143.dist-info}/RECORD +71 -67
  68. {angr-9.2.141.dist-info → angr-9.2.143.dist-info}/LICENSE +0 -0
  69. {angr-9.2.141.dist-info → angr-9.2.143.dist-info}/WHEEL +0 -0
  70. {angr-9.2.141.dist-info → angr-9.2.143.dist-info}/entry_points.txt +0 -0
  71. {angr-9.2.141.dist-info → angr-9.2.143.dist-info}/top_level.txt +0 -0
angr/__init__.py CHANGED
@@ -2,7 +2,7 @@
2
2
  # pylint: disable=wrong-import-position
3
3
  from __future__ import annotations
4
4
 
5
- __version__ = "9.2.141"
5
+ __version__ = "9.2.143"
6
6
 
7
7
  if bytes is str:
8
8
  raise Exception(
@@ -165,6 +165,19 @@ class CallingConventionAnalysis(Analysis):
165
165
  ):
166
166
  return
167
167
 
168
+ if (
169
+ hooker is not None
170
+ and hooker.cc is not None
171
+ and hooker.is_function
172
+ and not hooker.guessed_prototype
173
+ and hooker.prototype is not None
174
+ ):
175
+ # copy the calling convention and prototype from the SimProcedure instance
176
+ self.cc = hooker.cc
177
+ self.prototype = hooker.prototype
178
+ self.prototype_libname = hooker.library_name
179
+ return
180
+
168
181
  if self._function.prototype is None:
169
182
  # try our luck
170
183
  # we set ignore_binary_name to True because the binary name SimProcedures is "cle##externs" and does not
@@ -207,9 +220,9 @@ class CallingConventionAnalysis(Analysis):
207
220
  self.prototype = prototype # type: ignore
208
221
  return
209
222
  if self._function.is_plt:
210
- r = self._analyze_plt()
211
- if r is not None:
212
- self.cc, self.prototype = r
223
+ r_plt = self._analyze_plt()
224
+ if r_plt is not None:
225
+ self.cc, self.prototype, self.prototype_libname = r_plt
213
226
  return
214
227
 
215
228
  r = self._analyze_function()
@@ -265,11 +278,11 @@ class CallingConventionAnalysis(Analysis):
265
278
  self.cc = cc
266
279
  self.prototype = prototype
267
280
 
268
- def _analyze_plt(self) -> tuple[SimCC, SimTypeFunction | None] | None:
281
+ def _analyze_plt(self) -> tuple[SimCC, SimTypeFunction | None, str | None] | None:
269
282
  """
270
283
  Get the calling convention for a PLT stub.
271
284
 
272
- :return: A calling convention.
285
+ :return: A calling convention, the function type, as well as the library name if available.
273
286
  """
274
287
  assert self._function is not None
275
288
 
@@ -309,14 +322,15 @@ class CallingConventionAnalysis(Analysis):
309
322
  if self.project.is_hooked(real_func.addr):
310
323
  # prioritize the hooker
311
324
  hooker = self.project.hooked_by(real_func.addr)
312
- if hooker is not None and (
313
- not hooker.is_stub or (hooker.is_function and not hooker.guessed_prototype)
314
- ):
315
- return cc, hooker.prototype
325
+ if hooker is not None and hooker.is_function and not hooker.guessed_prototype:
326
+ # we only take the prototype from the SimProcedure if
327
+ # - the SimProcedure is a function
328
+ # - the prototype of the SimProcedure is not guessed
329
+ return cc, hooker.prototype, hooker.library_name
316
330
  if real_func.prototype is not None:
317
- return cc, real_func.prototype
331
+ return cc, real_func.prototype, real_func.prototype_libname
318
332
  else:
319
- return cc, real_func.prototype
333
+ return cc, real_func.prototype, real_func.prototype_libname
320
334
 
321
335
  if self.analyze_callsites:
322
336
  # determine the calling convention by analyzing its callsites
@@ -330,7 +344,7 @@ class CallingConventionAnalysis(Analysis):
330
344
  prototype = self._adjust_prototype(
331
345
  prototype, callsite_facts, update_arguments=UpdateArgumentsOption.AlwaysUpdate
332
346
  )
333
- return cc, prototype
347
+ return cc, prototype, None
334
348
 
335
349
  return None
336
350
 
@@ -1,10 +1,11 @@
1
1
  # pylint:disable=too-many-boolean-expressions
2
2
  from __future__ import annotations
3
- from typing import Any
3
+ from typing import Any, TYPE_CHECKING
4
4
 
5
5
  import pyvex
6
6
  import claripy
7
7
 
8
+ from angr import SIM_LIBRARIES, SIM_TYPE_COLLECTIONS
8
9
  from angr.utils.bits import s2u, u2s
9
10
  from angr.block import Block
10
11
  from angr.analyses.analysis import Analysis
@@ -13,9 +14,12 @@ from angr.knowledge_plugins.functions import Function
13
14
  from angr.codenode import BlockNode, HookNode
14
15
  from angr.engines.light import SimEngineNostmtVEX, SimEngineLight, SpOffset, RegisterOffset
15
16
  from angr.calling_conventions import SimRegArg, SimStackArg, default_cc
16
- from angr.sim_type import SimTypeBottom
17
+ from angr.sim_type import SimTypeBottom, dereference_simtype, SimTypeFunction
17
18
  from .utils import is_sane_register_variable
18
19
 
20
+ if TYPE_CHECKING:
21
+ from angr.codenode import CodeNode
22
+
19
23
 
20
24
  class FactCollectorState:
21
25
  """
@@ -224,9 +228,12 @@ class FactCollector(Analysis):
224
228
  callee_restored_regs = self._analyze_endpoints_for_restored_regs()
225
229
  self._determine_input_args(end_states, callee_restored_regs)
226
230
 
227
- def _analyze_startpoint(self):
231
+ def _analyze_startpoint(self) -> list[FactCollectorState]:
228
232
  func_graph = self.function.transition_graph
229
233
  startpoint = self.function.startpoint
234
+ if startpoint is None:
235
+ return []
236
+
230
237
  bp_as_gpr = self.function.info.get("bp_as_gpr", False)
231
238
  engine = SimEngineFactCollectorVEX(self.project, bp_as_gpr)
232
239
  init_state = FactCollectorState()
@@ -235,9 +242,9 @@ class FactCollector(Analysis):
235
242
  init_state.bp_value = init_state.sp_value
236
243
 
237
244
  traversed = set()
238
- queue: list[tuple[int, FactCollectorState, BlockNode | HookNode | Function, BlockNode | HookNode | None]] = [
239
- (0, init_state, startpoint, None)
240
- ]
245
+ queue: list[
246
+ tuple[int, FactCollectorState, CodeNode | BlockNode | HookNode | Function, BlockNode | HookNode | None]
247
+ ] = [(0, init_state, startpoint, None)]
241
248
  end_states: list[FactCollectorState] = []
242
249
  while queue:
243
250
  depth, state, node, retnode = queue.pop(0)
@@ -398,9 +405,24 @@ class FactCollector(Analysis):
398
405
  and not isinstance(func_succ.prototype.returnty, SimTypeBottom)
399
406
  ):
400
407
  # assume the function overwrites the return variable
401
- returnty_size = func_succ.prototype.returnty.with_arch(self.project.arch).size
402
- assert returnty_size is not None
403
- retval_size = returnty_size // self.project.arch.byte_width
408
+ proto = func_succ.prototype
409
+ if func_succ.prototype_libname is not None:
410
+ # we need to deref the prototype in case it uses SimTypeRef internally
411
+ type_collections = []
412
+ prototype_lib = SIM_LIBRARIES[func_succ.prototype_libname]
413
+ if prototype_lib.type_collection_names:
414
+ for typelib_name in prototype_lib.type_collection_names:
415
+ type_collections.append(SIM_TYPE_COLLECTIONS[typelib_name])
416
+ proto = dereference_simtype(proto, type_collections)
417
+
418
+ assert isinstance(proto, SimTypeFunction) and proto.returnty is not None
419
+ returnty_size = proto.returnty.with_arch(self.project.arch).size
420
+ if returnty_size is None:
421
+ # it may be None if somehow we cannot resolve a SimTypeRef; we fall back to the full
422
+ # machine word size
423
+ retval_size = self.project.arch.bytes
424
+ else:
425
+ retval_size = returnty_size // self.project.arch.byte_width
404
426
  retval_sizes.append(retval_size)
405
427
  continue
406
428
 
@@ -11,7 +11,7 @@ import pyvex
11
11
  from cle import ELF, PE, Blob, TLSObject, MachO, ExternObject, KernelObject, FunctionHintSource, Hex, Coff, SRec, XBE
12
12
  from cle.backends import NamedRegion
13
13
  import archinfo
14
- from archinfo.arch_soot import SootAddressDescriptor
14
+ from archinfo.arch_soot import SootAddressDescriptor, SootMethodDescriptor
15
15
  from archinfo.arch_arm import is_arm_arch, get_real_address_if_arm
16
16
 
17
17
  from angr.knowledge_plugins.functions.function_manager import FunctionManager
@@ -129,7 +129,7 @@ class CFGBase(Analysis):
129
129
 
130
130
  # Store all the functions analyzed before the set is cleared
131
131
  # Used for performance optimization
132
- self._updated_nonreturning_functions: set[int] | None = None
132
+ self._updated_nonreturning_functions: set[int | SootMethodDescriptor] | None = None
133
133
 
134
134
  self._normalize = normalize
135
135
 
@@ -246,7 +246,7 @@ class CFGBase(Analysis):
246
246
  )
247
247
 
248
248
  self._regions_size = sum((end - start) for start, end in regions)
249
- self._regions: dict[int, int] = SortedDict(regions)
249
+ self._regions: SortedDict = SortedDict(regions)
250
250
 
251
251
  l.debug("CFG recovery covers %d regions:", len(self._regions))
252
252
  for start, end in self._regions.items():
@@ -1556,6 +1556,7 @@ class CFGBase(Analysis):
1556
1556
  self.kb.functions[func_addr].alignment = True
1557
1557
  continue
1558
1558
  node = function.get_node(block.addr)
1559
+ assert node is not None
1559
1560
  successors = list(function.graph.successors(node))
1560
1561
  if len(successors) == 1 and successors[0].addr == node.addr:
1561
1562
  # self loop. mark this function as a function alignment
@@ -2151,6 +2152,11 @@ class CFGBase(Analysis):
2151
2152
  f = self.kb.functions.function(addr=addr)
2152
2153
  assert f is not None
2153
2154
 
2155
+ # copy over existing metadata
2156
+ if known_functions.contains_addr(addr):
2157
+ kf = known_functions.get_by_addr(addr)
2158
+ f.is_plt = kf.is_plt
2159
+
2154
2160
  blockaddr_to_function[addr] = f
2155
2161
 
2156
2162
  function_is_returning = False
@@ -2532,6 +2538,34 @@ class CFGBase(Analysis):
2532
2538
  # Other functions
2533
2539
  #
2534
2540
 
2541
+ @staticmethod
2542
+ def _is_noop_jump_block(block) -> bool:
2543
+ """
2544
+ Check if the block does nothing but jumping to a constant address.
2545
+
2546
+ :param block: The block instance. We assume the block is already optimized.
2547
+ :return: True if the entire block is a jump to a constant address, False otherwise.
2548
+ """
2549
+
2550
+ vex = block.vex
2551
+ if vex.jumpkind != "Ijk_Boring":
2552
+ return False
2553
+ if isinstance(vex.next, pyvex.expr.Const):
2554
+ return all(isinstance(stmt, pyvex.stmt.IMark) for stmt in vex.statements)
2555
+ if isinstance(vex.next, pyvex.expr.RdTmp):
2556
+ next_tmp = vex.next.tmp
2557
+ return all(
2558
+ isinstance(stmt, pyvex.stmt.IMark)
2559
+ or (
2560
+ isinstance(stmt, pyvex.stmt.WrTmp)
2561
+ and stmt.tmp == next_tmp
2562
+ and isinstance(stmt.data, pyvex.expr.Load)
2563
+ and isinstance(stmt.data.addr, pyvex.expr.Const)
2564
+ )
2565
+ for stmt in vex.statements
2566
+ )
2567
+ return False
2568
+
2535
2569
  @staticmethod
2536
2570
  def _is_noop_block(arch: archinfo.Arch, block) -> bool:
2537
2571
  """
@@ -2755,7 +2789,7 @@ class CFGBase(Analysis):
2755
2789
  cfg_node: CFGNode,
2756
2790
  irsb: pyvex.IRSB,
2757
2791
  func_addr: int,
2758
- stmt_idx: int | str = DEFAULT_STATEMENT,
2792
+ stmt_idx: int = DEFAULT_STATEMENT,
2759
2793
  ) -> tuple[bool, set[int], IndirectJump | None]:
2760
2794
  """
2761
2795
  Called when we encounter an indirect jump. We will try to resolve this indirect jump using timeless (fast)
@@ -1782,7 +1782,7 @@ class CFGFast(ForwardAnalysis[CFGNode, CFGNode, CFGJob, int], CFGBase): # pylin
1782
1782
  self.project.loader.discard_ro_memview()
1783
1783
 
1784
1784
  # Clean up
1785
- self._traced_addresses = None
1785
+ self._traced_addresses = None # type: ignore
1786
1786
  self._lifter_deregister_readonly_regions()
1787
1787
  self._function_returns = None
1788
1788
 
@@ -1838,6 +1838,7 @@ class CFGFast(ForwardAnalysis[CFGNode, CFGNode, CFGJob, int], CFGBase): # pylin
1838
1838
  xrefs = self.kb.xrefs.get_xrefs_by_dst(security_cookie_addr)
1839
1839
  tested_func_addrs = set()
1840
1840
  for xref in xrefs:
1841
+ assert xref.block_addr is not None
1841
1842
  cfg_node = self.model.get_any_node(xref.block_addr)
1842
1843
  if cfg_node is None:
1843
1844
  continue
@@ -2081,13 +2082,20 @@ class CFGFast(ForwardAnalysis[CFGNode, CFGNode, CFGJob, int], CFGBase): # pylin
2081
2082
 
2082
2083
  if (
2083
2084
  cfg_job.src_node is not None
2084
- and self.functions.contains_addr(cfg_job.src_node.addr)
2085
- and self.functions[cfg_job.src_node.addr].is_default_name
2086
2085
  and cfg_job.src_node.addr not in self.kb.labels
2087
2086
  and cfg_job.jumpkind == "Ijk_Boring"
2087
+ and self._is_noop_jump_block(cfg_job.src_node.block)
2088
2088
  ):
2089
- # assign a name to the caller function that jumps to this procedure
2090
- self.functions[cfg_job.src_node.addr].name = procedure.display_name
2089
+ # the caller node is very likely to be a PLT stub
2090
+ if not self.functions.contains_addr(cfg_job.src_node.addr):
2091
+ src_func = self.functions.function(addr=cfg_job.src_node.addr, create=True)
2092
+ else:
2093
+ src_func = self.functions.get_by_addr(cfg_job.src_node.addr)
2094
+ if len(src_func.block_addrs_set) <= 1 and src_func.is_default_name:
2095
+ # assign a name to the caller function that jumps to this procedure
2096
+ src_func.name = procedure.display_name
2097
+ # mark it as PLT
2098
+ src_func.is_plt = True
2091
2099
 
2092
2100
  if procedure.ADDS_EXITS:
2093
2101
  # Get two blocks ahead
@@ -3714,7 +3722,12 @@ class CFGFast(ForwardAnalysis[CFGNode, CFGNode, CFGJob, int], CFGBase): # pylin
3714
3722
  #
3715
3723
 
3716
3724
  def _graph_add_edge(
3717
- self, cfg_node: CFGNode, src_node: CFGNode | None, src_jumpkind: str, src_ins_addr: int, src_stmt_idx: int
3725
+ self,
3726
+ cfg_node: CFGNode,
3727
+ src_node: CFGNode | None,
3728
+ src_jumpkind: str,
3729
+ src_ins_addr: int | None,
3730
+ src_stmt_idx: int | None,
3718
3731
  ):
3719
3732
  """
3720
3733
  Add edge between nodes, or add node if entry point
@@ -4584,6 +4597,7 @@ class CFGFast(ForwardAnalysis[CFGNode, CFGNode, CFGJob, int], CFGBase): # pylin
4584
4597
  elif (
4585
4598
  lifted_block is not None
4586
4599
  and is_x86_x64_arch
4600
+ and lifted_block.bytes is not None
4587
4601
  and len(lifted_block.bytes) - irsb_size > 2
4588
4602
  and lifted_block.bytes[irsb_size : irsb_size + 2]
4589
4603
  in {
@@ -4659,7 +4673,7 @@ class CFGFast(ForwardAnalysis[CFGNode, CFGNode, CFGJob, int], CFGBase): # pylin
4659
4673
  self._seg_list.occupy(real_addr + irsb_size, nodecode_size, "nodecode")
4660
4674
 
4661
4675
  # Occupy the block in segment list
4662
- if irsb.size > 0:
4676
+ if irsb is not None and irsb.size > 0:
4663
4677
  self._seg_list.occupy(real_addr, irsb.size, "code")
4664
4678
 
4665
4679
  # Create a CFG node, and add it to the graph
@@ -4969,6 +4983,7 @@ class CFGFast(ForwardAnalysis[CFGNode, CFGNode, CFGJob, int], CFGBase): # pylin
4969
4983
 
4970
4984
  for assumption_addr in to_remove:
4971
4985
  # remove this assumption from the graph (since we may have new relationships formed later)
4986
+ assert self._decoding_assumption_relations is not None
4972
4987
  if assumption_addr in self._decoding_assumption_relations:
4973
4988
  self._decoding_assumption_relations.remove_node(assumption_addr)
4974
4989
 
@@ -5159,6 +5174,7 @@ class CFGFast(ForwardAnalysis[CFGNode, CFGNode, CFGJob, int], CFGBase): # pylin
5159
5174
  target_func = edges[0][1]
5160
5175
  if isinstance(target_func, (HookNode, Function)) and self.project.is_hooked(target_func.addr):
5161
5176
  hooker = self.project.hooked_by(target_func.addr)
5177
+ assert hooker is not None
5162
5178
  if hooker.DYNAMIC_RET:
5163
5179
  return self._is_call_returning(callsite_cfgnode, target_func.addr)
5164
5180
 
@@ -43,11 +43,22 @@ class ConstantResolver(IndirectJumpResolver):
43
43
  be resolved to a constant value. This resolver must be run after all other more specific resolvers.
44
44
  """
45
45
 
46
- def __init__(self, project):
46
+ def __init__(self, project, max_func_nodes: int = 512):
47
47
  super().__init__(project, timeless=False)
48
+ self.max_func_nodes = max_func_nodes
48
49
 
49
50
  def filter(self, cfg, addr, func_addr, block, jumpkind):
51
+ if not cfg.functions.contains_addr(func_addr):
52
+ # the function does not exist
53
+ return False
54
+
55
+ # for performance, we don't run constant resolver if the function is too large
56
+ func = cfg.functions.get_by_addr(func_addr)
57
+ if len(func.block_addrs_set) > self.max_func_nodes:
58
+ return False
59
+
50
60
  # we support both an indirect call and jump since the value can be resolved
61
+
51
62
  return jumpkind in {"Ijk_Boring", "Ijk_Call"}
52
63
 
53
64
  def resolve( # pylint:disable=unused-argument
@@ -6,6 +6,7 @@ from collections.abc import Sequence
6
6
  from collections import defaultdict, OrderedDict
7
7
  import logging
8
8
  import functools
9
+ import contextlib
9
10
 
10
11
  import pyvex
11
12
  import claripy
@@ -183,7 +184,11 @@ class ConstantValueManager:
183
184
  # determine blocks to run FCP on
184
185
 
185
186
  # - include at most three levels of superblock successors from the entrypoint
187
+ self.mapping = {}
186
188
  startpoint = self.func.startpoint
189
+ if startpoint is None:
190
+ return
191
+
187
192
  blocks = set()
188
193
  succ_and_levels = [(startpoint, 0)]
189
194
  while succ_and_levels:
@@ -1794,7 +1799,9 @@ class JumpTableResolver(IndirectJumpResolver):
1794
1799
  # swap the two tmps
1795
1800
  jump_base_addr.tmp, jump_base_addr.tmp_1 = jump_base_addr.tmp_1, jump_base_addr.tmp
1796
1801
  # Load the concrete base address
1797
- jump_base_addr.base_addr = state.solver.eval(state.scratch.temps[jump_base_addr.tmp_1])
1802
+ with contextlib.suppress(SimError):
1803
+ # silently eat the claripy exception
1804
+ jump_base_addr.base_addr = state.solver.eval(state.scratch.temps[jump_base_addr.tmp_1])
1798
1805
  else:
1799
1806
  # We do not support the cases where the base address involves more than one addition.
1800
1807
  # One such case exists in libc-2.27.so shipped with Ubuntu x86 where esi is used as the address of the
@@ -1,6 +1,8 @@
1
1
  from __future__ import annotations
2
+
2
3
  from angr.sim_type import SimCppClass, SimTypeCppFunction
3
4
  from angr.analyses import AnalysesHub
5
+ from angr.utils.cpp import is_cpp_funcname_ctor
4
6
  from . import Analysis, CFGFast, VtableFinder
5
7
 
6
8
 
@@ -33,17 +35,13 @@ class ClassIdentifier(Analysis):
33
35
  class_name = class_name.removeprefix("non-virtual thunk for ")
34
36
  if col_ind != -1:
35
37
  if class_name not in self.classes:
36
- ctor = False
37
- if func.demangled_name.find("{ctor}"):
38
- ctor = True
38
+ ctor = is_cpp_funcname_ctor(func.demangled_name)
39
39
  function_members = {func.addr: SimTypeCppFunction([], None, label=func.demangled_name, ctor=ctor)}
40
40
  new_class = SimCppClass(name=class_name, function_members=function_members)
41
41
  self.classes[class_name] = new_class
42
42
 
43
43
  else:
44
- ctor = False
45
- if func.demangled_name.find("{ctor}"):
46
- ctor = True
44
+ ctor = is_cpp_funcname_ctor(func.demangled_name)
47
45
  cur_class = self.classes[class_name]
48
46
  cur_class.function_members[func.addr] = SimTypeCppFunction(
49
47
  [], None, label=func.demangled_name, ctor=ctor
@@ -55,7 +53,10 @@ class ClassIdentifier(Analysis):
55
53
  vtable_calling_func = self.project.kb.functions.floor_func(ref.ins_addr)
56
54
  tmp_col_ind = vtable_calling_func.demangled_name.rfind("::")
57
55
  possible_constructor_class_name = vtable_calling_func.demangled_name[:tmp_col_ind]
58
- if "ctor" in vtable_calling_func.demangled_name and possible_constructor_class_name in self.classes:
56
+ if (
57
+ is_cpp_funcname_ctor(vtable_calling_func.demangled_name)
58
+ and possible_constructor_class_name in self.classes
59
+ ):
59
60
  self.classes[possible_constructor_class_name].vtable_ptrs.append(vtable.vaddr)
60
61
 
61
62
 
@@ -63,7 +63,7 @@ class CompleteCallingConventionsAnalysis(Analysis):
63
63
  max_function_size: int | None = None,
64
64
  workers: int = 0,
65
65
  cc_callback: Callable | None = None,
66
- prioritize_func_addrs: Iterable[int] | None = None,
66
+ prioritize_func_addrs: list[int] | set[int] | None = None,
67
67
  skip_other_funcs: bool = False,
68
68
  auto_start: bool = True,
69
69
  func_graphs: dict[int, networkx.DiGraph] | None = None,
@@ -130,9 +130,20 @@ class CompleteCallingConventionsAnalysis(Analysis):
130
130
  Infer calling conventions for all functions in the current project.
131
131
  """
132
132
 
133
- # get an ordering of functions based on the call graph
134
- # note that the call graph is a multi-digraph. we convert it to a digraph to speed up topological sort
135
- directed_callgraph = networkx.DiGraph(self.kb.functions.callgraph)
133
+ # special case: if both _prioritize_func_addrs and _skip_other_funcs are set, we only need to sort part of
134
+ # the call graph; even better, if there is only one function set, we don't need to sort the call graph at all!
135
+ if self._prioritize_func_addrs and self._skip_other_funcs:
136
+ if len(self._prioritize_func_addrs) == 1:
137
+ self._func_addrs = list(self._prioritize_func_addrs)
138
+ self._total_funcs = 1
139
+ return
140
+ directed_callgraph = networkx.DiGraph(self.kb.functions.callgraph)
141
+ directed_callgraph = directed_callgraph.subgraph(self._prioritize_func_addrs)
142
+ else:
143
+ # get an ordering of functions based on the call graph
144
+ # note that the call graph is a multi-digraph. we convert it to a digraph to speed up topological sort
145
+ directed_callgraph = networkx.DiGraph(self.kb.functions.callgraph)
146
+ assert isinstance(directed_callgraph, networkx.DiGraph)
136
147
  sorted_funcs = GraphUtils.quasi_topological_sort_nodes(directed_callgraph)
137
148
 
138
149
  total_funcs = 0
@@ -148,7 +159,7 @@ class CompleteCallingConventionsAnalysis(Analysis):
148
159
  continue
149
160
 
150
161
  if self._max_function_size is not None:
151
- func_size = sum(block.size for block in func.blocks)
162
+ func_size = sum(block.size for block in func.blocks if block.size is not None)
152
163
  if func_size > self._max_function_size:
153
164
  _l.info(
154
165
  "Skipping variable recovery for %r since its size (%d) is greater than the cutoff "
@@ -189,6 +200,7 @@ class CompleteCallingConventionsAnalysis(Analysis):
189
200
 
190
201
  def work(self):
191
202
  total_funcs = self._total_funcs
203
+ assert total_funcs is not None
192
204
  if self._workers == 0:
193
205
  self._update_progress(0)
194
206
  for idx, func_addr in enumerate(self._func_addrs):
@@ -211,6 +223,7 @@ class CompleteCallingConventionsAnalysis(Analysis):
211
223
  self._finish_progress()
212
224
 
213
225
  else:
226
+ assert self._remaining_funcs is not None and self._func_queue is not None
214
227
  self._remaining_funcs.value = len(self._func_addrs)
215
228
 
216
229
  # generate a call tree (obviously, it's acyclic)
@@ -383,7 +396,7 @@ class CompleteCallingConventionsAnalysis(Analysis):
383
396
  return (
384
397
  cc_analysis.cc,
385
398
  cc_analysis.prototype,
386
- func.prototype_libname,
399
+ cc_analysis.prototype_libname if cc_analysis.prototype_libname is not None else func.prototype_libname,
387
400
  self.kb.variables.get_function_manager(func_addr),
388
401
  )
389
402
  _l.info("Cannot determine calling convention for %r.", func)