angr 9.2.141__py3-none-macosx_11_0_arm64.whl → 9.2.143__py3-none-macosx_11_0_arm64.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 (72) 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/lib/angr_native.dylib +0 -0
  53. angr/procedures/definitions/__init__.py +3 -10
  54. angr/procedures/definitions/linux_kernel.py +5 -0
  55. angr/procedures/definitions/wdk_ntoskrnl.py +2 -0
  56. angr/procedures/win32_kernel/__fastfail.py +15 -0
  57. angr/sim_procedure.py +2 -2
  58. angr/simos/simos.py +14 -10
  59. angr/simos/windows.py +42 -1
  60. angr/utils/ail.py +41 -1
  61. angr/utils/cpp.py +17 -0
  62. angr/utils/doms.py +149 -0
  63. angr/utils/library.py +1 -1
  64. angr/utils/ssa/__init__.py +21 -14
  65. angr/utils/ssa/vvar_uses_collector.py +2 -2
  66. angr/utils/types.py +12 -1
  67. {angr-9.2.141.dist-info → angr-9.2.143.dist-info}/METADATA +7 -7
  68. {angr-9.2.141.dist-info → angr-9.2.143.dist-info}/RECORD +72 -68
  69. {angr-9.2.141.dist-info → angr-9.2.143.dist-info}/LICENSE +0 -0
  70. {angr-9.2.141.dist-info → angr-9.2.143.dist-info}/WHEEL +0 -0
  71. {angr-9.2.141.dist-info → angr-9.2.143.dist-info}/entry_points.txt +0 -0
  72. {angr-9.2.141.dist-info → angr-9.2.143.dist-info}/top_level.txt +0 -0
@@ -254,7 +254,7 @@ class SimFunctionArgument:
254
254
  if self.size not in (4, 8):
255
255
  raise ValueError(f"What do I do with a float {self.size} bytes long")
256
256
  value = claripy.FPV(value, claripy.FSORT_FLOAT if self.size == 4 else claripy.FSORT_DOUBLE)
257
- return value.raw_to_bv()
257
+ return value.raw_to_bv() # type:ignore
258
258
 
259
259
  def check_value_get(self, value):
260
260
  if self.is_fp:
@@ -578,8 +578,12 @@ class SimCC:
578
578
  # (if applicable) and the arguments. Probably zero.
579
579
  STACKARG_SP_DIFF = 0 # The amount of stack space reserved for the return address
580
580
  CALLER_SAVED_REGS: list[str] = [] # Caller-saved registers
581
- RETURN_ADDR: SimFunctionArgument # The location where the return address is stored, as a SimFunctionArgument
582
- RETURN_VAL: SimFunctionArgument # The location where the return value is stored, as a SimFunctionArgument
581
+ RETURN_ADDR: SimFunctionArgument | None = (
582
+ None # The location where the return address is stored, as a SimFunctionArgument
583
+ )
584
+ RETURN_VAL: SimFunctionArgument | None = (
585
+ None # The location where the return value is stored, as a SimFunctionArgument
586
+ )
583
587
  OVERFLOW_RETURN_VAL: SimFunctionArgument | None = (
584
588
  None # The second half of the location where a double-length return value is stored
585
589
  )
@@ -766,7 +770,11 @@ class SimCC:
766
770
  return (
767
771
  isinstance(val, (float, claripy.ast.FP))
768
772
  or (isinstance(val, claripy.ast.Base) and val.op.startswith("fp")) # type: ignore
769
- or (isinstance(val, claripy.ast.Base) and val.op == "Reverse" and val.args[0].op.startswith("fp"))
773
+ or (
774
+ isinstance(val, claripy.ast.Base)
775
+ and val.op == "Reverse" # type:ignore
776
+ and val.args[0].op.startswith("fp") # type:ignore
777
+ )
770
778
  )
771
779
 
772
780
  @staticmethod
@@ -922,8 +930,10 @@ class SimCC:
922
930
  allocator.apply(state, alloc_base)
923
931
 
924
932
  for loc, val in zip(arg_locs, vals):
933
+ assert loc is not None
925
934
  loc.set_value(state, val, stack_base=stack_base)
