134 lines
3.9 KiB
Python
Raw Permalink Normal View History

2017-06-21 01:29:38 -07:00
"""
Implementation of Process class for Linux
"""
from os import strerror
import os
import os.path
import signal
import ctypes
import ctypes.util
import logging
2024-08-01 00:23:25 -07:00
from pathlib import Path
2017-06-21 01:29:38 -07:00
from .abstract import Process as AbstractProcess
from .utils import ctypes_buffer_t, MemEditError
logger = logging.getLogger(__name__)
ptrace_commands = {
2020-11-01 20:16:46 -08:00
'PTRACE_GETREGS': 12,
'PTRACE_SETREGS': 13,
'PTRACE_ATTACH': 16,
'PTRACE_DETACH': 17,
'PTRACE_SYSCALL': 24,
'PTRACE_SEIZE': 16902,
}
2017-06-21 01:29:38 -07:00
# import ptrace() from libc
_libc = ctypes.CDLL(ctypes.util.find_library('c'), use_errno=True)
_ptrace = _libc.ptrace
_ptrace.argtypes = (ctypes.c_ulong,) * 4
_ptrace.restype = ctypes.c_long
def ptrace(
command: int,
pid: int = 0,
arg1: int = 0,
arg2: int = 0,
) -> int:
2017-06-21 01:29:38 -07:00
"""
Call ptrace() with the provided pid and arguments. See `man ptrace`.
2017-06-21 01:29:38 -07:00
"""
logger.debug(f'ptrace({command}, {pid}, {arg1}, {arg2})')
2017-06-21 01:29:38 -07:00
result = _ptrace(command, pid, arg1, arg2)
if result == -1:
2017-06-21 01:44:57 -07:00
err_no = ctypes.get_errno()
if err_no:
raise MemEditError(f'ptrace({command}, {pid}, {arg1}, {arg2})'
+ f' failed with error {err_no}: {strerror(err_no)}')
2017-06-21 01:29:38 -07:00
return result
class Process(AbstractProcess):
pid: int | None
2017-06-21 01:29:38 -07:00
def __init__(self, process_id: int) -> None:
2017-06-21 01:29:38 -07:00
ptrace(ptrace_commands['PTRACE_SEIZE'], process_id)
self.pid = process_id
def close(self) -> None:
if self.pid is None:
return
2019-10-27 13:05:27 -07:00
os.kill(self.pid, signal.SIGSTOP)
os.waitpid(self.pid, 0)
2017-06-21 01:29:38 -07:00
ptrace(ptrace_commands['PTRACE_DETACH'], self.pid, 0, 0)
os.kill(self.pid, signal.SIGCONT)
2017-06-21 01:29:38 -07:00
self.pid = None
def write_memory(self, base_address: int, write_buffer: ctypes_buffer_t) -> None:
2024-08-01 00:23:25 -07:00
with Path(f'/proc/{self.pid}/mem').open('rb+') as mem:
2017-06-21 01:29:38 -07:00
mem.seek(base_address)
mem.write(write_buffer)
def read_memory(self, base_address: int, read_buffer: ctypes_buffer_t) -> ctypes_buffer_t:
2024-08-01 00:23:25 -07:00
with Path(f'/proc/{self.pid}/mem').open('rb+') as mem:
2017-06-21 01:29:38 -07:00
mem.seek(base_address)
mem.readinto(read_buffer)
return read_buffer
2024-03-30 17:41:23 -07:00
def get_path(self) -> str | None:
2017-06-21 01:29:38 -07:00
try:
2024-08-01 00:23:25 -07:00
with Path(f'/proc/{self.pid}/cmdline').open('rb') as ff:
2024-03-30 17:41:23 -07:00
return ff.read().decode().split('\x00')[0]
2017-06-21 01:29:38 -07:00
except FileNotFoundError:
2024-03-30 17:41:23 -07:00
return None
2017-06-21 01:29:38 -07:00
@staticmethod
def list_available_pids() -> list[int]:
2017-06-21 01:29:38 -07:00
pids = []
for pid_str in os.listdir('/proc'):
try:
pids.append(int(pid_str))
except ValueError:
continue
return pids
@staticmethod
def get_pid_by_name(target_name: str) -> int | None:
2017-06-21 01:29:38 -07:00
for pid in Process.list_available_pids():
try:
logger.debug(f'Checking name for pid {pid}')
2024-08-01 00:23:25 -07:00
with Path(f'/proc/{pid}/cmdline').open('rb') as cmdline:
2017-06-21 01:29:38 -07:00
path = cmdline.read().decode().split('\x00')[0]
except FileNotFoundError:
continue
2024-08-01 00:23:25 -07:00
name = Path(path).name
logger.debug(f'Name was "{name}"')
2017-06-21 01:29:38 -07:00
if path is not None and name == target_name:
return pid
logger.info(f'Found no process with name {target_name}')
2017-06-21 01:29:38 -07:00
return None
def list_mapped_regions(self, writeable_only: bool = True) -> list[tuple[int, int]]:
2017-06-21 01:29:38 -07:00
regions = []
2024-08-01 00:23:25 -07:00
with Path(f'/proc/{self.pid}/maps').open('r') as maps:
2017-06-21 01:29:38 -07:00
for line in maps:
bounds, privileges = line.split()[0:2]
if 'r' not in privileges:
continue
if writeable_only and 'w' not in privileges:
continue
start, stop = (int(bound, 16) for bound in bounds.split('-'))
regions.append((start, stop))
return regions