Skip to content

Commit

Permalink
various optimizations + add basic support for OSX memory reading
Browse files Browse the repository at this point in the history
  • Loading branch information
n1nj4sec committed Apr 29, 2017
1 parent 10f372e commit 79587d3
Show file tree
Hide file tree
Showing 3 changed files with 186 additions and 4 deletions.
14 changes: 10 additions & 4 deletions memorpy/MemWorker.py
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ def mem_search(self, value, ftype = 'match', protec = PAGE_READWRITE | PAGE_READ
tmp.append((name, regex))
value = tmp

elif ftype != 'match' and ftype != 'group' and ftype != 're' and ftype != 'groups' and ftype!='ngroups':
elif ftype != 'match' and ftype != 'group' and ftype != 're' and ftype != 'groups' and ftype != 'ngroups' and ftype != 'lambda':
structtype, structlen = utils.type_unpack(ftype)
value = struct.pack(structtype, value)

Expand All @@ -182,7 +182,8 @@ def mem_search(self, value, ftype = 'match', protec = PAGE_READWRITE | PAGE_READ

elif ftype == 'float':
func = self.parse_float_function

elif ftype == 'lambda': # use a custm function
func = value
else:
func = self.parse_any_function

Expand Down Expand Up @@ -212,11 +213,16 @@ def mem_search(self, value, ftype = 'match', protec = PAGE_READWRITE | PAGE_READ
finally:
current_offset += chunk_size
chunk_read += chunk_size

if chunk_exc:
continue

if b:
for res in func(b, value, offset):
yield res
if ftype=="lambda":
for res in func(b, offset):
yield res
else:
for res in func(b, value, offset):
yield res


174 changes: 174 additions & 0 deletions memorpy/OSXProcess.py
Original file line number Diff line number Diff line change
@@ -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


2 changes: 2 additions & 0 deletions memorpy/Process.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,7 @@
from BaseProcess import *
if sys.platform=='win32':
from WinProcess import WinProcess as Process
elif sys.platform=='darwin':
from OSXProcess import OSXProcess as Process
else:
from LinProcess import LinProcess as Process

0 comments on commit 79587d3

Please sign in to comment.