926
- self.return_addr.set_value(state, ret_addr, stack_base=stack_base)
935
+ if self.return_addr is not None:
936
+ self.return_addr.set_value(state, ret_addr, stack_base=stack_base)
927
937
 
928
938
  def teardown_callsite(self, state, return_val=None, prototype=None, force_callee_cleanup=False):
929
939
  """
@@ -943,10 +953,10 @@ class SimCC:
943
953
  self.set_return_val(state, return_val, prototype.returnty)
944
954
  # ummmmmmmm hack
945
955
  loc = self.return_val(prototype.returnty)
946
- if isinstance(loc, SimReferenceArgument):
956
+ if self.RETURN_VAL is not None and isinstance(loc, SimReferenceArgument):
947
957
  self.RETURN_VAL.set_value(state, loc.ptr_loc.get_value(state))
948
958
 
949
- ret_addr = self.return_addr.get_value(state)
959
+ ret_addr = self.return_addr.get_value(state) if self.return_addr is not None else None
950
960
 
951
961
  if state.arch.sp_offset is not None and prototype is not None:
952
962
  if force_callee_cleanup or self.CALLEE_CLEANUP:
@@ -975,7 +985,7 @@ class SimCC:
975
985
 
976
986
  if arg.buffer:
977
987
  if isinstance(arg.value, claripy.ast.Bits):
978
- real_value = arg.value.chop(state.arch.byte_width)
988
+ real_value = arg.value.chop(state.arch.byte_width) # type:ignore
979
989
  elif type(arg.value) in (bytes, str):
980
990
  real_value = claripy.BVV(arg.value).chop(8)
981
991
  else:
@@ -1433,7 +1443,7 @@ class SimCCX86LinuxSyscall(SimCCSyscall):
1433
1443
 
1434
1444
  class SimCCX86WindowsSyscall(SimCCSyscall):
1435
1445
  # TODO: Make sure the information is correct
1436
- ARG_REGS = []
1446
+ ARG_REGS = ["ecx"]
1437
1447
  FP_ARG_REGS = []
1438
1448
  RETURN_VAL = SimRegArg("eax", 4)
1439
1449
  RETURN_ADDR = SimRegArg("ip_at_syscall", 4)
@@ -1673,7 +1683,7 @@ class SimCCAMD64LinuxSyscall(SimCCSyscall):
1673
1683
 
1674
1684
  class SimCCAMD64WindowsSyscall(SimCCSyscall):
1675
1685
  # TODO: Make sure the information is correct
1676
- ARG_REGS = []
1686
+ ARG_REGS = ["rcx"]
1677
1687
  FP_ARG_REGS = []
1678
1688
  RETURN_VAL = SimRegArg("rax", 8)
1679
1689
  RETURN_ADDR = SimRegArg("ip_at_syscall", 8)
@@ -9,7 +9,7 @@ import contextlib
9
9
  from typing import overload
10
10
 
11
11
  import networkx
12
- from itanium_demangler import parse
12
+ import pydemumble
13
13
 
14
14
  from cle.backends.symbol import Symbol
15
15
  from archinfo.arch_arm import get_real_address_if_arm
@@ -202,7 +202,8 @@ class Function(Serializable):
202
202
  if is_plt is not None:
203
203
  self.is_plt = is_plt
204
204
  else:
205
- # Whether this function is a PLT entry or not is fully relying on the PLT detection in CLE
205
+ # Whether this function is a PLT entry or not is primarily relying on the PLT detection in CLE; it may also
206
+ # be updated (to True) during CFG recovery.
206
207
  if self.project is None:
207
208
  raise ValueError(
208
209
  "'is_plt' must be specified if you do not specify a function manager for this new function."
@@ -1568,14 +1569,8 @@ class Function(Serializable):
1568
1569
 
1569
1570
  @property
1570
1571
  def demangled_name(self):
1571
- if self.name[0:2] == "_Z":
1572
- try:
1573
- ast = parse(self.name)
1574
- except (NotImplementedError, KeyError): # itanium demangler is not the most robust package in the world
1575
- return self.name
1576
- if ast:
1577
- return ast.__str__()
1578
- return self.name
1572
+ ast = pydemumble.demangle(self.name)
1573
+ return ast if ast else self.name
1579
1574
 
1580
1575
  def get_unambiguous_name(self, display_name: str | None = None) -> str:
1581
1576
  """
