smallworld-re 1.0.0__py3-none-any.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.
- smallworld/__init__.py +35 -0
- smallworld/analyses/__init__.py +14 -0
- smallworld/analyses/analysis.py +88 -0
- smallworld/analyses/code_coverage.py +31 -0
- smallworld/analyses/colorizer.py +682 -0
- smallworld/analyses/colorizer_summary.py +100 -0
- smallworld/analyses/field_detection/__init__.py +14 -0
- smallworld/analyses/field_detection/field_analysis.py +536 -0
- smallworld/analyses/field_detection/guards.py +26 -0
- smallworld/analyses/field_detection/hints.py +133 -0
- smallworld/analyses/field_detection/malloc.py +211 -0
- smallworld/analyses/forced_exec/__init__.py +3 -0
- smallworld/analyses/forced_exec/forced_exec.py +87 -0
- smallworld/analyses/underlays/__init__.py +4 -0
- smallworld/analyses/underlays/basic.py +13 -0
- smallworld/analyses/underlays/underlay.py +31 -0
- smallworld/analyses/unstable/__init__.py +4 -0
- smallworld/analyses/unstable/angr/__init__.py +0 -0
- smallworld/analyses/unstable/angr/base.py +12 -0
- smallworld/analyses/unstable/angr/divergence.py +274 -0
- smallworld/analyses/unstable/angr/model.py +383 -0
- smallworld/analyses/unstable/angr/nwbt.py +63 -0
- smallworld/analyses/unstable/angr/typedefs.py +170 -0
- smallworld/analyses/unstable/angr/utils.py +25 -0
- smallworld/analyses/unstable/angr/visitor.py +315 -0
- smallworld/analyses/unstable/angr_nwbt.py +106 -0
- smallworld/analyses/unstable/code_coverage.py +54 -0
- smallworld/analyses/unstable/code_reachable.py +44 -0
- smallworld/analyses/unstable/control_flow_tracer.py +71 -0
- smallworld/analyses/unstable/pointer_finder.py +90 -0
- smallworld/arch/__init__.py +0 -0
- smallworld/arch/aarch64_arch.py +286 -0
- smallworld/arch/amd64_arch.py +86 -0
- smallworld/arch/i386_arch.py +44 -0
- smallworld/emulators/__init__.py +14 -0
- smallworld/emulators/angr/__init__.py +7 -0
- smallworld/emulators/angr/angr.py +1652 -0
- smallworld/emulators/angr/default.py +15 -0
- smallworld/emulators/angr/exceptions.py +7 -0
- smallworld/emulators/angr/exploration/__init__.py +9 -0
- smallworld/emulators/angr/exploration/bounds.py +27 -0
- smallworld/emulators/angr/exploration/default.py +17 -0
- smallworld/emulators/angr/exploration/terminate.py +22 -0
- smallworld/emulators/angr/factory.py +55 -0
- smallworld/emulators/angr/machdefs/__init__.py +35 -0
- smallworld/emulators/angr/machdefs/aarch64.py +292 -0
- smallworld/emulators/angr/machdefs/amd64.py +192 -0
- smallworld/emulators/angr/machdefs/arm.py +387 -0
- smallworld/emulators/angr/machdefs/i386.py +221 -0
- smallworld/emulators/angr/machdefs/machdef.py +138 -0
- smallworld/emulators/angr/machdefs/mips.py +184 -0
- smallworld/emulators/angr/machdefs/mips64.py +189 -0
- smallworld/emulators/angr/machdefs/ppc.py +101 -0
- smallworld/emulators/angr/machdefs/riscv.py +261 -0
- smallworld/emulators/angr/machdefs/xtensa.py +255 -0
- smallworld/emulators/angr/memory/__init__.py +7 -0
- smallworld/emulators/angr/memory/default.py +10 -0
- smallworld/emulators/angr/memory/fixups.py +43 -0
- smallworld/emulators/angr/memory/memtrack.py +105 -0
- smallworld/emulators/angr/scratch.py +43 -0
- smallworld/emulators/angr/simos.py +53 -0
- smallworld/emulators/angr/utils.py +70 -0
- smallworld/emulators/emulator.py +1013 -0
- smallworld/emulators/hookable.py +252 -0
- smallworld/emulators/panda/__init__.py +5 -0
- smallworld/emulators/panda/machdefs/__init__.py +28 -0
- smallworld/emulators/panda/machdefs/aarch64.py +93 -0
- smallworld/emulators/panda/machdefs/amd64.py +71 -0
- smallworld/emulators/panda/machdefs/arm.py +89 -0
- smallworld/emulators/panda/machdefs/i386.py +36 -0
- smallworld/emulators/panda/machdefs/machdef.py +86 -0
- smallworld/emulators/panda/machdefs/mips.py +94 -0
- smallworld/emulators/panda/machdefs/mips64.py +91 -0
- smallworld/emulators/panda/machdefs/ppc.py +79 -0
- smallworld/emulators/panda/panda.py +575 -0
- smallworld/emulators/unicorn/__init__.py +13 -0
- smallworld/emulators/unicorn/machdefs/__init__.py +28 -0
- smallworld/emulators/unicorn/machdefs/aarch64.py +310 -0
- smallworld/emulators/unicorn/machdefs/amd64.py +326 -0
- smallworld/emulators/unicorn/machdefs/arm.py +321 -0
- smallworld/emulators/unicorn/machdefs/i386.py +137 -0
- smallworld/emulators/unicorn/machdefs/machdef.py +117 -0
- smallworld/emulators/unicorn/machdefs/mips.py +202 -0
- smallworld/emulators/unicorn/unicorn.py +684 -0
- smallworld/exceptions/__init__.py +5 -0
- smallworld/exceptions/exceptions.py +85 -0
- smallworld/exceptions/unstable/__init__.py +1 -0
- smallworld/exceptions/unstable/exceptions.py +25 -0
- smallworld/extern/__init__.py +4 -0
- smallworld/extern/ctypes.py +94 -0
- smallworld/extern/unstable/__init__.py +1 -0
- smallworld/extern/unstable/ghidra.py +129 -0
- smallworld/helpers.py +107 -0
- smallworld/hinting/__init__.py +8 -0
- smallworld/hinting/hinting.py +214 -0
- smallworld/hinting/hints.py +427 -0
- smallworld/hinting/unstable/__init__.py +2 -0
- smallworld/hinting/utils.py +19 -0
- smallworld/instructions/__init__.py +18 -0
- smallworld/instructions/aarch64.py +20 -0
- smallworld/instructions/arm.py +18 -0
- smallworld/instructions/bsid.py +67 -0
- smallworld/instructions/instructions.py +258 -0
- smallworld/instructions/mips.py +21 -0
- smallworld/instructions/x86.py +100 -0
- smallworld/logging.py +90 -0
- smallworld/platforms.py +95 -0
- smallworld/py.typed +0 -0
- smallworld/state/__init__.py +6 -0
- smallworld/state/cpus/__init__.py +32 -0
- smallworld/state/cpus/aarch64.py +563 -0
- smallworld/state/cpus/amd64.py +676 -0
- smallworld/state/cpus/arm.py +630 -0
- smallworld/state/cpus/cpu.py +71 -0
- smallworld/state/cpus/i386.py +239 -0
- smallworld/state/cpus/mips.py +374 -0
- smallworld/state/cpus/mips64.py +372 -0
- smallworld/state/cpus/powerpc.py +229 -0
- smallworld/state/cpus/riscv.py +357 -0
- smallworld/state/cpus/xtensa.py +80 -0
- smallworld/state/memory/__init__.py +7 -0
- smallworld/state/memory/code.py +70 -0
- smallworld/state/memory/elf/__init__.py +3 -0
- smallworld/state/memory/elf/elf.py +564 -0
- smallworld/state/memory/elf/rela/__init__.py +32 -0
- smallworld/state/memory/elf/rela/aarch64.py +27 -0
- smallworld/state/memory/elf/rela/amd64.py +32 -0
- smallworld/state/memory/elf/rela/arm.py +51 -0
- smallworld/state/memory/elf/rela/i386.py +32 -0
- smallworld/state/memory/elf/rela/mips.py +45 -0
- smallworld/state/memory/elf/rela/ppc.py +45 -0
- smallworld/state/memory/elf/rela/rela.py +63 -0
- smallworld/state/memory/elf/rela/riscv64.py +27 -0
- smallworld/state/memory/elf/rela/xtensa.py +15 -0
- smallworld/state/memory/elf/structs.py +55 -0
- smallworld/state/memory/heap.py +85 -0
- smallworld/state/memory/memory.py +181 -0
- smallworld/state/memory/stack/__init__.py +31 -0
- smallworld/state/memory/stack/aarch64.py +22 -0
- smallworld/state/memory/stack/amd64.py +42 -0
- smallworld/state/memory/stack/arm.py +66 -0
- smallworld/state/memory/stack/i386.py +22 -0
- smallworld/state/memory/stack/mips.py +34 -0
- smallworld/state/memory/stack/mips64.py +34 -0
- smallworld/state/memory/stack/ppc.py +34 -0
- smallworld/state/memory/stack/riscv.py +22 -0
- smallworld/state/memory/stack/stack.py +127 -0
- smallworld/state/memory/stack/xtensa.py +34 -0
- smallworld/state/models/__init__.py +6 -0
- smallworld/state/models/mmio.py +186 -0
- smallworld/state/models/model.py +163 -0
- smallworld/state/models/posix.py +455 -0
- smallworld/state/models/x86/__init__.py +2 -0
- smallworld/state/models/x86/microsoftcdecl.py +35 -0
- smallworld/state/models/x86/systemv.py +240 -0
- smallworld/state/state.py +962 -0
- smallworld/state/unstable/__init__.py +0 -0
- smallworld/state/unstable/elf.py +393 -0
- smallworld/state/x86_registers.py +30 -0
- smallworld/utils.py +935 -0
- smallworld_re-1.0.0.dist-info/LICENSE.txt +21 -0
- smallworld_re-1.0.0.dist-info/METADATA +189 -0
- smallworld_re-1.0.0.dist-info/RECORD +166 -0
- smallworld_re-1.0.0.dist-info/WHEEL +5 -0
- smallworld_re-1.0.0.dist-info/entry_points.txt +2 -0
- smallworld_re-1.0.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,1652 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
import logging
|
4
|
+
import typing
|
5
|
+
|
6
|
+
import angr
|
7
|
+
import claripy
|
8
|
+
import cle
|
9
|
+
|
10
|
+
from ... import exceptions, platforms, utils
|
11
|
+
from .. import emulator
|
12
|
+
from .default import configure_default_plugins, configure_default_strategy
|
13
|
+
from .factory import PatchedObjectFactory
|
14
|
+
from .machdefs import AngrMachineDef
|
15
|
+
from .simos import HookableSimOS
|
16
|
+
|
17
|
+
log = logging.getLogger(__name__)
|
18
|
+
|
19
|
+
|
20
|
+
class HookHandler(angr.SimProcedure):
|
21
|
+
"""SimProcedure for implementing "finish" hooks.
|
22
|
+
|
23
|
+
This requires a callback as an extra kwarg.
|
24
|
+
"""
|
25
|
+
|
26
|
+
def run(self, *args, callback, parent):
|
27
|
+
emu = ConcreteAngrEmulator(self.state, parent)
|
28
|
+
callback(emu)
|
29
|
+
return None
|
30
|
+
|
31
|
+
|
32
|
+
class AngrEmulator(
|
33
|
+
emulator.Emulator,
|
34
|
+
emulator.InstructionHookable,
|
35
|
+
emulator.FunctionHookable,
|
36
|
+
emulator.SyscallHookable,
|
37
|
+
emulator.MemoryReadHookable,
|
38
|
+
emulator.MemoryWriteHookable,
|
39
|
+
emulator.ConstrainedEmulator,
|
40
|
+
):
|
41
|
+
"""
|
42
|
+
Angr symbolic execution emulator
|
43
|
+
|
44
|
+
This is primarily designed to support symbolic execution,
|
45
|
+
although subclasses can configure angr however they like.
|
46
|
+
|
47
|
+
One challenge with symbolic execution is that
|
48
|
+
it doesn't work on a single machine state,
|
49
|
+
but rather multiple machine states representing
|
50
|
+
parallel execution paths.
|
51
|
+
|
52
|
+
As such, this interface doesn't yet fully support
|
53
|
+
all features of the base Emulator class;
|
54
|
+
it's not clear what reading or writing machine state
|
55
|
+
means when there's more than one state.
|
56
|
+
"""
|
57
|
+
|
58
|
+
PAGE_SIZE = 4096
|
59
|
+
|
60
|
+
name = "angr-emulator"
|
61
|
+
description = "an emulator using angr as its backend"
|
62
|
+
version = "0.0"
|
63
|
+
|
64
|
+
def __init__(self, platform: platforms.Platform, preinit=None, init=None):
|
65
|
+
# Initializaed bit; tells us if angr state is initialized
|
66
|
+
self._initialized: bool = False
|
67
|
+
|
68
|
+
# Dirty bit; tells us if we can modify self.state
|
69
|
+
self._dirty: bool = False
|
70
|
+
|
71
|
+
# Linear mode bit; tells us if we're running in forced linear execution
|
72
|
+
self._linear: bool = False
|
73
|
+
|
74
|
+
# Plugin preset; tells us which plugin preset to use.
|
75
|
+
self._plugin_preset = "default"
|
76
|
+
|
77
|
+
# The machine definition;
|
78
|
+
# helps translate between angr and smallworld
|
79
|
+
self.machdef: AngrMachineDef = AngrMachineDef.for_platform(platform)
|
80
|
+
|
81
|
+
self.platform = platform
|
82
|
+
self.preinit = preinit
|
83
|
+
self.init = init
|
84
|
+
|
85
|
+
# Cached state modifications,
|
86
|
+
# to be applied during initialization
|
87
|
+
self._code: typing.List[typing.Tuple[int, bytes]] = list()
|
88
|
+
self._register_contents: typing.Dict[
|
89
|
+
str, typing.Union[int, claripy.ast.bv.BV]
|
90
|
+
] = dict()
|
91
|
+
self._register_labels: typing.Dict[str, str] = dict()
|
92
|
+
self._memory_maps: typing.List[typing.Tuple[int, int]] = list()
|
93
|
+
self._memory_contents: typing.List[
|
94
|
+
typing.Tuple[int, typing.Union[bytes, claripy.ast.bv.BV]]
|
95
|
+
] = list()
|
96
|
+
self._memory_labels: typing.List[typing.Tuple[int, int, str]] = list()
|
97
|
+
self._instr_hooks: typing.List[
|
98
|
+
typing.Tuple[int, typing.Callable[[emulator.Emulator], None]]
|
99
|
+
] = list()
|
100
|
+
self._gb_instr_hook: typing.Optional[
|
101
|
+
typing.Callable[[emulator.Emulator], None]
|
102
|
+
] = None
|
103
|
+
self._func_hooks: typing.List[
|
104
|
+
typing.Tuple[int, typing.Callable[[emulator.Emulator], None]]
|
105
|
+
] = list()
|
106
|
+
self._syscall_hooks: typing.Dict[
|
107
|
+
int, typing.Callable[[emulator.Emulator], None]
|
108
|
+
] = dict()
|
109
|
+
self._gb_syscall_hook: typing.Optional[
|
110
|
+
typing.Callable[[emulator.Emulator, int], None]
|
111
|
+
] = None
|
112
|
+
self._read_hooks: typing.List[
|
113
|
+
typing.Tuple[
|
114
|
+
int,
|
115
|
+
int,
|
116
|
+
typing.Callable[
|
117
|
+
[emulator.Emulator, int, int, claripy.ast.bv.BV],
|
118
|
+
typing.Optional[claripy.ast.bv.BV],
|
119
|
+
],
|
120
|
+
]
|
121
|
+
] = list()
|
122
|
+
self._gb_read_hook: typing.Optional[
|
123
|
+
typing.Callable[
|
124
|
+
[emulator.Emulator, int, int, claripy.ast.bv.BV],
|
125
|
+
typing.Optional[claripy.ast.bv.BV],
|
126
|
+
]
|
127
|
+
] = None
|
128
|
+
self._write_hooks: typing.List[
|
129
|
+
typing.Tuple[
|
130
|
+
int,
|
131
|
+
int,
|
132
|
+
typing.Callable[[emulator.Emulator, int, int, claripy.ast.bv.BV], None],
|
133
|
+
]
|
134
|
+
] = list()
|
135
|
+
self._gb_write_hook: typing.Optional[
|
136
|
+
typing.Callable[[emulator.Emulator, int, int, claripy.ast.bv.BV], None]
|
137
|
+
] = None
|
138
|
+
self._code_bounds: typing.List[typing.Tuple[int, int]] = list()
|
139
|
+
self._exit_points: typing.Set[int] = set()
|
140
|
+
self._constraints: typing.List[claripy.ast.bool.Bool] = list()
|
141
|
+
self._extensions: typing.Dict[str, typing.Any] = dict()
|
142
|
+
|
143
|
+
def initialize(self):
|
144
|
+
"""Initialize the emulator
|
145
|
+
|
146
|
+
To take advantage of CLE, we need to know about all code
|
147
|
+
before we initialize the angr state objects.
|
148
|
+
However, applying register and memory changes requires access
|
149
|
+
to the angr state object.
|
150
|
+
|
151
|
+
SmallWorld doesn't support ordering how state is applied,
|
152
|
+
so I need to collect it, then apply it once emulation starts.
|
153
|
+
|
154
|
+
This function is invoked automatically when you cycle the emulator,
|
155
|
+
but you're free to invoke it early if you want.
|
156
|
+
"""
|
157
|
+
|
158
|
+
if self._initialized:
|
159
|
+
raise exceptions.ConfigurationError("Cannot reinitialize running emulator")
|
160
|
+
|
161
|
+
# Build a single, coherent byte stream out of our code
|
162
|
+
self._code.sort(key=lambda x: x[0])
|
163
|
+
|
164
|
+
code = utils.SparseIO()
|
165
|
+
segments = list()
|
166
|
+
|
167
|
+
if len(self._code) > 0:
|
168
|
+
code_ranges = utils.RangeCollection()
|
169
|
+
base_address = self._code[0][0]
|
170
|
+
for addr, data in self._code:
|
171
|
+
size = len(data)
|
172
|
+
code.seek(addr - base_address)
|
173
|
+
code.write(data)
|
174
|
+
code_ranges.add_range((addr, addr + size))
|
175
|
+
segments = list(
|
176
|
+
map(lambda x: (x[0] - base_address, x[0], x[1] - x[0]), code_ranges)
|
177
|
+
)
|
178
|
+
|
179
|
+
# Create an angr project using a blank byte stream,
|
180
|
+
# and registered as self-modifying so we can load more code later.
|
181
|
+
backend = cle.Blob(
|
182
|
+
None, code, is_main_bin=True, arch=self.machdef.angr_arch, segments=segments
|
183
|
+
)
|
184
|
+
loader = cle.Loader(backend)
|
185
|
+
self.proj: angr.Project = angr.Project(
|
186
|
+
loader,
|
187
|
+
engine=self.machdef.angr_engine,
|
188
|
+
selfmodifying_code=True,
|
189
|
+
simos=HookableSimOS,
|
190
|
+
)
|
191
|
+
# Override the default factory
|
192
|
+
self.proj.factory = PatchedObjectFactory(
|
193
|
+
self.proj, type(self.proj.factory.default_engine)
|
194
|
+
)
|
195
|
+
|
196
|
+
# Configure default plugin preset.
|
197
|
+
# Do this before creating the state, so the state inherits the correct preset.
|
198
|
+
# Also do this before preinit, so that analyses can inherit from the default.
|
199
|
+
configure_default_plugins(self)
|
200
|
+
|
201
|
+
# If preinit is specified, run it.
|
202
|
+
if self.preinit is not None:
|
203
|
+
self.preinit(self)
|
204
|
+
|
205
|
+
# Create a completely blank entry state
|
206
|
+
self.state = self.proj.factory.blank_state(
|
207
|
+
plugin_preset=self._plugin_preset,
|
208
|
+
add_options={
|
209
|
+
# angr.options.BYPASS_UNSUPPORTED_SYSCALL,
|
210
|
+
angr.options.SYMBOL_FILL_UNCONSTRAINED_REGISTERS,
|
211
|
+
angr.options.SYMBOL_FILL_UNCONSTRAINED_MEMORY,
|
212
|
+
},
|
213
|
+
remove_options={
|
214
|
+
angr.options.SIMPLIFY_CONSTRAINTS,
|
215
|
+
angr.options.SIMPLIFY_EXIT_GUARD,
|
216
|
+
angr.options.SIMPLIFY_EXIT_STATE,
|
217
|
+
angr.options.SIMPLIFY_EXIT_TARGET,
|
218
|
+
angr.options.SIMPLIFY_EXPRS,
|
219
|
+
angr.options.SIMPLIFY_MEMORY_READS,
|
220
|
+
angr.options.SIMPLIFY_MEMORY_WRITES,
|
221
|
+
angr.options.SIMPLIFY_REGISTER_READS,
|
222
|
+
angr.options.SIMPLIFY_REGISTER_WRITES,
|
223
|
+
},
|
224
|
+
)
|
225
|
+
|
226
|
+
# Create a simulation manager for our entry state
|
227
|
+
self.mgr = self.proj.factory.simulation_manager(self.state, save_unsat=True)
|
228
|
+
|
229
|
+
# Configure default simulation strategy.
|
230
|
+
configure_default_strategy(self)
|
231
|
+
|
232
|
+
# If we have an init runner, run it.
|
233
|
+
if self.init:
|
234
|
+
self.init(self)
|
235
|
+
|
236
|
+
# Mark this emulator as initialized
|
237
|
+
self._initialized = True
|
238
|
+
|
239
|
+
# Read back any changes that happened before initialization.
|
240
|
+
for name, content in self._register_contents.items():
|
241
|
+
self.write_register_content(name, content)
|
242
|
+
|
243
|
+
for name, label in self._register_labels.items():
|
244
|
+
self.write_register_label(name, label)
|
245
|
+
|
246
|
+
for addr, size in self._memory_maps:
|
247
|
+
self.map_memory(addr, size)
|
248
|
+
|
249
|
+
for addr, content in self._memory_contents:
|
250
|
+
self.write_memory_content(addr, content)
|
251
|
+
|
252
|
+
self._write_memory_label_bulk(self._memory_labels)
|
253
|
+
|
254
|
+
for addr, func in self._instr_hooks:
|
255
|
+
self.hook_instruction(addr, func)
|
256
|
+
|
257
|
+
if self._gb_instr_hook is not None:
|
258
|
+
self.hook_instructions(self._gb_instr_hook)
|
259
|
+
|
260
|
+
for addr, func in self._func_hooks:
|
261
|
+
self.hook_function(addr, func)
|
262
|
+
|
263
|
+
for num, func in self._syscall_hooks.items():
|
264
|
+
self.hook_syscall(num, func)
|
265
|
+
|
266
|
+
if self._gb_syscall_hook is not None:
|
267
|
+
self.hook_syscalls(self._gb_syscall_hook)
|
268
|
+
|
269
|
+
for start, end, func in self._read_hooks:
|
270
|
+
self.hook_memory_read_symbolic(start, end, func)
|
271
|
+
|
272
|
+
if self._gb_read_hook is not None:
|
273
|
+
self.hook_memory_reads_symbolic(self._gb_read_hook)
|
274
|
+
|
275
|
+
for start, end, func in self._write_hooks:
|
276
|
+
self.hook_memory_write_symbolic(start, end, func)
|
277
|
+
|
278
|
+
if self._gb_write_hook is not None:
|
279
|
+
self.hook_memory_writes_symbolic(self._gb_write_hook)
|
280
|
+
|
281
|
+
for start, end in self._code_bounds:
|
282
|
+
self.add_bound(start, end)
|
283
|
+
|
284
|
+
for addr in self._exit_points:
|
285
|
+
self.add_exit_point(addr)
|
286
|
+
|
287
|
+
for expr in self._constraints:
|
288
|
+
self.add_constraint(expr)
|
289
|
+
|
290
|
+
self.state.scratch.extensions |= self._extensions
|
291
|
+
|
292
|
+
def read_register_symbolic(self, name: str) -> claripy.ast.bv.BV:
|
293
|
+
if not self._initialized:
|
294
|
+
raise exceptions.ConfigurationError(
|
295
|
+
"Cannot read registers before initialization"
|
296
|
+
)
|
297
|
+
|
298
|
+
if self._dirty and not self._linear:
|
299
|
+
raise NotImplementedError(
|
300
|
+
"Reading registers not supported once execution begins."
|
301
|
+
)
|
302
|
+
|
303
|
+
if name == "pc":
|
304
|
+
name = self.machdef.pc_reg
|
305
|
+
|
306
|
+
(off, size) = self.machdef.angr_reg(name)
|
307
|
+
return self.state.registers.load(off, size)
|
308
|
+
|
309
|
+
def read_register_content(self, name: str) -> int:
|
310
|
+
out = self.read_register_symbolic(name)
|
311
|
+
if out.symbolic:
|
312
|
+
raise exceptions.SymbolicValueError(f"Register {name} is symbolic")
|
313
|
+
else:
|
314
|
+
return out.concrete_value
|
315
|
+
|
316
|
+
def read_register_type(self, name: str) -> typing.Optional[typing.Any]:
|
317
|
+
return None
|
318
|
+
|
319
|
+
def read_register_label(self, name: str) -> typing.Optional[str]:
|
320
|
+
if not self._initialized:
|
321
|
+
raise exceptions.ConfigurationError(
|
322
|
+
"Cannot read registers before initialization"
|
323
|
+
)
|
324
|
+
|
325
|
+
if self._dirty and not self._linear:
|
326
|
+
raise NotImplementedError(
|
327
|
+
"Reading register not supported once execution begins."
|
328
|
+
)
|
329
|
+
|
330
|
+
if name == "pc":
|
331
|
+
name = self.machdef.pc_reg
|
332
|
+
|
333
|
+
try:
|
334
|
+
# This considers all BVSs to be labeled values;
|
335
|
+
# if it has a name, we're giving it to you.
|
336
|
+
(off, size) = self.machdef.angr_reg(name)
|
337
|
+
out = self.state.registers.load(off, size)
|
338
|
+
if out.symbolic:
|
339
|
+
if out.op == "BVS":
|
340
|
+
# This is a "pure" label; we can return it.
|
341
|
+
return out.args[0]
|
342
|
+
else:
|
343
|
+
# This is a mixed expression; we can't return it
|
344
|
+
log.warn(f"Register {name} contains a symbolic expression")
|
345
|
+
raise exceptions.SymbolicValueError(
|
346
|
+
f"Register {name} contains a symbolic expression"
|
347
|
+
)
|
348
|
+
else:
|
349
|
+
# No propagated label
|
350
|
+
return None
|
351
|
+
except ValueError:
|
352
|
+
# TODO: Handle invalid registers more gracefully
|
353
|
+
return None
|
354
|
+
|
355
|
+
def write_register_content(
|
356
|
+
self, name: str, content: typing.Union[None, int, claripy.ast.bv.BV]
|
357
|
+
) -> None:
|
358
|
+
if not self._initialized and content is not None:
|
359
|
+
if name == "pc":
|
360
|
+
name = self.machdef.pc_reg
|
361
|
+
# Test that the angr register exists
|
362
|
+
_, _ = self.machdef.angr_reg(name)
|
363
|
+
self._register_contents[name] = content
|
364
|
+
return
|
365
|
+
elif self._dirty and not self._linear:
|
366
|
+
raise NotImplementedError(
|
367
|
+
"Writing registers not supported once execution begins."
|
368
|
+
)
|
369
|
+
# This will replace any value - symbolic or otherwise - currently in the emulator.
|
370
|
+
# Since labels are treated as symbolic values, it must be called before
|
371
|
+
# write_register_label().
|
372
|
+
|
373
|
+
if name == "pc":
|
374
|
+
name = self.machdef.pc_reg
|
375
|
+
(off, size) = self.machdef.angr_reg(name)
|
376
|
+
|
377
|
+
if content is None:
|
378
|
+
v = claripy.BVS("UNINITIALIZED", size * 8)
|
379
|
+
elif isinstance(content, int):
|
380
|
+
v = claripy.BVV(content, size * 8)
|
381
|
+
elif isinstance(content, claripy.ast.bv.BV):
|
382
|
+
v = content
|
383
|
+
else:
|
384
|
+
raise TypeError(
|
385
|
+
f"Content for register {name} was {type(content)}; expected (None | int | claripy.ast.bv.BV)"
|
386
|
+
)
|
387
|
+
|
388
|
+
if v.size() != size * 8:
|
389
|
+
raise exceptions.ConfigurationError(
|
390
|
+
f"Register {name} has size {size}, but content has size {v.size() // 8}"
|
391
|
+
)
|
392
|
+
|
393
|
+
self.state.registers.store(off, v)
|
394
|
+
|
395
|
+
def write_register_type(
|
396
|
+
self, name: str, type: typing.Optional[typing.Any] = None
|
397
|
+
) -> None:
|
398
|
+
pass
|
399
|
+
|
400
|
+
def write_register_label(
|
401
|
+
self, name: str, label: typing.Optional[str] = None
|
402
|
+
) -> None:
|
403
|
+
if label is None:
|
404
|
+
return
|
405
|
+
elif not self._initialized:
|
406
|
+
if name == "pc":
|
407
|
+
name = self.machdef.pc_reg
|
408
|
+
# Test that the angr register exists
|
409
|
+
_, _ = self.machdef.angr_reg(name)
|
410
|
+
self._register_labels[name] = label
|
411
|
+
elif self._dirty and not self._linear:
|
412
|
+
raise NotImplementedError(
|
413
|
+
"Writing registers not supported once execution begins."
|
414
|
+
)
|
415
|
+
else:
|
416
|
+
if name == "pc":
|
417
|
+
name = self.machdef.pc_reg
|
418
|
+
(off, size) = self.machdef.angr_reg(name)
|
419
|
+
|
420
|
+
# This will bind whatever value is currently in the register
|
421
|
+
# to a symbol named after the label
|
422
|
+
# The same label will ALWAYS map to the same symbol!
|
423
|
+
# This can introduce unintended restrictions on exploration,
|
424
|
+
# or even a contradictory state.
|
425
|
+
s = claripy.BVS(label, size * 8, explicit_name=True)
|
426
|
+
v = self.state.registers.load(off, size)
|
427
|
+
|
428
|
+
if not self.state.solver.satisfiable(extra_constraints=[v == s]):
|
429
|
+
# Creating this binding will definitely cause a contradiction.
|
430
|
+
# Have you already used this label somewhere else?
|
431
|
+
raise exceptions.ConfigurationError(
|
432
|
+
f"Contradiction binding register {name} to label {label}"
|
433
|
+
)
|
434
|
+
|
435
|
+
# Passing the last check doesn't guarantee you're safe.
|
436
|
+
# There may be over-constraints. Please be careful.
|
437
|
+
self.state.registers.store(off, s)
|
438
|
+
self.state.solver.add(v == s)
|
439
|
+
|
440
|
+
def read_memory_content(self, address: int, size: int) -> bytes:
|
441
|
+
if not self._initialized:
|
442
|
+
raise exceptions.ConfigurationError(
|
443
|
+
"Cannot read memory before initialization"
|
444
|
+
)
|
445
|
+
|
446
|
+
if self._dirty and not self._linear:
|
447
|
+
raise NotImplementedError(
|
448
|
+
"Reading memory not supported once execution begins."
|
449
|
+
)
|
450
|
+
v = self.state.memory.load(address, size)
|
451
|
+
if v.symbolic:
|
452
|
+
log.warn(f"Memory at {hex(address)} ({size} bytes) is symbolic")
|
453
|
+
raise exceptions.SymbolicValueError(f"Memory at {hex(address)} is symbolic")
|
454
|
+
|
455
|
+
# Annoyingly, there isn't an easy way to convert BVV to bytes.
|
456
|
+
return bytes([v.get_byte(i).concrete_value for i in range(0, size)])
|
457
|
+
|
458
|
+
def read_memory_symbolic(self, address: int, size: int) -> claripy.ast.bv.BV:
|
459
|
+
if not self._initialized:
|
460
|
+
raise exceptions.ConfigurationError(
|
461
|
+
"Cannot read memory before initialization"
|
462
|
+
)
|
463
|
+
|
464
|
+
if self._dirty and not self._linear:
|
465
|
+
raise NotImplementedError(
|
466
|
+
"Reading memory not supported once execution begins."
|
467
|
+
)
|
468
|
+
return self.state.memory.load(address, size)
|
469
|
+
|
470
|
+
def read_memory_type(self, address: int, size: int) -> typing.Optional[typing.Any]:
|
471
|
+
return None
|
472
|
+
|
473
|
+
def read_memory_label(self, address: int, size: int) -> typing.Optional[str]:
|
474
|
+
if not self._initialized:
|
475
|
+
raise exceptions.ConfigurationError(
|
476
|
+
"Cannot read memory before initialization"
|
477
|
+
)
|
478
|
+
|
479
|
+
if self._dirty and not self._linear:
|
480
|
+
raise NotImplementedError(
|
481
|
+
"Writing memory not supported once execution begins."
|
482
|
+
)
|
483
|
+
v = self.state.memory.load(address, size)
|
484
|
+
if v.symbolic():
|
485
|
+
if v.op == "Extract":
|
486
|
+
# You got a piece of a possibly-labeled expression
|
487
|
+
# Try parsing the inner expression to see if it's a single symbol.
|
488
|
+
v = v.args[2]
|
489
|
+
|
490
|
+
if v.op == "BVS":
|
491
|
+
# You got a single symbol; I'll treat it as a label
|
492
|
+
return v.args[0]
|
493
|
+
else:
|
494
|
+
# You got a symbolic expression; I can't decode it further
|
495
|
+
log.warn(f"Memory at {hex(address)} ({size} bytes) is symbolic: {v}")
|
496
|
+
raise exceptions.SymbolicValueError(
|
497
|
+
f"Memory at {hex(address)} is symbolic"
|
498
|
+
)
|
499
|
+
else:
|
500
|
+
# Definitely no labels here
|
501
|
+
return None
|
502
|
+
|
503
|
+
def map_memory(self, address: int, size: int) -> None:
|
504
|
+
if not self._initialized:
|
505
|
+
self._memory_maps.append((address, size))
|
506
|
+
else:
|
507
|
+
if self._dirty and not self._linear:
|
508
|
+
raise NotImplementedError(
|
509
|
+
"Mapping memory not supported once execution begins."
|
510
|
+
)
|
511
|
+
# Unlike Unicorn, angr doesn't care about pages.
|
512
|
+
region = (address, address + size)
|
513
|
+
self.state.scratch.memory_map.add_range(region)
|
514
|
+
|
515
|
+
def get_memory_map(self) -> typing.List[typing.Tuple[int, int]]:
|
516
|
+
if not self._initialized:
|
517
|
+
return list(map(lambda x: (x[0], x[0] + x[1]), self._memory_maps))
|
518
|
+
if self._dirty and not self._linear:
|
519
|
+
raise NotImplementedError(
|
520
|
+
"Mapping memory not supported once execution begins."
|
521
|
+
)
|
522
|
+
return list(self.state.scratch.memory_map.ranges)
|
523
|
+
|
524
|
+
def write_memory_content(
|
525
|
+
self, address: int, content: typing.Union[bytes, claripy.ast.bv.BV]
|
526
|
+
) -> None:
|
527
|
+
if not self._initialized:
|
528
|
+
self._memory_contents.append((address, content))
|
529
|
+
elif self._dirty and not self._linear:
|
530
|
+
raise NotImplementedError(
|
531
|
+
"Writing memory not supported once execution begins."
|
532
|
+
)
|
533
|
+
else:
|
534
|
+
log.info(f"Storing {len(content)} bytes at {hex(address)}")
|
535
|
+
if isinstance(content, bytes):
|
536
|
+
content = claripy.BVV(content)
|
537
|
+
self.state.memory.store(address, content, inspect=False)
|
538
|
+
|
539
|
+
def write_memory_type(
|
540
|
+
self, address: int, size: int, type: typing.Optional[typing.Any] = None
|
541
|
+
) -> None:
|
542
|
+
pass
|
543
|
+
|
544
|
+
def write_memory_label(
|
545
|
+
self, address: int, size: int, label: typing.Optional[str] = None
|
546
|
+
) -> None:
|
547
|
+
if label is None:
|
548
|
+
return
|
549
|
+
elif not self._initialized:
|
550
|
+
self._memory_labels.append((address, size, label))
|
551
|
+
elif self._dirty and not self._linear:
|
552
|
+
raise NotImplementedError(
|
553
|
+
"Writing memory not supported once execution begins."
|
554
|
+
)
|
555
|
+
else:
|
556
|
+
# This will bind whatever value is currently at this address
|
557
|
+
# to a symbol named after the label
|
558
|
+
#
|
559
|
+
# The same label will ALWAYS map to the same symbol!
|
560
|
+
# This can introduce unintended restrictions on exploration,
|
561
|
+
# or even a contradictory state.
|
562
|
+
#
|
563
|
+
# This will trigger angr's default value computation,
|
564
|
+
# which may cause spurious results in some analyses.
|
565
|
+
s = claripy.BVS(label, size * 8, explicit_name=True)
|
566
|
+
v = self.state.memory.load(address, size)
|
567
|
+
if not self.state.solver.satisfiable(extra_constraints=[v == s]):
|
568
|
+
# Creating this binding will definitely cause a contradiction.
|
569
|
+
# Have you already used this label somewhere else?
|
570
|
+
raise exceptions.ConfigurationError(
|
571
|
+
f"Contradiction binding memory at {hex(address)} to label {label}"
|
572
|
+
)
|
573
|
+
|
574
|
+
# Passing the last check doesn't mean you're safe.
|
575
|
+
# There may be over-constraints. Please be careful.
|
576
|
+
self.state.memory.store(address, s, inspect=False)
|
577
|
+
self.state.solver.add(v == s)
|
578
|
+
|
579
|
+
def _write_memory_label_bulk(
|
580
|
+
self, labels: typing.List[typing.Tuple[int, int, typing.Optional[str]]]
|
581
|
+
) -> None:
|
582
|
+
# Variant of write_memory_label that applies multiple labels in one go.
|
583
|
+
# This addresses a performance bottleneck during initialization;
|
584
|
+
# it's not really intended for public use,
|
585
|
+
# as it lacks some of the safety checks.
|
586
|
+
cs = list()
|
587
|
+
for addr, size, label in labels:
|
588
|
+
if label is None:
|
589
|
+
continue
|
590
|
+
s = claripy.BVS(label, size * 8, explicit_name=True)
|
591
|
+
v = self.state.memory.load(addr, size)
|
592
|
+
self.state.memory.store(addr, s)
|
593
|
+
cs.append(s == v)
|
594
|
+
c = claripy.And(*cs)
|
595
|
+
self.state.solver.add(c)
|
596
|
+
|
597
|
+
def write_code(self, address: int, content: bytes):
|
598
|
+
if self._initialized:
|
599
|
+
self.write_memory_content(address, content)
|
600
|
+
else:
|
601
|
+
self._code.append((address, content))
|
602
|
+
|
603
|
+
def hook_instruction(
|
604
|
+
self, address: int, function: typing.Callable[[emulator.Emulator], None]
|
605
|
+
) -> None:
|
606
|
+
if not self._initialized:
|
607
|
+
self._instr_hooks.append((address, function))
|
608
|
+
|
609
|
+
elif self._dirty and not self._linear:
|
610
|
+
raise NotImplementedError(
|
611
|
+
"Instruction hooking not supported once execution begins"
|
612
|
+
)
|
613
|
+
|
614
|
+
elif address in self.state.scratch.insn_bps:
|
615
|
+
raise exceptions.ConfigurationError(
|
616
|
+
"Instruction at address {hex(address)} is already hooked"
|
617
|
+
)
|
618
|
+
|
619
|
+
else:
|
620
|
+
|
621
|
+
def hook_handler(state):
|
622
|
+
emu = ConcreteAngrEmulator(state, self)
|
623
|
+
function(emu)
|
624
|
+
|
625
|
+
# An update to angr means some operations on `state`
|
626
|
+
# will clobber this variable, which causes bad problems.
|
627
|
+
# Unclobber it, just in case
|
628
|
+
state.inspect.action_attrs_set = True
|
629
|
+
|
630
|
+
bp = self.state.inspect.b(
|
631
|
+
"instruction",
|
632
|
+
when=angr.BP_BEFORE,
|
633
|
+
action=hook_handler,
|
634
|
+
instruction=address,
|
635
|
+
)
|
636
|
+
self.state.scratch.insn_bps[address] = bp
|
637
|
+
|
638
|
+
def unhook_instruction(self, address: int) -> None:
|
639
|
+
if not self._initialized:
|
640
|
+
self._instr_hooks = list(
|
641
|
+
filter(lambda x: x[0] != address, self._instr_hooks)
|
642
|
+
)
|
643
|
+
|
644
|
+
elif self._dirty and not self._linear:
|
645
|
+
raise NotImplementedError(
|
646
|
+
"Instruction hooking not supported once execution begins"
|
647
|
+
)
|
648
|
+
|
649
|
+
elif address not in self.state.scratch.insn_bps:
|
650
|
+
raise exceptions.ConfigurationError(
|
651
|
+
"Instruction at address {hex(address)} is not hooked"
|
652
|
+
)
|
653
|
+
|
654
|
+
else:
|
655
|
+
bp = self.state.scratch.insn_bps[address]
|
656
|
+
del self.state.scratch.insn_bps[address]
|
657
|
+
|
658
|
+
self.state.inspect.remove_breakpoint("instruction", bp)
|
659
|
+
|
660
|
+
def hook_instructions(
|
661
|
+
self, function: typing.Callable[[emulator.Emulator], None]
|
662
|
+
) -> None:
|
663
|
+
if not self._initialized:
|
664
|
+
self._gb_instr_hook = function
|
665
|
+
|
666
|
+
elif self._dirty and not self._linear:
|
667
|
+
raise NotImplementedError(
|
668
|
+
"Global instruction hooking not supported once execution begins"
|
669
|
+
)
|
670
|
+
|
671
|
+
elif self.state.scratch.global_insn_bp is not None:
|
672
|
+
raise exceptions.ConfigurationError(
|
673
|
+
"Global instruction hook already registered"
|
674
|
+
)
|
675
|
+
|
676
|
+
else:
|
677
|
+
|
678
|
+
def hook_handler(state):
|
679
|
+
emu = ConcreteAngrEmulator(state, self)
|
680
|
+
function(emu)
|
681
|
+
|
682
|
+
# An update to angr means some operations on `state`
|
683
|
+
# will clobber this variable, which causes bad problems.
|
684
|
+
# Unclobber it, just in case
|
685
|
+
state.inspect.action_attrs_set = True
|
686
|
+
|
687
|
+
self.state.scratch.global_insn_bp = self.state.inspect.b(
|
688
|
+
"instruction", when=angr.BP_BEFORE, action=hook_handler
|
689
|
+
)
|
690
|
+
|
691
|
+
def unhook_instructions(self) -> None:
|
692
|
+
if not self._initialized:
|
693
|
+
self._gb_instruction_hook = None
|
694
|
+
|
695
|
+
elif self._dirty and not self._linear:
|
696
|
+
raise NotImplementedError(
|
697
|
+
"Global instruction unhooking not supported once execution begins"
|
698
|
+
)
|
699
|
+
|
700
|
+
elif self.state.scratch.global_insn_bp is None:
|
701
|
+
raise exceptions.ConfigurationError("No global instruction hook present")
|
702
|
+
|
703
|
+
else:
|
704
|
+
bp = self.state.scratch.global_insn_bp
|
705
|
+
self.state.scratch.global_insn_bp = None
|
706
|
+
self.state.inspect.remove_breakpoint("instruction", bp)
|
707
|
+
|
708
|
+
def hook_function(
|
709
|
+
self, address: int, function: typing.Callable[[emulator.Emulator], None]
|
710
|
+
) -> None:
|
711
|
+
if not self._initialized:
|
712
|
+
self._func_hooks.append((address, function))
|
713
|
+
|
714
|
+
elif self._dirty and not self._linear:
|
715
|
+
raise NotImplementedError("Cannot hook functions once emulation starts")
|
716
|
+
|
717
|
+
else:
|
718
|
+
hook = HookHandler(callback=function, parent=self)
|
719
|
+
self.proj.hook(address, hook, 0)
|
720
|
+
|
721
|
+
self.map_memory(address, 1)
|
722
|
+
self.state.scratch.func_bps[address] = None
|
723
|
+
|
724
|
+
def unhook_function(self, address: int) -> None:
|
725
|
+
if not self._initialized:
|
726
|
+
self._func_hooks = list(filter(lambda x: x[0] != address, self._func_hooks))
|
727
|
+
|
728
|
+
elif self._dirty and not self._linear:
|
729
|
+
raise NotImplementedError("Cannot unhook functions once emulation starts")
|
730
|
+
|
731
|
+
else:
|
732
|
+
self.proj.unhook(address)
|
733
|
+
|
734
|
+
def hook_syscall(
|
735
|
+
self, number: int, function: typing.Callable[[emulator.Emulator], None]
|
736
|
+
) -> None:
|
737
|
+
if not self._initialized:
|
738
|
+
self._syscall_hooks[number] = function
|
739
|
+
elif self._dirty and not self._linear:
|
740
|
+
raise NotImplementedError("Cannot hook syscalls once emulation starts")
|
741
|
+
elif number in self.state.scratch.syscall_funcs:
|
742
|
+
raise exceptions.ConfigurationError(
|
743
|
+
f"Already have a syscall hook for {number}"
|
744
|
+
)
|
745
|
+
else:
|
746
|
+
|
747
|
+
def syscall_handler(state):
|
748
|
+
function(ConcreteAngrEmulator(state, self))
|
749
|
+
|
750
|
+
self.state.scratch.syscall_funcs[number] = syscall_handler
|
751
|
+
|
752
|
+
def unhook_syscall(self, number: int) -> None:
|
753
|
+
if not self._initialized:
|
754
|
+
del self._syscall_hooks[number]
|
755
|
+
|
756
|
+
elif self._dirty and not self._linear:
|
757
|
+
raise NotImplementedError("Cannot unhook syscalls once emulation starts")
|
758
|
+
|
759
|
+
elif number not in self.state.scratch.syscall_funcs:
|
760
|
+
raise exceptions.ConfigurationError(f"No syscall hook for {number}")
|
761
|
+
|
762
|
+
else:
|
763
|
+
del self.state.scratch.syscall_funcs[number]
|
764
|
+
|
765
|
+
def hook_syscalls(
|
766
|
+
self, function: typing.Callable[[emulator.Emulator, int], None]
|
767
|
+
) -> None:
|
768
|
+
if not self._initialized:
|
769
|
+
self._gb_syscall_hook = function
|
770
|
+
|
771
|
+
elif self._dirty and not self._linear:
|
772
|
+
raise NotImplementedError("Cannot hook syscalls once emulation starts")
|
773
|
+
|
774
|
+
elif self.state.scratch.global_syscall_func is not None:
|
775
|
+
raise exceptions.ConfigurationError("Already have a global syscall hook")
|
776
|
+
|
777
|
+
else:
|
778
|
+
|
779
|
+
def syscall_handler(state, number: int):
|
780
|
+
function(ConcreteAngrEmulator(state, self), number)
|
781
|
+
|
782
|
+
self.state.scratch.global_syscall_func = syscall_handler
|
783
|
+
|
784
|
+
def unhook_syscalls(self) -> None:
|
785
|
+
if self._dirty and not self._linear:
|
786
|
+
raise NotImplementedError("Cannot unhook syscalls once emulation starts")
|
787
|
+
|
788
|
+
if self.state.scratch.global_syscall_func is None:
|
789
|
+
raise exceptions.ConfigurationError("No global syscall hook registered")
|
790
|
+
self.state.scratch.global_syscall_func = None
|
791
|
+
|
792
|
+
def hook_memory_read_symbolic(
|
793
|
+
self,
|
794
|
+
start: int,
|
795
|
+
end: int,
|
796
|
+
function: typing.Callable[
|
797
|
+
[emulator.Emulator, int, int, claripy.ast.bv.BV],
|
798
|
+
typing.Optional[claripy.ast.bv.BV],
|
799
|
+
],
|
800
|
+
) -> None:
|
801
|
+
if not self._initialized:
|
802
|
+
self._read_hooks.append((start, end, function))
|
803
|
+
|
804
|
+
elif self._dirty and not self._linear:
|
805
|
+
raise NotImplementedError(
|
806
|
+
"Memory hooking not supported once execution begins"
|
807
|
+
)
|
808
|
+
|
809
|
+
elif (start, end) in self.state.scratch.mem_read_bps:
|
810
|
+
raise exceptions.ConfigurationError(
|
811
|
+
f"{hex(start)}-{hex(end)} already hooked for reads"
|
812
|
+
)
|
813
|
+
|
814
|
+
else:
|
815
|
+
# This uses angr's conditional breakpoint facility
|
816
|
+
def read_condition(state):
|
817
|
+
# The breakpoint condition.
|
818
|
+
# This needs to be a bit clever to detect reads at bound symbolic addresses.
|
819
|
+
# The actual address won't get concretized until later
|
820
|
+
read_start = state.inspect.mem_read_address
|
821
|
+
if not isinstance(read_start, int):
|
822
|
+
if read_start.symbolic:
|
823
|
+
try:
|
824
|
+
values = state.solver.eval_atmost(read_start, 1)
|
825
|
+
# Truly unbound address.
|
826
|
+
# Assume it won't collapse to our hook address
|
827
|
+
if len(values) < 1:
|
828
|
+
return False
|
829
|
+
read_start = values[0]
|
830
|
+
except angr.errors.SimUnsatError:
|
831
|
+
return False
|
832
|
+
except angr.errors.SimValueError:
|
833
|
+
return False
|
834
|
+
else:
|
835
|
+
read_start = read_start.concrete_value
|
836
|
+
state.inspect.mem_read_address = read_start
|
837
|
+
read_size = state.inspect.mem_read_length
|
838
|
+
|
839
|
+
if read_size is None:
|
840
|
+
return False
|
841
|
+
read_end = read_start + read_size
|
842
|
+
|
843
|
+
return start <= read_start and end >= read_end
|
844
|
+
|
845
|
+
def read_callback(state):
|
846
|
+
# The breakpoint action.
|
847
|
+
addr = state.inspect.mem_read_address
|
848
|
+
size = state.inspect.mem_read_length
|
849
|
+
expr = state.inspect.mem_read_expr
|
850
|
+
res = function(ConcreteAngrEmulator(state, self), addr, size, expr)
|
851
|
+
|
852
|
+
if res is None:
|
853
|
+
res = expr
|
854
|
+
state.inspect.mem_read_expr = res
|
855
|
+
|
856
|
+
# An update to angr means some operations on `state`
|
857
|
+
# will clobber this variable, which causes bad problems.
|
858
|
+
# Unclobber it, just in case
|
859
|
+
state.inspect.action_attrs_set = True
|
860
|
+
|
861
|
+
bp = self.state.inspect.b(
|
862
|
+
"mem_read",
|
863
|
+
when=angr.BP_AFTER,
|
864
|
+
condition=read_condition,
|
865
|
+
action=read_callback,
|
866
|
+
)
|
867
|
+
self.state.scratch.mem_read_bps[(start, end)] = bp
|
868
|
+
|
869
|
+
def hook_memory_read(
|
870
|
+
self,
|
871
|
+
start: int,
|
872
|
+
end: int,
|
873
|
+
function: typing.Callable[
|
874
|
+
[emulator.Emulator, int, int, bytes], typing.Optional[bytes]
|
875
|
+
],
|
876
|
+
):
|
877
|
+
# Most of the machinery is handled in hook_memory_read_symbolic
|
878
|
+
# We just need to concretize the inputs and claripy the outputs
|
879
|
+
def sym_callback(
|
880
|
+
emu: emulator.Emulator, addr: int, size: int, expr: claripy.ast.bv.BV
|
881
|
+
) -> typing.Optional[claripy.ast.bv.BV]:
|
882
|
+
if not isinstance(emu, ConcreteAngrEmulator):
|
883
|
+
raise TypeError("Where'd you get your emu?")
|
884
|
+
if expr.symbolic:
|
885
|
+
# Have the solver handle binding resolution for us.
|
886
|
+
try:
|
887
|
+
values = emu.state.solver.eval_atmost(expr, 1)
|
888
|
+
if len(values) < 1:
|
889
|
+
raise exceptions.AnalysisError(f"No possible values fpr {expr}")
|
890
|
+
value = values[0].to_bytes(size, byteorder=self.byteorder)
|
891
|
+
log.info("Collapsed symbolic {expr} to {values[0]:x} for MMIO")
|
892
|
+
except angr.errors.SimUnsatError:
|
893
|
+
raise exceptions.AnalysisError(f"No possible values for {expr}")
|
894
|
+
except angr.errors.SimValueError:
|
895
|
+
value = b"\x00" * size
|
896
|
+
else:
|
897
|
+
value = expr.concrete_value.to_bytes(size, byteorder=self.byteorder)
|
898
|
+
res = function(emu, addr, size, value)
|
899
|
+
if res is not None:
|
900
|
+
res_expr = claripy.BVV(res)
|
901
|
+
if self.platform.byteorder == platforms.Byteorder.LITTLE:
|
902
|
+
# Fix byte order if needed.
|
903
|
+
# I don't know _why_ this is needed,
|
904
|
+
# but encoding the result as little-endian on a little-endian
|
905
|
+
# system produces the incorrect value in the machine state.
|
906
|
+
res_expr = claripy.Reverse(res_expr)
|
907
|
+
return res_expr
|
908
|
+
return res
|
909
|
+
|
910
|
+
self.hook_memory_read_symbolic(start, end, sym_callback)
|
911
|
+
|
912
|
+
def unhook_memory_read(self, start: int, end: int):
|
913
|
+
if not self._initialized:
|
914
|
+
self._read_hooks = list(
|
915
|
+
filter(lambda x: x[0] != start and x[1] != end, self._read_hooks)
|
916
|
+
)
|
917
|
+
|
918
|
+
elif self._dirty and not self._linear:
|
919
|
+
raise NotImplementedError(
|
920
|
+
"Memory hooking not supported once execution begins"
|
921
|
+
)
|
922
|
+
|
923
|
+
elif (start, end) not in self.state.scratch.mem_read_bps:
|
924
|
+
raise exceptions.ConfigurationError(
|
925
|
+
f"{hex(start)} - {hex(end)} is not hooked for reads"
|
926
|
+
)
|
927
|
+
else:
|
928
|
+
bp = self.state.scratch.mem_read_bps[(start, end)]
|
929
|
+
del self.state.scratch.mem_read_bps[(start, end)]
|
930
|
+
self.state.inspect.remove_breakpoint("mem_read", bp=bp)
|
931
|
+
|
932
|
+
def hook_memory_reads_symbolic(
|
933
|
+
self,
|
934
|
+
function: typing.Callable[
|
935
|
+
[emulator.Emulator, int, int, claripy.ast.bv.BV],
|
936
|
+
typing.Optional[claripy.ast.bv.BV],
|
937
|
+
],
|
938
|
+
) -> None:
|
939
|
+
if not self._initialized:
|
940
|
+
self._gb_read_hook = function
|
941
|
+
|
942
|
+
elif self._dirty and not self._linear:
|
943
|
+
raise NotImplementedError(
|
944
|
+
"Memory hooking not supported once execution begins"
|
945
|
+
)
|
946
|
+
|
947
|
+
elif self.state.scratch.global_read_bp is not None:
|
948
|
+
raise exceptions.ConfigurationError(
|
949
|
+
"Global memory read hook already present"
|
950
|
+
)
|
951
|
+
|
952
|
+
else:
|
953
|
+
|
954
|
+
def read_callback(state):
|
955
|
+
# the breakpoint action.
|
956
|
+
addr = state.inspect.mem_read_address
|
957
|
+
if not isinstance(addr, int):
|
958
|
+
addr = addr.concrete_value
|
959
|
+
size = state.inspect.mem_read_length
|
960
|
+
expr = state.inspect.mem_read_expr
|
961
|
+
|
962
|
+
res = function(ConcreteAngrEmulator(state, self), addr, size, expr)
|
963
|
+
|
964
|
+
if self.platform.byteorder == platforms.byteorder.LITTLE:
|
965
|
+
# fix byte order if needed.
|
966
|
+
# i don't know _why_ this is needed,
|
967
|
+
# but encoding the result as little-endian on a little-endian
|
968
|
+
# system produces the incorrect value in the machine state.
|
969
|
+
res = claripy.Reverse(res)
|
970
|
+
state.inspect.mem_read_expr = res
|
971
|
+
|
972
|
+
# An update to angr means some operations on `state`
|
973
|
+
# will clobber this variable, which causes bad problems.
|
974
|
+
# Unclobber it, just in case
|
975
|
+
state.inspect.action_attrs_set = True
|
976
|
+
|
977
|
+
bp = self.state.inspect.b(
|
978
|
+
"mem_read",
|
979
|
+
when=angr.BP_AFTER,
|
980
|
+
action=read_callback,
|
981
|
+
)
|
982
|
+
self.state.scratch.global_read_bp = bp
|
983
|
+
|
984
|
+
def hook_memory_reads(
|
985
|
+
self,
|
986
|
+
function: typing.Callable[
|
987
|
+
[emulator.Emulator, int, int, bytes], typing.Optional[bytes]
|
988
|
+
],
|
989
|
+
):
|
990
|
+
# Symbolic version does most of the lifting;
|
991
|
+
# we just need to translate the values
|
992
|
+
def sym_callback(
|
993
|
+
emu: emulator.Emulator, addr: int, size: int, expr: claripy.ast.bv.BV
|
994
|
+
) -> typing.Optional[claripy.ast.bv.BV]:
|
995
|
+
if not isinstance(emu, ConcreteAngrEmulator):
|
996
|
+
raise TypeError("Where'd you get your emu?")
|
997
|
+
if expr.symbolic:
|
998
|
+
# Have the solver handle binding resolution for us.
|
999
|
+
try:
|
1000
|
+
values = emu.state.solver.eval_atmost(expr, 1)
|
1001
|
+
if len(values) < 1:
|
1002
|
+
raise exceptions.AnalysisError(f"No possible values fpr {expr}")
|
1003
|
+
value = values[0].to_bytes(size, byteorder=self.byteorder)
|
1004
|
+
log.info("Collapsed symbolic {expr} to {values[0]:x} for MMIO")
|
1005
|
+
except angr.errors.SimUnsatError:
|
1006
|
+
raise exceptions.AnalysisError(f"No possible values for {expr}")
|
1007
|
+
except angr.errors.SimValueError:
|
1008
|
+
raise exceptions.AnalysisError(
|
1009
|
+
f"Unbound value for MMIO write to {hex(addr)}: {expr}"
|
1010
|
+
)
|
1011
|
+
else:
|
1012
|
+
value = expr.concrete_value.to_bytes(size, byteorder=self.byteorder)
|
1013
|
+
res = function(emu, addr, size, value)
|
1014
|
+
if res is not None:
|
1015
|
+
return claripy.BVV(res)
|
1016
|
+
return res
|
1017
|
+
|
1018
|
+
self.hook_memory_reads_symbolic(sym_callback)
|
1019
|
+
|
1020
|
+
def unhook_memory_reads(self) -> None:
|
1021
|
+
if not self._initialized:
|
1022
|
+
self._gb_read_hook = None
|
1023
|
+
|
1024
|
+
elif self._dirty and not self._linear:
|
1025
|
+
raise NotImplementedError(
|
1026
|
+
"Memory unhooking not supported once execution begins"
|
1027
|
+
)
|
1028
|
+
|
1029
|
+
elif self.state.scratch.global_read_bp is not None:
|
1030
|
+
raise exceptions.ConfigurationError("Global memory read hook not present")
|
1031
|
+
|
1032
|
+
else:
|
1033
|
+
bp = self.state.scratch.global_read_bp
|
1034
|
+
self.state.scratch.global_read_bp = None
|
1035
|
+
self.state.inspect.remove_breakpoint("mem_read", bp)
|
1036
|
+
|
1037
|
+
def hook_memory_write_symbolic(
|
1038
|
+
self,
|
1039
|
+
start: int,
|
1040
|
+
end: int,
|
1041
|
+
function: typing.Callable[
|
1042
|
+
[emulator.Emulator, int, int, claripy.ast.bv.BV], None
|
1043
|
+
],
|
1044
|
+
) -> None:
|
1045
|
+
if not self._initialized:
|
1046
|
+
self._write_hooks.append((start, end, function))
|
1047
|
+
|
1048
|
+
elif self._dirty and not self._linear:
|
1049
|
+
raise NotImplementedError(
|
1050
|
+
"Memory hooking not supported once execution begins"
|
1051
|
+
)
|
1052
|
+
|
1053
|
+
elif (start, end) in self.state.scratch.mem_write_bps:
|
1054
|
+
raise exceptions.ConfigurationError(
|
1055
|
+
f"{hex(start)} - {hex(end)} already hooked for writes"
|
1056
|
+
)
|
1057
|
+
|
1058
|
+
else:
|
1059
|
+
|
1060
|
+
def write_condition(state):
|
1061
|
+
write_start = state.inspect.mem_write_address
|
1062
|
+
if not isinstance(write_start, int):
|
1063
|
+
if write_start.symbolic:
|
1064
|
+
# Need to use the solver to resolve this.
|
1065
|
+
try:
|
1066
|
+
values = state.solver.eval_atmost(write_start, 1)
|
1067
|
+
if len(values) < 1:
|
1068
|
+
return False
|
1069
|
+
write_start = values[0]
|
1070
|
+
except angr.errors.SimUnsatError:
|
1071
|
+
return False
|
1072
|
+
except angr.errors.SimValueError:
|
1073
|
+
return False
|
1074
|
+
else:
|
1075
|
+
write_start = write_start.concrete_value
|
1076
|
+
# Populate concrete value back to the inspect struct
|
1077
|
+
state.inspect.mem_write_address = write_start
|
1078
|
+
write_size = state.inspect.mem_write_length
|
1079
|
+
|
1080
|
+
if write_size is None:
|
1081
|
+
# Some ISAs don't populate mem_write_length.
|
1082
|
+
# Infer length from value
|
1083
|
+
write_size = len(state.inspect.mem_write_expr) // 8
|
1084
|
+
# Populate concrete value back to the inspect struct
|
1085
|
+
state.inspect.mem_write_length = write_size
|
1086
|
+
write_end = write_start + write_size
|
1087
|
+
|
1088
|
+
return start <= write_start and end >= write_end
|
1089
|
+
|
1090
|
+
def write_callback(state):
|
1091
|
+
addr = state.inspect.mem_write_address
|
1092
|
+
size = state.inspect.mem_write_length
|
1093
|
+
expr = state.inspect.mem_write_expr
|
1094
|
+
|
1095
|
+
if size is None:
|
1096
|
+
size = len(expr)
|
1097
|
+
|
1098
|
+
function(ConcreteAngrEmulator(state, self), addr, size, expr)
|
1099
|
+
|
1100
|
+
# An update to angr means some operations on `state`
|
1101
|
+
# will clobber this variable, which causes bad problems.
|
1102
|
+
# Unclobber it, just in case
|
1103
|
+
state.inspect.action_attrs_set = True
|
1104
|
+
|
1105
|
+
bp = self.state.inspect.b(
|
1106
|
+
"mem_write",
|
1107
|
+
when=angr.BP_BEFORE,
|
1108
|
+
condition=write_condition,
|
1109
|
+
action=write_callback,
|
1110
|
+
)
|
1111
|
+
self.state.scratch.mem_write_bps[(start, end)] = bp
|
1112
|
+
|
1113
|
+
def hook_memory_write(
|
1114
|
+
self,
|
1115
|
+
start: int,
|
1116
|
+
end: int,
|
1117
|
+
function: typing.Callable[[emulator.Emulator, int, int, bytes], None],
|
1118
|
+
) -> None:
|
1119
|
+
def sym_callback(
|
1120
|
+
emu: emulator.Emulator, addr: int, size: int, expr: claripy.ast.bv.BV
|
1121
|
+
) -> None:
|
1122
|
+
if not isinstance(emu, ConcreteAngrEmulator):
|
1123
|
+
raise TypeError("Where'd you get your emu?")
|
1124
|
+
if expr.symbolic:
|
1125
|
+
# Have the solver handle binding resolution for us.
|
1126
|
+
try:
|
1127
|
+
values = emu.state.solver.eval_atmost(expr, 1)
|
1128
|
+
if len(values) < 1:
|
1129
|
+
raise exceptions.AnalysisError(f"No possible values fpr {expr}")
|
1130
|
+
value = values[0].to_bytes(size, byteorder=self.byteorder)
|
1131
|
+
log.info("Collapsed symbolic {expr} to {values[0]:x} for MMIO")
|
1132
|
+
except angr.errors.SimUnsatError:
|
1133
|
+
raise exceptions.AnalysisError(f"No possible values for {expr}")
|
1134
|
+
except angr.errors.SimValueError:
|
1135
|
+
raise exceptions.AnalysisError(
|
1136
|
+
f"Unbound value for MMIO write to {hex(addr)}: {expr}"
|
1137
|
+
)
|
1138
|
+
else:
|
1139
|
+
value = expr.concrete_value.to_bytes(size, byteorder=self.byteorder)
|
1140
|
+
function(emu, addr, size, value)
|
1141
|
+
|
1142
|
+
self.hook_memory_write_symbolic(start, end, sym_callback)
|
1143
|
+
|
1144
|
+
def unhook_memory_write(self, start: int, end: int) -> None:
|
1145
|
+
if not self._initialized:
|
1146
|
+
self._write_hooks = list(
|
1147
|
+
filter(lambda x: x[0] != start or x[1] != end, self._write_hooks)
|
1148
|
+
)
|
1149
|
+
|
1150
|
+
elif self._dirty and not self._linear:
|
1151
|
+
raise NotImplementedError(
|
1152
|
+
"Memory hooking not supported once execution begins"
|
1153
|
+
)
|
1154
|
+
|
1155
|
+
elif (start, end) not in self.state.scratch.mem_write_bps:
|
1156
|
+
raise exceptions.ConfigurationError(
|
1157
|
+
f"{hex(start)} - {hex(end)} is not hooked for writes"
|
1158
|
+
)
|
1159
|
+
|
1160
|
+
else:
|
1161
|
+
bp = self.state.scratch.mem_write_bps[(start, end)]
|
1162
|
+
del self.state.scratch.mem_write_bps[(start, end)]
|
1163
|
+
self.state.inspect.remove_breakpoint("mem_write", bp=bp)
|
1164
|
+
|
1165
|
+
def hook_memory_writes_symbolic(
|
1166
|
+
self,
|
1167
|
+
function: typing.Callable[
|
1168
|
+
[emulator.Emulator, int, int, claripy.ast.bv.BV], None
|
1169
|
+
],
|
1170
|
+
) -> None:
|
1171
|
+
if not self._initialized:
|
1172
|
+
self._gb_write_hook = function
|
1173
|
+
|
1174
|
+
elif self._dirty and not self._linear:
|
1175
|
+
raise NotImplementedError(
|
1176
|
+
"Memory hooking not supported once execution begins"
|
1177
|
+
)
|
1178
|
+
|
1179
|
+
elif self.state.scratch.global_write_bp is not None:
|
1180
|
+
raise exceptions.ConfigurationError(
|
1181
|
+
"Global memory write hook already present"
|
1182
|
+
)
|
1183
|
+
|
1184
|
+
else:
|
1185
|
+
|
1186
|
+
def write_callback(state):
|
1187
|
+
addr = state.inspect.mem_write_address
|
1188
|
+
if not isinstance(addr, int):
|
1189
|
+
addr = addr.concrete_value
|
1190
|
+
size = state.inspect.mem_write_length
|
1191
|
+
expr = state.inspect.mem_write_expr
|
1192
|
+
function(ConcreteAngrEmulator(state, self), addr, size, expr)
|
1193
|
+
|
1194
|
+
# An update to angr means some operations on `state`
|
1195
|
+
# will clobber this variable, which causes bad problems.
|
1196
|
+
# Unclobber it, just in case
|
1197
|
+
state.inspect.action_attrs_set = True
|
1198
|
+
|
1199
|
+
bp = self.state.inspect.b(
|
1200
|
+
"mem_write",
|
1201
|
+
when=angr.BP_BEFORE,
|
1202
|
+
action=write_callback,
|
1203
|
+
)
|
1204
|
+
|
1205
|
+
self.state.scratch.global_write_bp = bp
|
1206
|
+
|
1207
|
+
def hook_memory_writes(
|
1208
|
+
self, function: typing.Callable[[emulator.Emulator, int, int, bytes], None]
|
1209
|
+
) -> None:
|
1210
|
+
def sym_callback(
|
1211
|
+
emu: emulator.Emulator, addr: int, size: int, expr: claripy.ast.bv.BV
|
1212
|
+
) -> None:
|
1213
|
+
if not isinstance(emu, ConcreteAngrEmulator):
|
1214
|
+
raise TypeError("Where'd you get your emu?")
|
1215
|
+
if expr.symbolic:
|
1216
|
+
# Have the solver handle binding resolution for us.
|
1217
|
+
try:
|
1218
|
+
values = emu.state.solver.eval_atmost(expr, 1)
|
1219
|
+
if len(values) < 1:
|
1220
|
+
raise exceptions.AnalysisError(f"No possible values fpr {expr}")
|
1221
|
+
value = values[0].to_bytes(size, byteorder=self.byteorder)
|
1222
|
+
log.info("Collapsed symbolic {expr} to {values[0]:x} for MMIO")
|
1223
|
+
except angr.errors.SimUnsatError:
|
1224
|
+
raise exceptions.AnalysisError(f"No possible values for {expr}")
|
1225
|
+
except angr.errors.SimValueError:
|
1226
|
+
raise exceptions.AnalysisError(
|
1227
|
+
f"Unbound value for MMIO write to {hex(addr)}: {expr}"
|
1228
|
+
)
|
1229
|
+
else:
|
1230
|
+
value = expr.concrete_value.to_bytes(size, byteorder=self.byteorder)
|
1231
|
+
function(emu, addr, size, value)
|
1232
|
+
|
1233
|
+
self.hook_memory_writes_symbolic(sym_callback)
|
1234
|
+
|
1235
|
+
def unhook_memory_writes(self) -> None:
|
1236
|
+
if self._dirty and not self._linear:
|
1237
|
+
raise NotImplementedError(
|
1238
|
+
"Memory unhooking not supported once execution begins"
|
1239
|
+
)
|
1240
|
+
if self.state.scratch.global_write_bp is not None:
|
1241
|
+
raise exceptions.ConfigurationError("Global memory write hook not present")
|
1242
|
+
|
1243
|
+
bp = self.state.scratch.global_read_bp
|
1244
|
+
self.state.scratch.global_read_bp = None
|
1245
|
+
self.state.inspect.remove_breakpoint("mem_write", bp)
|
1246
|
+
|
1247
|
+
def _step(self, single_insn: bool):
|
1248
|
+
"""Common routine for all step functions.
|
1249
|
+
|
1250
|
+
This is rather verbose, so let's only write it once.
|
1251
|
+
|
1252
|
+
Arguments:
|
1253
|
+
single_insn: True to step one instruction. False to step to the end of the block
|
1254
|
+
"""
|
1255
|
+
# If we have not been initialized, initialize ourself
|
1256
|
+
if not self._initialized:
|
1257
|
+
self.initialize()
|
1258
|
+
|
1259
|
+
# As soon as we start executing, disable value access
|
1260
|
+
self._dirty = True
|
1261
|
+
if self._linear:
|
1262
|
+
if self.state._ip.concrete_value not in self.state.scratch.func_bps:
|
1263
|
+
disas = self.state.block(opt_level=0).disassembly
|
1264
|
+
if disas is not None and len(disas.insns) > 0:
|
1265
|
+
log.info(f"Stepping through {disas.insns[0]}")
|
1266
|
+
else:
|
1267
|
+
# Capstone only supports a subset of the instructions supported by LibVEX.
|
1268
|
+
# I can only disassemble what I can disassemble.
|
1269
|
+
log.info(f"Stepping through {self.state._ip} (untranslatable!)")
|
1270
|
+
else:
|
1271
|
+
log.info(f"Stepping through {self.state._ip} (hook)")
|
1272
|
+
|
1273
|
+
# Step execution once, however the user asked for it.
|
1274
|
+
if single_insn:
|
1275
|
+
# Not all architectures support single-step execution.
|
1276
|
+
# In particular, angr can't lift delay slot ISAs one instruction at a time,
|
1277
|
+
# since it has to lift the instruction and the slot as one unit.
|
1278
|
+
if not self.machdef.supports_single_step:
|
1279
|
+
raise exceptions.ConfigurationError(
|
1280
|
+
f"AngrEmulator does not support single-instruction stepping for {self.platform}"
|
1281
|
+
)
|
1282
|
+
num_inst = 1
|
1283
|
+
else:
|
1284
|
+
num_inst = None
|
1285
|
+
self.mgr.step(
|
1286
|
+
num_inst=num_inst,
|
1287
|
+
successor_func=self.machdef.successors,
|
1288
|
+
thumb=self.machdef.is_thumb,
|
1289
|
+
)
|
1290
|
+
|
1291
|
+
# Test for exceptional states
|
1292
|
+
if len(self.mgr.errored) > 0:
|
1293
|
+
raise exceptions.EmulationError(
|
1294
|
+
self.mgr.errored[0].error
|
1295
|
+
) from self.mgr.errored[0].error
|
1296
|
+
|
1297
|
+
# Handle linear execution mode
|
1298
|
+
if self._linear:
|
1299
|
+
if len(self.mgr.active) > 1:
|
1300
|
+
log.warn("Path diverged! Detailes stored in simulation manager.")
|
1301
|
+
raise exceptions.EmulationStop("Path diverged in linear mode")
|
1302
|
+
elif len(self.mgr.active) > 0:
|
1303
|
+
self.state = self.mgr.active[0]
|
1304
|
+
elif len(self.mgr.deadended) > 0:
|
1305
|
+
self.state = self.mgr.deadended[0]
|
1306
|
+
elif len(self.mgr.unconstrained) > 0:
|
1307
|
+
self.state = self.mgr.unconstrained[0]
|
1308
|
+
else:
|
1309
|
+
raise exceptions.AnalysisError(
|
1310
|
+
"No states in expected stashes for linear execution"
|
1311
|
+
)
|
1312
|
+
|
1313
|
+
# Filter out exited or invalid states
|
1314
|
+
self.mgr.move(
|
1315
|
+
from_stash="active",
|
1316
|
+
to_stash="unconstrained",
|
1317
|
+
filter_func=lambda x: (x._ip.symbolic),
|
1318
|
+
)
|
1319
|
+
|
1320
|
+
def filter_func(state):
|
1321
|
+
ip = state._ip.concrete_value
|
1322
|
+
if (
|
1323
|
+
not self.state.scratch.bounds.is_empty()
|
1324
|
+
and not self.state.scratch.bounds.contains_value(ip)
|
1325
|
+
):
|
1326
|
+
return True
|
1327
|
+
if not self.state.scratch.memory_map.contains_value(ip):
|
1328
|
+
return True
|
1329
|
+
if ip in self.state.scratch.exit_points:
|
1330
|
+
return True
|
1331
|
+
return False
|
1332
|
+
|
1333
|
+
self.mgr.move(
|
1334
|
+
from_stash="active", to_stash="deadended", filter_func=filter_func
|
1335
|
+
)
|
1336
|
+
|
1337
|
+
# Stop if we're out of active states
|
1338
|
+
if len(self.mgr.active) == 0:
|
1339
|
+
raise exceptions.EmulationStop()
|
1340
|
+
|
1341
|
+
def step_instruction(self) -> None:
|
1342
|
+
self._step(True)
|
1343
|
+
|
1344
|
+
def step_block(self) -> None:
|
1345
|
+
self._step(False)
|
1346
|
+
|
1347
|
+
def step(self) -> None:
|
1348
|
+
self._step(True)
|
1349
|
+
|
1350
|
+
def run(self):
|
1351
|
+
log.info("Starting angr run")
|
1352
|
+
try:
|
1353
|
+
# Continue stepping as long as we have steps.
|
1354
|
+
while True:
|
1355
|
+
self._step(False)
|
1356
|
+
except exceptions.EmulationStop:
|
1357
|
+
return
|
1358
|
+
|
1359
|
+
def visit_states(
|
1360
|
+
self,
|
1361
|
+
function: typing.Callable[[emulator.Emulator], None],
|
1362
|
+
stash: str = "active",
|
1363
|
+
) -> None:
|
1364
|
+
"""Visit every state in the selected frontier
|
1365
|
+
|
1366
|
+
This lets you work around the fact that most operations
|
1367
|
+
only work at emulation start, or in linear mode.
|
1368
|
+
"""
|
1369
|
+
if not self._initialized:
|
1370
|
+
raise NotImplementedError("Cannot visit states before initialization")
|
1371
|
+
for s in self.mgr.stashes[stash]:
|
1372
|
+
function(ConcreteAngrEmulator(s, self))
|
1373
|
+
|
1374
|
+
def enable_linear(self):
|
1375
|
+
"""Enable linear execution
|
1376
|
+
|
1377
|
+
This doesn't actually concretize anything;
|
1378
|
+
it just kills execution when it hits an unconstrained branch.
|
1379
|
+
"""
|
1380
|
+
if self._dirty:
|
1381
|
+
raise NotImplementedError(
|
1382
|
+
"Enabling linear mode not supported once execution begins"
|
1383
|
+
)
|
1384
|
+
self._linear = True
|
1385
|
+
log.warn("Linear execution mode enabled")
|
1386
|
+
|
1387
|
+
def get_bounds(self) -> typing.List[typing.Tuple[int, int]]:
|
1388
|
+
if not self._initialized:
|
1389
|
+
return list(self._code_bounds)
|
1390
|
+
|
1391
|
+
if self._dirty and not self._linear:
|
1392
|
+
raise NotImplementedError(
|
1393
|
+
"Accessing bounds not supported once execution begins"
|
1394
|
+
)
|
1395
|
+
return list(self.state.scratch.bounds.ranges)
|
1396
|
+
|
1397
|
+
def add_bound(self, start: int, end: int) -> None:
|
1398
|
+
if not self._initialized:
|
1399
|
+
self._code_bounds.append((start, end))
|
1400
|
+
|
1401
|
+
elif self._dirty and not self._linear:
|
1402
|
+
raise NotImplementedError(
|
1403
|
+
"Accessing bounds not supported once execution begins"
|
1404
|
+
)
|
1405
|
+
|
1406
|
+
else:
|
1407
|
+
self.state.scratch.bounds.add_range((start, end))
|
1408
|
+
|
1409
|
+
def remove_bound(self, start: int, end: int) -> None:
|
1410
|
+
if not self._initialized:
|
1411
|
+
self._code_bounds = list(
|
1412
|
+
filter(lambda x: x[0] != start or x[1] != end, self._code_bounds)
|
1413
|
+
)
|
1414
|
+
|
1415
|
+
elif self._dirty and not self._linear:
|
1416
|
+
raise NotImplementedError(
|
1417
|
+
"Accessing bounds not supported once execution begins"
|
1418
|
+
)
|
1419
|
+
|
1420
|
+
else:
|
1421
|
+
self.state.scratch.bounds.remove_range((start, end))
|
1422
|
+
|
1423
|
+
def get_exit_points(self) -> typing.Set[int]:
|
1424
|
+
if not self._initialized:
|
1425
|
+
return set(self._exit_points)
|
1426
|
+
|
1427
|
+
if self._dirty and not self._linear:
|
1428
|
+
raise NotImplementedError(
|
1429
|
+
"Accessing exit points not supported once execution begins"
|
1430
|
+
)
|
1431
|
+
return set(self.state.scratch.exit_points)
|
1432
|
+
|
1433
|
+
def add_exit_point(self, address: int) -> None:
|
1434
|
+
if not self._initialized:
|
1435
|
+
self._exit_points.add(address)
|
1436
|
+
elif self._dirty and not self._linear:
|
1437
|
+
raise NotImplementedError(
|
1438
|
+
"Accessing exit points not supported once execution begins"
|
1439
|
+
)
|
1440
|
+
else:
|
1441
|
+
self.state.scratch.exit_points.add(address)
|
1442
|
+
|
1443
|
+
def remove_exit_point(self, address: int) -> None:
|
1444
|
+
if not self._initialized:
|
1445
|
+
self._exit_points.remove(address)
|
1446
|
+
|
1447
|
+
elif self._dirty and not self._linear:
|
1448
|
+
raise NotImplementedError(
|
1449
|
+
"Accessing exit points not supported once execution begins"
|
1450
|
+
)
|
1451
|
+
|
1452
|
+
else:
|
1453
|
+
self.state.scratch.exit_points.remove(address)
|
1454
|
+
|
1455
|
+
def add_constraint(self, expr: claripy.ast.bool.Bool) -> None:
|
1456
|
+
if not self._initialized:
|
1457
|
+
self._constraints.append(expr)
|
1458
|
+
elif self._dirty and not self._linear:
|
1459
|
+
raise NotImplementedError(
|
1460
|
+
"Accessing constraints not supported once execution begins"
|
1461
|
+
)
|
1462
|
+
else:
|
1463
|
+
self.state.solver.add(expr)
|
1464
|
+
|
1465
|
+
def get_constraints(self) -> typing.List[claripy.ast.bool.Bool]:
|
1466
|
+
if not self._initialized:
|
1467
|
+
return list(self._constraints)
|
1468
|
+
elif self._dirty and not self._linear:
|
1469
|
+
raise NotImplementedError(
|
1470
|
+
"Accessing constraints not supported once execution begins"
|
1471
|
+
)
|
1472
|
+
else:
|
1473
|
+
return list(self.state.solver.constraints)
|
1474
|
+
|
1475
|
+
def satisfiable(
|
1476
|
+
self,
|
1477
|
+
extra_constraints: typing.List[claripy.ast.bool.Bool] = [],
|
1478
|
+
) -> bool:
|
1479
|
+
if not self._initialized:
|
1480
|
+
raise exceptions.ConfigurationError(
|
1481
|
+
"Solver not available before initialization"
|
1482
|
+
)
|
1483
|
+
elif self._dirty and not self._linear:
|
1484
|
+
raise NotImplementedError(
|
1485
|
+
"Accessing solver not supported once execution begins"
|
1486
|
+
)
|
1487
|
+
return self.state.solver.satisfiable(extra_constraints=extra_constraints)
|
1488
|
+
|
1489
|
+
def eval_atmost(self, expr: claripy.ast.bv.BV, most: int) -> typing.List[int]:
|
1490
|
+
if not self._initialized:
|
1491
|
+
raise exceptions.ConfigurationError(
|
1492
|
+
"Solver not available before initialization"
|
1493
|
+
)
|
1494
|
+
elif self._dirty and not self._linear:
|
1495
|
+
raise NotImplementedError(
|
1496
|
+
"Accessing solver not supported once execution begins"
|
1497
|
+
)
|
1498
|
+
try:
|
1499
|
+
return self.state.solver.eval_atmost(expr, most)
|
1500
|
+
except angr.errors.SimUnsatError:
|
1501
|
+
raise exceptions.UnsatError("No solutions")
|
1502
|
+
except angr.errors.SimValueError:
|
1503
|
+
raise exceptions.SymbolicValueError("Too many solutions")
|
1504
|
+
|
1505
|
+
def eval_atleast(self, expr: claripy.ast.bv.BV, least: int) -> typing.List[int]:
|
1506
|
+
if not self._initialized:
|
1507
|
+
raise exceptions.ConfigurationError(
|
1508
|
+
"Solver not available before initialization"
|
1509
|
+
)
|
1510
|
+
elif self._dirty and not self._linear:
|
1511
|
+
raise NotImplementedError(
|
1512
|
+
"Accessing solver not supported once execution begins"
|
1513
|
+
)
|
1514
|
+
try:
|
1515
|
+
return self.state.solver.eval_atleast(expr, least)
|
1516
|
+
except angr.errors.SimUnsatError:
|
1517
|
+
raise exceptions.UnsatError("No solutions")
|
1518
|
+
except angr.errors.SimValueError:
|
1519
|
+
raise exceptions.SymbolicValueError("Not enough solutions")
|
1520
|
+
|
1521
|
+
def add_extension(self, name: str, ext: typing.Any) -> None:
|
1522
|
+
"""Add extra data to the machine state.
|
1523
|
+
|
1524
|
+
This allows users to tie data structures
|
1525
|
+
to the emulator's execution states,
|
1526
|
+
allowing path-specific data tracking.
|
1527
|
+
|
1528
|
+
Only one extension with a given name can be tied
|
1529
|
+
to a given state. Usually, extensions are assigned
|
1530
|
+
before emulation starts, but it's perfectly
|
1531
|
+
possible to assign them later, either in linear mode,
|
1532
|
+
or through the emulator stub provided to a hook.
|
1533
|
+
|
1534
|
+
All extensions must be compatible with `copy.deepcopy()`.
|
1535
|
+
|
1536
|
+
Arguments:
|
1537
|
+
name: ID string used to retrieve the extension
|
1538
|
+
ext: The extension
|
1539
|
+
|
1540
|
+
Raises:
|
1541
|
+
KeyError: `name` is already taken
|
1542
|
+
"""
|
1543
|
+
|
1544
|
+
if not self._initialized:
|
1545
|
+
if name in self._extensions:
|
1546
|
+
raise KeyError(f"Extension with name {name} exists")
|
1547
|
+
self._extensions[name] = ext
|
1548
|
+
elif self._dirty and not self._linear:
|
1549
|
+
raise NotImplementedError("Cannot extend state once execution has begun")
|
1550
|
+
else:
|
1551
|
+
if name in self.state.scratch.extensions:
|
1552
|
+
raise KeyError(f"Extension with name {name} exists")
|
1553
|
+
self.state.scratch.extensions[name] = ext
|
1554
|
+
|
1555
|
+
def get_extension(self, name: str) -> typing.Any:
|
1556
|
+
"""Fetch extra data from the machine state
|
1557
|
+
|
1558
|
+
Arguments:
|
1559
|
+
name: ID string used to retrieve the extension
|
1560
|
+
|
1561
|
+
Returns:
|
1562
|
+
Whatever was assigned to `name` using `add_extension`
|
1563
|
+
|
1564
|
+
Raises:
|
1565
|
+
KeyError: If no extension exists tied to `name`
|
1566
|
+
"""
|
1567
|
+
if not self._initialized:
|
1568
|
+
return self._extensions[name]
|
1569
|
+
elif self._dirty and not self._linear:
|
1570
|
+
raise NotImplementedError("Cannot extend state once execution has begun")
|
1571
|
+
else:
|
1572
|
+
return self.state.scratch.extensions[name]
|
1573
|
+
|
1574
|
+
@property
|
1575
|
+
def byteorder(self) -> typing.Literal["big", "little"]:
|
1576
|
+
"""Get the byteorder string for this platform
|
1577
|
+
|
1578
|
+
Returns:
|
1579
|
+
'big' or 'little', appropriately-typed as literals.
|
1580
|
+
"""
|
1581
|
+
if self.platform.byteorder == platforms.Byteorder.BIG:
|
1582
|
+
return "big"
|
1583
|
+
elif self.platform.byteorder == platforms.Byteorder.LITTLE:
|
1584
|
+
return "little"
|
1585
|
+
else:
|
1586
|
+
raise ValueError("Unsupported byteorder!")
|
1587
|
+
|
1588
|
+
def __repr__(self):
|
1589
|
+
if self._initialized:
|
1590
|
+
return f"Angr ({self.mgr})"
|
1591
|
+
else:
|
1592
|
+
return "Angr (Uninitialized)"
|
1593
|
+
|
1594
|
+
|
1595
|
+
class ConcreteAngrEmulator(AngrEmulator):
|
1596
|
+
"""
|
1597
|
+
"Emulator" for angr state access
|
1598
|
+
|
1599
|
+
The primary AngrEmulator class is designed for symbolic execution;
|
1600
|
+
it represents many states being explored in parallel.
|
1601
|
+
As such, the notion of accessing state doesn't work; which state do you want?
|
1602
|
+
|
1603
|
+
For hook evaluation, we do need an emulator-like object
|
1604
|
+
that can access a single state. Hence, this wrapper class.
|
1605
|
+
|
1606
|
+
NOTE: This is NOT a full emulator!
|
1607
|
+
Think of it more as a view onto an AngrEmulator instance.
|
1608
|
+
If you want to explore a single path using angr,
|
1609
|
+
use AngrEmulator and call enable_linear() before exploration.
|
1610
|
+
"""
|
1611
|
+
|
1612
|
+
@property
|
1613
|
+
def PAGE_SIZE(self):
|
1614
|
+
# Page Size should be inherited from the parent.
|
1615
|
+
return self.pagesize
|
1616
|
+
|
1617
|
+
def __init__(self, state: angr.SimState, parent: AngrEmulator):
|
1618
|
+
# Do NOT call the superclass constructor.
|
1619
|
+
# It initializes the angr project, and we've already done that.
|
1620
|
+
self._initialized: bool = True
|
1621
|
+
self._dirty: bool = False
|
1622
|
+
self._linear: bool = False
|
1623
|
+
|
1624
|
+
# Force-initialize relevant state from the parent
|
1625
|
+
self.platform: platforms.Platform = parent.platform
|
1626
|
+
self.proj: angr.Project = parent.proj
|
1627
|
+
self.state: angr.SimState = state
|
1628
|
+
self.machdef: AngrMachineDef = parent.machdef
|
1629
|
+
self.pagesize: int = parent.PAGE_SIZE
|
1630
|
+
|
1631
|
+
# Function hooking is not supported;
|
1632
|
+
# it relies on state global to the angr project, not individual states.
|
1633
|
+
def hook_function(
|
1634
|
+
self, address: int, function: typing.Callable[[emulator.Emulator], None]
|
1635
|
+
) -> None:
|
1636
|
+
raise NotImplementedError("Function hooking not supported inside a hook.")
|
1637
|
+
|
1638
|
+
def unhook_function(self, address: int) -> None:
|
1639
|
+
raise NotImplementedError("Function hooking not supported inside a hook.")
|
1640
|
+
|
1641
|
+
# Execution is not supported; this is not a complete emulator.
|
1642
|
+
def run(self) -> None:
|
1643
|
+
raise NotImplementedError("Running not supported inside a hook.")
|
1644
|
+
|
1645
|
+
def _step(self, single_insn: bool) -> None:
|
1646
|
+
raise NotImplementedError("Stepping not supported inside a hook.")
|
1647
|
+
|
1648
|
+
def __repr__(self):
|
1649
|
+
return f"Angr Hook ({self.state})"
|
1650
|
+
|
1651
|
+
|
1652
|
+
__all__ = ["AngrEmulator"]
|