angr 9.2.141__py3-none-manylinux2014_aarch64.whl → 9.2.143__py3-none-manylinux2014_aarch64.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.
- angr/__init__.py +1 -1
- angr/analyses/calling_convention/calling_convention.py +26 -12
- angr/analyses/calling_convention/fact_collector.py +31 -9
- angr/analyses/cfg/cfg_base.py +38 -4
- angr/analyses/cfg/cfg_fast.py +23 -7
- angr/analyses/cfg/indirect_jump_resolvers/const_resolver.py +12 -1
- angr/analyses/cfg/indirect_jump_resolvers/jumptable.py +8 -1
- angr/analyses/class_identifier.py +8 -7
- angr/analyses/complete_calling_conventions.py +19 -6
- angr/analyses/decompiler/ail_simplifier.py +138 -98
- angr/analyses/decompiler/clinic.py +73 -5
- angr/analyses/decompiler/condition_processor.py +7 -7
- angr/analyses/decompiler/decompilation_cache.py +2 -1
- angr/analyses/decompiler/decompiler.py +10 -2
- angr/analyses/decompiler/dephication/graph_vvar_mapping.py +4 -6
- angr/analyses/decompiler/optimization_passes/base_ptr_save_simplifier.py +8 -2
- angr/analyses/decompiler/optimization_passes/condition_constprop.py +110 -46
- angr/analyses/decompiler/optimization_passes/ite_region_converter.py +8 -0
- angr/analyses/decompiler/optimization_passes/lowered_switch_simplifier.py +1 -1
- angr/analyses/decompiler/optimization_passes/optimization_pass.py +2 -0
- angr/analyses/decompiler/optimization_passes/register_save_area_simplifier.py +29 -7
- angr/analyses/decompiler/optimization_passes/stack_canary_simplifier.py +6 -0
- angr/analyses/decompiler/optimization_passes/win_stack_canary_simplifier.py +9 -1
- angr/analyses/decompiler/peephole_optimizations/simplify_pc_relative_loads.py +15 -1
- angr/analyses/decompiler/region_identifier.py +70 -47
- angr/analyses/decompiler/sequence_walker.py +8 -0
- angr/analyses/decompiler/ssailification/rewriting.py +47 -17
- angr/analyses/decompiler/ssailification/rewriting_engine.py +13 -0
- angr/analyses/decompiler/stack_item.py +36 -0
- angr/analyses/decompiler/structured_codegen/c.py +14 -9
- angr/analyses/decompiler/structuring/phoenix.py +3 -3
- angr/analyses/decompiler/utils.py +13 -0
- angr/analyses/find_objects_static.py +2 -1
- angr/analyses/reaching_definitions/engine_vex.py +13 -0
- angr/analyses/reaching_definitions/function_handler.py +24 -10
- angr/analyses/reaching_definitions/function_handler_library/stdio.py +1 -0
- angr/analyses/reaching_definitions/function_handler_library/stdlib.py +45 -12
- angr/analyses/reaching_definitions/function_handler_library/string.py +77 -21
- angr/analyses/reaching_definitions/function_handler_library/unistd.py +21 -1
- angr/analyses/reaching_definitions/rd_state.py +11 -7
- angr/analyses/s_liveness.py +44 -6
- angr/analyses/s_propagator.py +40 -29
- angr/analyses/s_reaching_definitions/s_rda_model.py +48 -37
- angr/analyses/s_reaching_definitions/s_rda_view.py +6 -3
- angr/analyses/s_reaching_definitions/s_reaching_definitions.py +21 -21
- angr/analyses/typehoon/simple_solver.py +35 -8
- angr/analyses/typehoon/typehoon.py +3 -1
- angr/analyses/variable_recovery/engine_ail.py +6 -6
- angr/calling_conventions.py +20 -10
- angr/knowledge_plugins/functions/function.py +5 -10
- angr/knowledge_plugins/variables/variable_manager.py +27 -0
- angr/procedures/definitions/__init__.py +3 -10
- angr/procedures/definitions/linux_kernel.py +5 -0
- angr/procedures/definitions/wdk_ntoskrnl.py +2 -0
- angr/procedures/win32_kernel/__fastfail.py +15 -0
- angr/sim_procedure.py +2 -2
- angr/simos/simos.py +14 -10
- angr/simos/windows.py +42 -1
- angr/utils/ail.py +41 -1
- angr/utils/cpp.py +17 -0
- angr/utils/doms.py +149 -0
- angr/utils/library.py +1 -1
- angr/utils/ssa/__init__.py +21 -14
- angr/utils/ssa/vvar_uses_collector.py +2 -2
- angr/utils/types.py +12 -1
- {angr-9.2.141.dist-info → angr-9.2.143.dist-info}/METADATA +7 -7
- {angr-9.2.141.dist-info → angr-9.2.143.dist-info}/RECORD +71 -67
- {angr-9.2.141.dist-info → angr-9.2.143.dist-info}/LICENSE +0 -0
- {angr-9.2.141.dist-info → angr-9.2.143.dist-info}/WHEEL +0 -0
- {angr-9.2.141.dist-info → angr-9.2.143.dist-info}/entry_points.txt +0 -0
- {angr-9.2.141.dist-info → angr-9.2.143.dist-info}/top_level.txt +0 -0
angr/calling_conventions.py
CHANGED
|
@@ -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
|
|
582
|
-
|
|
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 (
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
1572
|
-
|
|
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
|
"""
|
|
@@ -7,8 +7,7 @@ import inspect
|
|
|
7
7
|
from collections import defaultdict
|
|
8
8
|
from typing import TYPE_CHECKING
|
|
9
9
|
|
|
10
|
-
import
|
|
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
|
-
|
|
349
|
-
|
|
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.
|
|
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]
|
|
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"
|