Skip to content

Commit d85a9a2

Browse files
authored
Syscall specific hooks (#2389)
* Non state specific functioning * State specific functioning * Add None to add_hook call in hook decorator * Moved will/did_invoke_syscall * Added functionality for hooking by function name to state specific hooks * Added functionality for hooking by sys function name to non state specific hooks * State specific tests
1 parent 5a258f4 commit d85a9a2

File tree

6 files changed

+300
-47
lines changed

6 files changed

+300
-47
lines changed

manticore/native/cpu/abstractcpu.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -503,6 +503,7 @@ class Cpu(Eventful):
503503
"read_memory",
504504
"decode_instruction",
505505
"execute_instruction",
506+
"invoke_syscall",
506507
"set_descriptor",
507508
"map_memory",
508509
"protect_memory",

manticore/native/manticore.py

Lines changed: 87 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
import os
66
import shlex
77
import time
8-
from typing import Callable, Optional
8+
from typing import Callable, Optional, Union
99
import sys
1010
from elftools.elf.elffile import ELFFile
1111
from elftools.elf.sections import SymbolTableSection
@@ -39,17 +39,24 @@ def __init__(self, path_or_state, argv=None, workspace_url=None, policy="random"
3939
initial_state = _make_initial_state(path_or_state, argv=argv, **kwargs)
4040
else:
4141
initial_state = path_or_state
42-
4342
super().__init__(initial_state, workspace_url=workspace_url, policy=policy, **kwargs)
4443

4544
# Move the following into a linux plugin
4645
self._assertions = {}
4746
self.trace = None
47+
self._linux_machine_arch: str # used when looking up syscall numbers for sys hooks
4848
# sugar for 'will_execute_instruction"
4949
self._hooks = {}
5050
self._after_hooks = {}
51+
self._sys_hooks = {}
52+
self._sys_after_hooks = {}
5153
self._init_hooks = set()
5254

55+
from ..platforms.linux import Linux
56+
57+
if isinstance(initial_state.platform, Linux):
58+
self._linux_machine_arch = initial_state.platform.current.machine
59+
5360
# self.subscribe('will_generate_testcase', self._generate_testcase_callback)
5461

5562
############################################################################
@@ -215,54 +222,91 @@ def init(self, f):
215222
self.subscribe("will_run", self._init_callback)
216223
return f
217224

218-
def hook(self, pc, after=False):
225+
def hook(
226+
self, pc_or_sys: Optional[Union[int, str]], after: bool = False, syscall: bool = False
227+
):
219228
"""
220229
A decorator used to register a hook function for a given instruction address.
221230
Equivalent to calling :func:`~add_hook`.
222231
223-
:param pc: Address of instruction to hook
224-
:type pc: int or None
232+
:param pc_or_sys: Address of instruction, syscall number, or syscall name to remove hook from
233+
:type pc_or_sys: int or None if `syscall` = False. int, str, or None if `syscall` = True
234+
:param after: Hook after PC (or after syscall) executes?
235+
:param syscall: Catch a syscall invocation instead of instruction?
225236
"""
226237

227238
def decorator(f):
228-
self.add_hook(pc, f, after)
239+
self.add_hook(pc_or_sys, f, after, None, syscall)
229240
return f
230241

231242
return decorator
232243

233244
def add_hook(
234245
self,
235-
pc: Optional[int],
246+
pc_or_sys: Optional[Union[int, str]],
236247
callback: HookCallback,
237248
after: bool = False,
238249
state: Optional[State] = None,
250+
syscall: bool = False,
239251
):
240252
"""
241-
Add a callback to be invoked on executing a program counter. Pass `None`
242-
for pc to invoke callback on every instruction. `callback` should be a callable
243-
that takes one :class:`~manticore.core.state.State` argument.
253+
Add a callback to be invoked on executing a program counter (or syscall). Pass `None`
254+
for `pc_or_sys` to invoke callback on every instruction (or syscall). `callback` should
255+
be a callable that takes one :class:`~manticore.core.state.State` argument.
244256
245-
:param pc: Address of instruction to hook
257+
:param pc_or_sys: Address of instruction, syscall number, or syscall name to remove hook from
258+
:type pc_or_sys: int or None if `syscall` = False. int, str, or None if `syscall` = True
246259
:param callback: Hook function
247-
:param after: Hook after PC executes?
260+
:param after: Hook after PC (or after syscall) executes?
248261
:param state: Optionally, add hook for this state only, else all states
262+
:param syscall: Catch a syscall invocation instead of instruction?
249263
"""
250-
if not (isinstance(pc, int) or pc is None):
251-
raise TypeError(f"pc must be either an int or None, not {pc.__class__.__name__}")
264+
if not (isinstance(pc_or_sys, int) or pc_or_sys is None or syscall):
265+
raise TypeError(f"pc must be either an int or None, not {pc_or_sys.__class__.__name__}")
266+
elif not (isinstance(pc_or_sys, (int, str)) or pc_or_sys is None) and syscall:
267+
raise TypeError(
268+
f"syscall must be either an int, string, or None, not {pc_or_sys.__class__.__name__}"
269+
)
270+
271+
if isinstance(pc_or_sys, str):
272+
from ..platforms import linux_syscalls
273+
274+
table = getattr(linux_syscalls, self._linux_machine_arch)
275+
for index, name in table.items():
276+
if name == pc_or_sys:
277+
pc_or_sys = index
278+
break
279+
if isinstance(pc_or_sys, str):
280+
logger.warning(
281+
f"{pc_or_sys} is not a valid syscall name in architecture {self._linux_machine_arch}. "
282+
"Please refer to manticore/platforms/linux_syscalls.py to find the correct name."
283+
)
284+
return
252285

253286
if state is None:
254287
# add hook to all states
255-
hooks, when, hook_callback = (
256-
(self._hooks, "will_execute_instruction", self._hook_callback)
257-
if not after
258-
else (self._after_hooks, "did_execute_instruction", self._after_hook_callback)
259-
)
260-
hooks.setdefault(pc, set()).add(callback)
288+
if not syscall:
289+
hooks, when, hook_callback = (
290+
(self._hooks, "will_execute_instruction", self._hook_callback)
291+
if not after
292+
else (self._after_hooks, "did_execute_instruction", self._after_hook_callback)
293+
)
294+
else:
295+
hooks, when, hook_callback = (
296+
(self._sys_hooks, "will_invoke_syscall", self._sys_hook_callback)
297+
if not after
298+
else (
299+
self._sys_after_hooks,
300+
"did_invoke_syscall",
301+
self._sys_after_hook_callback,
302+
)
303+
)
304+
hooks.setdefault(pc_or_sys, set()).add(callback)
261305
if hooks:
262306
self.subscribe(when, hook_callback)
263307
else:
264308
# only hook for the specified state
265-
state.add_hook(pc, callback, after)
309+
state.add_hook(pc_or_sys, callback, after, syscall)
266310

267311
def _hook_callback(self, state, pc, instruction):
268312
"Invoke all registered generic hooks"
@@ -293,6 +337,28 @@ def _after_hook_callback(self, state, last_pc, pc, instruction):
293337
for cb in self._after_hooks.get(None, []):
294338
cb(state)
295339

340+
def _sys_hook_callback(self, state, syscall_num):
341+
"Invoke all registered generic hooks"
342+
343+
# Invoke all syscall_num-specific hooks
344+
for cb in self._sys_hooks.get(syscall_num, []):
345+
cb(state)
346+
347+
# Invoke all syscall_num-agnostic hooks
348+
for cb in self._sys_hooks.get(None, []):
349+
cb(state)
350+
351+
def _sys_after_hook_callback(self, state, syscall_num):
352+
"Invoke all registered generic hooks"
353+
354+
# Invoke all syscall_num-specific hooks
355+
for cb in self._sys_after_hooks.get(syscall_num, []):
356+
cb(state)
357+
358+
# Invoke all syscall_num-agnostic hooks
359+
for cb in self._sys_after_hooks.get(None, []):
360+
cb(state)
361+
296362
def _init_callback(self, ready_states):
297363
for cb in self._init_hooks:
298364
# We _should_ only ever have one starting state. Right now we're putting

0 commit comments

Comments
 (0)