angr 9.2.143__py3-none-macosx_11_0_arm64.whl → 9.2.145__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 +13 -1
- angr/analyses/calling_convention/fact_collector.py +41 -5
- angr/analyses/cfg/cfg_base.py +7 -2
- angr/analyses/cfg/cfg_emulated.py +13 -4
- angr/analyses/cfg/cfg_fast.py +35 -61
- angr/analyses/cfg/indirect_jump_resolvers/__init__.py +2 -0
- angr/analyses/cfg/indirect_jump_resolvers/constant_value_manager.py +107 -0
- angr/analyses/cfg/indirect_jump_resolvers/default_resolvers.py +2 -1
- angr/analyses/cfg/indirect_jump_resolvers/jumptable.py +2 -101
- angr/analyses/cfg/indirect_jump_resolvers/syscall_resolver.py +92 -0
- angr/analyses/decompiler/ail_simplifier.py +5 -0
- angr/analyses/decompiler/clinic.py +163 -69
- angr/analyses/decompiler/decompiler.py +4 -4
- angr/analyses/decompiler/optimization_passes/base_ptr_save_simplifier.py +1 -1
- angr/analyses/decompiler/optimization_passes/optimization_pass.py +5 -5
- angr/analyses/decompiler/optimization_passes/return_duplicator_base.py +5 -0
- angr/analyses/decompiler/optimization_passes/win_stack_canary_simplifier.py +58 -2
- angr/analyses/decompiler/peephole_optimizations/__init__.py +2 -0
- angr/analyses/decompiler/peephole_optimizations/a_sub_a_shr_const_shr_const.py +37 -0
- angr/analyses/decompiler/ssailification/rewriting_engine.py +2 -0
- angr/analyses/decompiler/ssailification/ssailification.py +10 -2
- angr/analyses/decompiler/ssailification/traversal_engine.py +17 -2
- angr/analyses/decompiler/structured_codegen/c.py +25 -4
- angr/analyses/disassembly.py +3 -3
- angr/analyses/fcp/fcp.py +1 -4
- angr/analyses/s_reaching_definitions/s_reaching_definitions.py +21 -22
- angr/analyses/stack_pointer_tracker.py +61 -25
- angr/analyses/typehoon/dfa.py +13 -3
- angr/analyses/typehoon/typehoon.py +60 -18
- angr/analyses/typehoon/typevars.py +11 -7
- angr/analyses/variable_recovery/engine_ail.py +13 -17
- angr/analyses/variable_recovery/engine_base.py +26 -30
- angr/analyses/variable_recovery/variable_recovery_fast.py +17 -21
- angr/knowledge_plugins/functions/function.py +29 -15
- angr/knowledge_plugins/key_definitions/constants.py +2 -2
- angr/knowledge_plugins/key_definitions/liveness.py +4 -4
- angr/lib/angr_native.dylib +0 -0
- angr/state_plugins/unicorn_engine.py +24 -8
- angr/storage/memory_mixins/paged_memory/page_backer_mixins.py +1 -2
- angr/storage/memory_mixins/paged_memory/pages/mv_list_page.py +2 -2
- angr/utils/funcid.py +27 -2
- angr/utils/graph.py +26 -20
- {angr-9.2.143.dist-info → angr-9.2.145.dist-info}/METADATA +11 -8
- {angr-9.2.143.dist-info → angr-9.2.145.dist-info}/RECORD +49 -46
- {angr-9.2.143.dist-info → angr-9.2.145.dist-info}/WHEEL +1 -1
- {angr-9.2.143.dist-info → angr-9.2.145.dist-info}/LICENSE +0 -0
- {angr-9.2.143.dist-info → angr-9.2.145.dist-info}/entry_points.txt +0 -0
- {angr-9.2.143.dist-info → angr-9.2.145.dist-info}/top_level.txt +0 -0
angr/__init__.py
CHANGED
|
@@ -864,7 +864,19 @@ class CallingConventionAnalysis(Analysis):
|
|
|
864
864
|
else:
|
|
865
865
|
int_args.append(arg)
|
|
866
866
|
|
|
867
|
-
|
|
867
|
+
initial_stack_args = sorted([a for a in args if isinstance(a, SimStackArg)], key=lambda a: a.stack_offset)
|
|
868
|
+
# ensure stack args are consecutive if necessary
|
|
869
|
+
if cc.STACKARG_SP_DIFF is not None and initial_stack_args:
|
|
870
|
+
arg_by_offset = {a.stack_offset: a for a in initial_stack_args}
|
|
871
|
+
init_stackarg_offset = cc.STACKARG_SP_DIFF + cc.STACKARG_SP_BUFF
|
|
872
|
+
int_arg_size = self.project.arch.bytes
|
|
873
|
+
for stackarg_offset in range(init_stackarg_offset, max(arg_by_offset), int_arg_size):
|
|
874
|
+
if stackarg_offset not in arg_by_offset:
|
|
875
|
+
arg_by_offset[stackarg_offset] = SimStackArg(stackarg_offset, int_arg_size)
|
|
876
|
+
stack_args = [arg_by_offset[offset] for offset in sorted(arg_by_offset)]
|
|
877
|
+
else:
|
|
878
|
+
stack_args = initial_stack_args
|
|
879
|
+
|
|
868
880
|
stack_int_args = [a for a in stack_args if not a.is_fp]
|
|
869
881
|
stack_fp_args = [a for a in stack_args if a.is_fp]
|
|
870
882
|
# match int args first
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# pylint:disable=too-many-boolean-expressions
|
|
2
2
|
from __future__ import annotations
|
|
3
3
|
from typing import Any, TYPE_CHECKING
|
|
4
|
+
from collections import defaultdict
|
|
4
5
|
|
|
5
6
|
import pyvex
|
|
6
7
|
import claripy
|
|
@@ -30,6 +31,7 @@ class FactCollectorState:
|
|
|
30
31
|
"bp_value",
|
|
31
32
|
"callee_stored_regs",
|
|
32
33
|
"reg_reads",
|
|
34
|
+
"reg_reads_count",
|
|
33
35
|
"reg_writes",
|
|
34
36
|
"simple_stack",
|
|
35
37
|
"sp_value",
|
|
@@ -44,6 +46,7 @@ class FactCollectorState:
|
|
|
44
46
|
|
|
45
47
|
self.callee_stored_regs: dict[int, int] = {} # reg offset -> stack offset
|
|
46
48
|
self.reg_reads = {}
|
|
49
|
+
self.reg_reads_count = defaultdict(int)
|
|
47
50
|
self.reg_writes: set[int] = set()
|
|
48
51
|
self.stack_reads = {}
|
|
49
52
|
self.stack_writes: set[int] = set()
|
|
@@ -51,6 +54,7 @@ class FactCollectorState:
|
|
|
51
54
|
self.bp_value = 0
|
|
52
55
|
|
|
53
56
|
def register_read(self, offset: int, size_in_bytes: int):
|
|
57
|
+
self.reg_reads_count[offset] += 1
|
|
54
58
|
if offset in self.reg_writes:
|
|
55
59
|
return
|
|
56
60
|
if offset not in self.reg_reads:
|
|
@@ -58,6 +62,14 @@ class FactCollectorState:
|
|
|
58
62
|
else:
|
|
59
63
|
self.reg_reads[offset] = max(self.reg_reads[offset], size_in_bytes)
|
|
60
64
|
|
|
65
|
+
def register_read_undo(self, offset: int) -> None:
|
|
66
|
+
if offset not in self.reg_reads or offset not in self.reg_reads_count:
|
|
67
|
+
return
|
|
68
|
+
self.reg_reads_count[offset] -= 1
|
|
69
|
+
if self.reg_reads_count[offset] == 0:
|
|
70
|
+
self.reg_reads.pop(offset)
|
|
71
|
+
self.reg_reads_count.pop(offset)
|
|
72
|
+
|
|
61
73
|
def register_written(self, offset: int, size_in_bytes: int):
|
|
62
74
|
for o in range(size_in_bytes):
|
|
63
75
|
self.reg_writes.add(offset + o)
|
|
@@ -84,6 +96,7 @@ class FactCollectorState:
|
|
|
84
96
|
new_state.sp_value = self.sp_value
|
|
85
97
|
new_state.bp_value = self.bp_value
|
|
86
98
|
new_state.simple_stack = self.simple_stack.copy()
|
|
99
|
+
new_state.reg_reads_count = self.reg_reads_count.copy()
|
|
87
100
|
if with_tmps:
|
|
88
101
|
new_state.tmps = self.tmps.copy()
|
|
89
102
|
return new_state
|
|
@@ -119,6 +132,26 @@ class SimEngineFactCollectorVEX(
|
|
|
119
132
|
|
|
120
133
|
def _handle_stmt_Put(self, stmt):
|
|
121
134
|
v = self._expr(stmt.data)
|
|
135
|
+
# there are cases like VMOV.F32 S0, S0
|
|
136
|
+
# so we need to check if this register write is actually a no-op
|
|
137
|
+
if isinstance(stmt.data, pyvex.IRExpr.RdTmp):
|
|
138
|
+
t = self.state.tmps.get(stmt.data.tmp, None)
|
|
139
|
+
if isinstance(t, RegisterOffset) and t.reg == stmt.offset:
|
|
140
|
+
same_ins_read = False
|
|
141
|
+
for i in range(self.stmt_idx, -1, -1):
|
|
142
|
+
if i >= self.block.vex.stmts_used:
|
|
143
|
+
break
|
|
144
|
+
prev_stmt = self.block.vex.statements[i]
|
|
145
|
+
if isinstance(prev_stmt, pyvex.IRStmt.IMark):
|
|
146
|
+
break
|
|
147
|
+
if isinstance(prev_stmt, pyvex.IRStmt.WrTmp) and prev_stmt.tmp == stmt.data.tmp:
|
|
148
|
+
same_ins_read = True
|
|
149
|
+
break
|
|
150
|
+
if same_ins_read:
|
|
151
|
+
# we need to revert the read operation as well
|
|
152
|
+
self.state.register_read_undo(stmt.offset)
|
|
153
|
+
return
|
|
154
|
+
|
|
122
155
|
if stmt.offset == self.arch.sp_offset and isinstance(v, SpOffset):
|
|
123
156
|
self.state.sp_value = v.offset
|
|
124
157
|
elif stmt.offset == self.arch.bp_offset and isinstance(v, SpOffset):
|
|
@@ -210,7 +243,7 @@ class FactCollector(Analysis):
|
|
|
210
243
|
decision on the calling convention and prototype of a function.
|
|
211
244
|
"""
|
|
212
245
|
|
|
213
|
-
def __init__(self, func: Function, max_depth: int =
|
|
246
|
+
def __init__(self, func: Function, max_depth: int = 30):
|
|
214
247
|
self.function = func
|
|
215
248
|
self._max_depth = max_depth
|
|
216
249
|
|
|
@@ -285,14 +318,17 @@ class FactCollector(Analysis):
|
|
|
285
318
|
for _, succ, data in func_graph.out_edges(node, data=True):
|
|
286
319
|
edge_type = data.get("type")
|
|
287
320
|
outside = data.get("outside", False)
|
|
288
|
-
if
|
|
321
|
+
if depth + 1 <= self._max_depth:
|
|
289
322
|
if edge_type == "fake_return":
|
|
290
|
-
|
|
323
|
+
if succ not in traversed:
|
|
324
|
+
ret_succ = succ
|
|
291
325
|
elif edge_type == "transition" and not outside:
|
|
292
|
-
|
|
293
|
-
|
|
326
|
+
if succ not in traversed:
|
|
327
|
+
successor_added = True
|
|
328
|
+
queue.append((depth + 1, state.copy(), succ, None))
|
|
294
329
|
elif edge_type == "call" or (edge_type == "transition" and outside):
|
|
295
330
|
# a call or a tail-call
|
|
331
|
+
# note that it's ok to traverse a called function multiple times
|
|
296
332
|
if not isinstance(succ, Function):
|
|
297
333
|
if self.kb.functions.contains_addr(succ.addr):
|
|
298
334
|
succ = self.kb.functions.get_by_addr(succ.addr)
|
angr/analyses/cfg/cfg_base.py
CHANGED
|
@@ -1701,7 +1701,12 @@ class CFGBase(Analysis):
|
|
|
1701
1701
|
self._update_progress(progress)
|
|
1702
1702
|
|
|
1703
1703
|
self._graph_bfs_custom(
|
|
1704
|
-
self.graph,
|
|
1704
|
+
self.graph,
|
|
1705
|
+
[fn],
|
|
1706
|
+
self._graph_traversal_handler,
|
|
1707
|
+
blockaddr_to_function,
|
|
1708
|
+
tmp_functions,
|
|
1709
|
+
traversed_cfg_nodes,
|
|
1705
1710
|
)
|
|
1706
1711
|
|
|
1707
1712
|
to_remove = set()
|
|
@@ -2731,7 +2736,7 @@ class CFGBase(Analysis):
|
|
|
2731
2736
|
relifted = self.project.factory.block(block.addr, size=block.size, opt_level=1, cross_insn_opt=True).vex
|
|
2732
2737
|
except SimError:
|
|
2733
2738
|
return False, []
|
|
2734
|
-
if isinstance(relifted.next, pyvex.IRExpr.Const):
|
|
2739
|
+
if not relifted.jumpkind.startswith("Ijk_Sys") and isinstance(relifted.next, pyvex.IRExpr.Const):
|
|
2735
2740
|
# yes!
|
|
2736
2741
|
return True, [relifted.next.con.value]
|
|
2737
2742
|
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
|
+
from typing import TYPE_CHECKING
|
|
2
3
|
import itertools
|
|
3
4
|
import logging
|
|
4
5
|
import sys
|
|
5
6
|
from collections import defaultdict
|
|
6
7
|
from functools import reduce
|
|
8
|
+
import contextlib
|
|
7
9
|
|
|
8
10
|
import angr
|
|
9
11
|
import claripy
|
|
@@ -45,7 +47,10 @@ from angr.analyses.backward_slice import BackwardSlice
|
|
|
45
47
|
from angr.analyses.loopfinder import LoopFinder, Loop
|
|
46
48
|
from .cfg_base import CFGBase
|
|
47
49
|
from .cfg_job_base import BlockID, CFGJobBase
|
|
48
|
-
|
|
50
|
+
|
|
51
|
+
if TYPE_CHECKING:
|
|
52
|
+
from angr.knowledge_plugins.cfg import CFGNode
|
|
53
|
+
|
|
49
54
|
|
|
50
55
|
l = logging.getLogger(name=__name__)
|
|
51
56
|
|
|
@@ -505,6 +510,8 @@ class CFGEmulated(ForwardAnalysis, CFGBase): # pylint: disable=abstract-method
|
|
|
505
510
|
:return: None
|
|
506
511
|
"""
|
|
507
512
|
|
|
513
|
+
assert self._starts is not None
|
|
514
|
+
|
|
508
515
|
if not isinstance(max_loop_unrolling_times, int) or max_loop_unrolling_times < 0:
|
|
509
516
|
raise AngrCFGError(
|
|
510
517
|
"Max loop unrolling times must be set to an integer greater than or equal to 0 if "
|
|
@@ -586,6 +593,7 @@ class CFGEmulated(ForwardAnalysis, CFGBase): # pylint: disable=abstract-method
|
|
|
586
593
|
|
|
587
594
|
graph_copy.remove_node(new_end_node)
|
|
588
595
|
src, dst = loop_backedge
|
|
596
|
+
assert src is not None and dst is not None
|
|
589
597
|
if graph_copy.has_edge(src, dst): # It might have been removed before
|
|
590
598
|
# Duplicate the dst node
|
|
591
599
|
new_dst = dst.copy()
|
|
@@ -713,9 +721,10 @@ class CFGEmulated(ForwardAnalysis, CFGBase): # pylint: disable=abstract-method
|
|
|
713
721
|
# FIXME: start should also take a CFGNode instance
|
|
714
722
|
|
|
715
723
|
start_node = self.get_any_node(start)
|
|
724
|
+
assert start_node is not None
|
|
716
725
|
|
|
717
726
|
node_wrapper = (start_node, 0)
|
|
718
|
-
stack = [node_wrapper]
|
|
727
|
+
stack: list[tuple[CFGNode, int]] = [node_wrapper]
|
|
719
728
|
traversed_nodes = {start_node}
|
|
720
729
|
subgraph_nodes = {start_node}
|
|
721
730
|
|
|
@@ -727,6 +736,7 @@ class CFGEmulated(ForwardAnalysis, CFGBase): # pylint: disable=abstract-method
|
|
|
727
736
|
edges = self.graph.out_edges(n, data=True)
|
|
728
737
|
|
|
729
738
|
for _, dst, data in edges:
|
|
739
|
+
assert dst is not None
|
|
730
740
|
if dst not in traversed_nodes:
|
|
731
741
|
# We see a new node!
|
|
732
742
|
traversed_nodes.add(dst)
|
|
@@ -1687,9 +1697,8 @@ class CFGEmulated(ForwardAnalysis, CFGBase): # pylint: disable=abstract-method
|
|
|
1687
1697
|
|
|
1688
1698
|
for block_id in pending_exits_to_remove:
|
|
1689
1699
|
l.debug(
|
|
1690
|
-
"Removing all pending exits to %#x since the target function
|
|
1700
|
+
"Removing all pending exits to %#x since the target function does not return",
|
|
1691
1701
|
self._block_id_addr(block_id),
|
|
1692
|
-
next(iter(self._pending_jobs[block_id])).returning_source,
|
|
1693
1702
|
)
|
|
1694
1703
|
|
|
1695
1704
|
for to_remove in self._pending_jobs[block_id]:
|
angr/analyses/cfg/cfg_fast.py
CHANGED
|
@@ -31,13 +31,10 @@ from angr import sim_options as o
|
|
|
31
31
|
from angr.errors import (
|
|
32
32
|
AngrCFGError,
|
|
33
33
|
AngrSkipJobNotice,
|
|
34
|
-
AngrUnsupportedSyscallError,
|
|
35
34
|
SimEngineError,
|
|
36
35
|
SimMemoryError,
|
|
37
36
|
SimTranslationError,
|
|
38
37
|
SimValueError,
|
|
39
|
-
SimOperationError,
|
|
40
|
-
SimError,
|
|
41
38
|
SimIRSBNoDecodeError,
|
|
42
39
|
)
|
|
43
40
|
from angr.utils.constants import DEFAULT_STATEMENT
|
|
@@ -200,7 +197,7 @@ class PendingJobs:
|
|
|
200
197
|
return self._pop_job(next(reversed(self._jobs.keys())))
|
|
201
198
|
|
|
202
199
|
# Prioritize returning functions
|
|
203
|
-
for func_addr in reversed(self._jobs
|
|
200
|
+
for func_addr in reversed(self._jobs):
|
|
204
201
|
if func_addr not in self._returning_functions:
|
|
205
202
|
continue
|
|
206
203
|
return self._pop_job(func_addr)
|
|
@@ -621,6 +618,7 @@ class CFGFast(ForwardAnalysis[CFGNode, CFGNode, CFGJob, int], CFGBase): # pylin
|
|
|
621
618
|
nodecode_window_size=512,
|
|
622
619
|
nodecode_threshold=0.3,
|
|
623
620
|
nodecode_step=16483,
|
|
621
|
+
check_funcret_max_job=500,
|
|
624
622
|
indirect_calls_always_return: bool | None = None,
|
|
625
623
|
jumptable_resolver_resolves_calls: bool | None = None,
|
|
626
624
|
start=None, # deprecated
|
|
@@ -680,6 +678,12 @@ class CFGFast(ForwardAnalysis[CFGNode, CFGNode, CFGJob, int], CFGBase): # pylin
|
|
|
680
678
|
table resolver and must be resolved using their specific resolvers. By default,
|
|
681
679
|
we will only disable JumpTableResolver from resolving indirect calls for large
|
|
682
680
|
binaries (region > 50 KB).
|
|
681
|
+
:param check_funcret_max_job When popping return-site jobs out of the job queue, angr will prioritize jobs
|
|
682
|
+
for which the callee is known to return. This check may be slow when there are
|
|
683
|
+
a large amount of jobs in different caller functions, and this situation often
|
|
684
|
+
occurs in obfuscated binaries where many functions never return. This parameter
|
|
685
|
+
acts as a threshold to disable this check when the number of jobs in the queue
|
|
686
|
+
exceeds this threshold.
|
|
683
687
|
:param int start: (Deprecated) The beginning address of CFG recovery.
|
|
684
688
|
:param int end: (Deprecated) The end address of CFG recovery.
|
|
685
689
|
:param CFGArchOptions arch_options: Architecture-specific options.
|
|
@@ -768,6 +772,7 @@ class CFGFast(ForwardAnalysis[CFGNode, CFGNode, CFGJob, int], CFGBase): # pylin
|
|
|
768
772
|
self._force_complete_scan = force_complete_scan
|
|
769
773
|
self._use_elf_eh_frame = elf_eh_frame
|
|
770
774
|
self._use_exceptions = exceptions
|
|
775
|
+
self._check_funcret_max_job = check_funcret_max_job
|
|
771
776
|
|
|
772
777
|
self._nodecode_window_size = nodecode_window_size
|
|
773
778
|
self._nodecode_threshold = nodecode_threshold
|
|
@@ -1535,6 +1540,19 @@ class CFGFast(ForwardAnalysis[CFGNode, CFGNode, CFGJob, int], CFGBase): # pylin
|
|
|
1535
1540
|
}:
|
|
1536
1541
|
func.info["is_alloca_probe"] = True
|
|
1537
1542
|
|
|
1543
|
+
elif self.project.arch.name == "X86":
|
|
1544
|
+
# determine if the function is __alloca_probe
|
|
1545
|
+
func = self.kb.functions.get_by_addr(func_addr) if self.kb.functions.contains_addr(func_addr) else None
|
|
1546
|
+
if func is not None and len(func.block_addrs_set) == 4:
|
|
1547
|
+
block_bytes = {func.get_block(block_addr).bytes for block_addr in func.block_addrs_set}
|
|
1548
|
+
if block_bytes == {
|
|
1549
|
+
b"-\x00\x10\x00\x00\x85\x00\xeb\xe9",
|
|
1550
|
+
b";\xc8r\n",
|
|
1551
|
+
b"Q\x8dL$\x04+\xc8\x1b\xc0\xf7\xd0#\xc8\x8b\xc4%\x00\xf0\xff\xff;\xc8r\n",
|
|
1552
|
+
b"\x8b\xc1Y\x94\x8b\x00\x89\x04$\xc3",
|
|
1553
|
+
}:
|
|
1554
|
+
func.info["is_alloca_probe"] = True
|
|
1555
|
+
|
|
1538
1556
|
if self._collect_data_ref and self.project is not None and ":" in self.project.arch.name:
|
|
1539
1557
|
# this is a pcode arch - use Clinic to recover data references
|
|
1540
1558
|
|
|
@@ -1824,7 +1842,7 @@ class CFGFast(ForwardAnalysis[CFGNode, CFGNode, CFGJob, int], CFGBase): # pylin
|
|
|
1824
1842
|
|
|
1825
1843
|
if (
|
|
1826
1844
|
self.project.simos is not None
|
|
1827
|
-
and self.project.arch.name
|
|
1845
|
+
and self.project.arch.name in {"X86", "AMD64"}
|
|
1828
1846
|
and self.project.simos.name == "Win32"
|
|
1829
1847
|
and isinstance(self.project.loader.main_object, cle.PE)
|
|
1830
1848
|
):
|
|
@@ -2576,38 +2594,16 @@ class CFGFast(ForwardAnalysis[CFGNode, CFGNode, CFGJob, int], CFGBase): # pylin
|
|
|
2576
2594
|
jobs: list[CFGJob] = []
|
|
2577
2595
|
|
|
2578
2596
|
if is_syscall:
|
|
2579
|
-
|
|
2580
|
-
|
|
2581
|
-
mode="fastpath",
|
|
2582
|
-
addr=cfg_node.addr,
|
|
2583
|
-
add_options={o.SYMBOL_FILL_UNCONSTRAINED_MEMORY, o.SYMBOL_FILL_UNCONSTRAINED_REGISTERS},
|
|
2597
|
+
resolved, resolved_targets, ij = self._indirect_jump_encountered(
|
|
2598
|
+
addr, cfg_node, irsb, current_function_addr, stmt_idx
|
|
2584
2599
|
)
|
|
2585
|
-
|
|
2586
|
-
|
|
2587
|
-
|
|
2588
|
-
|
|
2589
|
-
iter(
|
|
2590
|
-
succ
|
|
2591
|
-
for succ in successors.flat_successors
|
|
2592
|
-
if succ.history.jumpkind and succ.history.jumpkind.startswith("Ijk_Sys")
|
|
2593
|
-
),
|
|
2594
|
-
None,
|
|
2595
|
-
)
|
|
2596
|
-
else:
|
|
2597
|
-
succ = None
|
|
2598
|
-
if succ is None:
|
|
2599
|
-
# For some reason, there is no such successor with a syscall jumpkind
|
|
2600
|
-
target_addr = self._unresolvable_call_target_addr
|
|
2600
|
+
target_addr = None
|
|
2601
|
+
if resolved:
|
|
2602
|
+
if len(resolved_targets) == 1:
|
|
2603
|
+
(target_addr,) = resolved_targets
|
|
2601
2604
|
else:
|
|
2602
|
-
|
|
2603
|
-
|
|
2604
|
-
if syscall_stub: # can be None if simos is not a subclass of SimUserspace
|
|
2605
|
-
syscall_addr = syscall_stub.addr
|
|
2606
|
-
target_addr = syscall_addr
|
|
2607
|
-
else:
|
|
2608
|
-
target_addr = self._unresolvable_call_target_addr
|
|
2609
|
-
except AngrUnsupportedSyscallError:
|
|
2610
|
-
target_addr = self._unresolvable_call_target_addr
|
|
2605
|
+
if ij is not None:
|
|
2606
|
+
self._indirect_jumps_to_resolve.add(ij)
|
|
2611
2607
|
|
|
2612
2608
|
new_function_addr = target_addr.method if isinstance(target_addr, SootAddressDescriptor) else target_addr
|
|
2613
2609
|
|
|
@@ -2732,30 +2728,6 @@ class CFGFast(ForwardAnalysis[CFGNode, CFGNode, CFGJob, int], CFGBase): # pylin
|
|
|
2732
2728
|
|
|
2733
2729
|
return jobs
|
|
2734
2730
|
|
|
2735
|
-
def _simulate_block_with_resilience(self, state):
|
|
2736
|
-
"""
|
|
2737
|
-
Execute a basic block with "On Error Resume Next". Give up when there is no way moving forward.
|
|
2738
|
-
|
|
2739
|
-
:param SimState state: The initial state to start simulation with.
|
|
2740
|
-
:return: A SimSuccessors instance or None if we are unable to resume execution with resilience.
|
|
2741
|
-
:rtype: SimSuccessors or None
|
|
2742
|
-
"""
|
|
2743
|
-
|
|
2744
|
-
stmt_idx = 0
|
|
2745
|
-
successors = None # make PyCharm's linting happy
|
|
2746
|
-
|
|
2747
|
-
while True:
|
|
2748
|
-
try:
|
|
2749
|
-
successors = self.project.factory.successors(state, skip_stmts=stmt_idx)
|
|
2750
|
-
break
|
|
2751
|
-
except SimOperationError as ex:
|
|
2752
|
-
stmt_idx = ex.stmt_idx + 1
|
|
2753
|
-
continue
|
|
2754
|
-
except SimError:
|
|
2755
|
-
return None
|
|
2756
|
-
|
|
2757
|
-
return successors
|
|
2758
|
-
|
|
2759
2731
|
def _is_branching_to_outside(self, src_addr, target_addr, current_function_addr):
|
|
2760
2732
|
"""
|
|
2761
2733
|
Determine if a branch is branching to a different function (i.e., branching to outside the current function).
|
|
@@ -3236,7 +3208,7 @@ class CFGFast(ForwardAnalysis[CFGNode, CFGNode, CFGJob, int], CFGBase): # pylin
|
|
|
3236
3208
|
if jump.jumpkind == "Ijk_Boring":
|
|
3237
3209
|
unresolvable_target_addr = self._unresolvable_jump_target_addr
|
|
3238
3210
|
simprocedure_name = "UnresolvableJumpTarget"
|
|
3239
|
-
elif jump.jumpkind == "Ijk_Call":
|
|
3211
|
+
elif jump.jumpkind == "Ijk_Call" or jump.jumpkind.startswith("Ijk_Sys"):
|
|
3240
3212
|
unresolvable_target_addr = self._unresolvable_call_target_addr
|
|
3241
3213
|
simprocedure_name = "UnresolvableCallTarget"
|
|
3242
3214
|
else:
|
|
@@ -3707,7 +3679,9 @@ class CFGFast(ForwardAnalysis[CFGNode, CFGNode, CFGJob, int], CFGBase): # pylin
|
|
|
3707
3679
|
|
|
3708
3680
|
def _pop_pending_job(self, returning=True) -> CFGJob | None:
|
|
3709
3681
|
while self._pending_jobs:
|
|
3710
|
-
job = self._pending_jobs.pop_job(
|
|
3682
|
+
job = self._pending_jobs.pop_job(
|
|
3683
|
+
returning=returning if len(self._pending_jobs) < self._check_funcret_max_job else False
|
|
3684
|
+
)
|
|
3711
3685
|
if job is not None and job.job_type == CFGJobType.DATAREF_HINTS and self._seg_list.is_occupied(job.addr):
|
|
3712
3686
|
# ignore this hint from data refs because the target address has already been analyzed
|
|
3713
3687
|
continue
|
|
@@ -10,6 +10,7 @@ from .arm_elf_fast import ArmElfFastResolver
|
|
|
10
10
|
from .const_resolver import ConstantResolver
|
|
11
11
|
from .amd64_pe_iat import AMD64PeIatResolver
|
|
12
12
|
from .memload_resolver import MemoryLoadResolver
|
|
13
|
+
from .syscall_resolver import SyscallResolver
|
|
13
14
|
|
|
14
15
|
|
|
15
16
|
__all__ = (
|
|
@@ -21,6 +22,7 @@ __all__ = (
|
|
|
21
22
|
"MemoryLoadResolver",
|
|
22
23
|
"MipsElfFastResolver",
|
|
23
24
|
"MipsElfGotResolver",
|
|
25
|
+
"SyscallResolver",
|
|
24
26
|
"X86ElfPicPltResolver",
|
|
25
27
|
"X86PeIatResolver",
|
|
26
28
|
)
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
from typing import TYPE_CHECKING, Any
|
|
3
|
+
import logging
|
|
4
|
+
|
|
5
|
+
import claripy
|
|
6
|
+
|
|
7
|
+
from angr.code_location import CodeLocation
|
|
8
|
+
from angr.project import Project
|
|
9
|
+
from angr.analyses.propagator.vex_vars import VEXReg
|
|
10
|
+
from .propagator_utils import PropagatorLoadCallback
|
|
11
|
+
|
|
12
|
+
if TYPE_CHECKING:
|
|
13
|
+
from angr import SimState
|
|
14
|
+
from angr.knowledge_plugins import Function
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
l = logging.getLogger(name=__name__)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class ConstantValueManager:
|
|
21
|
+
"""
|
|
22
|
+
Manages the loading of registers who hold constant values.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
__slots__ = (
|
|
26
|
+
"func",
|
|
27
|
+
"indirect_jump_addr",
|
|
28
|
+
"kb",
|
|
29
|
+
"mapping",
|
|
30
|
+
"project",
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
def __init__(self, project: Project, kb, func: Function, ij_addr: int):
|
|
34
|
+
self.project = project
|
|
35
|
+
self.kb = kb
|
|
36
|
+
self.func = func
|
|
37
|
+
self.indirect_jump_addr = ij_addr
|
|
38
|
+
|
|
39
|
+
self.mapping: dict[Any, dict[Any, claripy.ast.Base]] | None = None
|
|
40
|
+
|
|
41
|
+
def reg_read_callback(self, state: SimState):
|
|
42
|
+
if self.mapping is None:
|
|
43
|
+
self._build_mapping()
|
|
44
|
+
assert self.mapping is not None
|
|
45
|
+
|
|
46
|
+
codeloc = CodeLocation(state.scratch.bbl_addr, state.scratch.stmt_idx, ins_addr=state.scratch.ins_addr)
|
|
47
|
+
if codeloc in self.mapping:
|
|
48
|
+
reg_read_offset = state.inspect.reg_read_offset
|
|
49
|
+
if isinstance(reg_read_offset, claripy.ast.BV) and reg_read_offset.op == "BVV":
|
|
50
|
+
reg_read_offset = reg_read_offset.args[0]
|
|
51
|
+
variable = VEXReg(reg_read_offset, state.inspect.reg_read_length)
|
|
52
|
+
if variable in self.mapping[codeloc]:
|
|
53
|
+
v = self.mapping[codeloc][variable]
|
|
54
|
+
if isinstance(v, int):
|
|
55
|
+
v = claripy.BVV(v, state.inspect.reg_read_length * state.arch.byte_width)
|
|
56
|
+
state.inspect.reg_read_expr = v
|
|
57
|
+
|
|
58
|
+
def _build_mapping(self):
|
|
59
|
+
# constant propagation
|
|
60
|
+
l.debug("JumpTable: Propagating for %r at %#x.", self.func, self.indirect_jump_addr)
|
|
61
|
+
|
|
62
|
+
# determine blocks to run FCP on
|
|
63
|
+
|
|
64
|
+
# - include at most three levels of superblock successors from the entrypoint
|
|
65
|
+
self.mapping = {}
|
|
66
|
+
startpoint = self.func.startpoint
|
|
67
|
+
if startpoint is None:
|
|
68
|
+
return
|
|
69
|
+
|
|
70
|
+
blocks = set()
|
|
71
|
+
succ_and_levels = [(startpoint, 0)]
|
|
72
|
+
while succ_and_levels:
|
|
73
|
+
new_succs = []
|
|
74
|
+
for node, level in succ_and_levels:
|
|
75
|
+
if node in blocks:
|
|
76
|
+
continue
|
|
77
|
+
blocks.add(node)
|
|
78
|
+
if node.addr == self.indirect_jump_addr:
|
|
79
|
+
# stop at the indirect jump block
|
|
80
|
+
continue
|
|
81
|
+
for _, succ, data in self.func.graph.out_edges(node, data=True):
|
|
82
|
+
new_level = level if data.get("type") == "fake_return" else level + 1
|
|
83
|
+
if new_level <= 3:
|
|
84
|
+
new_succs.append((succ, new_level))
|
|
85
|
+
succ_and_levels = new_succs
|
|
86
|
+
|
|
87
|
+
# - include at most six levels of predecessors from the indirect jump block
|
|
88
|
+
ij_block = self.func.get_node(self.indirect_jump_addr)
|
|
89
|
+
preds = [ij_block]
|
|
90
|
+
for _ in range(6):
|
|
91
|
+
new_preds = []
|
|
92
|
+
for node in preds:
|
|
93
|
+
if node in blocks:
|
|
94
|
+
continue
|
|
95
|
+
blocks.add(node)
|
|
96
|
+
new_preds += list(self.func.graph.predecessors(node))
|
|
97
|
+
preds = new_preds
|
|
98
|
+
if not preds:
|
|
99
|
+
break
|
|
100
|
+
|
|
101
|
+
prop = self.project.analyses.FastConstantPropagation(
|
|
102
|
+
self.func,
|
|
103
|
+
blocks=blocks,
|
|
104
|
+
vex_cross_insn_opt=True,
|
|
105
|
+
load_callback=PropagatorLoadCallback(self.project).propagator_load_callback,
|
|
106
|
+
)
|
|
107
|
+
self.mapping = prop.replacements
|
|
@@ -11,6 +11,7 @@ from . import ConstantResolver
|
|
|
11
11
|
from . import ArmElfFastResolver
|
|
12
12
|
from . import AMD64PeIatResolver
|
|
13
13
|
from . import MipsElfGotResolver
|
|
14
|
+
from . import SyscallResolver
|
|
14
15
|
|
|
15
16
|
DEFAULT_RESOLVERS = {
|
|
16
17
|
"X86": {
|
|
@@ -58,7 +59,7 @@ DEFAULT_RESOLVERS = {
|
|
|
58
59
|
ArmElfFastResolver,
|
|
59
60
|
]
|
|
60
61
|
},
|
|
61
|
-
"ALL": [MemoryLoadResolver, JumpTableResolver, ConstantResolver],
|
|
62
|
+
"ALL": [MemoryLoadResolver, JumpTableResolver, ConstantResolver, SyscallResolver],
|
|
62
63
|
}
|
|
63
64
|
|
|
64
65
|
|