Skip to content

Commit

Permalink
basic read support for linux !
Browse files Browse the repository at this point in the history
  • Loading branch information
n1nj4sec committed Oct 13, 2016
1 parent 8770100 commit 85ac321
Show file tree
Hide file tree
Showing 11 changed files with 701 additions and 530 deletions.
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
# memorpy
Python library using ctypes to search/edit windows programs memory. Some functions are not implemented as I only implemented what I needed at the time I wrote this ;) But the basic functions should works just fine
Python library using ctypes to search/edit windows and linux programs memory. Some functions are not implemented as I only implemented what I needed at the time I wrote this ;) But the basic functions should works just fine

#TODO
implement LinProcess.write method on linux

#usage examples :
In this example open a notepad.exe (x32) and type in some text we will edit from memory !
Expand Down
6 changes: 3 additions & 3 deletions memorpy/Address.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ def __init__(self, value, process, default_type = 'uint'):
self.default_type = default_type
self.symbolic_name = None

def read(self, type = None, maxlen = None):
def read(self, type = None, maxlen = None, errors='raise'):
if maxlen is None:
try:
int(type)
Expand All @@ -41,9 +41,9 @@ def read(self, type = None, maxlen = None):
if not type:
type = self.default_type
if not maxlen:
return self.process.read(self.value, type=type)
return self.process.read(self.value, type=type, errors=errors)
else:
return self.process.read(self.value, type=type, maxlen=maxlen)
return self.process.read(self.value, type=type, maxlen=maxlen, errors=errors)

def write(self, data, type = None):
if not type:
Expand Down
56 changes: 56 additions & 0 deletions memorpy/BaseProcess.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
#!/usr/bin/env python
# -*- coding: UTF8 -*-

""" 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 read(self, address, type = 'uint', maxlen = 50, errors='raise'):
if type == 's' or type == 'string':
s = self.read_bytes(int(address), bytes=maxlen)
news = ''
for c in s:
if c == '\x00':
return news
news += c
if errors=='ignore':
return news
raise ProcessException('string > maxlen')
else:
if type == 'bytes' or type == 'b':
return self.read_bytes(int(address), bytes=maxlen)
s, l = utils.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 = utils.type_unpack(type)
return self.write_bytes(int(address), struct.pack(s, data))
else:
return self.write_bytes(int(address), data)


112 changes: 112 additions & 0 deletions memorpy/LinProcess.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
# 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 os
from BaseProcess import BaseProcess, ProcessException

c_ptrace = ctypes.CDLL("libc.so.6").ptrace
c_pid_t = ctypes.c_int32 # This assumes pid_t is int32_t
c_ptrace.argtypes = [ctypes.c_int, c_pid_t, ctypes.c_void_p, ctypes.c_void_p]

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:
ptrace=False # no need to ptrace the process when root
else:
ptrace=True
self._open(ptrace)

def close(self):
if self.ptrace_started:
self.ptrace_detach()

def _open(self, ptrace):
if ptrace:
self.ptrace_attach()

@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(16 if attach else 17) #PTRACE_ATTACH or PTRACE_DETACH
c_pid = c_pid_t(self.pid)
null = ctypes.c_void_p()
err = c_ptrace(op, c_pid, null, null)
if err != 0:
raise OSError("%s : Error using ptrace"%err)
self.ptrace_started=True

def iter_region(self, start_offset=None, end_offset=None, protec=None):
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]+) ([-rwpsx]+)', line)
start, end, region_protec = int(m.group(1), 16), int(m.group(2), 16), m.group(3)
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
yield start, chunk

def ptrace_attach(self):
return self._ptrace(True)

def ptrace_detach(self):
return self._ptrace(False)

def write_bytes(self, address, data):
raise NotImplementedError

def read_bytes(self, address, bytes = 4):
data=b''
with open("/proc/" + str(self.pid) + "/mem", 'r', 0) as mem_file:
mem_file.seek(address)
data=mem_file.read(bytes)
return data


8 changes: 8 additions & 0 deletions memorpy/LinStructures.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#!/usr/bin/env python
# -*- coding: UTF8 -*-

#Use some Windows constants for compatibility
PAGE_EXECUTE_READWRITE = 64
PAGE_EXECUTE_READ = 32
PAGE_READONLY = 2
PAGE_READWRITE = 4
49 changes: 7 additions & 42 deletions memorpy/MemWorker.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
#
# 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
Expand All @@ -36,21 +36,6 @@ class MemWorker(object):

def __init__(self, pid=None, name=None, end_offset = None, start_offset = None, debug=True):
self.process = Process.Process(name=name, pid=pid, debug=debug)
if self.process.is_64bit():
si=self.process.GetNativeSystemInfo()
max_addr=si.lpMaximumApplicationAddress
else:
si=self.process.GetSystemInfo()
max_addr=2147418111
min_addr=si.lpMinimumApplicationAddress
if end_offset:
self.end_offset = end_offset
else:
self.end_offset = max_addr
if start_offset:
self.start_offset = start_offset
else:
self.start_offset = min_addr

def Address(self, value, default_type = 'uint'):
""" wrapper to instanciate an Address class for the memworker.process"""
Expand Down Expand Up @@ -118,40 +103,18 @@ def mem_search(self, value, ftype = 'match', protec = PAGE_READWRITE | PAGE_READ
regex = re.compile(value)
else:
regex = value
if start_offset is None:
offset = self.start_offset
start_offset=self.start_offset
else:
offset = start_offset
if end_offset is None:
end_offset = self.end_offset
if ftype == 'float':
structtype, structlen = utils.type_unpack(ftype)
elif ftype != 'match' and ftype != 'group' and ftype != 're':
structtype, structlen = utils.type_unpack(ftype)
value = struct.pack(structtype, value)
while True:
if offset >= end_offset:
break
totalread = 0
mbi = self.process.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
for offset, chunk in self.process.iter_region(start_offset=start_offset, end_offset=end_offset, protec=protec):
b = ''
totalread=0
current_offset=offset
chunk_size=10000000
chunk_read=0
chunk_exc=False
while chunk_read < chunk:
try:
if chunk_size>chunk:
Expand All @@ -160,10 +123,13 @@ def mem_search(self, value, ftype = 'match', protec = PAGE_READWRITE | PAGE_READ
totalread += chunk_size
except Exception as e:
logger.warning(e)
continue
chunk_exc=True
break
finally:
current_offset +=chunk_size
chunk_read += chunk_size
if chunk_exc:
continue

if b:
if ftype == 're':
Expand Down Expand Up @@ -194,5 +160,4 @@ def mem_search(self, value, ftype = 'match', protec = PAGE_READWRITE | PAGE_READ
yield self.Address(soffset, 'bytes')
index = b.find(value, index + 1)

offset += totalread

Loading

0 comments on commit 85ac321

Please sign in to comment.