forked from jan/mem_edit
Compare commits
14 commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 260d67bf81 | |||
| 4deaa41d7e | |||
| 8b5d5af95b | |||
| e8c6c4f74c | |||
| e842f81575 | |||
| 49a7c21ed2 | |||
| 6321d4221c | |||
| d0bbed8db1 | |||
| 83e105dc30 | |||
| 53b1b1ade8 | |||
| 571ecc7a95 | |||
| 5021d5fb9a | |||
| 522999cd61 | |||
| 3b766be616 |
9 changed files with 79 additions and 22 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -4,4 +4,5 @@ __pycache__
|
||||||
*.pyc
|
*.pyc
|
||||||
|
|
||||||
*.egg-info/
|
*.egg-info/
|
||||||
|
build/
|
||||||
dist/
|
dist/
|
||||||
|
|
|
||||||
|
|
@ -1,2 +1,3 @@
|
||||||
include README.md
|
include README.md
|
||||||
include LICENSE.md
|
include LICENSE.md
|
||||||
|
include mem_edit/VERSION
|
||||||
|
|
|
||||||
14
README.md
14
README.md
|
|
@ -18,19 +18,19 @@
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
**Dependencies:**
|
**Dependencies:**
|
||||||
* python 3 (written and tested with 3.5)
|
* python 3 (written and tested with 3.7)
|
||||||
* ctypes
|
* ctypes
|
||||||
* typing (for type annotations)
|
* typing (for type annotations)
|
||||||
|
|
||||||
|
|
||||||
Install with pip, from PyPI (preferred):
|
Install with pip, from PyPI (preferred):
|
||||||
```bash
|
```bash
|
||||||
pip install mem_edit
|
pip3 install mem_edit
|
||||||
```
|
```
|
||||||
|
|
||||||
Install with pip from git repository
|
Install with pip from git repository
|
||||||
```bash
|
```bash
|
||||||
pip install git+https://mpxd.net/code/jan/mem_edit.git@release
|
pip3 install git+https://mpxd.net/code/jan/mem_edit.git@release
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -55,7 +55,7 @@ Increment a magic number (unsigned long 1234567890) found in 'magic.exe':
|
||||||
pid = Process.get_pid_by_name('magic.exe')
|
pid = Process.get_pid_by_name('magic.exe')
|
||||||
with Process.open_process(pid) as p:
|
with Process.open_process(pid) as p:
|
||||||
addrs = p.search_all_memory(magic_number)
|
addrs = p.search_all_memory(magic_number)
|
||||||
|
|
||||||
# We don't want to edit if there's more than one result...
|
# We don't want to edit if there's more than one result...
|
||||||
assert(len(addrs) == 1)
|
assert(len(addrs) == 1)
|
||||||
|
|
||||||
|
|
@ -104,7 +104,7 @@ Read and alter a structure:
|
||||||
s = MyStruct()
|
s = MyStruct()
|
||||||
s.first_member = 1234567890
|
s.first_member = 1234567890
|
||||||
s.second_member = 0x1234
|
s.second_member = 0x1234
|
||||||
|
|
||||||
addrs = p.search_all_memory(s)
|
addrs = p.search_all_memory(s)
|
||||||
print(addrs)
|
print(addrs)
|
||||||
|
|
||||||
|
|
|
||||||
1
mem_edit/VERSION
Normal file
1
mem_edit/VERSION
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
0.3
|
||||||
|
|
@ -12,12 +12,17 @@ To get started, try:
|
||||||
|
|
||||||
"""
|
"""
|
||||||
import platform
|
import platform
|
||||||
|
import pathlib
|
||||||
|
|
||||||
from .utils import MemEditError
|
from .utils import MemEditError
|
||||||
|
|
||||||
|
|
||||||
__author__ = 'Jan Petykiewicz'
|
__author__ = 'Jan Petykiewicz'
|
||||||
|
|
||||||
|
with open(pathlib.Path(__file__).parent / 'VERSION', 'r') as f:
|
||||||
|
__version__ = f.read().strip()
|
||||||
|
version = __version__
|
||||||
|
|
||||||
|
|
||||||
system = platform.system()
|
system = platform.system()
|
||||||
if system == 'Windows':
|
if system == 'Windows':
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,8 @@ import copy
|
||||||
import ctypes
|
import ctypes
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from .utils import ctypes_buffer_t, search_buffer, ctypes_equal
|
from . import utils
|
||||||
|
from .utils import ctypes_buffer_t
|
||||||
|
|
||||||
|
|
||||||
logging.basicConfig(level=logging.INFO)
|
logging.basicConfig(level=logging.INFO)
|
||||||
|
|
@ -240,7 +241,7 @@ class Process(metaclass=ABCMeta):
|
||||||
values = [self.read_memory(base + offset, buffer) for offset, buffer in targets]
|
values = [self.read_memory(base + offset, buffer) for offset, buffer in targets]
|
||||||
return values
|
return values
|
||||||
|
|
||||||
def search_addresses(self, addresses: List[int], needle_buffer: ctypes_buffer_t) -> List[int]:
|
def search_addresses(self, addresses: List[int], needle_buffer: ctypes_buffer_t, verbatim: bool=True) -> List[int]:
|
||||||
"""
|
"""
|
||||||
Search for the provided value at each of the provided addresses, and return the addresses
|
Search for the provided value at each of the provided addresses, and return the addresses
|
||||||
where it is found.
|
where it is found.
|
||||||
|
|
@ -249,18 +250,26 @@ class Process(metaclass=ABCMeta):
|
||||||
:param needle_buffer: The value to search for. This should be a ctypes object of the same
|
:param needle_buffer: The value to search for. This should be a ctypes object of the same
|
||||||
sorts as used by .read_memory(...), which will be compared to the contents of
|
sorts as used by .read_memory(...), which will be compared to the contents of
|
||||||
memory at each of the given addresses.
|
memory at each of the given addresses.
|
||||||
|
:param verbatim: If True, perform bitwise comparison when searching for needle_buffer.
|
||||||
|
If False, perform utils.ctypes_equal-based comparison. Default True.
|
||||||
:return: List of addresses where the needle_buffer was found.
|
:return: List of addresses where the needle_buffer was found.
|
||||||
"""
|
"""
|
||||||
found = []
|
found = []
|
||||||
read_buffer = copy.copy(needle_buffer)
|
read_buffer = copy.copy(needle_buffer)
|
||||||
|
|
||||||
|
if verbatim:
|
||||||
|
def compare(a, b):
|
||||||
|
return bytes(read_buffer) == bytes(needle_buffer)
|
||||||
|
else:
|
||||||
|
compare = utils.ctypes_equal
|
||||||
|
|
||||||
for address in addresses:
|
for address in addresses:
|
||||||
read = self.read_memory(address, read_buffer)
|
self.read_memory(address, read_buffer)
|
||||||
if ctypes_equal(needle_buffer, read):
|
if compare(needle_buffer, read_buffer):
|
||||||
found.append(address)
|
found.append(address)
|
||||||
return found
|
return found
|
||||||
|
|
||||||
def search_all_memory(self, needle_buffer, writeable_only=True) -> List[int]:
|
def search_all_memory(self, needle_buffer: ctypes_buffer_t, writeable_only: bool=True, verbatim: bool=True) -> List[int]:
|
||||||
"""
|
"""
|
||||||
Search the entire memory space accessible to the process for the provided value.
|
Search the entire memory space accessible to the process for the provided value.
|
||||||
|
|
||||||
|
|
@ -268,14 +277,22 @@ class Process(metaclass=ABCMeta):
|
||||||
sorts as used by .read_memory(...), which will be compared to the contents of
|
sorts as used by .read_memory(...), which will be compared to the contents of
|
||||||
memory at each accessible address.
|
memory at each accessible address.
|
||||||
:param writeable_only: If True, only search regions where the process has write access.
|
:param writeable_only: If True, only search regions where the process has write access.
|
||||||
|
Default True.
|
||||||
|
:param verbatim: If True, perform bitwise comparison when searching for needle_buffer.
|
||||||
|
If False, perform utils.ctypes_equal-based comparison. Default True.
|
||||||
:return: List of addresses where the needle_buffer was found.
|
:return: List of addresses where the needle_buffer was found.
|
||||||
"""
|
"""
|
||||||
found = []
|
found = []
|
||||||
|
if verbatim:
|
||||||
|
search = utils.search_buffer_verbatim
|
||||||
|
else:
|
||||||
|
search = utils.search_buffer
|
||||||
|
|
||||||
for start, stop in self.list_mapped_regions(writeable_only):
|
for start, stop in self.list_mapped_regions(writeable_only):
|
||||||
try:
|
try:
|
||||||
region_buffer = (ctypes.c_byte * (stop - start))()
|
region_buffer = (ctypes.c_byte * (stop - start))()
|
||||||
self.read_memory(start, region_buffer)
|
self.read_memory(start, region_buffer)
|
||||||
found += [offset + start for offset in search_buffer(needle_buffer, region_buffer)]
|
found += [offset + start for offset in search(needle_buffer, region_buffer)]
|
||||||
except OSError:
|
except OSError:
|
||||||
logger.error('Failed to read in range 0x{} - 0x{}'.format(start, stop))
|
logger.error('Failed to read in range 0x{} - 0x{}'.format(start, stop))
|
||||||
return found
|
return found
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,7 @@ def ptrace(command: int, pid: int = 0, arg1: int = 0, arg2: int = 0) -> int:
|
||||||
"""
|
"""
|
||||||
Call ptrace() with the provided pid and arguments. See the ```man ptrace```.
|
Call ptrace() with the provided pid and arguments. See the ```man ptrace```.
|
||||||
"""
|
"""
|
||||||
logger.debug('ptrace({}, {}, {}, {})'.format(command, pid, arg1, arg2))
|
logger.debug('ptrace({}, {}, {}, {})'.format(command, pid, arg1, arg2))
|
||||||
result = _ptrace(command, pid, arg1, arg2)
|
result = _ptrace(command, pid, arg1, arg2)
|
||||||
if result == -1:
|
if result == -1:
|
||||||
err_no = ctypes.get_errno()
|
err_no = ctypes.get_errno()
|
||||||
|
|
@ -58,7 +58,7 @@ class Process(AbstractProcess):
|
||||||
self.pid = process_id
|
self.pid = process_id
|
||||||
|
|
||||||
def close(self):
|
def close(self):
|
||||||
os.kill(self.pid, signal.SIGSTOP)
|
os.kill(self.pid, signal.SIGSTOP)
|
||||||
ptrace(ptrace_commands['PTRACE_DETACH'], self.pid, 0, 0)
|
ptrace(ptrace_commands['PTRACE_DETACH'], self.pid, 0, 0)
|
||||||
self.pid = None
|
self.pid = None
|
||||||
|
|
||||||
|
|
@ -78,7 +78,7 @@ class Process(AbstractProcess):
|
||||||
with open('/proc/{}/cmdline', 'rb') as f:
|
with open('/proc/{}/cmdline', 'rb') as f:
|
||||||
return f.read().decode().split('\x00')[0]
|
return f.read().decode().split('\x00')[0]
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
return ''
|
return ''
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def list_available_pids() -> List[int]:
|
def list_available_pids() -> List[int]:
|
||||||
|
|
|
||||||
|
|
@ -23,9 +23,32 @@ class MemEditError(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def search_buffer_verbatim(needle_buffer: ctypes_buffer_t, haystack_buffer: ctypes_buffer_t) -> List[int]:
|
||||||
|
"""
|
||||||
|
Search for a buffer inside another buffer, using a direct (bitwise) comparison
|
||||||
|
|
||||||
|
:param needle_buffer: Buffer to search for.
|
||||||
|
:param haystack_buffer: Buffer to search in.
|
||||||
|
:return: List of offsets where the needle_buffer was found.
|
||||||
|
"""
|
||||||
|
found = []
|
||||||
|
|
||||||
|
haystack = bytes(haystack_buffer)
|
||||||
|
needle = bytes(needle_buffer)
|
||||||
|
|
||||||
|
start = 0
|
||||||
|
result = haystack.find(needle, start)
|
||||||
|
while start < len(haystack) and result != -1:
|
||||||
|
found.append(result)
|
||||||
|
start = result + 1
|
||||||
|
result = haystack.find(needle, start)
|
||||||
|
return found
|
||||||
|
|
||||||
|
|
||||||
def search_buffer(needle_buffer: ctypes_buffer_t, haystack_buffer: ctypes_buffer_t) -> List[int]:
|
def search_buffer(needle_buffer: ctypes_buffer_t, haystack_buffer: ctypes_buffer_t) -> List[int]:
|
||||||
"""
|
"""
|
||||||
Search for a buffer inside another buffer.
|
Search for a buffer inside another buffer, using ctypes_equal for comparison.
|
||||||
|
Much slower than search_buffer_verbatim.
|
||||||
|
|
||||||
:param needle_buffer: Buffer to search for.
|
:param needle_buffer: Buffer to search for.
|
||||||
:param haystack_buffer: Buffer to search in.
|
:param haystack_buffer: Buffer to search in.
|
||||||
|
|
@ -46,7 +69,7 @@ def ctypes_equal(a: ctypes_buffer_t, b: ctypes_buffer_t) -> bool:
|
||||||
"""
|
"""
|
||||||
if not type(a) == type(b):
|
if not type(a) == type(b):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if isinstance(a, ctypes.Array):
|
if isinstance(a, ctypes.Array):
|
||||||
return a[:] == b[:]
|
return a[:] == b[:]
|
||||||
elif isinstance(a, ctypes.Structure) or isinstance(a, ctypes.Union):
|
elif isinstance(a, ctypes.Structure) or isinstance(a, ctypes.Union):
|
||||||
|
|
|
||||||
17
setup.py
17
setup.py
|
|
@ -1,10 +1,18 @@
|
||||||
#!/usr/bin/env python
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
from setuptools import setup, find_packages
|
from setuptools import setup, find_packages
|
||||||
|
|
||||||
|
with open('README.md', 'r') as f:
|
||||||
|
long_description = f.read()
|
||||||
|
|
||||||
|
with open('mem_edit/VERSION', 'r') as f:
|
||||||
|
version = f.read().strip()
|
||||||
|
|
||||||
setup(name='mem_edit',
|
setup(name='mem_edit',
|
||||||
version='0.1',
|
version=version,
|
||||||
description='Multi-platform library for memory editing',
|
description='Multi-platform library for memory editing',
|
||||||
|
long_description=long_description,
|
||||||
|
long_description_content_type='text/markdown',
|
||||||
author='Jan Petykiewicz',
|
author='Jan Petykiewicz',
|
||||||
author_email='anewusername@gmail.com',
|
author_email='anewusername@gmail.com',
|
||||||
url='https://mpxd.net/code/jan/mem_edit',
|
url='https://mpxd.net/code/jan/mem_edit',
|
||||||
|
|
@ -26,7 +34,6 @@ setup(name='mem_edit',
|
||||||
'trainer',
|
'trainer',
|
||||||
],
|
],
|
||||||
classifiers=[
|
classifiers=[
|
||||||
'Programming Language :: Python',
|
|
||||||
'Programming Language :: Python :: 3',
|
'Programming Language :: Python :: 3',
|
||||||
'Development Status :: 4 - Beta',
|
'Development Status :: 4 - Beta',
|
||||||
'Environment :: Other Environment',
|
'Environment :: Other Environment',
|
||||||
|
|
@ -42,8 +49,10 @@ setup(name='mem_edit',
|
||||||
'Topic :: Utilities',
|
'Topic :: Utilities',
|
||||||
],
|
],
|
||||||
packages=find_packages(),
|
packages=find_packages(),
|
||||||
|
package_data={
|
||||||
|
'mem_edit': ['VERSION']
|
||||||
|
},
|
||||||
install_requires=[
|
install_requires=[
|
||||||
'ctypes',
|
|
||||||
'typing',
|
'typing',
|
||||||
],
|
],
|
||||||
extras_require={
|
extras_require={
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue