You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
mem_edit/mem_edit/linux.py

126 lines
3.8 KiB
Python

7 years ago
"""
Implementation of Process class for Linux
"""
from typing import List, Tuple
from os import strerror
import os
import os.path
import signal
import ctypes
import ctypes.util
import logging
from .abstract import Process as AbstractProcess
from .utils import ctypes_buffer_t, MemEditError
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
ptrace_commands = {
'PTRACE_GETREGS': 12,
'PTRACE_SETREGS': 13,
'PTRACE_ATTACH': 16,
'PTRACE_DETACH': 17,
'PTRACE_SYSCALL': 24,
'PTRACE_SEIZE': 16902,
}
# 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:
"""
Call ptrace() with the provided pid and arguments. See the ```man ptrace```.
"""
5 years ago
logger.debug('ptrace({}, {}, {}, {})'.format(command, pid, arg1, arg2))
7 years ago
result = _ptrace(command, pid, arg1, arg2)
if result == -1:
7 years ago
err_no = ctypes.get_errno()
if err_no:
7 years ago
raise MemEditError('ptrace({}, {}, {}, {})'.format(command, pid, arg1, arg2) +
7 years ago
' failed with error {}: {}'.format(err_no, strerror(err_no)))
7 years ago
return result
class Process(AbstractProcess):
pid = None
def __init__(self, process_id: int):
ptrace(ptrace_commands['PTRACE_SEIZE'], process_id)
self.pid = process_id
def close(self):
5 years ago
os.kill(self.pid, signal.SIGSTOP)
7 years ago
ptrace(ptrace_commands['PTRACE_DETACH'], self.pid, 0, 0)
self.pid = None
def write_memory(self, base_address: int, write_buffer: ctypes_buffer_t):
with open('/proc/{}/mem'.format(self.pid), 'rb+') as mem:
mem.seek(base_address)
mem.write(write_buffer)
def read_memory(self, base_address: int, read_buffer: ctypes_buffer_t) -> ctypes_buffer_t:
with open('/proc/{}/mem'.format(self.pid), 'rb+') as mem:
mem.seek(base_address)
mem.readinto(read_buffer)
return read_buffer
def get_path(self) -> str:
try:
7 years ago
with open('/proc/{}/cmdline', 'rb') as f:
return f.read().decode().split('\x00')[0]
7 years ago
except FileNotFoundError:
5 years ago
return ''
7 years ago
@staticmethod
def list_available_pids() -> List[int]:
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 or None:
7 years ago
for pid in Process.list_available_pids():
try:
logger.info('Checking name for pid {}'.format(pid))
with open('/proc/{}/cmdline'.format(pid), 'rb') as cmdline:
path = cmdline.read().decode().split('\x00')[0]
except FileNotFoundError:
continue
name = os.path.basename(path)
logger.info('Name was "{}"'.format(name))
if path is not None and name == target_name:
return pid
logger.info('Found no process with name {}'.format(target_name))
return None
def list_mapped_regions(self, writeable_only: bool = True) -> List[Tuple[int, int]]:
regions = []
with open('/proc/{}/maps'.format(self.pid), 'r') as maps:
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