@@ -32,6 +32,7 @@ from angr.knowledge_plugins.types import TypesStore
32
32
  from .variable_access import VariableAccess, VariableAccessSort
33
33
 
34
34
  if TYPE_CHECKING:
35
+ from angr.analyses.decompiler.stack_item import StackItem
35
36
  from angr.code_location import CodeLocation
36
37
 
37
38
  l = logging.getLogger(name=__name__)
@@ -1141,6 +1142,32 @@ class VariableManagerInternal(Serializable):
1141
1142
  return False
1142
1143
  return True
1143
1144
 
1145
+ def get_stackvar_max_sizes(self, stack_items: dict[int, StackItem]) -> dict[SimStackVariable, int]:
1146
+ """
1147
+ Get the maximum size of each stack variable regardless of the type of each stack variable, under the assumption
1148
+ that stack variables do not overlap.
1149
+
1150
+ :return: A dictionary from SimStackVariable to its maximum size.
1151
+ """
1152
+
1153
+ stackvars_by_offset = defaultdict(list)
1154
+ for v in self._variables:
1155
+ if isinstance(v, SimStackVariable):
1156
+ offset = v.offset
1157
+ stackvars_by_offset[offset].append(v)
1158
+
1159
+ max_sizes = {}
1160
+ offsets = sorted(list(stackvars_by_offset) + list(stack_items))
1161
+ for i, offset in enumerate(offsets):
1162
+ if i + 1 < len(offsets):
1163
+ next_off = offsets[i + 1]
1164
+ sz = next_off - offset
1165
+ if offset in stackvars_by_offset:
1166
+ for v in stackvars_by_offset[offset]:
1167
+ max_sizes[v] = max(v.size, sz)
1168
+
1169
+ return max_sizes
1170
+
1144
1171
 
1145
1172
  class VariableManager(KnowledgeBasePlugin):
1146
1173
  """
Binary file
@@ -7,8 +7,7 @@ import inspect
7
7
  from collections import defaultdict
8
8
  from typing import TYPE_CHECKING
9
9
 
10
- import itanium_demangler
11
-
10
+ import pydemumble
12
11
  import archinfo
13
12
 
14
13
  from angr.errors import AngrMissingTypeError
@@ -345,14 +344,8 @@ class SimCppLibrary(SimLibrary):
345
344
 
346
345
  @staticmethod
347
346
  def _try_demangle(name):
348
- if name[0:2] == "_Z":
349
- try:
350
- ast = itanium_demangler.parse(name)
351
- except NotImplementedError:
352
- return name
353
- if ast:
354
- return str(ast)
355
- return name
347
+ ast = pydemumble.demangle(name)
348
+ return ast if ast else name
356
349
 
357
350
  @staticmethod
358
351
  def _proto_from_demangled_name(name: str) -> SimTypeFunction | None:
@@ -3,6 +3,7 @@ import logging
3
3
 
4
4
  from angr.sim_type import SimTypeFunction, SimTypePointer, SimTypeLong, SimStruct, SimTypeInt, SimTypeChar, SimTypeBottom, SimTypeFd, SimTypeLongLong
5
5
  from angr.procedures import SIM_PROCEDURES as P
6
+ from angr.calling_conventions import SYSCALL_CC
6
7
  from . import SimSyscallLibrary
7
8
 
8
9
  _l = logging.getLogger(__name__)
@@ -11,6 +12,10 @@ _l = logging.getLogger(__name__)
11
12
  lib = SimSyscallLibrary()
12
13
  lib.set_library_names('linux')
13
14
  lib.add_all_from_dict(P['linux_kernel'])
15
+ for arch, os_name_to_cc in SYSCALL_CC.items():
16
+ linux_syscall_cc = os_name_to_cc.get("Linux")
17
+ if linux_syscall_cc:
18
+ lib.set_default_cc(arch, linux_syscall_cc)
14
19
 
15
20
  lib.add('open', P['posix']['open'])
16
21
  lib.add('read', P['posix']['read'])
@@ -20,6 +20,8 @@ lib.add_all_from_dict(P["win32_kernel"])
20
20
  lib.set_library_names("ntoskrnl.exe")
21
21
  prototypes = \
22
22
  {
23
+ # int 29h
24
+ '__fastfail': SimTypeFunction([SimTypeInt(signed=False, label="Int")], SimTypeBottom(label="void"), arg_names=["code"]),
23
25
  #
24
26
  'NtQueryObject': SimTypeFunction([SimTypePointer(SimTypeInt(signed=True, label="Int"), label="IntPtr", offset=0), SimTypeInt(signed=False, label="OBJECT_INFORMATION_CLASS"), SimTypePointer(SimTypeBottom(label="Void"), offset=0), SimTypeInt(signed=False, label="UInt32"), SimTypePointer(SimTypeInt(signed=False, label="UInt32"), offset=0)], SimTypeInt(signed=True, label="Int32"), arg_names=["Handle", "ObjectInformationClass", "ObjectInformation", "ObjectInformationLength", "ReturnLength"]),
25
27
  #
@@ -0,0 +1,15 @@
1
+ from __future__ import annotations
2
+ import angr
3
+
4
+
5
+ class __fastfail(angr.SimProcedure):
6
+ """
7
+ Immediately terminates the calling process with minimum overhead.
8
+
9
+ https://learn.microsoft.com/en-us/cpp/intrinsics/fastfail?view=msvc-170
10
+ """
11
+
12
+ NO_RET = True
13
+
14
+ def run(self, _): # pylint:disable=arguments-differ
15
+ self.exit(0xC0000409)
angr/sim_procedure.py CHANGED
@@ -3,7 +3,7 @@ import inspect
3
3
  import copy
4
4
  import itertools
5
5
  import logging
6
- from typing import TYPE_CHECKING
6
+ from typing import Any, TYPE_CHECKING
7
7
 
8
8
  import claripy
9
9
  from cle import SymbolType
@@ -339,7 +339,7 @@ class SimProcedure:
339
339
  ALT_NAMES = None # alternative names
340
340
  local_vars: tuple[str, ...] = ()
341
341
 
342
- def run(self, *args, **kwargs): # pylint: disable=unused-argument
342
+ def run(self, *args, **kwargs) -> Any: # pylint: disable=unused-argument
343
343
  """
