diff options
Diffstat (limited to 'foreign/client_handling/lazagne/config/lib/memorpy')
16 files changed, 1873 insertions, 0 deletions
| diff --git a/foreign/client_handling/lazagne/config/lib/memorpy/Address.py b/foreign/client_handling/lazagne/config/lib/memorpy/Address.py new file mode 100644 index 0000000..c85ea6f --- /dev/null +++ b/foreign/client_handling/lazagne/config/lib/memorpy/Address.py @@ -0,0 +1,111 @@ +# Author: Nicolas VERDIER +# This file is part of memorpy. +# +# memorpy is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# memorpy is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with memorpy.  If not, see <http://www.gnu.org/licenses/>. + +from .utils import * + +class AddressException(Exception): +    pass + + +class Address(object): +    """ this class is used to have better representation of memory addresses """ + +    def __init__(self, value, process, default_type = 'uint'): +        self.value = int(value) +        self.process = process +        self.default_type = default_type +        self.symbolic_name = None + +    def read(self, type = None, maxlen = None, errors='raise'): +        if maxlen is None: +            try: +                int(type) +                maxlen = int(type) +                type = None +            except: +                pass + +        if not type: +            type = self.default_type +        if not maxlen: +            return self.process.read(self.value, type=type, errors=errors) +        else: +            return self.process.read(self.value, type=type, maxlen=maxlen, errors=errors) + +    def write(self, data, type = None): +        if not type: +            type = self.default_type +        return self.process.write(self.value, data, type=type) + +    def symbol(self): +        return self.process.get_symbolic_name(self.value) + +    def get_instruction(self): +        return self.process.get_instruction(self.value) + +    def dump(self, ftype = 'bytes', size = 512, before = 32): +        buf = self.process.read_bytes(self.value - before, size) +        print(hex_dump(buf, self.value - before, ftype=ftype)) + +    def __nonzero__(self): +        return self.value is not None and self.value != 0 + +    def __add__(self, other): +        return Address(self.value + int(other), self.process, self.default_type) + +    def __sub__(self, other): +        return Address(self.value - int(other), self.process, self.default_type) + +    def __repr__(self): +        if not self.symbolic_name: +            self.symbolic_name = self.symbol() +        return str('<Addr: %s' % self.symbolic_name + '>') + +    def __str__(self): +        if not self.symbolic_name: +            self.symbolic_name = self.symbol() +        return str('<Addr: %s' % self.symbolic_name + ' : "%s" (%s)>' % (str(self.read()).encode('unicode_escape'), self.default_type)) + +    def __int__(self): +        return int(self.value) + +    def __hex__(self): +        return hex(self.value) + +    def __get__(self, instance, owner): +        return self.value + +    def __set__(self, instance, value): +        self.value = int(value) + +    def __lt__(self, other): +        return self.value < int(other) + +    def __le__(self, other): +        return self.value <= int(other) + +    def __eq__(self, other): +        return self.value == int(other) + +    def __ne__(self, other): +        return self.value != int(other) + +    def __gt__(self, other): +        return self.value > int(other) + +    def __ge__(self, other): +        return self.value >= int(other) + diff --git a/foreign/client_handling/lazagne/config/lib/memorpy/BaseProcess.py b/foreign/client_handling/lazagne/config/lib/memorpy/BaseProcess.py new file mode 100644 index 0000000..8766b54 --- /dev/null +++ b/foreign/client_handling/lazagne/config/lib/memorpy/BaseProcess.py @@ -0,0 +1,66 @@ +#!/usr/bin/env python +# -*- coding: UTF8 -*- + +import struct + +from .utils import * + + +""" Base class for process not linked to any platform """ + +class ProcessException(Exception): +    pass + +class BaseProcess(object): + +    def __init__(self, *args, **kwargs): +        """ Create and Open a process object from its pid or from its name """ +        self.h_process = None +        self.pid = None +        self.isProcessOpen = False +        self.buffer = None +        self.bufferlen = 0 + +    def __del__(self): +        self.close() + +    def close(self): +        pass +    def iter_region(self, *args, **kwargs): +        raise NotImplementedError +    def write_bytes(self, address, data): +        raise NotImplementedError + +    def read_bytes(self, address, bytes = 4): +        raise NotImplementedError + +    def get_symbolic_name(self, address): +        return '0x%08X' % int(address) + +    def read(self, address, type = 'uint', maxlen = 50, errors='raise'): +        if type == 's' or type == 'string': +            s = self.read_bytes(int(address), bytes=maxlen) + +            try: +                idx = s.index(b'\x00') +                return s[:idx] +            except: +                if errors == 'ignore': +                    return s + +                raise ProcessException('string > maxlen') + +        else: +            if type == 'bytes' or type == 'b': +                return self.read_bytes(int(address), bytes=maxlen) +            s, l = type_unpack(type) +            return struct.unpack(s, self.read_bytes(int(address), bytes=l))[0] + +    def write(self, address, data, type = 'uint'): +        if type != 'bytes': +            s, l = type_unpack(type) +            return self.write_bytes(int(address), struct.pack(s, data)) +        else: +            return self.write_bytes(int(address), data) +    + diff --git a/foreign/client_handling/lazagne/config/lib/memorpy/LinProcess.py b/foreign/client_handling/lazagne/config/lib/memorpy/LinProcess.py new file mode 100644 index 0000000..6cde871 --- /dev/null +++ b/foreign/client_handling/lazagne/config/lib/memorpy/LinProcess.py @@ -0,0 +1,296 @@ +# Author: Nicolas VERDIER +# This file is part of memorpy. +# +# memorpy is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# memorpy is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with memorpy.  If not, see <http://www.gnu.org/licenses/>. + +import copy +import struct +# import utils +import platform +import ctypes, re, sys +from ctypes import create_string_buffer, byref, c_int, c_void_p, c_long, c_size_t, c_ssize_t, POINTER, get_errno +import errno +import os +import signal +from .BaseProcess import BaseProcess, ProcessException +from .structures import * +import logging + +logger = logging.getLogger('memorpy') + +libc=ctypes.CDLL("libc.so.6", use_errno=True) +get_errno_loc = libc.__errno_location +get_errno_loc.restype = POINTER(c_int) + +def errcheck(ret, func, args): +    if ret == -1: +        _errno = get_errno() or errno.EPERM +        raise OSError(os.strerror(_errno)) +    return ret + +c_ptrace = libc.ptrace +c_pid_t = ctypes.c_int32 # This assumes pid_t is int32_t +c_ptrace.argtypes = [c_int, c_pid_t, c_void_p, c_void_p] +c_ptrace.restype = c_long +mprotect = libc.mprotect +mprotect.restype = c_int +mprotect.argtypes = [c_void_p, c_size_t, c_int] +LARGE_FILE_SUPPORT=False +try: +    c_off64_t=ctypes.c_longlong +    lseek64 = libc.lseek64 +    lseek64.argtypes = [c_int, c_off64_t, c_int] +    lseek64.errcheck=errcheck +    open64 = libc.open64 +    open64.restype = c_int +    open64.argtypes = [c_void_p, c_int] +    open64.errcheck=errcheck +    pread64=libc.pread64 +    pread64.argtypes = [c_int, c_void_p, c_size_t, c_off64_t] +    pread64.restype = c_ssize_t +    pread64.errcheck=errcheck +    c_close=libc.close +    c_close.argtypes = [c_int] +    c_close.restype = c_int +    LARGE_FILE_SUPPORT=True +except: +    logger.warning("no Large File Support") + +class LinProcess(BaseProcess): +    def __init__(self, pid=None, name=None, debug=True, ptrace=None): +        """ Create and Open a process object from its pid or from its name """ +        super(LinProcess, self).__init__() +        self.mem_file=None +        self.ptrace_started=False +        if pid is not None: +            self.pid=pid +        elif name is not None: +            self.pid=LinProcess.pid_from_name(name) +        else: +            raise ValueError("You need to instanciate process with at least a name or a pid") +        if ptrace is None: +            if os.getuid()==0: +                self.read_ptrace=False # no need to ptrace the process when root to read memory +            else: +                self.read_ptrace=True +        self._open() + +    def check_ptrace_scope(self): +        """ check ptrace scope and raise an exception if privileges are unsufficient + +        The sysctl settings (writable only with CAP_SYS_PTRACE) are: + +        0 - classic ptrace permissions: a process can PTRACE_ATTACH to any other +            process running under the same uid, as long as it is dumpable (i.e. +            did not transition uids, start privileged, or have called +            prctl(PR_SET_DUMPABLE...) already). Similarly, PTRACE_TRACEME is +            unchanged. + +        1 - restricted ptrace: a process must have a predefined relationship +            with the inferior it wants to call PTRACE_ATTACH on. By default, +            this relationship is that of only its descendants when the above +            classic criteria is also met. To change the relationship, an +            inferior can call prctl(PR_SET_PTRACER, debugger, ...) to declare +            an allowed debugger PID to call PTRACE_ATTACH on the inferior. +            Using PTRACE_TRACEME is unchanged. + +        2 - admin-only attach: only processes with CAP_SYS_PTRACE may use ptrace +            with PTRACE_ATTACH, or through children calling PTRACE_TRACEME. + +        3 - no attach: no processes may use ptrace with PTRACE_ATTACH nor via +            PTRACE_TRACEME. Once set, this sysctl value cannot be changed. +        """ +        try: +            with open("/proc/sys/kernel/yama/ptrace_scope",'rb') as f: +                ptrace_scope=int(f.read().strip()) +            if ptrace_scope==3: +                logger.warning("yama/ptrace_scope == 3 (no attach). :/") +            if os.getuid()==0: +                return +            elif ptrace_scope == 1: +                logger.warning("yama/ptrace_scope == 1 (restricted). you can't ptrace other process ... get root") +            elif ptrace_scope == 2: +                logger.warning("yama/ptrace_scope == 2 (admin-only). Warning: check you have CAP_SYS_PTRACE") + +        except IOError: +            pass + +        except Exception as e: +            logger.warning("Error getting ptrace_scope ?? : %s"%e) + +    def close(self): +        if self.mem_file: +            if not LARGE_FILE_SUPPORT: +                self.mem_file.close() +            else: +                c_close(self.mem_file) +            self.mem_file=None +        if self.ptrace_started: +            self.ptrace_detach() + +    def __del__(self): +        self.close() + +    def _open(self): +        self.isProcessOpen = True +        self.check_ptrace_scope() +        if os.getuid()!=0: +            #to raise an exception if ptrace is not allowed +            self.ptrace_attach() +            self.ptrace_detach() + +        #open file descriptor +        if not LARGE_FILE_SUPPORT: +            self.mem_file=open("/proc/" + str(self.pid) + "/mem", 'rb', 0) +        else: +            path=create_string_buffer(b"/proc/%d/mem" % self.pid) +            self.mem_file=open64(byref(path), os.O_RDONLY) + +    @staticmethod +    def list(): +        processes=[] +        for pid in os.listdir("/proc"): +            try: +                exe=os.readlink("/proc/%s/exe"%pid) +                processes.append({"pid":int(pid), "name":exe}) +            except: +                pass +        return processes + +    @staticmethod +    def pid_from_name(name): +        #quick and dirty, works with all linux not depending on ps output +        for pid in os.listdir("/proc"): +            try: +                int(pid) +            except: +                continue +            pname="" +            with open("/proc/%s/cmdline"%pid,'r') as f: +                pname=f.read() +            if name in pname: +                return int(pid) +        raise ProcessException("No process with such name: %s"%name) + +    ## Partial interface to ptrace(2), only for PTRACE_ATTACH and PTRACE_DETACH. +    def _ptrace(self, attach): +        op = ctypes.c_int(PTRACE_ATTACH if attach else PTRACE_DETACH) +        c_pid = c_pid_t(self.pid) +        null = ctypes.c_void_p() + +        if not attach: +            os.kill(self.pid, signal.SIGSTOP) +            os.waitpid(self.pid, 0) + +        err = c_ptrace(op, c_pid, null, null) + +        if not attach: +            os.kill(self.pid, signal.SIGCONT) + +        if err != 0: +            raise OSError("%s: %s"%( +                'PTRACE_ATTACH' if attach else 'PTRACE_DETACH', +                errno.errorcode.get(ctypes.get_errno(), 'UNKNOWN') +            )) + +    def iter_region(self, start_offset=None, end_offset=None, protec=None, optimizations=None): +        """ +            optimizations : +                i for inode==0 (no file mapping) +                s to avoid scanning shared regions +                x to avoid scanning x regions +                r don't scan ronly regions +        """ +        with open("/proc/" + str(self.pid) + "/maps", 'r') as maps_file: +            for line in maps_file: +                m = re.match(r'([0-9A-Fa-f]+)-([0-9A-Fa-f]+)\s+([-rwpsx]+)\s+([0-9A-Fa-f]+)\s+([0-9A-Fa-f]+:[0-9A-Fa-f]+)\s+([0-9]+)\s*(.*)', line) +                if not m: +                    continue +                start, end, region_protec, offset, dev, inode, pathname = int(m.group(1), 16), int(m.group(2), 16), m.group(3), m.group(4), m.group(5), int(m.group(6)), m.group(7) +                if start_offset is not None: +                    if start < start_offset: +                        continue +                if end_offset is not None: +                    if start > end_offset: +                        continue +                chunk=end-start +                if 'r' in region_protec: # TODO: handle protec parameter +                    if optimizations: +                        if 'i' in optimizations and inode != 0: +                            continue +                        if 's' in optimizations and 's' in region_protec: +                            continue +                        if 'x' in optimizations and 'x' in region_protec: +                            continue +                        if 'r' in optimizations and not 'w' in region_protec: +                            continue +                    yield start, chunk + +    def ptrace_attach(self): +        if not self.ptrace_started: +            res=self._ptrace(True) +            self.ptrace_started=True +        return res + +    def ptrace_detach(self): +        if self.ptrace_started: +            res=self._ptrace(False) +            self.ptrace_started=False +        return res + +    def write_bytes(self, address, data): +        if not self.ptrace_started: +            self.ptrace_attach() + +        c_pid = c_pid_t(self.pid) +        null = ctypes.c_void_p() + + +        #we can only copy data per range of 4 or 8 bytes +        word_size=ctypes.sizeof(ctypes.c_void_p) +        #mprotect(address, len(data)+(len(data)%word_size), PROT_WRITE|PROT_READ) +        for i in range(0, len(data), word_size): +            word=data[i:i+word_size] +            if len(word)<word_size: #we need to let some data untouched, so let's read at given offset to complete our 8 bytes +                existing_data=self.read_bytes(int(address)+i+len(word), bytes=(word_size-len(word))) +                word+=existing_data +            if sys.byteorder=="little": +                word=word[::-1] + +            attempt=0 +            err = c_ptrace(ctypes.c_int(PTRACE_POKEDATA), c_pid, int(address)+i, int(word.encode("hex"), 16)) +            if err != 0: +                error=errno.errorcode.get(ctypes.get_errno(), 'UNKNOWN') +                raise OSError("Error using PTRACE_POKEDATA: %s"%error) + +        self.ptrace_detach() +        return True + +    def read_bytes(self, address, bytes = 4): +        if self.read_ptrace: +            self.ptrace_attach() +        data=b'' +        if not LARGE_FILE_SUPPORT: +            mem_file.seek(address) +            data=mem_file.read(bytes) +        else: +            lseek64(self.mem_file, address, os.SEEK_SET) +            data=b"" +            try: +                data=os.read(self.mem_file, bytes) +            except Exception as e: +                logger.info("Error reading %s at %s: %s"%((bytes),address, e)) +        if self.read_ptrace: +            self.ptrace_detach() +        return data diff --git a/foreign/client_handling/lazagne/config/lib/memorpy/LinStructures.py b/foreign/client_handling/lazagne/config/lib/memorpy/LinStructures.py new file mode 100644 index 0000000..e1c6e60 --- /dev/null +++ b/foreign/client_handling/lazagne/config/lib/memorpy/LinStructures.py @@ -0,0 +1,20 @@ +#!/usr/bin/env python +# -*- coding: UTF8 -*- + +PROT_NONE = 0 +PROT_READ = 1 +PROT_WRITE = 2 +PROT_EXEC = 4 +PROT_PRIVATE = 8 +PROT_SHARED = 16 + +#Use some Windows constants for compatibility +PAGE_EXECUTE_READWRITE = PROT_EXEC | PROT_READ | PROT_WRITE +PAGE_EXECUTE_READ = PROT_EXEC | PROT_READ +PAGE_READONLY = PROT_READ +PAGE_READWRITE = PROT_READ | PROT_WRITE + +PTRACE_POKEDATA = 5  +PTRACE_ATTACH = 16 +PTRACE_DETACH =17 +PTRACE_CONT = 7 diff --git a/foreign/client_handling/lazagne/config/lib/memorpy/Locator.py b/foreign/client_handling/lazagne/config/lib/memorpy/Locator.py new file mode 100644 index 0000000..0468db2 --- /dev/null +++ b/foreign/client_handling/lazagne/config/lib/memorpy/Locator.py @@ -0,0 +1,96 @@ +# Author: Nicolas VERDIER +# This file is part of memorpy. +# +# memorpy is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# memorpy is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with memorpy.  If not, see <http://www.gnu.org/licenses/>. + +import copy +import time +import struct + +from .Address import Address + + +class Locator(object): +    """  +            take a memoryworker and a type to search +            then you can feed the locator with values and it will reduce the addresses possibilities +    """ + +    def __init__(self, mw, type = 'unknown', start = None, end = None): +        self.mw = mw +        self.type = type +        self.last_iteration = {} +        self.last_value = None +        self.start = start +        self.end = end + +    def find(self, value, erase_last = True): +        return self.feed(value, erase_last) + +    def feed(self, value, erase_last = True): +        self.last_value = value +        new_iter = copy.copy(self.last_iteration) +        if self.type == 'unknown': +            all_types = ['uint', +             'int', +             'long', +             'ulong', +             'float', +             'double', +             'short', +             'ushort'] +        else: +            all_types = [self.type] +        for type in all_types: +            if type not in new_iter: +                try: +                    new_iter[type] = [ Address(x, self.mw.process, type) for x in self.mw.mem_search(value, type, start_offset=self.start, end_offset=self.end) ] +                except struct.error: +                    new_iter[type] = [] +            else: +                l = [] +                for address in new_iter[type]: +                    try: +                        found = self.mw.process.read(address, type) +                        if int(found) == int(value): +                            l.append(Address(address, self.mw.process, type)) +                    except Exception as e: +                        pass + +                new_iter[type] = l + +        if erase_last: +            del self.last_iteration +            self.last_iteration = new_iter +        return new_iter + +    def get_addresses(self): +        return self.last_iteration + +    def diff(self, erase_last = False): +        return self.get_modified_addr(erase_last) + +    def get_modified_addr(self, erase_last = False): +        last = self.last_iteration +        new = self.feed(self.last_value, erase_last=erase_last) +        ret = {} +        for type, l in last.iteritems(): +            typeset = set(new[type]) +            for addr in l: +                if addr not in typeset: +                    if type not in ret: +                        ret[type] = [] +                    ret[type].append(addr) + +        return ret diff --git a/foreign/client_handling/lazagne/config/lib/memorpy/MemWorker.py b/foreign/client_handling/lazagne/config/lib/memorpy/MemWorker.py new file mode 100644 index 0000000..4a971bb --- /dev/null +++ b/foreign/client_handling/lazagne/config/lib/memorpy/MemWorker.py @@ -0,0 +1,226 @@ +# Author: Nicolas VERDIER +# This file is part of memorpy. +# +# memorpy is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# memorpy is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with memorpy.  If not, see <http://www.gnu.org/licenses/>. +import sys +import string +import re +import logging +import traceback +import binascii +import struct + +from .Process import * +from .utils import * +from .Address import Address +from .BaseProcess import ProcessException +from .structures import * + +logger = logging.getLogger('memorpy') + +REGEX_TYPE=type(re.compile("^plop$")) +class MemWorker(object): + +    def __init__(self, pid=None, name=None, end_offset = None, start_offset = None, debug=True): +        self.process = Process(name=name, pid=pid, debug=debug) + +    def __enter__(self): +        return self + +    def __exit__(self, type, value, traceback): +        self.process.close() + +    def Address(self, value, default_type = 'uint'): +        """ wrapper to instanciate an Address class for the memworker.process""" +        return Address(value, process=self.process, default_type=default_type) + +    def umem_replace(self, regex, replace): +        """ like search_replace_mem but works with unicode strings """ +        regex = re_to_unicode(regex) +        replace = replace.encode('utf-16-le') +        return self.mem_replace(re.compile(regex, re.UNICODE), replace) + +    def mem_replace(self, regex, replace): +        """ search memory for a pattern and replace all found occurrences """ +        allWritesSucceed = True +        for _, start_offset in self.mem_search(regex, ftype='re'): +            if self.process.write_bytes(start_offset, replace) == 1: +                logger.debug('Write at offset %s succeeded !' % start_offset) +            else: +                allWritesSucceed = False +                logger.debug('Write at offset %s failed !' % start_offset) + +        return allWritesSucceed + +    def umem_search(self, regex): +        """ like mem_search but works with unicode strings """ +        regex = re_to_unicode(regex) +        for _, i in self.mem_search(str(regex), ftype='re'): +            yield i + +    def group_search(self, group, start_offset = None, end_offset = None): +        regex = '' +        for value, type in group: +            if type == 'f' or type == 'float': +                f = struct.pack('<f', float(value)) +                regex += '..' + f[2:4] +            else: +                raise NotImplementedError('unknown type %s' % type) + +        return self.mem_search(regex, ftype='re', start_offset=start_offset, end_offset=end_offset) + +    def search_address(self, addr): +        a = '%08X' % addr +        logger.debug('searching address %s' % a) +        regex = '' +        for i in range(len(a) - 2, -1, -2): +            regex += binascii.unhexlify(a[i:i + 2]) + +        for _, a in self.mem_search(re.escape(regex), ftype='re'): +            yield a + +    def parse_re_function(self, b, value, offset): +        for name, regex in value: +            for res in regex.finditer(b): +                yield name, self.Address(offset+res.start(), 'bytes') +                """ +                index = b.find(res) +                while index != -1: +                    soffset = offset + index +                    if soffset not in duplicates_cache: +                        duplicates_cache.add(soffset) +                        yield name, self.Address(soffset, 'bytes') +                    index = b.find(res, index + len(res)) +                """ + +    def parse_float_function(self, b, value, offset): +        for index in range(0, len(b)): +            try: +                structtype, structlen = type_unpack('float') +                tmpval = struct.unpack(structtype, b[index:index + 4])[0] +                if int(value) == int(tmpval): +                    soffset = offset + index +                    yield self.Address(soffset, 'float') +            except Exception as e: +                pass + +    def parse_named_groups_function(self, b, value, offset=None): +        for name, regex in value: +            for res in regex.finditer(b): +                yield name, res.groupdict() + +    def parse_groups_function(self, b, value, offset=None): +        for name, regex in value: +            for res in regex.finditer(b): +                yield name, res.groups() + +    def parse_any_function(self, b, value, offset): +        index = b.find(value) +        while index != -1: +            soffset = offset + index +            yield self.Address(soffset, 'bytes') +            index = b.find(value, index + 1) + +    def mem_search(self, value, ftype = 'match', protec = PAGE_READWRITE | PAGE_READONLY, optimizations=None, start_offset = None, end_offset = None): +        """  +                iterator returning all indexes where the pattern has been found +        """ +         +        # pre-compile regex to run faster +        if ftype == 're' or ftype == 'groups' or ftype == 'ngroups': +             +            # value should be an array of regex +            if type(value) is not list: +                value = [value] +             +            tmp = [] +            for reg in value: +                if type(reg) is tuple: +                    name = reg[0] +                    if type(reg[1]) != REGEX_TYPE: +                        regex = re.compile(reg[1], re.IGNORECASE) +                    else: +                        regex=reg[1] +                elif type(reg) == REGEX_TYPE: +                    name = '' +                    regex=reg +                else: +                    name = '' +                    regex = re.compile(reg, re.IGNORECASE) + + +                tmp.append((name, regex)) +            value = tmp + +        elif ftype != 'match' and ftype != 'group' and ftype != 're' and ftype != 'groups' and ftype != 'ngroups' and ftype != 'lambda': +            structtype, structlen = type_unpack(ftype) +            value = struct.pack(structtype, value) + +        # different functions avoid if statement before parsing the buffer +        if ftype == 're': +            func = self.parse_re_function         +         +        elif ftype == 'groups': +            func = self.parse_groups_function + +        elif ftype == 'ngroups': +            func = self.parse_named_groups_function + +        elif ftype == 'float': +            func = self.parse_float_function +        elif ftype == 'lambda': # use a custm function +            func = value +        else: +            func = self.parse_any_function + +        if not self.process.isProcessOpen: +            raise ProcessException("Can't read_bytes, process %s is not open" % (self.process.pid)) + +        for offset, chunk_size in self.process.iter_region(start_offset=start_offset, end_offset=end_offset, protec=protec, optimizations=optimizations): +            b = b'' +            current_offset = offset +            chunk_read = 0 +            chunk_exc = False +            while chunk_read < chunk_size: +                try: +                    b += self.process.read_bytes(current_offset, chunk_size) +                except IOError as e: +                    print(traceback.format_exc()) +                    if e.errno == 13: +                        raise +                    else: +                        logger.warning(e) +                    chunk_exc=True +                    break +                except Exception as e: +                    print('coucou') +                    logger.warning(e) +                    chunk_exc = True +                    break +                finally: +                    current_offset += chunk_size +                    chunk_read += chunk_size + +            if chunk_exc: +                continue + +            if b: +                if ftype=="lambda": +                    for res in func(b.decode('latin'), offset): +                        yield res +                else: +                    for res in func(b.decode('latin'), value, offset): +                        yield res + + diff --git a/foreign/client_handling/lazagne/config/lib/memorpy/OSXProcess.py b/foreign/client_handling/lazagne/config/lib/memorpy/OSXProcess.py new file mode 100644 index 0000000..bd702cd --- /dev/null +++ b/foreign/client_handling/lazagne/config/lib/memorpy/OSXProcess.py @@ -0,0 +1,174 @@ +# Author: Nicolas VERDIER +# This file is part of memorpy. +# +# memorpy is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# memorpy is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with memorpy.  If not, see <http://www.gnu.org/licenses/>. + +import copy +import struct +import utils +import platform +import ctypes, re, sys +import ctypes.util +import errno +import os +import signal +from .BaseProcess import BaseProcess, ProcessException +from .structures import * +import logging +import subprocess + +logger = logging.getLogger('memorpy') + +libc = ctypes.CDLL(ctypes.util.find_library('c')) + +VM_REGION_BASIC_INFO_64    = 9 + +class vm_region_basic_info_64(ctypes.Structure): +    _fields_ = [ +        ('protection',      ctypes.c_uint32), +        ('max_protection',  ctypes.c_uint32), +        ('inheritance',     ctypes.c_uint32), +        ('shared',          ctypes.c_uint32), +        ('reserved',        ctypes.c_uint32), +        ('offset',          ctypes.c_ulonglong), +        ('behavior',        ctypes.c_uint32), +        ('user_wired_count',ctypes.c_ushort), +] + +VM_REGION_BASIC_INFO_COUNT_64 = ctypes.sizeof(vm_region_basic_info_64) / 4 + +VM_PROT_READ    = 1 +VM_PROT_WRITE    = 2 +VM_PROT_EXECUTE    = 4 + +class OSXProcess(BaseProcess): +    def __init__(self, pid=None, name=None, debug=True): +        """ Create and Open a process object from its pid or from its name """ +        super(OSXProcess, self).__init__() +        if pid is not None: +            self.pid=pid +        elif name is not None: +            self.pid=OSXProcess.pid_from_name(name) +        else: +            raise ValueError("You need to instanciate process with at least a name or a pid") +        self.task=None +        self.mytask=None +        self._open() + +    def close(self): +        pass + +    def __del__(self): +        pass + +    def _open(self): +        self.isProcessOpen = True +        self.task = ctypes.c_uint32() +        self.mytask=libc.mach_task_self() +        ret=libc.task_for_pid(self.mytask, ctypes.c_int(self.pid), ctypes.pointer(self.task)) +        if ret!=0: +            raise ProcessException("task_for_pid failed with error code : %s"%ret) + +    @staticmethod +    def list(): +        #TODO list processes with ctypes +        processes=[] +        res=subprocess.check_output("ps A", shell=True) +        for line in res.split('\n'): +            try: +                tab=line.split() +                pid=int(tab[0]) +                exe=' '.join(tab[4:]) +                processes.append({"pid":int(pid), "name":exe}) +            except: +                pass +        return processes + +    @staticmethod +    def pid_from_name(name): +        for dic in OSXProcess.list(): +            if name in dic['exe']: +                return dic['pid'] + + +    def iter_region(self, start_offset=None, end_offset=None, protec=None, optimizations=None): +        """ +            optimizations : +                i for inode==0 (no file mapping) +                s to avoid scanning shared regions +                x to avoid scanning x regions +                r don't scan ronly regions +        """ +        maps = [] +        address = ctypes.c_ulong(0) +        mapsize = ctypes.c_ulong(0) +        name    = ctypes.c_uint32(0) +        count   = ctypes.c_uint32(VM_REGION_BASIC_INFO_COUNT_64) +        info    = vm_region_basic_info_64() + +        while True: +            r = libc.mach_vm_region(self.task, ctypes.pointer(address), +                                   ctypes.pointer(mapsize), VM_REGION_BASIC_INFO_64, +                                   ctypes.pointer(info), ctypes.pointer(count), +                                   ctypes.pointer(name)) +            # If we get told "invalid address", we have crossed into kernel land... +            if r == 1: +                break + +            if r != 0: +                raise ProcessException('mach_vm_region failed with error code %s' % r) +            if start_offset is not None: +                if address.value < start_offset: +                    address.value += mapsize.value +                    continue +            if end_offset is not None: +                if address.value > end_offset: +                    break +            p = info.protection +            if p & VM_PROT_EXECUTE: +                if optimizations and 'x' in optimizations: +                    address.value += mapsize.value +                    continue +            if info.shared: +                if optimizations and 's' in optimizations: +                    address.value += mapsize.value +                    continue +            if p & VM_PROT_READ: +                if not (p & VM_PROT_WRITE): +                    if optimizations and 'r' in optimizations: +                        address.value += mapsize.value +                        continue +                yield address.value, mapsize.value +             +            address.value += mapsize.value + + +    def write_bytes(self, address, data): +        raise NotImplementedError("write not implemented on OSX") +        return True + +    def read_bytes(self, address, bytes = 4): +        pdata = ctypes.c_void_p(0) +        data_cnt = ctypes.c_uint32(0) +         +        ret = libc.mach_vm_read(self.task, ctypes.c_ulonglong(address), ctypes.c_longlong(bytes), ctypes.pointer(pdata), ctypes.pointer(data_cnt)); +        #if ret==1: +        #    return "" +        if ret!=0: +            raise ProcessException("mach_vm_read returned : %s"%ret) +        buf=ctypes.string_at(pdata.value, data_cnt.value) +        libc.vm_deallocate(self.mytask, pdata, data_cnt) +        return buf + + diff --git a/foreign/client_handling/lazagne/config/lib/memorpy/Process.py b/foreign/client_handling/lazagne/config/lib/memorpy/Process.py new file mode 100644 index 0000000..b586cd8 --- /dev/null +++ b/foreign/client_handling/lazagne/config/lib/memorpy/Process.py @@ -0,0 +1,13 @@ +#!/usr/bin/env python +# -*- coding: UTF8 -*- + +import sys +from .BaseProcess import * +if sys.platform=='win32': +    from .WinProcess import WinProcess as Process +elif sys.platform=='darwin': +    from .OSXProcess import OSXProcess as Process +elif 'sunos' in sys.platform: +    from .SunProcess import SunProcess as Process +else: +    from .LinProcess import LinProcess as Process diff --git a/foreign/client_handling/lazagne/config/lib/memorpy/SunProcess.py b/foreign/client_handling/lazagne/config/lib/memorpy/SunProcess.py new file mode 100644 index 0000000..831c7f6 --- /dev/null +++ b/foreign/client_handling/lazagne/config/lib/memorpy/SunProcess.py @@ -0,0 +1,167 @@ +# This file is part of memorpy. +# +# memorpy is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# memorpy is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with memorpy.  If not, see <http://www.gnu.org/licenses/>. + +from .BaseProcess import BaseProcess, ProcessException +import struct +import os + +MA_READ      =    0x04 +MA_WRITE     =    0x02 +MA_EXEC      =    0x01 +MA_SHARED    =    0x08 +MA_ANON      =    0x40 +MA_ISM       =    0x80 +MA_NORESERVE =    0x100 +MA_SHM       =    0x200 +MA_RESERVED1 =    0x400 +MA_OSM       =    0x800 + +PSINFO_T = struct.Struct( +    'iiiIIIIIIIILLLLHHLLLLLL16s80siiLLciILLcccchi8sLLIIIIII' +) + +MAP_T = struct.Struct( +    'LL64sQiiii' +) + +class SunProcess(BaseProcess): +    def __init__(self, pid=None, name=None, debug=True, ptrace=None): +        ''' Create and Open a process object from its pid or from its name ''' +        super(SunProcess, self).__init__() +        self.pid = int(pid) +        self.pas = None +        self.writable = False +        if name and not self.pid: +            self.pid = SunProcess.pid_from_name(name) +        if not name and not self.pid: +            raise ValueError('You need to instanciate process with at least a name or a pid') +        try: +            self._open() +        except: +            pass + +    def close(self): +        if self.pas: +            self.pas.close() + +    def __del__(self): +        self.close() + +    def _open(self): +        try: +            self.pas = open('/proc/%d/as'%(self.pid), 'w+') +            self.writable = True +        except IOError: +            self.pas = open('/proc/%d/as'%(self.pid)) + +        self.isProcessOpen = True + +    @staticmethod +    def _name_args(pid): +        with open('/proc/%d/psinfo'%(int(pid))) as psinfo: +            items = PSINFO_T.unpack_from(psinfo.read()) +            return items[23].rstrip('\x00'), items[24].rstrip('\x00') + +    @staticmethod +    def list(): +        processes=[] +        for pid in os.listdir('/proc'): +            try: +                pid = int(pid) +                name, _ = SunProcess._name_args(pid) +                processes.append({ +                    'pid': pid, +                    'name': name +                }) +            except: +                pass + +        return processes + +    @staticmethod +    def pid_from_name(name): +        processes=[] +        for pid in os.listdir('/proc'): +            try: +                pid = int(pid) +                pname, cmdline = SunProcess._name_args(pid) +                if name in pname: +                    return pid +                if name in cmdline.split(' ', 1)[0]: +                    return pid +            except: +                pass + +        raise ProcessException('No process with such name: %s'%name) + +    def iter_region(self, start_offset=None, end_offset=None, protec=None, optimizations=None): +        """ +            optimizations : +                i for inode==0 (no file mapping) +                s to avoid scanning shared regions +                x to avoid scanning x regions +                r don't scan ronly regions +        """ +        if not self.isProcessOpen: +            return + +        with open('/proc/%d/map'%(self.pid)) as maps_file: +            while True: +                mapping = maps_file.read(MAP_T.size) + +                if not mapping: +                    break + +                start, size, name, offset, flags, pagesize, shmid, filler = MAP_T.unpack(mapping) + +                if start_offset is not None: +                    if start < start_offset: +                        continue + +                if end_offset is not None: +                    if start > end_offset: +                        continue + +                if not flags & MA_READ: +                    continue + +                if optimizations: +                    if 'i' in optimizations and not flags & MA_ANON: +                        continue +                    if 's' in optimizations and flags & MA_SHM: +                        continue +                    # in sunos it's quite common when this flag is set, so let's use other letter +                    if 'X' in optimizations and flags & MA_EXEC: +                        continue +                    if 'r' in optimizations and not flags & MA_WRITE: +                        continue + +                yield start, size + +    def write_bytes(self, address, data): +        if not self.pas or not self.writable: +            return False + +        self.pas.seek(address) +        self.pas.write(data) + +        return True + +    def read_bytes(self, address, bytes = 4): +        if not self.pas: +            return + +        self.pas.seek(address) +        return self.pas.read(bytes) diff --git a/foreign/client_handling/lazagne/config/lib/memorpy/WinProcess.py b/foreign/client_handling/lazagne/config/lib/memorpy/WinProcess.py new file mode 100644 index 0000000..18c7054 --- /dev/null +++ b/foreign/client_handling/lazagne/config/lib/memorpy/WinProcess.py @@ -0,0 +1,312 @@ +# Author: Nicolas VERDIER +# This file is part of memorpy. +# +# memorpy is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# memorpy is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with memorpy.  If not, see <http://www.gnu.org/licenses/>. + +from ctypes import pointer, sizeof, windll, create_string_buffer, c_ulong, byref, GetLastError, c_bool, WinError +from .structures import * +import copy +import struct +# import utils +import platform +from .BaseProcess import BaseProcess, ProcessException + +psapi       = windll.psapi +kernel32    = windll.kernel32 +advapi32    = windll.advapi32 + +IsWow64Process=None +if hasattr(kernel32,'IsWow64Process'): +    IsWow64Process=kernel32.IsWow64Process +    IsWow64Process.restype = c_bool +    IsWow64Process.argtypes = [c_void_p, POINTER(c_bool)] + +class WinProcess(BaseProcess): + +    def __init__(self, pid=None, name=None, debug=True): +        """ Create and Open a process object from its pid or from its name """ +        super(WinProcess, self).__init__() +        if pid: +            self._open(int(pid), debug=debug) +             +        elif name: +            self._open_from_name(name, debug=debug) +        else: +            raise ValueError("You need to instanciate process with at least a name or a pid") +         +        if self.is_64bit(): +            si = self.GetNativeSystemInfo() +            self.max_addr = si.lpMaximumApplicationAddress +        else: +            si = self.GetSystemInfo() +            self.max_addr = 2147418111 +        self.min_addr = si.lpMinimumApplicationAddress + + +    def __del__(self): +        self.close() + +    def is_64bit(self): +        if not "64" in platform.machine(): +            return False +        iswow64 = c_bool(False) +        if IsWow64Process is None: +            return False +        if not IsWow64Process(self.h_process, byref(iswow64)): +            raise WinError() +        return not iswow64.value + +    @staticmethod +    def list(): +        processes=[] +        arr = c_ulong * 256 +        lpidProcess= arr() +        cb = sizeof(lpidProcess) +        cbNeeded = c_ulong() +        hModule = c_ulong() +        count = c_ulong() +        modname = create_string_buffer(100) +        PROCESS_QUERY_INFORMATION = 0x0400 +        PROCESS_VM_READ = 0x0010 + +        psapi.EnumProcesses(byref(lpidProcess), cb, byref(cbNeeded)) +        nReturned = cbNeeded.value/sizeof(c_ulong()) + +        pidProcess = [i for i in lpidProcess][:nReturned] +        for pid in pidProcess: +            proc={ "pid": int(pid) } +            hProcess = kernel32.OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, False, pid) +            if hProcess: +                psapi.EnumProcessModules(hProcess, byref(hModule), sizeof(hModule), byref(count)) +                psapi.GetModuleBaseNameA(hProcess, hModule.value, modname, sizeof(modname)) +                proc["name"]=modname.value +                kernel32.CloseHandle(hProcess) +            processes.append(proc) +        return processes + +    @staticmethod +    def processes_from_name(processName): +        processes = [] +        for process in WinProcess.list(): +            if processName == process.get("name", None) or (process.get("name","").lower().endswith(".exe") and process.get("name","")[:-4]==processName): +                processes.append(process) + +        if len(processes) > 0: +            return processes + +    @staticmethod +    def name_from_process(dwProcessId): +        process_list = WinProcess.list() +        for process in process_list: +            if process.pid == dwProcessId: +                return process.get("name", None) + +        return False + +    def _open(self, dwProcessId, debug=False): +        if debug: +            ppsidOwner              = DWORD() +            ppsidGroup              = DWORD() +            ppDacl                  = DWORD() +            ppSacl                  = DWORD() +            ppSecurityDescriptor    = SECURITY_DESCRIPTOR() + +            process = kernel32.OpenProcess(262144, 0, dwProcessId) +            advapi32.GetSecurityInfo(kernel32.GetCurrentProcess(), 6, 0, byref(ppsidOwner), byref(ppsidGroup), byref(ppDacl), byref(ppSacl), byref(ppSecurityDescriptor)) +            advapi32.SetSecurityInfo(process, 6, DACL_SECURITY_INFORMATION | UNPROTECTED_DACL_SECURITY_INFORMATION, None, None, ppSecurityDescriptor.dacl, ppSecurityDescriptor.group) +            kernel32.CloseHandle(process) +        self.h_process = kernel32.OpenProcess(2035711, 0, dwProcessId) +        if self.h_process is not None: +            self.isProcessOpen = True +            self.pid = dwProcessId +            return True +        return False + +    def close(self): +        if self.h_process is not None: +            ret = kernel32.CloseHandle(self.h_process) == 1 +            if ret: +                self.h_process = None +                self.pid = None +                self.isProcessOpen = False +            return ret +        return False + +    def _open_from_name(self, processName, debug=False): +        processes = self.processes_from_name(processName) +        if not processes: +            raise ProcessException("can't get pid from name %s" % processName) +        elif len(processes)>1: +            raise ValueError("There is multiple processes with name %s. Please select a process from its pid instead"%processName) +        if debug: +            self._open(processes[0]["pid"], debug=True) +        else: +            self._open(processes[0]["pid"], debug=False) + +    def GetSystemInfo(self): +        si = SYSTEM_INFO() +        kernel32.GetSystemInfo(byref(si)) +        return si + +    def GetNativeSystemInfo(self): +        si = SYSTEM_INFO() +        kernel32.GetNativeSystemInfo(byref(si)) +        return si + +    def VirtualQueryEx(self, lpAddress): +        mbi = MEMORY_BASIC_INFORMATION() +        if not VirtualQueryEx(self.h_process, lpAddress, byref(mbi), sizeof(mbi)): +            raise ProcessException('Error VirtualQueryEx: 0x%08X' % lpAddress) +        return mbi + +    def VirtualQueryEx64(self, lpAddress): +        mbi = MEMORY_BASIC_INFORMATION64() +        if not VirtualQueryEx64(self.h_process, lpAddress, byref(mbi), sizeof(mbi)): +            raise ProcessException('Error VirtualQueryEx: 0x%08X' % lpAddress) +        return mbi + +    def VirtualProtectEx(self, base_address, size, protection): +        old_protect = c_ulong(0) +        if not kernel32.VirtualProtectEx(self.h_process, base_address, size, protection, byref(old_protect)): +            raise ProcessException('Error: VirtualProtectEx(%08X, %d, %08X)' % (base_address, size, protection)) +        return old_protect.value + +    def iter_region(self, start_offset=None, end_offset=None, protec=None, optimizations=None): +         +        offset = start_offset or self.min_addr +        end_offset = end_offset or self.max_addr + +        while True: +            if offset >= end_offset: +                break +            mbi = self.VirtualQueryEx(offset) +            offset = mbi.BaseAddress +            chunk = mbi.RegionSize +            protect = mbi.Protect +            state = mbi.State +            #print "offset: %s, chunk:%s"%(offset, chunk) +            if state & MEM_FREE or state & MEM_RESERVE: +                offset += chunk +                continue +            if protec: +                if not protect & protec or protect & PAGE_NOCACHE or protect & PAGE_WRITECOMBINE or protect & PAGE_GUARD: +                    offset += chunk +                    continue +            yield offset, chunk +            offset += chunk + +    def write_bytes(self, address, data): +        address = int(address) +        if not self.isProcessOpen: +            raise ProcessException("Can't write_bytes(%s, %s), process %s is not open" % (address, data, self.pid)) +        buffer = create_string_buffer(data) +        sizeWriten = c_size_t(0) +        bufferSize = sizeof(buffer) - 1 +        _address = address +        _length = bufferSize + 1 +        try: +            old_protect = self.VirtualProtectEx(_address, _length, PAGE_EXECUTE_READWRITE) +        except: +            pass + +        res = kernel32.WriteProcessMemory(self.h_process, address, buffer, bufferSize, byref(sizeWriten)) +        try: +            self.VirtualProtectEx(_address, _length, old_protect) +        except: +            pass + +        return res + +    def read_bytes(self, address, bytes = 4, use_NtWow64ReadVirtualMemory64=False): +        #print "reading %s bytes from addr %s"%(bytes, address) +        if use_NtWow64ReadVirtualMemory64: +            if NtWow64ReadVirtualMemory64 is None: +                raise WindowsError("NtWow64ReadVirtualMemory64 is not available from a 64bit process") +            RpM = NtWow64ReadVirtualMemory64 +        else: +            RpM = ReadProcessMemory + +        address = int(address) +        buffer = create_string_buffer(bytes) +        bytesread = c_size_t(0) +        data = b'' +        length = bytes +        while length: +            if RpM(self.h_process, address, buffer, bytes, byref(bytesread)) or (use_NtWow64ReadVirtualMemory64 and GetLastError() == 0): +                if bytesread.value: +                    data += buffer.raw[:bytesread.value] +                    length -= bytesread.value +                    address += bytesread.value +                if not len(data): +                    raise ProcessException('Error %s in ReadProcessMemory(%08x, %d, read=%d)' % (GetLastError(), +                     address, +                     length, +                     bytesread.value)) +                return data +            else: +                if GetLastError()==299: #only part of ReadProcessMemory has been done, let's return it +                    data += buffer.raw[:bytesread.value] +                    return data +                raise WinError() +            # data += buffer.raw[:bytesread.value] +            # length -= bytesread.value +            # address += bytesread.value +        return data + +    +    def list_modules(self): +        module_list = [] +        if self.pid is not None: +            hModuleSnap = CreateToolhelp32Snapshot(TH32CS_CLASS.SNAPMODULE, self.pid) +            if hModuleSnap is not None: +                module_entry = MODULEENTRY32() +                module_entry.dwSize = sizeof(module_entry) +                success = Module32First(hModuleSnap, byref(module_entry)) +                while success: +                    if module_entry.th32ProcessID == self.pid: +                        module_list.append(copy.copy(module_entry)) +                    success = Module32Next(hModuleSnap, byref(module_entry)) + +                kernel32.CloseHandle(hModuleSnap) +        return module_list + +    def get_symbolic_name(self, address): +        for m in self.list_modules(): +            if int(m.modBaseAddr) <= int(address) < int(m.modBaseAddr + m.modBaseSize): +                return '%s+0x%08X' % (m.szModule, int(address) - m.modBaseAddr) + +        return '0x%08X' % int(address) + +    def hasModule(self, module): +        if module[-4:] != '.dll': +            module += '.dll' +        module_list = self.list_modules() +        for m in module_list: +            if module in m.szExePath.split('\\'): +                return True +        return False +     + +    def get_instruction(self, address): +        """ +        Pydasm disassemble utility function wrapper. Returns the pydasm decoded instruction in self.instruction. +        """ +        import pydasm +        try: +            data = self.read_bytes(int(address), 32) +        except: +            return 'Unable to disassemble at %08x' % address + +        return pydasm.get_instruction(data, pydasm.MODE_32) + diff --git a/foreign/client_handling/lazagne/config/lib/memorpy/WinStructures.py b/foreign/client_handling/lazagne/config/lib/memorpy/WinStructures.py new file mode 100644 index 0000000..ac49d36 --- /dev/null +++ b/foreign/client_handling/lazagne/config/lib/memorpy/WinStructures.py @@ -0,0 +1,190 @@ +# Author: Nicolas VERDIER +# This file is part of memorpy. +# +# memorpy is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# memorpy is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with memorpy.  If not, see <http://www.gnu.org/licenses/>. + +from ctypes import Structure, c_long, c_int, c_uint, c_char, c_void_p, c_ubyte, c_ushort, c_ulong, c_ulonglong, windll, POINTER, sizeof, c_bool, c_size_t, c_longlong +from ctypes.wintypes import * + +if sizeof(c_void_p) == 8: +    ULONG_PTR = c_ulonglong +else: +    ULONG_PTR = c_ulong + + +class SECURITY_DESCRIPTOR(Structure):  +    _fields_ = [ +        ('SID', DWORD), +        ('group', DWORD), +        ('dacl', DWORD), +        ('sacl', DWORD), +        ('test', DWORD) +    ] +PSECURITY_DESCRIPTOR = POINTER(SECURITY_DESCRIPTOR) + +class MEMORY_BASIC_INFORMATION(Structure): +    _fields_ = [('BaseAddress', c_void_p), +     ('AllocationBase', c_void_p), +     ('AllocationProtect', DWORD), +     ('RegionSize', c_size_t), +     ('State', DWORD), +     ('Protect', DWORD), +     ('Type', DWORD)] + +# https://msdn.microsoft.com/fr-fr/library/windows/desktop/aa366775(v=vs.85).aspx +class MEMORY_BASIC_INFORMATION64(Structure): +    _fields_ = [('BaseAddress', c_ulonglong), +     ('AllocationBase', c_ulonglong), +     ('AllocationProtect', DWORD), +     ('alignement1', DWORD), +     ('RegionSize', c_ulonglong), +     ('State', DWORD), +     ('Protect', DWORD), +     ('Type', DWORD), +     ('alignement2', DWORD)] + + + +class SYSTEM_INFO(Structure): +    _fields_ = [('wProcessorArchitecture', WORD), +     ('wReserved', WORD), +     ('dwPageSize', DWORD), +     ('lpMinimumApplicationAddress', LPVOID), +     ('lpMaximumApplicationAddress', LPVOID), +     ('dwActiveProcessorMask', ULONG_PTR), +     ('dwNumberOfProcessors', DWORD), +     ('dwProcessorType', DWORD), +     ('dwAllocationGranularity', DWORD), +     ('wProcessorLevel', WORD), +     ('wProcessorRevision', WORD)] + + +class PROCESSENTRY32(Structure): +    _fields_ = [('dwSize', c_uint), +     ('cntUsage', c_uint), +     ('th32ProcessID', c_uint), +     ('th32DefaultHeapID', c_uint), +     ('th32ModuleID', c_uint), +     ('cntThreads', c_uint), +     ('th32ParentProcessID', c_uint), +     ('pcPriClassBase', c_long), +     ('dwFlags', DWORD), +     #('dwFlags', ULONG_PTR), +     ('szExeFile', c_char * 260), +     ('th32MemoryBase', c_long), +     ('th32AccessKey', c_long)] + + +class MODULEENTRY32(Structure): +    _fields_ = [('dwSize', c_uint), +     ('th32ModuleID', c_uint), +     ('th32ProcessID', c_uint), +     ('GlblcntUsage', c_uint), +     ('ProccntUsage', c_uint), +     ('modBaseAddr', c_uint), +     ('modBaseSize', c_uint), +     ('hModule', c_uint), +     ('szModule', c_char * 256), +     ('szExePath', c_char * 260)] + + +class THREADENTRY32(Structure): +    _fields_ = [('dwSize', c_uint), +     ('cntUsage', c_uint), +     ('th32ThreadID', c_uint), +     ('th32OwnerProcessID', c_uint), +     ('tpBasePri', c_uint), +     ('tpDeltaPri', c_uint), +     ('dwFlags', c_uint)] + + +class TH32CS_CLASS(object): +    INHERIT = 2147483648 +    SNAPHEAPLIST = 1 +    SNAPMODULE = 8 +    SNAPMODULE32 = 16 +    SNAPPROCESS = 2 +    SNAPTHREAD = 4 +    ALL = 2032639 + + +Module32First = windll.kernel32.Module32First +Module32First.argtypes = [c_void_p, POINTER(MODULEENTRY32)] +Module32First.rettype = c_int +Module32Next = windll.kernel32.Module32Next +Module32Next.argtypes = [c_void_p, POINTER(MODULEENTRY32)] +Module32Next.rettype = c_int + +Process32First = windll.kernel32.Process32First +Process32First.argtypes = [c_void_p, POINTER(PROCESSENTRY32)] +Process32First.rettype = c_int +Process32Next = windll.kernel32.Process32Next +Process32Next.argtypes = [c_void_p, POINTER(PROCESSENTRY32)] +Process32Next.rettype = c_int + +CreateToolhelp32Snapshot = windll.kernel32.CreateToolhelp32Snapshot +CreateToolhelp32Snapshot.reltype = c_long +CreateToolhelp32Snapshot.argtypes = [c_int, c_int] + +CloseHandle = windll.kernel32.CloseHandle +CloseHandle.argtypes = [c_void_p] +CloseHandle.rettype = c_int + +OpenProcess = windll.kernel32.OpenProcess +OpenProcess.argtypes = [c_void_p, c_int, c_long] +OpenProcess.rettype = c_long +OpenProcessToken = windll.advapi32.OpenProcessToken +OpenProcessToken.argtypes = (HANDLE, DWORD, POINTER(HANDLE)) +OpenProcessToken.restype = BOOL + +ReadProcessMemory = windll.kernel32.ReadProcessMemory +ReadProcessMemory.argtypes = [HANDLE, LPCVOID, LPVOID, c_size_t, POINTER(c_size_t)] +ReadProcessMemory = windll.kernel32.ReadProcessMemory + +WriteProcessMemory = windll.kernel32.WriteProcessMemory +WriteProcessMemory.argtypes = [HANDLE, LPVOID, LPCVOID, c_size_t, POINTER(c_size_t)] +WriteProcessMemory.restype = BOOL + +if sizeof(c_void_p) == 8: +    NtWow64ReadVirtualMemory64=None +else: +    try: +        NtWow64ReadVirtualMemory64 = windll.ntdll.NtWow64ReadVirtualMemory64 +        NtWow64ReadVirtualMemory64.argtypes = [HANDLE, c_longlong, LPVOID, c_ulonglong, POINTER(c_ulong)] # NTSTATUS (__stdcall *NtWow64ReadVirtualMemory64)(HANDLE ProcessHandle, PVOID64 BaseAddress, PVOID Buffer, ULONGLONG BufferSize, PULONGLONG NumberOfBytesRead); +        NtWow64ReadVirtualMemory64.restype = BOOL +    except: +        NtWow64ReadVirtualMemory64=None + +VirtualQueryEx = windll.kernel32.VirtualQueryEx +VirtualQueryEx.argtypes = [HANDLE, LPCVOID, POINTER(MEMORY_BASIC_INFORMATION), c_size_t] +VirtualQueryEx.restype = c_size_t + +#VirtualQueryEx64 = windll.kernel32.VirtualQueryEx +#VirtualQueryEx64.argtypes = [HANDLE, LPCVOID, POINTER(MEMORY_BASIC_INFORMATION64), c_size_t] +#VirtualQueryEx64.restype = c_size_t + +PAGE_EXECUTE_READWRITE = 64 +PAGE_EXECUTE_READ = 32 +PAGE_READONLY = 2 +PAGE_READWRITE = 4 +PAGE_NOCACHE = 512 +PAGE_WRITECOMBINE = 1024 +PAGE_GUARD = 256 + +MEM_COMMIT = 4096 +MEM_FREE = 65536 +MEM_RESERVE = 8192 + +UNPROTECTED_DACL_SECURITY_INFORMATION = 536870912 +DACL_SECURITY_INFORMATION = 4
\ No newline at end of file diff --git a/foreign/client_handling/lazagne/config/lib/memorpy/__init__.py b/foreign/client_handling/lazagne/config/lib/memorpy/__init__.py new file mode 100644 index 0000000..853fcea --- /dev/null +++ b/foreign/client_handling/lazagne/config/lib/memorpy/__init__.py @@ -0,0 +1,32 @@ +# Author: Nicolas VERDIER +# This file is part of memorpy. +# +# memorpy is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# memorpy is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with memorpy.  If not, see <http://www.gnu.org/licenses/>. + + +import logging +logger=logging.getLogger("memorpy") +logger.setLevel(logging.WARNING) +ch = logging.StreamHandler() +ch.setLevel(logging.WARNING) +logger.addHandler(ch) + +import sys +from .MemWorker import * +from .Locator import * +from .Address import * +from .Process import * +from .utils import * +#if sys.platform=="win32": +#    from wintools import *  #not a necessary dependency, just used for debugging diff --git a/foreign/client_handling/lazagne/config/lib/memorpy/structures.py b/foreign/client_handling/lazagne/config/lib/memorpy/structures.py new file mode 100644 index 0000000..a08c2ee --- /dev/null +++ b/foreign/client_handling/lazagne/config/lib/memorpy/structures.py @@ -0,0 +1,8 @@ +#!/usr/bin/env python +# -*- coding: UTF8 -*- + +import sys +if sys.platform=="win32": +    from .WinStructures import * +else: +    from .LinStructures import * diff --git a/foreign/client_handling/lazagne/config/lib/memorpy/utils.py b/foreign/client_handling/lazagne/config/lib/memorpy/utils.py new file mode 100644 index 0000000..5b1c58a --- /dev/null +++ b/foreign/client_handling/lazagne/config/lib/memorpy/utils.py @@ -0,0 +1,121 @@ +# Author: Nicolas VERDIER +# This file is part of memorpy. +# +# memorpy is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# memorpy is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with memorpy.  If not, see <http://www.gnu.org/licenses/>. + +import re +import struct + +def re_to_unicode(s): +    newstring = '' +    for c in s: +        newstring += re.escape(c) + '\\x00' + +    return newstring + + +def type_unpack(type): +    """ return the struct and the len of a particular type """ +    type = type.lower() +    s = None +    l = None +    if type == 'short': +        s = 'h' +        l = 2 +    elif type == 'ushort': +        s = 'H' +        l = 2 +    elif type == 'int': +        s = 'i' +        l = 4 +    elif type == 'uint': +        s = 'I' +        l = 4 +    elif type == 'long': +        s = 'l' +        l = 4 +    elif type == 'ulong': +        s = 'L' +        l = 4 +    elif type == 'float': +        s = 'f' +        l = 4 +    elif type == 'double': +        s = 'd' +        l = 8 +    else: +        raise TypeError('Unknown type %s' % type) +    return ('<' + s, l) + + +def hex_dump(data, addr = 0, prefix = '', ftype = 'bytes'): +    """ +    function originally from pydbg, modified to display other types +    """ +    dump = prefix +    slice = '' +    if ftype != 'bytes': +        structtype, structlen = type_unpack(ftype) +        for i in range(0, len(data), structlen): +            if addr % 16 == 0: +                dump += ' ' +                for char in slice: +                    if ord(char) >= 32 and ord(char) <= 126: +                        dump += char +                    else: +                        dump += '.' + +                dump += '\n%s%08X: ' % (prefix, addr) +                slice = '' +            tmpval = 'NaN' +            try: +                packedval = data[i:i + structlen] +                tmpval = struct.unpack(structtype, packedval)[0] +            except Exception as e: +                print(e) + +            if tmpval == 'NaN': +                dump += '{:<15} '.format(tmpval) +            elif ftype == 'float': +                dump += '{:<15.4f} '.format(tmpval) +            else: +                dump += '{:<15} '.format(tmpval) +            addr += structlen + +    else: +        for byte in data: +            if addr % 16 == 0: +                dump += ' ' +                for char in slice: +                    if ord(char) >= 32 and ord(char) <= 126: +                        dump += char +                    else: +                        dump += '.' + +                dump += '\n%s%08X: ' % (prefix, addr) +                slice = '' +            dump += '%02X ' % byte +            slice += chr(byte) +            addr += 1 + +    remainder = addr % 16 +    if remainder != 0: +        dump += '   ' * (16 - remainder) + ' ' +    for char in slice: +        if ord(char) >= 32 and ord(char) <= 126: +            dump += char +        else: +            dump += '.' + +    return dump + '\n' diff --git a/foreign/client_handling/lazagne/config/lib/memorpy/version.py b/foreign/client_handling/lazagne/config/lib/memorpy/version.py new file mode 100644 index 0000000..51b8469 --- /dev/null +++ b/foreign/client_handling/lazagne/config/lib/memorpy/version.py @@ -0,0 +1,6 @@ +#!/usr/bin/env python +# -*- coding: UTF8 -*- + +version=(1,7) +version_string="%s.%s"%version + diff --git a/foreign/client_handling/lazagne/config/lib/memorpy/wintools.py b/foreign/client_handling/lazagne/config/lib/memorpy/wintools.py new file mode 100644 index 0000000..f2bf936 --- /dev/null +++ b/foreign/client_handling/lazagne/config/lib/memorpy/wintools.py @@ -0,0 +1,35 @@ +# Author: Nicolas VERDIER +# This file is part of memorpy. +# +# memorpy is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# memorpy is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with memorpy.  If not, see <http://www.gnu.org/licenses/>. + +from ctypes import windll +import time + +def start_winforeground_daemon(): +	import threading +	t=threading.Thread(target=window_foreground_loop) +	t.daemon=True +	t.start() + +def window_foreground_loop(timeout=20): +	""" set the windows python console to the foreground (for example when you are working with a fullscreen program) """ +	hwnd = windll.kernel32.GetConsoleWindow() +	HWND_TOPMOST 	= -1  +	SWP_NOMOVE 		= 2 +	SWP_NOSIZE 		= 1 +	while True: +		windll.user32.SetWindowPos(hwnd, HWND_TOPMOST, 0,0,0,0, SWP_NOMOVE | SWP_NOSIZE) +		time.sleep(timeout) +	
\ No newline at end of file | 
