From 42f581e3e19430c1458be1a86d6c1b74eb148f0e Mon Sep 17 00:00:00 2001 From: Jakob Date: Sat, 2 Sep 2017 18:28:20 -0400 Subject: Began to implement runtime manipulation utilities. --- hypodermic/memory.py | 8 ++-- hypodermic/process.py | 129 +++++++++++++++++++++++++++++++++++++++++++++++++- setup.py | 2 +- wrapper/ptrace.c | 72 ++++++++++++++++++++++++++++ 4 files changed, 205 insertions(+), 6 deletions(-) diff --git a/hypodermic/memory.py b/hypodermic/memory.py index 6ffa5d1..0d86855 100644 --- a/hypodermic/memory.py +++ b/hypodermic/memory.py @@ -42,7 +42,7 @@ def parse_device(line: str) -> Device: object. Args: - line(str): The line to parse. + line (str): The line to parse. Returns: The parsed Device object. @@ -56,7 +56,7 @@ def parse_perms(line: str) -> Perms: object. Args: - line(str): The line to parse. + line (str): The line to parse. Returns: The parsed Perms object. @@ -69,7 +69,7 @@ def parse_region(line: str) -> Region: object. Args: - line(str): The line to parse. + line (str): The line to parse. Returns: The parsed Region object. @@ -90,7 +90,7 @@ def maps(pid: int) -> list: Args: pid (int): The pid of the process to get memory mapping - information for. + information for. Raises: TypeError: If the pid argument is not an int. diff --git a/hypodermic/process.py b/hypodermic/process.py index 6fc1519..9a321e0 100644 --- a/hypodermic/process.py +++ b/hypodermic/process.py @@ -21,8 +21,60 @@ import ctypes import os.path import re +from elftools.elf.elffile import ELFFile + from hypodermic.memory import Region, maps +AMD64_INDICES = { + "r15": 0, + "r14": 1, + "r13": 2, + "r12": 3, + "rbp": 4, + "rbx": 5, + "r11": 6, + "r10": 7, + "r9": 8, + "r8": 9, + "rax": 10, + "rcx": 11, + "rdx": 12, + "rsi": 13, + "rdi": 14, + "orig_rax": 15, + "rip": 16, + "cs": 17, + "eflags": 18, + "rsp": 19, + "ss": 20, + "fs_base": 21, + "gs_base": 22, + "ds": 23, + "es": 24, + "fs": 25, + "gs": 26 +} + +I386_INDICES = { + "ebx": 0, + "ecx": 1, + "edx": 2, + "esi": 3, + "edi": 4, + "ebp": 5, + "eax": 6, + "xds": 7, + "xes": 8, + "xfs": 9, + "xgs": 10, + "orig_eax": 11, + "eip": 12, + "xcs": 13, + "eflags": 14, + "esp": 15, + "xss": 16 +} + class Process(object): """Process attached via ptrace. @@ -84,6 +136,10 @@ class Process(object): self._attach = self._so.attach self._detach = self._so.detach self._cont = self._so.cont + self._getreg32 = self._so.getreg32 + self._getreg32.restype = ctypes.c_ulong + self._getreg64 = self._so.getreg64 + self._getreg64.restype = ctypes.c_ulonglong def detach(self): """Explicitly detaches from the process. @@ -94,7 +150,7 @@ class Process(object): if not self._is_parent and self._detach(ctypes.c_int(self.pid)): raise OSError("Could not detach from pid {}".format(self.pid)) - def cont(self): + def continue_until_haulted(self): """Continues until the program is haulted. Raises: @@ -103,6 +159,77 @@ class Process(object): if self._cont(ctypes.c_int(self.pid)): raise OSError("Could not continue") + def write_bytes(self, address: int, src: bytes) -> int: + """Writes data into process memory. + + Args: + address (int): The address at which to write the bytes. + src (:obj:`bytes`): The bytes to write. + + Raises: + ValueError: If the address does not exist in the process + address space. + + Returns: + The number of bytes written. + """ + for region in self.maps: + if address >= region.start and address + len(src) < region.end: + break + else: + raise ValueError("address was not in the process address space") + + with open("/proc/{}/mem".format(self.pid), "wb") as mem: + mem.seek(address) + return mem.write(src) + + def read_bytes(self, address: int, n: int) -> bytes: + """Reads data from process memory. + + Args: + address (int): The address at which to read from. + n (int): The number of bytes to read. + + Raises: + ValueError: If the address does not exist in the process + address space. + + Returns: + A `bytes` object containing the bytes read. + """ + for region in self.maps: + if address >= region.start and address + n < region.end: + break + else: + raise ValueError("address was not in the process address space") + + with open("/proc/{}/mem".format(self.pid), "rb") as mem: + mem.seek(address) + return mem.read(n) + + def get_register(self, reg: str) -> int: + """Returns the value of the given register. + + Args: + reg (str): The register to inspect. (e.g. "rax") + + Returns: + An integer representing the value of the register. + """ + regs = AMD64_INDICES if self.arch == "x64" else I386_INDICES + + if reg not in regs: + raise ValueError("{} is not a valid register".format(reg)) + + if self.arch == "x64": + return self._getreg64(self.pid, regs.get(reg)) + return self._getreg32(self.pid, regs.get(reg)) + + @property + def arch(self) -> str: + with open("/proc/{}/exe".format(self.pid), "rb") as elf: + return ELFFile(elf).get_machine_arch() + @property def maps(self) -> list: """Obtain the process' memory map. diff --git a/setup.py b/setup.py index 58ca457..4788a81 100644 --- a/setup.py +++ b/setup.py @@ -30,7 +30,7 @@ setup( packages=["hypodermic"], include_package_data=True, ext_modules=[lib], - install_requires=[], + install_requires=["pyelftools"], extras_require={}, tests_require=[], entry_points={"console_scripts": ["hypodermic = hypodermic.main:main"]}, diff --git a/wrapper/ptrace.c b/wrapper/ptrace.c index 706b13e..7e5dfa8 100644 --- a/wrapper/ptrace.c +++ b/wrapper/ptrace.c @@ -84,3 +84,75 @@ int cont(int pid) { return 0; } + + +/* user_regs_struct is copied from sys/user.h so that we can debug a + 32-bit executable on a 64-bit platform. */ +struct amd64_user_regs_struct { + __extension__ unsigned long long r15; + __extension__ unsigned long long r14; + __extension__ unsigned long long r13; + __extension__ unsigned long long r12; + __extension__ unsigned long long rbp; + __extension__ unsigned long long rbx; + __extension__ unsigned long long r11; + __extension__ unsigned long long r10; + __extension__ unsigned long long r9; + __extension__ unsigned long long r8; + __extension__ unsigned long long rax; + __extension__ unsigned long long rcx; + __extension__ unsigned long long rdx; + __extension__ unsigned long long rsi; + __extension__ unsigned long long rdi; + __extension__ unsigned long long orig_rax; + __extension__ unsigned long long rip; + __extension__ unsigned long long cs; + __extension__ unsigned long long eflags; + __extension__ unsigned long long rsp; + __extension__ unsigned long long ss; + __extension__ unsigned long long fs_base; + __extension__ unsigned long long gs_base; + __extension__ unsigned long long ds; + __extension__ unsigned long long es; + __extension__ unsigned long long fs; + __extension__ unsigned long long gs; +}; + + +struct i386_user_regs_struct { + unsigned long ebx; + unsigned long ecx; + unsigned long edx; + unsigned long esi; + unsigned long edi; + unsigned long ebp; + unsigned long eax; + unsigned long xds; + unsigned long xes; + unsigned long xfs; + unsigned long xgs; + unsigned long orig_eax; + unsigned long eip; + unsigned long xcs; + unsigned long eflags; + unsigned long esp; + unsigned long xss; +}; + + +unsigned long long getreg64(int pid, int idx) { + struct amd64_user_regs_struct regs; + + ptrace(PTRACE_GETREGS, pid, NULL, ®s); + + return ((unsigned long long *) ®s)[idx]; +} + + +unsigned long getreg32(int pid, int idx) { + struct i386_user_regs_struct regs; + + ptrace(PTRACE_GETREGS, pid, NULL, ®s); + + return ((unsigned long *) ®s)[idx]; +} -- cgit v1.3