angr 9.2.124__py3-none-macosx_11_0_arm64.whl → 9.2.125__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/__init__.py +9 -1
- angr/analyses/codecave.py +77 -0
- angr/analyses/decompiler/clinic.py +31 -1
- angr/analyses/decompiler/decompiler.py +4 -0
- angr/analyses/decompiler/optimization_passes/__init__.py +3 -0
- angr/analyses/decompiler/optimization_passes/inlined_string_transformation_simplifier.py +6 -0
- angr/analyses/decompiler/optimization_passes/tag_slicer.py +41 -0
- angr/analyses/decompiler/peephole_optimizations/constant_derefs.py +2 -2
- angr/analyses/patchfinder.py +137 -0
- angr/analyses/pathfinder.py +282 -0
- angr/analyses/smc.py +159 -0
- angr/angrdb/models.py +1 -2
- angr/engines/vex/heavy/heavy.py +2 -0
- angr/exploration_techniques/spiller_db.py +1 -2
- angr/knowledge_plugins/functions/function.py +4 -0
- angr/knowledge_plugins/functions/function_manager.py +18 -9
- angr/knowledge_plugins/functions/function_parser.py +1 -1
- angr/knowledge_plugins/functions/soot_function.py +1 -0
- angr/lib/angr_native.dylib +0 -0
- angr/misc/ux.py +2 -2
- angr/project.py +17 -1
- angr/state_plugins/history.py +6 -4
- angr/utils/bits.py +4 -0
- angr/utils/tagged_interval_map.py +112 -0
- {angr-9.2.124.dist-info → angr-9.2.125.dist-info}/METADATA +6 -6
- {angr-9.2.124.dist-info → angr-9.2.125.dist-info}/RECORD +31 -25
- {angr-9.2.124.dist-info → angr-9.2.125.dist-info}/LICENSE +0 -0
- {angr-9.2.124.dist-info → angr-9.2.125.dist-info}/WHEEL +0 -0
- {angr-9.2.124.dist-info → angr-9.2.125.dist-info}/entry_points.txt +0 -0
- {angr-9.2.124.dist-info → angr-9.2.125.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
# pylint:disable=missing-class-docstring
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
from enum import Enum, auto
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
from weakref import ref
|
|
6
|
+
from collections import defaultdict
|
|
7
|
+
|
|
8
|
+
from networkx import DiGraph
|
|
9
|
+
from networkx.algorithms.shortest_paths import single_target_shortest_path_length
|
|
10
|
+
|
|
11
|
+
from angr.sim_state import SimState
|
|
12
|
+
from angr.engines.successors import SimSuccessors
|
|
13
|
+
from angr.knowledge_plugins.cfg import CFGModel, CFGNode
|
|
14
|
+
from .analysis import Analysis, AnalysesHub
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class Unreachable(Exception):
|
|
18
|
+
pass
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@dataclass(eq=False)
|
|
22
|
+
class SimStateMarker:
|
|
23
|
+
addr: int
|
|
24
|
+
parent: SimStateMarker | None = None
|
|
25
|
+
banned: bool = False
|
|
26
|
+
misses: int = 0
|
|
27
|
+
|
|
28
|
+
def __repr__(self):
|
|
29
|
+
inner_repr = "None" if self.parent is None else "..."
|
|
30
|
+
return f"SimStateMarker(addr={self.addr:#x}, parent={inner_repr}, banned={self.banned}, misses={self.misses})"
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class SuccessorsKind(Enum):
|
|
34
|
+
SAT = auto()
|
|
35
|
+
UNSAT = auto()
|
|
36
|
+
MISSING = auto()
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@dataclass
|
|
40
|
+
class TestPathReport:
|
|
41
|
+
path_markers: dict[int, SimStateMarker]
|
|
42
|
+
termination: SuccessorsKind
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def nilref():
|
|
46
|
+
return None
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class Pathfinder(Analysis):
|
|
50
|
+
def __init__(self, start_state: SimState, goal_addr: int, cfg: CFGModel, cache_size=10000):
|
|
51
|
+
self.start_state = start_state
|
|
52
|
+
self.goal_addr = goal_addr
|
|
53
|
+
self.goal_state: SimState | None = None
|
|
54
|
+
self.cfg = cfg
|
|
55
|
+
self.cache_size = cache_size
|
|
56
|
+
|
|
57
|
+
# HACK HACK HACK HACK TODO FIXME FISH PLEASE GET RID OF THIS
|
|
58
|
+
extra_edges = []
|
|
59
|
+
for node in self.cfg.graph.nodes:
|
|
60
|
+
if node.is_syscall:
|
|
61
|
+
for pred in self.cfg.graph.pred[node]:
|
|
62
|
+
for succ, data in self.cfg.graph.succ[pred].items():
|
|
63
|
+
if data["jumpkind"] == "Ijk_FakeRet":
|
|
64
|
+
extra_edges.append((node, succ))
|
|
65
|
+
for node, succ in extra_edges:
|
|
66
|
+
self.cfg.graph.add_edge(node, succ, jumpkind="Ijk_Ret")
|
|
67
|
+
|
|
68
|
+
goal_node = self.cfg.get_any_node(goal_addr)
|
|
69
|
+
if goal_node is None:
|
|
70
|
+
raise ValueError(f"Node {goal_addr:#x} is not in graph")
|
|
71
|
+
|
|
72
|
+
self.start_marker = SimStateMarker(start_state.addr)
|
|
73
|
+
self.transition_cache: DiGraph[SimStateMarker] = DiGraph()
|
|
74
|
+
self.transition_cache.add_node(self.start_marker, state=ref(start_state))
|
|
75
|
+
self.base_heuristic: dict[int, int] = {
|
|
76
|
+
node.addr: dist for node, dist in single_target_shortest_path_length(cfg.graph, goal_node)
|
|
77
|
+
}
|
|
78
|
+
self.state_cache = {}
|
|
79
|
+
self.unsat_markers = set()
|
|
80
|
+
self.extra_weight = defaultdict(int)
|
|
81
|
+
|
|
82
|
+
self._search_frontier_marker = self.start_marker
|
|
83
|
+
self._search_path: list[tuple[int, str]] = [(self.start_marker.addr, "Ijk_Boring")]
|
|
84
|
+
self._search_stack = []
|
|
85
|
+
self._search_backtrack_to = {self.start_marker}
|
|
86
|
+
self._search_address_backtrack_points = {self.start_marker.addr: self.start_marker}
|
|
87
|
+
|
|
88
|
+
def cache_state(self, state: SimState):
|
|
89
|
+
self.state_cache[state] = self.state_cache.pop(state, None)
|
|
90
|
+
if len(self.state_cache) > self.cache_size:
|
|
91
|
+
self.state_cache.pop(next(iter(self.state_cache)))
|
|
92
|
+
|
|
93
|
+
def marker_to_state(self, marker: SimStateMarker) -> SimState | None:
|
|
94
|
+
return self.transition_cache.nodes[marker]["state"]()
|
|
95
|
+
|
|
96
|
+
def analyze(self) -> bool:
|
|
97
|
+
while True:
|
|
98
|
+
search_path = self.find_best_hypothesis_path()
|
|
99
|
+
result = self.test_path(search_path)
|
|
100
|
+
if result.termination == SuccessorsKind.SAT:
|
|
101
|
+
self.goal_state = self.marker_to_state(result.path_markers[len(search_path) - 1])
|
|
102
|
+
return True
|
|
103
|
+
marker = result.path_markers[max(result.path_markers)]
|
|
104
|
+
marker.banned = True
|
|
105
|
+
self._search_backtrack_to.add(marker)
|
|
106
|
+
if result.termination == SuccessorsKind.UNSAT:
|
|
107
|
+
self.unsat_markers.add(marker)
|
|
108
|
+
|
|
109
|
+
def _search_backtrack(self):
|
|
110
|
+
if self._search_address_backtrack_points[self._search_frontier_marker.addr] is self._search_frontier_marker:
|
|
111
|
+
self._search_address_backtrack_points.pop(self._search_frontier_marker.addr)
|
|
112
|
+
|
|
113
|
+
self._search_frontier_marker = self._search_frontier_marker.parent
|
|
114
|
+
if self._search_frontier_marker is None:
|
|
115
|
+
raise Unreachable
|
|
116
|
+
|
|
117
|
+
addr, jumpkind = self._search_path.pop()
|
|
118
|
+
if jumpkind == "Ijk_Ret":
|
|
119
|
+
self._search_stack.append(addr)
|
|
120
|
+
elif jumpkind == "Ijk_Call" or jumpkind.startswith("Ijk_Sys"):
|
|
121
|
+
self._search_stack.pop()
|
|
122
|
+
|
|
123
|
+
def find_best_hypothesis_path(self) -> tuple[int, ...]:
|
|
124
|
+
assert self._search_backtrack_to, "Uhh every iteration should set at least one backtrack point"
|
|
125
|
+
if self.start_marker in self._search_backtrack_to:
|
|
126
|
+
self._search_frontier_marker = self.start_marker
|
|
127
|
+
self._search_path: list[tuple[int, str]] = [(self.start_marker.addr, "Ijk_Boring")]
|
|
128
|
+
self._search_stack = []
|
|
129
|
+
self._search_backtrack_to = set()
|
|
130
|
+
else:
|
|
131
|
+
while self._search_backtrack_to:
|
|
132
|
+
self._search_backtrack_to.discard(self._search_frontier_marker)
|
|
133
|
+
try:
|
|
134
|
+
self._search_backtrack()
|
|
135
|
+
except Unreachable as e:
|
|
136
|
+
raise RuntimeError("oops") from e
|
|
137
|
+
|
|
138
|
+
while self._search_path[-1][0] != self.goal_addr:
|
|
139
|
+
banned = {
|
|
140
|
+
marker.addr for marker in self.transition_cache.succ[self._search_frontier_marker] if marker.banned
|
|
141
|
+
}
|
|
142
|
+
current_node = self.cfg.get_any_node(self._search_path[-1][0])
|
|
143
|
+
options = [
|
|
144
|
+
(node, data["jumpkind"], self.base_heuristic[node.addr] + self.extra_weight[node.addr])
|
|
145
|
+
for node, data in self.cfg.graph.succ[current_node].items()
|
|
146
|
+
if data["jumpkind"] != "Ijk_FakeRet"
|
|
147
|
+
and node.addr not in banned
|
|
148
|
+
and node.addr in self.base_heuristic
|
|
149
|
+
and (data["jumpkind"] != "Ijk_Ret" or node.addr == self._search_stack[-1])
|
|
150
|
+
]
|
|
151
|
+
if not options:
|
|
152
|
+
# backtrack
|
|
153
|
+
self._search_frontier_marker.banned = True
|
|
154
|
+
self._search_backtrack()
|
|
155
|
+
continue
|
|
156
|
+
|
|
157
|
+
best_node, best_jumpkind, best_weight = min(
|
|
158
|
+
options,
|
|
159
|
+
default=(None, None),
|
|
160
|
+
key=lambda xyz: xyz[2],
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
assert isinstance(best_jumpkind, str)
|
|
164
|
+
assert isinstance(best_node, CFGNode)
|
|
165
|
+
self.extra_weight[best_node.addr] += 1
|
|
166
|
+
self._search_path.append((best_node.addr, best_jumpkind))
|
|
167
|
+
|
|
168
|
+
if best_jumpkind == "Ijk_Call" or best_jumpkind.startswith("Ijk_Sys"):
|
|
169
|
+
self._search_stack.append(
|
|
170
|
+
next(
|
|
171
|
+
iter(
|
|
172
|
+
node.addr
|
|
173
|
+
for node, data in self.cfg.graph.succ[current_node].items()
|
|
174
|
+
if data["jumpkind"] == "Ijk_FakeRet"
|
|
175
|
+
),
|
|
176
|
+
None,
|
|
177
|
+
)
|
|
178
|
+
)
|
|
179
|
+
elif best_jumpkind == "Ijk_Ret":
|
|
180
|
+
self._search_stack.pop()
|
|
181
|
+
|
|
182
|
+
frontier_marker_nullable = next(
|
|
183
|
+
(
|
|
184
|
+
marker
|
|
185
|
+
for marker in self.transition_cache.succ[self._search_frontier_marker]
|
|
186
|
+
if marker.addr == best_node.addr
|
|
187
|
+
),
|
|
188
|
+
None,
|
|
189
|
+
)
|
|
190
|
+
if frontier_marker_nullable is None:
|
|
191
|
+
new_marker = SimStateMarker(best_node.addr, self._search_frontier_marker)
|
|
192
|
+
self.transition_cache.add_node(new_marker, state=nilref)
|
|
193
|
+
self.transition_cache.add_edge(self._search_frontier_marker, new_marker)
|
|
194
|
+
self._search_frontier_marker = new_marker
|
|
195
|
+
else:
|
|
196
|
+
self._search_frontier_marker = frontier_marker_nullable
|
|
197
|
+
|
|
198
|
+
if self._search_frontier_marker.addr not in self._search_address_backtrack_points:
|
|
199
|
+
self._search_address_backtrack_points[self._search_frontier_marker.addr] = self._search_frontier_marker
|
|
200
|
+
|
|
201
|
+
# TODO does this go above the above stanza?
|
|
202
|
+
if sum(weight == best_weight for _, _, weight in options) != 1:
|
|
203
|
+
self._search_backtrack_to.add(self._search_address_backtrack_points[self._search_frontier_marker.addr])
|
|
204
|
+
|
|
205
|
+
return tuple(addr for addr, _ in self._search_path)
|
|
206
|
+
|
|
207
|
+
def diagnose_unsat(self, state: SimState):
|
|
208
|
+
pass
|
|
209
|
+
|
|
210
|
+
def test_path(self, bbl_addr_trace: tuple[int, ...]) -> TestPathReport:
|
|
211
|
+
assert bbl_addr_trace[0] == self.start_marker.addr, "Paths must begin with the start state"
|
|
212
|
+
|
|
213
|
+
known_markers = [self.start_marker]
|
|
214
|
+
for addr in bbl_addr_trace[1:]:
|
|
215
|
+
for succ in self.transition_cache.succ[known_markers[-1]]:
|
|
216
|
+
if succ.addr == addr:
|
|
217
|
+
break
|
|
218
|
+
else:
|
|
219
|
+
break
|
|
220
|
+
known_markers.append(succ)
|
|
221
|
+
|
|
222
|
+
marker = None
|
|
223
|
+
for ri, marker_ in enumerate(reversed(known_markers)):
|
|
224
|
+
i = len(known_markers) - 1 - ri
|
|
225
|
+
state: SimState = self.transition_cache.nodes[marker_]["state"]()
|
|
226
|
+
marker = marker_
|
|
227
|
+
if state is not None:
|
|
228
|
+
break
|
|
229
|
+
else:
|
|
230
|
+
assert False, "The first item in known_markers should always have a resolvable weakref"
|
|
231
|
+
|
|
232
|
+
while i != len(bbl_addr_trace) - 1:
|
|
233
|
+
assert state.addr == bbl_addr_trace[i]
|
|
234
|
+
|
|
235
|
+
marker.misses += 1
|
|
236
|
+
successors = state.step(strict_block_end=True)
|
|
237
|
+
succ, kind = find_successor(successors, bbl_addr_trace[i + 1])
|
|
238
|
+
|
|
239
|
+
# cache state
|
|
240
|
+
if i + 1 < len(known_markers):
|
|
241
|
+
succ_marker = known_markers[i + 1]
|
|
242
|
+
else:
|
|
243
|
+
succ_marker = SimStateMarker(bbl_addr_trace[i + 1], parent=marker)
|
|
244
|
+
self.transition_cache.add_node(succ_marker)
|
|
245
|
+
self.transition_cache.add_edge(marker, succ_marker)
|
|
246
|
+
self.transition_cache.nodes[succ_marker]["state"] = ref(succ) if succ is not None else nilref
|
|
247
|
+
if succ is not None:
|
|
248
|
+
self.cache_state(succ)
|
|
249
|
+
|
|
250
|
+
if kind == SuccessorsKind.SAT:
|
|
251
|
+
assert succ is not None
|
|
252
|
+
state = succ
|
|
253
|
+
marker = succ_marker
|
|
254
|
+
i += 1
|
|
255
|
+
continue
|
|
256
|
+
if kind == SuccessorsKind.UNSAT:
|
|
257
|
+
assert succ is not None
|
|
258
|
+
return TestPathReport(
|
|
259
|
+
path_markers={i: marker, i + 1: succ_marker},
|
|
260
|
+
termination=SuccessorsKind.UNSAT,
|
|
261
|
+
)
|
|
262
|
+
return TestPathReport(path_markers={i: marker, i + 1: succ_marker}, termination=SuccessorsKind.MISSING)
|
|
263
|
+
|
|
264
|
+
return TestPathReport(path_markers={i: marker}, termination=SuccessorsKind.SAT)
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
def find_successor(successors: SimSuccessors, target_addr: int) -> tuple[SimState | None, SuccessorsKind]:
|
|
268
|
+
for succ in successors.flat_successors:
|
|
269
|
+
if succ.addr == target_addr:
|
|
270
|
+
return succ, SuccessorsKind.SAT
|
|
271
|
+
for succ in successors.unsat_successors:
|
|
272
|
+
if succ.addr == target_addr:
|
|
273
|
+
return succ, SuccessorsKind.UNSAT
|
|
274
|
+
for succ in successors.unconstrained_successors:
|
|
275
|
+
succ2 = succ.copy()
|
|
276
|
+
succ2.add_constraints(succ2._ip == target_addr)
|
|
277
|
+
if succ2.satisfiable():
|
|
278
|
+
return succ2, SuccessorsKind.SAT
|
|
279
|
+
return None, SuccessorsKind.MISSING
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
AnalysesHub.register_default("Pathfinder", Pathfinder)
|
angr/analyses/smc.py
ADDED
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
import logging
|
|
3
|
+
import random
|
|
4
|
+
|
|
5
|
+
from enum import auto, IntFlag
|
|
6
|
+
from collections.abc import Generator
|
|
7
|
+
|
|
8
|
+
import angr
|
|
9
|
+
from angr.analyses import Analysis, AnalysesHub
|
|
10
|
+
from angr.knowledge_plugins import Function
|
|
11
|
+
from angr.sim_state import SimState
|
|
12
|
+
|
|
13
|
+
from angr.utils.tagged_interval_map import TaggedIntervalMap
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
log = logging.getLogger(__name__)
|
|
17
|
+
log.setLevel(logging.INFO)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class TraceActions(IntFlag):
|
|
21
|
+
"""
|
|
22
|
+
Describe memory access actions.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
WRITE = auto()
|
|
26
|
+
EXECUTE = auto()
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class TraceClassifier:
|
|
30
|
+
"""
|
|
31
|
+
Classify traces.
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
def __init__(self, state: SimState | None = None):
|
|
35
|
+
self.map = TaggedIntervalMap()
|
|
36
|
+
if state:
|
|
37
|
+
self.instrument(state)
|
|
38
|
+
|
|
39
|
+
def act_mem_write(self, state) -> None:
|
|
40
|
+
"""
|
|
41
|
+
SimInspect callback for memory writes.
|
|
42
|
+
"""
|
|
43
|
+
addr = state.solver.eval(state.inspect.mem_write_address)
|
|
44
|
+
length = state.inspect.mem_write_length
|
|
45
|
+
if not isinstance(length, int):
|
|
46
|
+
length = state.solver.eval(length)
|
|
47
|
+
self.map.add(addr, length, TraceActions.WRITE)
|
|
48
|
+
|
|
49
|
+
def act_instruction(self, state) -> None:
|
|
50
|
+
"""
|
|
51
|
+
SimInspect callback for instruction execution.
|
|
52
|
+
"""
|
|
53
|
+
addr = state.inspect.instruction
|
|
54
|
+
if addr is None:
|
|
55
|
+
log.warning("Symbolic addr")
|
|
56
|
+
return
|
|
57
|
+
|
|
58
|
+
# FIXME: Ensure block size is correct
|
|
59
|
+
self.map.add(addr, state.block().size, TraceActions.EXECUTE)
|
|
60
|
+
|
|
61
|
+
def instrument(self, state) -> None:
|
|
62
|
+
"""
|
|
63
|
+
Instrument `state` for tracing.
|
|
64
|
+
"""
|
|
65
|
+
state.inspect.b("mem_write", when=angr.BP_BEFORE, action=self.act_mem_write)
|
|
66
|
+
state.inspect.b("instruction", when=angr.BP_BEFORE, action=self.act_instruction)
|
|
67
|
+
|
|
68
|
+
def get_smc_address_and_lengths(self) -> Generator[tuple[int, int]]:
|
|
69
|
+
"""
|
|
70
|
+
Evaluate the trace to find which areas of memory were both written to and executed.
|
|
71
|
+
"""
|
|
72
|
+
smc_flags = TraceActions.WRITE | TraceActions.EXECUTE
|
|
73
|
+
for addr, size, flags in self.map.irange():
|
|
74
|
+
if (flags & smc_flags) == smc_flags:
|
|
75
|
+
yield (addr, size)
|
|
76
|
+
|
|
77
|
+
def determine_smc(self) -> bool:
|
|
78
|
+
"""
|
|
79
|
+
Evaluate the trace to find areas of memory that were both written to and executed.
|
|
80
|
+
"""
|
|
81
|
+
return any(self.get_smc_address_and_lengths())
|
|
82
|
+
|
|
83
|
+
def pp(self):
|
|
84
|
+
for a, b, c in self.map.irange():
|
|
85
|
+
print(f"{a:8x} {b} {c}")
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
class SelfModifyingCodeAnalysis(Analysis):
|
|
89
|
+
"""
|
|
90
|
+
Determine if some piece of code is self-modifying.
|
|
91
|
+
|
|
92
|
+
This determination is made by simply executing. If an address is executed
|
|
93
|
+
that is also written to, the code is determined to be self-modifying. The
|
|
94
|
+
determination is stored in the `result` property. The `regions` property
|
|
95
|
+
contains a list of (addr, length) regions that were both written to and
|
|
96
|
+
executed.
|
|
97
|
+
"""
|
|
98
|
+
|
|
99
|
+
result: bool
|
|
100
|
+
regions: list[tuple[int, int]]
|
|
101
|
+
|
|
102
|
+
def __init__(self, subject: None | int | str | Function, max_bytes: int = 0, state: SimState | None = None):
|
|
103
|
+
"""
|
|
104
|
+
:param subject: Subject of analysis
|
|
105
|
+
:param max_bytes: Maximum number of bytes from subject address. 0 for no limit (default).
|
|
106
|
+
:param state: State to begin executing from from.
|
|
107
|
+
"""
|
|
108
|
+
assert self.project.selfmodifying_code
|
|
109
|
+
|
|
110
|
+
if subject is None:
|
|
111
|
+
subject = self.project.entry
|
|
112
|
+
if isinstance(subject, str):
|
|
113
|
+
try:
|
|
114
|
+
addr = self.project.kb.labels.lookup(subject)
|
|
115
|
+
except KeyError:
|
|
116
|
+
addr = self.project.kb.functions[subject].addr
|
|
117
|
+
elif isinstance(subject, Function):
|
|
118
|
+
addr = subject.addr
|
|
119
|
+
elif isinstance(subject, int):
|
|
120
|
+
addr = subject
|
|
121
|
+
else:
|
|
122
|
+
raise ValueError("Not a supported subject")
|
|
123
|
+
|
|
124
|
+
if state is None:
|
|
125
|
+
init_state = self.project.factory.call_state(addr)
|
|
126
|
+
else:
|
|
127
|
+
init_state = state.copy()
|
|
128
|
+
init_state.regs.pc = addr
|
|
129
|
+
|
|
130
|
+
init_state.options -= angr.sim_options.simplification
|
|
131
|
+
|
|
132
|
+
self._trace_classifier = TraceClassifier(init_state)
|
|
133
|
+
simgr = self.project.factory.simgr(init_state)
|
|
134
|
+
|
|
135
|
+
kwargs = {}
|
|
136
|
+
if max_bytes:
|
|
137
|
+
kwargs["filter_func"] = lambda s: (
|
|
138
|
+
"active" if s.solver.eval(addr <= s.regs.pc) and s.solver.eval(s.regs.pc < addr + max_bytes) else "oob"
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
# FIXME: Early out on SMC detect
|
|
142
|
+
# FIXME: Configurable step threshold
|
|
143
|
+
# FIXME: Loop analysis
|
|
144
|
+
|
|
145
|
+
for n in range(100):
|
|
146
|
+
self._update_progress(n)
|
|
147
|
+
simgr.step(n=3)
|
|
148
|
+
random.shuffle(simgr.active)
|
|
149
|
+
simgr.split(from_stash="active", to_stash=simgr.DROP, limit=10)
|
|
150
|
+
|
|
151
|
+
# Classify any out of bound entrypoints
|
|
152
|
+
for state_ in simgr.stashes["oob"]:
|
|
153
|
+
self._trace_classifier.act_instruction(state_)
|
|
154
|
+
|
|
155
|
+
self.regions = list(self._trace_classifier.get_smc_address_and_lengths())
|
|
156
|
+
self.result = len(self.regions) > 0
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
AnalysesHub.register_default("SMC", SelfModifyingCodeAnalysis)
|
angr/angrdb/models.py
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
from sqlalchemy import Column, Integer, String, Boolean, BLOB, ForeignKey
|
|
3
|
-
from sqlalchemy.orm import relationship
|
|
4
|
-
from sqlalchemy.ext.declarative import declarative_base
|
|
3
|
+
from sqlalchemy.orm import declarative_base, relationship
|
|
5
4
|
|
|
6
5
|
Base = declarative_base()
|
|
7
6
|
|
angr/engines/vex/heavy/heavy.py
CHANGED
|
@@ -90,6 +90,7 @@ class HeavyVEXMixin(SuccessorsMixin, ClaripyDataMixin, SimStateStorageMixin, VEX
|
|
|
90
90
|
num_inst=None,
|
|
91
91
|
extra_stop_points=None,
|
|
92
92
|
opt_level=None,
|
|
93
|
+
strict_block_end=None,
|
|
93
94
|
**kwargs,
|
|
94
95
|
):
|
|
95
96
|
if not pyvex.lifting.lifters[self.state.arch.name] or type(successors.addr) is not int:
|
|
@@ -144,6 +145,7 @@ class HeavyVEXMixin(SuccessorsMixin, ClaripyDataMixin, SimStateStorageMixin, VEX
|
|
|
144
145
|
num_inst=num_inst,
|
|
145
146
|
extra_stop_points=extra_stop_points,
|
|
146
147
|
opt_level=opt_level,
|
|
148
|
+
strict_block_end=strict_block_end,
|
|
147
149
|
)
|
|
148
150
|
|
|
149
151
|
if (
|
|
@@ -5,8 +5,7 @@ import datetime
|
|
|
5
5
|
try:
|
|
6
6
|
import sqlalchemy
|
|
7
7
|
from sqlalchemy import Column, Integer, String, Boolean, DateTime, create_engine
|
|
8
|
-
from sqlalchemy.orm import sessionmaker
|
|
9
|
-
from sqlalchemy.ext.declarative import declarative_base
|
|
8
|
+
from sqlalchemy.orm import declarative_base, sessionmaker
|
|
10
9
|
from sqlalchemy.exc import OperationalError
|
|
11
10
|
|
|
12
11
|
Base = declarative_base()
|
|
@@ -56,6 +56,7 @@ class Function(Serializable):
|
|
|
56
56
|
"addr",
|
|
57
57
|
"is_simprocedure",
|
|
58
58
|
"_name",
|
|
59
|
+
"previous_names",
|
|
59
60
|
"is_default_name",
|
|
60
61
|
"from_signature",
|
|
61
62
|
"binary_name",
|
|
@@ -224,6 +225,7 @@ class Function(Serializable):
|
|
|
224
225
|
else:
|
|
225
226
|
self.is_default_name = False
|
|
226
227
|
self._name = name
|
|
228
|
+
self.previous_names = []
|
|
227
229
|
self.from_signature = None
|
|
228
230
|
|
|
229
231
|
# Determine the name the binary where this function is.
|
|
@@ -274,6 +276,7 @@ class Function(Serializable):
|
|
|
274
276
|
|
|
275
277
|
@name.setter
|
|
276
278
|
def name(self, v):
|
|
279
|
+
self.previous_names.append(self._name)
|
|
277
280
|
self._name = v
|
|
278
281
|
self._function_manager._kb.labels[self.addr] = v
|
|
279
282
|
|
|
@@ -1667,6 +1670,7 @@ class Function(Serializable):
|
|
|
1667
1670
|
func._endpoints = self._endpoints.copy()
|
|
1668
1671
|
func._call_sites = self._call_sites.copy()
|
|
1669
1672
|
func._project = self._project
|
|
1673
|
+
func.previous_names = list(self.previous_names)
|
|
1670
1674
|
func.is_plt = self.is_plt
|
|
1671
1675
|
func.is_simprocedure = self.is_simprocedure
|
|
1672
1676
|
func.binary_name = self.binary_name
|
|
@@ -313,7 +313,7 @@ class FunctionManager(KnowledgeBasePlugin, collections.abc.Mapping):
|
|
|
313
313
|
if isinstance(k, self.function_address_types):
|
|
314
314
|
f = self.function(addr=k)
|
|
315
315
|
elif type(k) is str:
|
|
316
|
-
f = self.function(name=k)
|
|
316
|
+
f = self.function(name=k) or self.function(name=k, check_previous_names=True)
|
|
317
317
|
else:
|
|
318
318
|
raise ValueError(f"FunctionManager.__getitem__ does not support keys of type {type(k)}")
|
|
319
319
|
|
|
@@ -350,9 +350,9 @@ class FunctionManager(KnowledgeBasePlugin, collections.abc.Mapping):
|
|
|
350
350
|
def get_by_addr(self, addr) -> Function:
|
|
351
351
|
return self._function_map.get(addr)
|
|
352
352
|
|
|
353
|
-
def get_by_name(self, name: str) -> Generator[Function]:
|
|
353
|
+
def get_by_name(self, name: str, check_previous_names: bool = False) -> Generator[Function]:
|
|
354
354
|
for f in self._function_map.values():
|
|
355
|
-
if f.name == name:
|
|
355
|
+
if f.name == name or (check_previous_names and name in f.previous_names):
|
|
356
356
|
yield f
|
|
357
357
|
|
|
358
358
|
def _function_added(self, func: Function):
|
|
@@ -411,7 +411,7 @@ class FunctionManager(KnowledgeBasePlugin, collections.abc.Mapping):
|
|
|
411
411
|
except KeyError:
|
|
412
412
|
return None
|
|
413
413
|
|
|
414
|
-
def query(self, query: str) -> Function | None:
|
|
414
|
+
def query(self, query: str, check_previous_names: bool = False) -> Function | None:
|
|
415
415
|
"""
|
|
416
416
|
Query for a function using selectors to disambiguate. Supported variations:
|
|
417
417
|
|
|
@@ -430,19 +430,21 @@ class FunctionManager(KnowledgeBasePlugin, collections.abc.Mapping):
|
|
|
430
430
|
addr = int(matches.group(2), 0)
|
|
431
431
|
try:
|
|
432
432
|
func = self._function_map.get(addr)
|
|
433
|
-
if func.name == name:
|
|
433
|
+
if func.name == name or (check_previous_names and name in func.previous_names):
|
|
434
434
|
return func
|
|
435
435
|
except KeyError:
|
|
436
436
|
pass
|
|
437
437
|
|
|
438
438
|
obj_name = selector or self._kb._project.loader.main_object.binary_basename
|
|
439
|
-
for func in self.get_by_name(name):
|
|
439
|
+
for func in self.get_by_name(name, check_previous_names=check_previous_names):
|
|
440
440
|
if func.binary_name == obj_name:
|
|
441
441
|
return func
|
|
442
442
|
|
|
443
443
|
return None
|
|
444
444
|
|
|
445
|
-
def function(
|
|
445
|
+
def function(
|
|
446
|
+
self, addr=None, name=None, check_previous_names=False, create=False, syscall=False, plt=None
|
|
447
|
+
) -> Function | None:
|
|
446
448
|
"""
|
|
447
449
|
Get a function object from the function manager.
|
|
448
450
|
|
|
@@ -457,6 +459,13 @@ class FunctionManager(KnowledgeBasePlugin, collections.abc.Mapping):
|
|
|
457
459
|
:return: The Function instance, or None if the function is not found and create is False.
|
|
458
460
|
:rtype: Function or None
|
|
459
461
|
"""
|
|
462
|
+
if name is not None and name.startswith("sub_"):
|
|
463
|
+
try:
|
|
464
|
+
addr = int(name.split("_")[-1], 16)
|
|
465
|
+
name = None
|
|
466
|
+
except ValueError:
|
|
467
|
+
pass
|
|
468
|
+
|
|
460
469
|
if addr is not None:
|
|
461
470
|
try:
|
|
462
471
|
f = self._function_map.get(addr)
|
|
@@ -472,11 +481,11 @@ class FunctionManager(KnowledgeBasePlugin, collections.abc.Mapping):
|
|
|
472
481
|
f.is_syscall = True
|
|
473
482
|
return f
|
|
474
483
|
elif name is not None:
|
|
475
|
-
func = self.query(name)
|
|
484
|
+
func = self.query(name, check_previous_names=check_previous_names)
|
|
476
485
|
if func is not None:
|
|
477
486
|
return func
|
|
478
487
|
|
|
479
|
-
for func in self.get_by_name(name):
|
|
488
|
+
for func in self.get_by_name(name, check_previous_names=check_previous_names):
|
|
480
489
|
if plt is None or func.is_plt == plt:
|
|
481
490
|
return func
|
|
482
491
|
|
|
@@ -33,7 +33,7 @@ class FunctionParser:
|
|
|
33
33
|
obj.is_syscall = function.is_syscall
|
|
34
34
|
obj.is_simprocedure = function.is_simprocedure
|
|
35
35
|
obj.returning = function.returning
|
|
36
|
-
obj.alignment = function.
|
|
36
|
+
obj.alignment = function.is_alignment
|
|
37
37
|
obj.binary_name = function.binary_name or ""
|
|
38
38
|
obj.normalized = function.normalized
|
|
39
39
|
|
|
@@ -34,6 +34,7 @@ class SootFunction(Function):
|
|
|
34
34
|
# block nodes (basic block nodes) at whose ends the function terminates
|
|
35
35
|
# in theory, if everything works fine, endpoints == ret_sites | jumpout_sites | callout_sites
|
|
36
36
|
self._endpoints = defaultdict(set)
|
|
37
|
+
self.previous_names = []
|
|
37
38
|
|
|
38
39
|
self._call_sites = {}
|
|
39
40
|
self.addr = addr
|
angr/lib/angr_native.dylib
CHANGED
|
Binary file
|
angr/misc/ux.py
CHANGED
|
@@ -20,9 +20,9 @@ def deprecated(replacement=None):
|
|
|
20
20
|
def inner(*args, **kwargs):
|
|
21
21
|
if func not in already_complained:
|
|
22
22
|
if replacement is None:
|
|
23
|
-
warnings.warn(f"Don't use {func.__name__}", DeprecationWarning, stacklevel=
|
|
23
|
+
warnings.warn(f"Don't use {func.__name__}", DeprecationWarning, stacklevel=2)
|
|
24
24
|
else:
|
|
25
|
-
warnings.warn(f"Use {replacement} instead of {func.__name__}", DeprecationWarning, stacklevel=
|
|
25
|
+
warnings.warn(f"Use {replacement} instead of {func.__name__}", DeprecationWarning, stacklevel=2)
|
|
26
26
|
already_complained.add(func)
|
|
27
27
|
return func(*args, **kwargs)
|
|
28
28
|
|
angr/project.py
CHANGED
|
@@ -236,7 +236,7 @@ class Project:
|
|
|
236
236
|
self._initialize_analyses_hub()
|
|
237
237
|
|
|
238
238
|
# Step 5.3: ...etc
|
|
239
|
-
self.
|
|
239
|
+
self._knowledge_bases = {"default": KnowledgeBase(self, name="global")}
|
|
240
240
|
|
|
241
241
|
self.is_java_project = isinstance(self.arch, ArchSoot)
|
|
242
242
|
self.is_java_jni_project = isinstance(self.arch, ArchSoot) and getattr(
|
|
@@ -257,6 +257,22 @@ class Project:
|
|
|
257
257
|
# Step 7: Run OS-specific configuration
|
|
258
258
|
self.simos.configure_project()
|
|
259
259
|
|
|
260
|
+
@property
|
|
261
|
+
def kb(self):
|
|
262
|
+
return self._knowledge_bases["default"]
|
|
263
|
+
|
|
264
|
+
@kb.setter
|
|
265
|
+
def kb(self, kb):
|
|
266
|
+
self._knowledge_bases["default"] = kb
|
|
267
|
+
|
|
268
|
+
def get_kb(self, name):
|
|
269
|
+
try:
|
|
270
|
+
return self._knowledge_bases[name]
|
|
271
|
+
except KeyError:
|
|
272
|
+
kb = KnowledgeBase(self, name)
|
|
273
|
+
self._knowledge_bases[name] = kb
|
|
274
|
+
return kb
|
|
275
|
+
|
|
260
276
|
@property
|
|
261
277
|
def analyses(self) -> AnalysesHubWithDefault:
|
|
262
278
|
result = self._analyses
|