344
344
  Implement the actual procedure here!
345
345
  """
angr/simos/simos.py CHANGED
@@ -1,4 +1,5 @@
1
1
  from __future__ import annotations
2
+ from typing import TYPE_CHECKING
2
3
  import logging
3
4
  import struct
4
5
 
@@ -14,6 +15,9 @@ from angr.procedures import SIM_PROCEDURES as P
14
15
  from angr import sim_options as o
15
16
  from angr.storage.file import SimFileStream, SimFileBase
16
17
 
18
+ if TYPE_CHECKING:
19
+ from angr.sim_procedure import SimProcedure
20
+
17
21
 
18
22
  _l = logging.getLogger(name=__name__)
19
23
 
@@ -46,7 +50,7 @@ class SimOS:
46
50
  self.unresolvable_call_target = self.project.loader.extern_object.allocate()
47
51
  self.project.hook(self.unresolvable_call_target, P["stubs"]["UnresolvableCallTarget"]())
48
52
 
49
- def irelative_resolver(resolver_addr):
53
+ def irelative_resolver(resolver_addr: int) -> int | None:
50
54
  # autohooking runs before this does, might have provided this already
51
55
  # in that case, we want to advertise the _resolver_ address, since it is now
52
56
  # providing the behavior of the actual function
@@ -70,7 +74,7 @@ class SimOS:
70
74
  _l.error("Resolver at %#x failed to resolve!", resolver_addr)
71
75
  return None
72
76
 
73
- return val.concrete_value
77
+ return val.concrete_value if val is not None and val.concrete else None
74
78
 
75
79
  self.project.loader.perform_irelative_relocs(irelative_resolver)
76
80
 
@@ -79,7 +83,7 @@ class SimOS:
79
83
 
80
84
  if sym is not None:
81
85
  addr, _ = self.prepare_function_symbol(name, basic_addr=sym.rebased_addr)
82
- if self.project.is_hooked(addr) and not self.project.hooked_by(addr).is_stub:
86
+ if self.project.is_hooked(addr) and not self.project.hooked_by(addr).is_stub: # type: ignore
83
87
  return
84
88
  self.project.hook(addr, hook)
85
89
 
@@ -242,7 +246,7 @@ class SimOS:
242
246
  return self.state_entry(**kwargs)
243
247
 
244
248
  def state_call(self, addr, *args, **kwargs):
245
- cc = kwargs.pop("cc", default_cc(self.arch.name, platform=self.name)(self.project.arch))
249
+ cc = kwargs.pop("cc", default_cc(self.arch.name, platform=self.name)(self.project.arch)) # type: ignore
246
250
  state = kwargs.pop("base_state", None)
247
251
  toc = kwargs.pop("toc", None)
248
252
 
@@ -326,22 +330,22 @@ class SimOS:
326
330
  # Dummy stuff to allow this API to be used freely
327
331
 
328
332
  # pylint: disable=unused-argument, no-self-use
329
- def syscall(self, state, allow_unsupported=True):
333
+ def syscall(self, state: SimState, allow_unsupported: bool = True) -> SimProcedure | None:
330
334
  return None
331
335
 
332
- def syscall_abi(self, state) -> str:
336
+ def syscall_abi(self, state: SimState) -> str | None:
333
337
  return None
334
338
 
335
- def syscall_cc(self, state) -> angr.calling_conventions.SimCCSyscall | None:
339
+ def syscall_cc(self, state: SimState) -> angr.calling_conventions.SimCCSyscall | None:
336
340
  raise NotImplementedError
337
341
 
338
- def is_syscall_addr(self, addr):
342
+ def is_syscall_addr(self, addr) -> bool:
339
343
  return False
340
344
 
341
- def syscall_from_addr(self, addr, allow_unsupported=True):
345
+ def syscall_from_addr(self, addr, allow_unsupported=True) -> SimProcedure | None:
342
346
  return None
343
347
 
344
- def syscall_from_number(self, number, allow_unsupported=True, abi=None):
348
+ def syscall_from_number(self, number, allow_unsupported=True, abi=None) -> SimProcedure | None:
345
349
  return None
346
350
 
347
351
  def setup_gdt(self, state, gdt):
angr/simos/windows.py CHANGED
@@ -15,6 +15,7 @@ from angr import sim_options as o
15
15
  from angr.tablespecs import StringTableSpec
16
16
  from angr.procedures import SIM_LIBRARIES as L
17
17
  from angr.procedures.definitions import load_win32api_definitions
18
+ from angr.calling_conventions import SYSCALL_CC
18
19
  from .simos import SimOS
19
20
 
20
21
  _l = logging.getLogger(name=__name__)
@@ -26,6 +27,8 @@ VS_SECURITY_COOKIES = {"AMD64": _VS_Security_Cookie(0x2B992DDFA232, 48), "X86":
26
27
 
27
28
 
28
29
  class SecurityCookieInit(enum.Enum):
30
+ """Security cooke initialization value initialization method."""
31
+
29
32
  NONE = 0
30
33
  RANDOM = 1
31
34
  STATIC = 2
@@ -48,6 +51,11 @@ class SimWindows(SimOS):
48
51
  self.acmdln_ptr = None
49
52
  self.wcmdln_ptr = None
50
53
 
54
+ self.fastfail = L["ntoskrnl.exe"].get("__fastfail", self.arch)
55
+ self.fastfail.addr = self._find_or_make(self.fastfail.display_name)
56
+ self.fastfail.cc = SYSCALL_CC[self.arch.name]["Win32"](self.arch)
57
+ self._syscall_handlers = {self.fastfail.addr: self.fastfail}
58
+
51
59
  def configure_project(self):
52
60
  super().configure_project()
53
61
 
@@ -66,11 +74,15 @@ class SimWindows(SimOS):
66
74
  self.acmdln_ptr = self._find_or_make("_acmdln")
67
75
  self.wcmdln_ptr = self._find_or_make("_wcmdln")
68
76
 
69
- self.is_dump = isinstance(self.project.loader.main_object, cle.backends.Minidump)
77
+ self.project.hook(self.fastfail.addr, self.fastfail)
70
78
 
71
79
  if not self.is_dump:
72
80
  self.project.loader.tls.new_thread()
73
81
 
82
+ @property
83
+ def is_dump(self) -> bool:
84
+ return isinstance(self.project.loader.main_object, cle.backends.Minidump)
85
+
74
86
  def _find_or_make(self, name):
75
87
  sym = self.project.loader.find_symbol(name)
76
88
  if sym is None:
@@ -420,6 +432,35 @@ class SimWindows(SimOS):
420
432
  successors.add_successor(exc_state, self._exception_handler, claripy.true(), "Ijk_Exception")
421
433
  successors.processed = True
422
434
 
435
+ def syscall(self, state, allow_unsupported=True):
436
+ """
437
+ Given a state, return the procedure corresponding to the current syscall.
438
+ This procedure will have .syscall_number, .display_name, and .addr set.
439
+
440
+ :param state: The state to get the syscall number from
441
+ :param allow_unsupported: Whether to return a "dummy" sycall instead of raising an unsupported exception
442
+ """
443
+ if state.block(state.history.jump_source).bytes.hex() == "cd29": # int 29h
444
+ return self.fastfail
445
+ return None
446
+
447
+ def is_syscall_addr(self, addr):
448
+ """
449
+ Return whether or not the given address corresponds to a syscall implementation.
450
+ """
451
+ return addr in self._syscall_handlers
452
+
453
+ def syscall_from_addr(self, addr, allow_unsupported=True):
454
+ """
455
+ Get a syscall SimProcedure from an address.
456
+
457
+ :param addr: The address to convert to a syscall SimProcedure
458
+ :param allow_unsupported: Whether to return a dummy procedure for an unsupported syscall instead of raising an
459
+ exception.
460
+ :return: The SimProcedure for the syscall, or None if the address is not a syscall address.
461
+ """
462
+ return self._syscall_handlers.get(addr, None)
463
+
423
464
  # these two methods load and store register state from a struct CONTEXT
424
465
  # https://www.nirsoft.net/kernel_struct/vista/CONTEXT.html
425
466
  @staticmethod
angr/utils/ail.py CHANGED
@@ -3,7 +3,7 @@ from __future__ import annotations
3
3
  from ailment import AILBlockWalkerBase
4
4
  from ailment.block import Block
5
5
  from ailment.expression import Expression, VirtualVariable, Phi
6
- from ailment.statement import Assignment, Statement
6
+ from ailment.statement import Assignment, Statement, ConditionalJump
7
7
 
8
8
 
9
9
  def is_phi_assignment(stmt: Statement) -> bool:
@@ -28,3 +28,43 @@ class HasExprWalker(AILBlockWalkerBase):
28
28
  self.contains_exprs = True
29
29
  if not self.contains_exprs:
30
30
  super()._handle_expr(expr_idx, expr, stmt_idx, stmt, block)
31
+
32
+
33
+ def is_head_controlled_loop_block(block: Block) -> bool:
34
+ """
35
+ Determine if the block is a "head-controlled loop." A head-controlled loop (for the lack of a better term) is a
36
+ single-block that contains a conditional jump towards the beginning of the block. This conditional jump controls
37
+ whether the loop body (the remaining statements after the conditional jump) will be executed or not. It is usually
38
+ the result of lifting rep stosX instructions on x86 and amd64.
39
+
40
+ A head-controlled loop block looks like the following (lifted from rep stosq qword ptr [rdi], rax):
41
+
42
+ ## Block 4036df
43
+ 00 | 0x4036df | LABEL_4036df:
44
+ 01 | 0x4036df | vvar_27{reg 72} = 𝜙@64b []
45
+ 02 | 0x4036df | vvar_28{reg 24} = 𝜙@64b []
46
+ 03 | 0x4036df | t1 = rcx<8>
47
+ 04 | 0x4036df | t4 = (t1 == 0x0<64>)
48
+ 05 | 0x4036df | if (t4) { Goto 0x4036e2<64> } else { Goto 0x4036df<64> }
49
+ 06 | 0x4036df | t5 = (t1 - 0x1<64>)
50
+ 07 | 0x4036df | rcx<8> = t5
51
+ 08 | 0x4036df | t7 = d<8>
52
+ 09 | 0x4036df | t6 = (t7 << 0x3<8>)
53
+ 10 | 0x4036df | t2 = rax<8>
54
+ 11 | 0x4036df | t3 = rdi<8>
55
+ 12 | 0x4036df | STORE(addr=t3, data=t2, size=8, endness=Iend_LE, guard=None)
56
+ 13 | 0x4036df | t8 = (t3 + t6)
57
+ 14 | 0x4036df | rdi<8> = t8
58
+
59
+ Where statement 5 is the conditional jump that controls the execution of the remaining statements of this block.
60
+
61
+ :param block: An AIL block.
62
+ :return: True if the block represents a head-controlled loop block, False otherwise.
63
+ """
64
+
65
+ if not block.statements:
66
+ return False
67
+ last_stmt = block.statements[-1]
68
+ if isinstance(last_stmt, ConditionalJump):
69
+ return False
70
+ return any(isinstance(stmt, ConditionalJump) for stmt in block.statements[:-1])
angr/utils/cpp.py ADDED
@@ -0,0 +1,17 @@
1
+ from __future__ import annotations
2
+
3
+
4
+ def is_cpp_funcname_ctor(name: str) -> bool:
5
+ """
6
+ Check if a demangled C++ function name is a constructor.
7
+
8
+ :param name: The demangled C++ function name.
9
+ :return: True if the function name is a constructor, False otherwise.
10
+ """
11
+
12
+ # With pydemumble, constructor names look like:
13
+ # A::A()
14
+ if "::" not in name:
15
+ return False
16
+ parts = name.split("::")
17
+ return bool(len(parts) == 2 and parts[0] and parts[0] + "()" == parts[1])
angr/utils/doms.py ADDED
@@ -0,0 +1,149 @@
1
+ # pylint:disable=consider-using-dict-items
2
+ from __future__ import annotations
3
+ from typing import Any
4
+ from collections import defaultdict
5
+
6
+ import networkx
7
+
8
+ from angr.utils.graph import shallow_reverse
9
+
10
+
11
+ class IncrementalDominators:
12
+ """
13
+ This class allows for incrementally updating dominators and post-dominators for graphs. The graph must only be
14
+ modified by replacing nodes, not adding nodes or edges.
15
+ """
16
+
17
+ def __init__(self, graph: networkx.DiGraph, start, post: bool = False):
18
+ self.graph = graph
19
+ self.start = start
20
+ self._post: bool = post # calculate post-dominators if True
21
+ self._pre: bool = not post # calculate dominators
22
+
23
+ self._doms: dict[Any, Any] = {}
24
+ self._dfs: dict[Any, set[Any]] | None = None # initialized on-demand
25
+ self._inverted_dom_tree: dict[Any, Any] | None = None # initialized on demand
26
+
27
+ self._doms = self.init_doms()
28
+
29
+ def init_doms(self) -> dict[Any, Any]:
30
+ if self._post:
31
+ t = shallow_reverse(self.graph)
32
+ doms = networkx.immediate_dominators(t, self.start)
33
+ else:
34
+ doms = networkx.immediate_dominators(self.graph, self.start)
35
+ return doms
36
+
37
+ def init_dfs(self) -> dict[Any, set[Any]]:
38
+ _pred = self.graph.predecessors if self._pre else self.graph.successors
39
+ df: dict = {}
40
+ for u in self._doms:
41
+ _preds = list(_pred(u)) # type:ignore
42
+ if len(_preds) >= 2:
43
+ for v in _preds:
44
+ if v in self._doms:
45
+ while v is not self._doms[u]:
46
+ if v not in df:
47
+ df[v] = set()
48
+ df[v].add(u)
49
+ v = self._doms[v]
50
+ return df
51
+
52
+ def _update_inverted_domtree(self):
53
+ # recalculate the dominators for dominatees of replaced nodes
54
+ if self._inverted_dom_tree is None:
55
+ self._inverted_dom_tree = defaultdict(list)
56
+ for dtee, dtor in self._doms.items():
57
+ self._inverted_dom_tree[dtor].append(dtee)
58
+
59
+ def graph_updated(self, new_node: Any, replaced_nodes: set[Any], replaced_head: Any):
60
+ self._update_inverted_domtree()
61
+ assert self._inverted_dom_tree is not None
62
+
63
+ # recalculate the dominators for impacted nodes
64
+ new_dom = self._doms[replaced_head]
65
+ while new_dom in replaced_nodes and new_dom is not self.start:
66
+ new_dom = self._doms[new_dom]
67
+
68
+ if self.start in replaced_nodes:
69
+ self.start = new_node
70
+ if new_dom in replaced_nodes:
71
+ new_dom = new_node
72
+
73
+ new_node_doms = []
74
+ for rn in replaced_nodes:
75
+ if rn not in self._inverted_dom_tree:
76
+ continue
77
+ for dtee in self._inverted_dom_tree[rn]:
78
+ self._doms[dtee] = new_node
79
+ new_node_doms.append(dtee)
80
+ self._doms[new_node] = new_dom
81
+
82
+ if self._dfs is not None:
83
+ # update dominance frontiers
84
+ if replaced_head in self._dfs:
85
+ self._dfs[new_node] = self._dfs[replaced_head]
86
+ for rn in replaced_nodes:
87
+ if rn in self._dfs:
88
+ del self._dfs[rn]
89
+ for df in self._dfs.values():
90
+ if rn in df:
91
+ df.remove(rn)
92
+ df.add(new_node)
93
+
94
+ # keep inverted dom tree up-to-date
95
+ self._inverted_dom_tree[new_dom].append(new_node)
96
+ self._inverted_dom_tree[new_node] = new_node_doms
97
+ for rn in replaced_nodes:
98
+ if rn in self._doms:
99
+ d = self._doms[rn]
100
+ del self._doms[rn]
101
+ self._inverted_dom_tree[d].remove(rn)
102
+ if rn in self._inverted_dom_tree:
103
+ del self._inverted_dom_tree[rn]
104
+
105
+ def idom(self, node: Any) -> Any | None:
106
+ """
107
+ Get the immediate dominator of a given node.
108
+ """
109
+
110
+ return self._doms.get(node, None)
111
+
112
+ def df(self, node: Any) -> set[Any]:
113
+ """
114
+ Generate the dominance frontier of a node.
115
+ """
116
+ if self._dfs is None:
117
+ self._dfs = self.init_dfs()
118
+ return self._dfs.get(node, set())
119
+
120
+ def dominates(self, dominator_node: Any, node: Any) -> bool:
121
+ """
122
+ Tests if dominator_node dominates (or post-dominates) node.
123
+ """
124
+
125
+ n = node
126
+ while n:
127
+ if n is dominator_node:
128
+ return True
129
+ d = self.idom(n)
130
+ n = d if d is not None and n is not d else None
131
+ return False
132
+
133
+ def _debug_check(self):
134
+ true_doms = self.init_doms()
135
+ if len(true_doms) != len(self._doms):
136
+ raise ValueError("dominators do not match")
137
+ for k in true_doms:
138
+ if true_doms[k] != self._doms[k]:
139
+ print(f"{k!r}: {true_doms[k]!r} {self._doms[k]!r}")
140
+ raise ValueError("dominators do not match")
141
+
142
+ if self._dfs is not None:
143
+ dfs = self.init_dfs()
144
+ if len(dfs) != len(self._dfs):
145
+ raise ValueError("dfs do not match")
146
+ for k in dfs:
147
+ if dfs[k] != self._dfs[k]:
148
+ print(f"{k!r}: {dfs[k]!r} {self._dfs[k]!r}")
149
+ raise ValueError("dfs do not match")
angr/utils/library.py CHANGED
@@ -168,7 +168,7 @@ def parsedcprotos2py(
168
168
  proto_.returnty = SimTypeFd(label=proto_.returnty.label)
169
169
  for i, arg in enumerate(proto_.args):
170
170
  if (func_name, i) in fd_spots:
171
- proto_.args[i] = SimTypeFd(label=arg.label)
171
+ proto_.args = proto_.args[:i] + (SimTypeFd(label=arg.label),) + proto_.args[i + 1 :]
172
172
 
173
173
  line1 = " " * 8 + "#" + ((" " + decl) if decl else "") + "\n"
174
174
  line2 = " " * 8 + repr(func_name) + ": " + (proto_._init_str() if proto_ is not None else "None") + "," + "\n"