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.
- 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/lib/angr_native.dylib +0 -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 +72 -68
- {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/__init__.py
CHANGED
|
@@ -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
|
-
|
|
211
|
-
if
|
|
212
|
-
self.cc, self.prototype =
|
|
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
|
-
|
|
314
|
-
|
|
315
|
-
|
|
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[
|
|
239
|
-
|
|
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
|
-
|
|
402
|
-
|
|
403
|
-
|
|
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
|
|
angr/analyses/cfg/cfg_base.py
CHANGED
|
@@ -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:
|
|
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
|
|
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)
|
angr/analyses/cfg/cfg_fast.py
CHANGED
|
@@ -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
|
-
#
|
|
2090
|
-
self.functions
|
|
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,
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
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
|
|
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:
|
|
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
|
-
#
|
|
134
|
-
#
|
|
135
|
-
|
|
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)
|