summaryrefslogtreecommitdiff
path: root/foreign/client_handling/lazagne/config/lib/memorpy
diff options
context:
space:
mode:
authorAL-LCL <alvin@alvinhavel.com>2023-05-19 11:01:49 +0200
committerAL-LCL <alvin@alvinhavel.com>2023-05-19 11:01:49 +0200
commit20dbeb2f38684c65ff0a4b99012c161295708e88 (patch)
treea5b8445f55da2fbbb92443b68e9d7354a290c598 /foreign/client_handling/lazagne/config/lib/memorpy
NeoRATHEADmain
Diffstat (limited to 'foreign/client_handling/lazagne/config/lib/memorpy')
-rw-r--r--foreign/client_handling/lazagne/config/lib/memorpy/Address.py111
-rw-r--r--foreign/client_handling/lazagne/config/lib/memorpy/BaseProcess.py66
-rw-r--r--foreign/client_handling/lazagne/config/lib/memorpy/LinProcess.py296
-rw-r--r--foreign/client_handling/lazagne/config/lib/memorpy/LinStructures.py20
-rw-r--r--foreign/client_handling/lazagne/config/lib/memorpy/Locator.py96
-rw-r--r--foreign/client_handling/lazagne/config/lib/memorpy/MemWorker.py226
-rw-r--r--foreign/client_handling/lazagne/config/lib/memorpy/OSXProcess.py174
-rw-r--r--foreign/client_handling/lazagne/config/lib/memorpy/Process.py13
-rw-r--r--foreign/client_handling/lazagne/config/lib/memorpy/SunProcess.py167
-rw-r--r--foreign/client_handling/lazagne/config/lib/memorpy/WinProcess.py312
-rw-r--r--foreign/client_handling/lazagne/config/lib/memorpy/WinStructures.py190
-rw-r--r--foreign/client_handling/lazagne/config/lib/memorpy/__init__.py32
-rw-r--r--foreign/client_handling/lazagne/config/lib/memorpy/structures.py8
-rw-r--r--foreign/client_handling/lazagne/config/lib/memorpy/utils.py121
-rw-r--r--foreign/client_handling/lazagne/config/lib/memorpy/version.py6
-rw-r--r--foreign/client_handling/lazagne/config/lib/memorpy/wintools.py35
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