├── requirements.txt ├── MANIFEST.in ├── LICENSE ├── tests └── testing_script.py ├── setup.py ├── .gitignore ├── README.md └── ReadWriteMemory └── __init__.py /requirements.txt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include *.txt 2 | include LICENSE 3 | recursive-include tests *.py 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Victor Santiago 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /tests/testing_script.py: -------------------------------------------------------------------------------- 1 | from ReadWriteMemory import ReadWriteMemory 2 | from random import randint 3 | 4 | rwm = ReadWriteMemory() 5 | process = rwm.get_process_by_name('ac_client.exe') 6 | process.open() 7 | 8 | print('\nPrint the Process information.') 9 | print(process.__dict__) 10 | 11 | health_pointer = process.get_pointer(0x004e4dbc, offsets=[0xf4]) 12 | ammo_pointer = process.get_pointer(0x004df73c, offsets=[0x378, 0x14, 0x0]) 13 | grenade_pointer = process.get_pointer(0x004df73c, offsets=[0x35c, 0x14, 0x0]) 14 | print(health_pointer) 15 | 16 | health = process.read(health_pointer) 17 | ammo = process.read(ammo_pointer) 18 | grenade = process.read(grenade_pointer) 19 | 20 | print('\nPrinting the current values.') 21 | print({'Health': health, 'Ammo': ammo, 'Grenade': grenade}) 22 | 23 | process.write(health_pointer, randint(1, 100)) 24 | process.write(ammo_pointer, randint(1, 20)) 25 | process.write(grenade_pointer, randint(1, 5)) 26 | 27 | health = process.read(health_pointer) 28 | ammo = process.read(ammo_pointer) 29 | grenade = process.read(grenade_pointer) 30 | 31 | print('\nPrinting the new modified random values.') 32 | print({'Health': health, 'Ammo': ammo, 'Grenade': grenade}) 33 | 34 | process.close() 35 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | with open('README.md', 'r', encoding='utf-8') as f: 4 | long_description = f.read() 5 | 6 | setup(name='ReadWriteMemory', 7 | packages=['ReadWriteMemory'], 8 | version='0.1.5', 9 | license='MIT', 10 | description='ReadWriteMemory Class to work with Windows process memory and hacking video games.', 11 | long_description=long_description, 12 | long_description_content_type='text/markdown', 13 | author='Victor M Santiago', 14 | author_email='vsantiago113sec@gmail.com', 15 | url='https://github.com/vsantiago113/ReadWriteMemory', 16 | download_url='https://github.com/vsantiago113/ReadWriteMemory/archive/0.1.5.zip', 17 | keywords=['ReadWriteMemory', 'Hacking', 'Cheat Engine'], 18 | python_requires='>=3.4.0', 19 | classifiers=[ 20 | 'Development Status :: 5 - Production/Stable', 21 | 'Topic :: Utilities', 22 | 'License :: OSI Approved :: MIT License', 23 | 'Operating System :: MacOS :: MacOS X', 24 | 'Operating System :: Microsoft :: Windows', 25 | 'Operating System :: POSIX :: Linux', 26 | 'Programming Language :: Python', 27 | 'Intended Audience :: Developers', 28 | 'Programming Language :: Python :: 3.4', 29 | 'Programming Language :: Python :: 3.5', 30 | 'Programming Language :: Python :: 3.6', 31 | 'Programming Language :: Python :: 3.7', 32 | 'Programming Language :: Python :: 3.8', 33 | ], 34 | ) 35 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | Test/build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | 49 | # Translations 50 | *.mo 51 | *.pot 52 | 53 | # Django stuff: 54 | *.log 55 | local_settings.py 56 | 57 | # Flask stuff: 58 | instance/ 59 | .webassets-cache 60 | 61 | # Scrapy stuff: 62 | .scrapy 63 | 64 | # Sphinx documentation 65 | docs/_build/ 66 | 67 | # PyBuilder 68 | target/ 69 | 70 | # Jupyter Notebook 71 | .ipynb_checkpoints 72 | 73 | # pyenv 74 | .python-version 75 | 76 | # celery beat schedule file 77 | celerybeat-schedule 78 | 79 | # SageMath parsed files 80 | *.sage.py 81 | 82 | # dotenv 83 | .env 84 | 85 | # virtualenv 86 | .venv 87 | venv/ 88 | ENV/ 89 | 90 | # Spyder project settings 91 | .spyderproject 92 | .spyproject 93 | 94 | # Rope project settings 95 | .ropeproject 96 | 97 | # mkdocs documentation 98 | /site 99 | 100 | # mypy 101 | .mypy_cache/ 102 | 103 | # PyCharm 104 | .idea/ 105 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ReadWriteMemory 2 | ![PyPI - Status](https://img.shields.io/pypi/status/ReadWriteMemory) 3 | ![PyPI - Format](https://img.shields.io/pypi/format/ReadWriteMemory) 4 | ![GitHub](https://img.shields.io/github/license/vsantiago113/ReadWriteMemory) 5 | ![GitHub release (latest by date)](https://img.shields.io/github/v/release/vsantiago113/ReadWriteMemory) 6 | ![PyPI - Python Version](https://img.shields.io/pypi/pyversions/ReadWriteMemory) 7 | 8 | ### Description 9 | The ReadWriteMemory Class is made on Python for reading and writing to the memory of any process. This Class does not depend on any extra modules and only uses standard Python libraries like ctypes. 10 | 11 | --- 12 | 13 | ### [Documentation](https://vsantiago113.github.io/readwritememory.github.io/) 14 | 15 | --- 16 | 17 | ### Requirements 18 | Python 3.4+
19 | OS: Windows 7, 8 and 10
20 | 21 | --- 22 | 23 | #### Windows API’s in this module:
24 | [EnumProcesses](https://docs.microsoft.com/en-us/windows/win32/api/psapi/nf-psapi-enumprocesses)
25 | [GetProcessImageFileName](https://docs.microsoft.com/en-us/windows/win32/api/psapi/nf-psapi-getprocessimagefilenamea)
26 | [OpenProcess](https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-openprocess)
27 | [Process Security and Access Rights](https://docs.microsoft.com/en-us/windows/win32/procthread/process-security-and-access-rights)
28 | [CloseHandle](https://docs.microsoft.com/en-us/windows/win32/api/handleapi/nf-handleapi-closehandle)
29 | [GetLastError](https://docs.microsoft.com/en-us/windows/win32/api/errhandlingapi/nf-errhandlingapi-getlasterror)
30 | [ReadProcessMemory](https://docs.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-readprocessmemory)
31 | [WriteProcessMemory](https://docs.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-writeprocessmemory)
32 | 33 | --- 34 | 35 | ## Usage 36 | 37 | ### Import and instantiate the Class 38 | ```python 39 | from ReadWriteMemory import ReadWriteMemory 40 | 41 | rwm = ReadWriteMemory() 42 | ``` 43 | 44 | ### Get a Process by name 45 | ```python 46 | from ReadWriteMemory import ReadWriteMemory 47 | 48 | rwm = ReadWriteMemory() 49 | 50 | process = rwm.get_process_by_name('ac_client.exe') 51 | ``` 52 | 53 | ### Get a Process by ID 54 | ```python 55 | from ReadWriteMemory import ReadWriteMemory 56 | 57 | rwm = ReadWriteMemory() 58 | 59 | process = rwm.get_process_by_id(1337) 60 | ``` 61 | 62 | ### Get Base Address of the Process 63 | ```py 64 | from ReadWriteMemory import ReadWriteMemory 65 | 66 | rwm = ReadWriteMemory() 67 | 68 | process = rwm.get_process_by_name('ac_client.exe') 69 | # Remember to open the process first in order to get the base address of the process 70 | process.open() 71 | base_address = process.get_base_address() 72 | ``` 73 | 74 | ### Get the list of running processes ID's from the current system 75 | ```python 76 | from ReadWriteMemory import ReadWriteMemory 77 | 78 | rwm = ReadWriteMemory() 79 | 80 | processes_ids = rwm.enumerate_processes() 81 | ``` 82 | 83 | ### Print the Process information 84 | ```python 85 | from ReadWriteMemory import ReadWriteMemory 86 | 87 | rwm = ReadWriteMemory() 88 | 89 | process = rwm.get_process_by_name('ac_client.exe') 90 | print(process.__dict__) 91 | ``` 92 | 93 | ### Print the Process HELP docs 94 | ```python 95 | from ReadWriteMemory import ReadWriteMemory 96 | 97 | rwm = ReadWriteMemory() 98 | 99 | process = rwm.get_process_by_name('ac_client.exe') 100 | help(process) 101 | ``` 102 | 103 | ### Exception: ReadWriteMemoryError 104 | ````python 105 | from ReadWriteMemory import ReadWriteMemory 106 | from ReadWriteMemory import ReadWriteMemoryError 107 | 108 | rwm = ReadWriteMemory() 109 | try: 110 | process = rwm.get_process_by_name('ac_client.exe') 111 | except ReadWriteMemoryError as error: 112 | print(error) 113 | ```` 114 | 115 | ### Open the Process 116 | To be able to read or write to the process's memory first you need to call the open() method. 117 | ```python 118 | from ReadWriteMemory import ReadWriteMemory 119 | 120 | rwm = ReadWriteMemory() 121 | 122 | process = rwm.get_process_by_name('ac_client.exe') 123 | process.open() 124 | ``` 125 | 126 | ### Set the pointers for example: to get health, ammo and grenades 127 | The offsets must be a list in the correct order, if the address does not have any offsets then just pass the address. You need to pass two arguments, first the process address as hex and a list of offsets as hex. 128 | ```python 129 | from ReadWriteMemory import ReadWriteMemory 130 | 131 | rwm = ReadWriteMemory() 132 | 133 | process = rwm.get_process_by_name('ac_client.exe') 134 | process.open() 135 | 136 | health_pointer = process.get_pointer(0x004e4dbc, offsets=[0xf4]) 137 | ammo_pointer = process.get_pointer(0x004df73c, offsets=[0x378, 0x14, 0x0]) 138 | grenade_pointer = process.get_pointer(0x004df73c, offsets=[0x35c, 0x14, 0x0]) 139 | ``` 140 | 141 | ### Read the values for the health, ammo and grenades from the Process's memory 142 | ```python 143 | from ReadWriteMemory import ReadWriteMemory 144 | 145 | rwm = ReadWriteMemory() 146 | 147 | process = rwm.get_process_by_name('ac_client.exe') 148 | process.open() 149 | 150 | health_pointer = process.get_pointer(0x004e4dbc, offsets=[0xf4]) 151 | ammo_pointer = process.get_pointer(0x004df73c, offsets=[0x378, 0x14, 0x0]) 152 | grenade_pointer = process.get_pointer(0x004df73c, offsets=[0x35c, 0x14, 0x0]) 153 | 154 | health = process.read(health_pointer) 155 | ammo = process.read(ammo_pointer) 156 | grenade = process.read(grenade_pointer) 157 | ``` 158 | 159 | ### Print the health, ammo and grenade values 160 | ```python 161 | from ReadWriteMemory import ReadWriteMemory 162 | 163 | rwm = ReadWriteMemory() 164 | 165 | process = rwm.get_process_by_name('ac_client.exe') 166 | process.open() 167 | 168 | health_pointer = process.get_pointer(0x004e4dbc, offsets=[0xf4]) 169 | ammo_pointer = process.get_pointer(0x004df73c, offsets=[0x378, 0x14, 0x0]) 170 | grenade_pointer = process.get_pointer(0x004df73c, offsets=[0x35c, 0x14, 0x0]) 171 | 172 | health = process.read(health_pointer) 173 | ammo = process.read(ammo_pointer) 174 | grenade = process.read(grenade_pointer) 175 | 176 | print({'Health': health, 'Ammo': ammo, 'Grenade': grenade}) 177 | ``` 178 | 179 | ### Write some random values for health, ammo and grenade to the Process's memory 180 | ```python 181 | from ReadWriteMemory import ReadWriteMemory 182 | from random import randint 183 | 184 | rwm = ReadWriteMemory() 185 | 186 | process = rwm.get_process_by_name('ac_client.exe') 187 | process.open() 188 | 189 | health_pointer = process.get_pointer(0x004e4dbc, offsets=[0xf4]) 190 | ammo_pointer = process.get_pointer(0x004df73c, offsets=[0x378, 0x14, 0x0]) 191 | grenade_pointer = process.get_pointer(0x004df73c, offsets=[0x35c, 0x14, 0x0]) 192 | 193 | process.write(health_pointer, randint(1, 100)) 194 | process.write(ammo_pointer, randint(1, 20)) 195 | process.write(grenade_pointer, randint(1, 5)) 196 | ``` 197 | 198 | ### Close the Process's handle when you are done using it. 199 | ```python 200 | from ReadWriteMemory import ReadWriteMemory 201 | 202 | rwm = ReadWriteMemory() 203 | 204 | process = rwm.get_process_by_name('ac_client.exe') 205 | process.open() 206 | 207 | process.close() 208 | ``` 209 | 210 | ### Examples 211 | Check out the code inside the Test folder on the python file named testing_script.py. 212 | The AssaultCube game used for this test is version v1.1.0.4 If you use a different version then you will have to use CheatEngine to find the memory addresses. 213 | [https://github.com/assaultcube/AC/releases/tag/v1.1.0.4](https://github.com/assaultcube/AC/releases/tag/v1.1.0.4)
214 | For more examples check out the AssaultCube game trainer: 215 | [https://github.com/vsantiago113/ACTrainer](https://github.com/vsantiago113/ACTrainer) 216 | ```python 217 | from ReadWriteMemory import ReadWriteMemory 218 | from random import randint 219 | 220 | rwm = ReadWriteMemory() 221 | process = rwm.get_process_by_name('ac_client.exe') 222 | process.open() 223 | 224 | print('\nPrint the Process information.') 225 | print(process.__dict__) 226 | 227 | health_pointer = process.get_pointer(0x004e4dbc, offsets=[0xf4]) 228 | ammo_pointer = process.get_pointer(0x004df73c, offsets=[0x378, 0x14, 0x0]) 229 | grenade_pointer = process.get_pointer(0x004df73c, offsets=[0x35c, 0x14, 0x0]) 230 | print(health_pointer) 231 | 232 | health = process.read(health_pointer) 233 | ammo = process.read(ammo_pointer) 234 | grenade = process.read(grenade_pointer) 235 | 236 | print('\nPrinting the current values.') 237 | print({'Health': health, 'Ammo': ammo, 'Grenade': grenade}) 238 | 239 | process.write(health_pointer, randint(1, 100)) 240 | process.write(ammo_pointer, randint(1, 20)) 241 | process.write(grenade_pointer, randint(1, 5)) 242 | 243 | health = process.read(health_pointer) 244 | ammo = process.read(ammo_pointer) 245 | grenade = process.read(grenade_pointer) 246 | 247 | print('\nPrinting the new modified random values.') 248 | print({'Health': health, 'Ammo': ammo, 'Grenade': grenade}) 249 | 250 | process.close() 251 | 252 | ``` 253 | -------------------------------------------------------------------------------- /ReadWriteMemory/__init__.py: -------------------------------------------------------------------------------- 1 | from typing import Any, List 2 | import os.path 3 | import ctypes 4 | import ctypes.wintypes 5 | 6 | # Process Permissions 7 | PROCESS_QUERY_INFORMATION = 0x0400 8 | PROCESS_VM_OPERATION = 0x0008 9 | PROCESS_VM_READ = 0x0010 10 | PROCESS_VM_WRITE = 0x0020 11 | PROCESS_ALL_ACCESS = 0x1f0fff 12 | 13 | MAX_PATH = 260 14 | 15 | 16 | class ReadWriteMemoryError(Exception): 17 | pass 18 | 19 | 20 | class Process(object): 21 | """ 22 | The Process class holds the information about the requested process. 23 | """ 24 | def __init__(self, name: str = '', pid: int = -1, handle: int = -1, error_code: str = None): 25 | """ 26 | :param name: The name of the executable file for the specified process. 27 | :param pid: The process ID. 28 | :param handle: The process handle. 29 | :param error_code: The error code from a process failure. 30 | """ 31 | self.name = name 32 | self.pid = pid 33 | self.handle = handle 34 | self.error_code = error_code 35 | 36 | def __repr__(self) -> str: 37 | return f'{self.__class__.__name__}: "{self.name}"' 38 | 39 | def open(self): 40 | """ 41 | Open the process with the Query, Operation, Read and Write permissions and return the process handle. 42 | 43 | :return: True if the handle exists if not return False 44 | """ 45 | dw_desired_access = (PROCESS_QUERY_INFORMATION | PROCESS_VM_OPERATION | PROCESS_VM_READ | PROCESS_VM_WRITE) 46 | b_inherit_handle = True 47 | self.handle = ctypes.windll.kernel32.OpenProcess(dw_desired_access, b_inherit_handle, self.pid) 48 | if not self.handle: 49 | raise ReadWriteMemoryError(f'Unable to open process <{self.name}>') 50 | 51 | def close(self) -> int: 52 | """ 53 | Closes the handle of the process. 54 | 55 | :return: The last error code from the result after an attempt to close the handle. 56 | """ 57 | ctypes.windll.kernel32.CloseHandle(self.handle) 58 | return self.get_last_error() 59 | 60 | def get_all_access_handle(self): 61 | """ 62 | Gets full access handle of the process. 63 | 64 | :return: handle of the process 65 | """ 66 | b_inherit_handle = True 67 | self.handle = ctypes.windll.kernel32.OpenProcess(PROCESS_ALL_ACCESS, b_inherit_handle, self.pid) 68 | 69 | @staticmethod 70 | def get_last_error() -> int: 71 | """ 72 | Get the last error code. 73 | 74 | :return: The last error code. 75 | """ 76 | return ctypes.windll.kernel32.GetLastError() 77 | 78 | def get_pointer(self, lp_base_address: hex, offsets: List[hex] = ()) -> int: 79 | """ 80 | Get the pointer of a given address. 81 | 82 | :param lp_base_address: The address from where you want to get the pointer. 83 | :param offsets: a list of offets. 84 | 85 | :return: The pointer of a give address. 86 | """ 87 | temp_address = self.read(lp_base_address) 88 | pointer = 0x0 89 | if not offsets: 90 | return lp_base_address 91 | else: 92 | for offset in offsets: 93 | pointer = int(str(temp_address), 0) + int(str(offset), 0) 94 | temp_address = self.read(pointer) 95 | return pointer 96 | 97 | def get_modules(self) -> List[int]: 98 | """ 99 | Get the process's modules. 100 | :return: A list of the process's modules adresses in decimal. 101 | :return: An empty list if the process is not open. 102 | """ 103 | modules = (ctypes.wintypes.HMODULE * MAX_PATH)() 104 | ctypes.windll.psapi.EnumProcessModules(self.handle, modules, ctypes.sizeof(modules), None) 105 | return [x for x in tuple(modules) if x != None] 106 | 107 | def get_base_address(self): 108 | """ 109 | Get the base address of the process. 110 | :return: The base address of the process. 111 | """ 112 | return self.get_modules()[0] 113 | 114 | def thread(self, address: int): 115 | """ 116 | Create a remote thread to the address. 117 | If you don't know what you're doing, the process can crash. 118 | """ 119 | ctypes.windll.kernel32.CreateRemoteThread(self.handle, 0, 0, address, 0, 0, 0) 120 | self.close() #the thread stays in the process 121 | self.open() #just for better code understanding 122 | 123 | def read(self, lp_base_address: int) -> Any: 124 | """ 125 | Read data from the process's memory. 126 | 127 | :param lp_base_address: The process's pointer 128 | 129 | :return: The data from the process's memory if succeed if not raises an exception. 130 | """ 131 | try: 132 | read_buffer = ctypes.c_uint() 133 | lp_buffer = ctypes.byref(read_buffer) 134 | n_size = ctypes.sizeof(read_buffer) 135 | lp_number_of_bytes_read = ctypes.c_ulong(0) 136 | ctypes.windll.kernel32.ReadProcessMemory(self.handle, ctypes.c_void_p(lp_base_address), lp_buffer, 137 | n_size, lp_number_of_bytes_read) 138 | return read_buffer.value 139 | except (BufferError, ValueError, TypeError) as error: 140 | if self.handle: 141 | self.close() 142 | self.error_code = self.get_last_error() 143 | error = {'msg': str(error), 'Handle': self.handle, 'PID': self.pid, 144 | 'Name': self.name, 'ErrorCode': self.error_code} 145 | ReadWriteMemoryError(error) 146 | 147 | def readString(self, lp_base_address: int, length: int) -> Any: 148 | """ 149 | Read data from the process's memory. 150 | 151 | :param lp_base_address: The process's pointer 152 | :param length: The length of string 153 | 154 | :return: The data from the process's memory if succeed if not raises an exception. 155 | """ 156 | try: 157 | read_buffer = ctypes.create_string_buffer(length) 158 | lp_number_of_bytes_read = ctypes.c_ulong(0) 159 | ctypes.windll.kernel32.ReadProcessMemory(self.handle, lp_base_address, read_buffer, length, lp_number_of_bytes_read) 160 | bufferArray = bytearray(read_buffer) 161 | found_terminator = bufferArray.find(b'\x00') 162 | if found_terminator != -1: 163 | return bufferArray[:found_terminator].decode('utf-8') 164 | print("[ReadMemory/Error]: terminator not found.\naddress: %s" % hex(lp_base_address)) 165 | return "" 166 | except (BufferError, ValueError, TypeError) as error: 167 | if self.handle: 168 | self.close() 169 | self.error_code = self.get_last_error() 170 | error = {'msg': str(error), 'Handle': self.handle, 'PID': self.pid, 171 | 'Name': self.name, 'ErrorCode': self.error_code} 172 | ReadWriteMemoryError(error) 173 | 174 | def readByte(self, lp_base_address: int, length: int = 1) -> List[hex]: 175 | """ 176 | Read data from the process's memory. 177 | :param lp_base_address: The process's pointer {don't use offsets} 178 | :param length: The length of the bytes to read 179 | :return: The data from the process's memory if succeed if not raises an exception. 180 | """ 181 | try: 182 | read_buffer = ctypes.c_ubyte() 183 | lp_buffer = ctypes.byref(read_buffer) 184 | n_size = ctypes.sizeof(read_buffer) 185 | lp_number_of_bytes_read = ctypes.c_ulong(0) 186 | return [hex(read_buffer.value) for x in range(length) if ctypes.windll.kernel32.ReadProcessMemory(self.handle, ctypes.c_void_p(lp_base_address + x), lp_buffer, n_size, lp_number_of_bytes_read)] 187 | 188 | except (BufferError, ValueError, TypeError) as error: 189 | if self.handle: 190 | self.close() 191 | self.error_code = self.get_last_error() 192 | error = {'msg': str(error), 'Handle': self.handle, 'PID': self.pid, 193 | 'Name': self.name, 'ErrorCode': self.error_code} 194 | ReadWriteMemoryError(error) 195 | 196 | def readDouble(self, lp_base_address: int) -> Any: 197 | """ 198 | Read data from the process's memory. 199 | 200 | :param lp_base_address: The process's pointer 201 | 202 | :return: The data from the process's memory if succeed if not raises an exception. 203 | """ 204 | try: 205 | read_buffer = ctypes.c_double() 206 | lp_buffer = ctypes.byref(read_buffer) 207 | n_size = ctypes.sizeof(read_buffer) 208 | lp_number_of_bytes_read = ctypes.c_ulong(0) 209 | ctypes.windll.kernel32.ReadProcessMemory(self.handle, ctypes.c_void_p(lp_base_address), lp_buffer, 210 | n_size, lp_number_of_bytes_read) 211 | return read_buffer.value 212 | except (BufferError, ValueError, TypeError) as error: 213 | if self.handle: 214 | self.close() 215 | self.error_code = self.get_last_error() 216 | error = {'msg': str(error), 'Handle': self.handle, 'PID': self.pid, 217 | 'Name': self.name, 'ErrorCode': self.error_code} 218 | ReadWriteMemoryError(error) 219 | 220 | def write(self, lp_base_address: int, value: int) -> bool: 221 | """ 222 | Write data to the process's memory. 223 | 224 | :param lp_base_address: The process' pointer. 225 | :param value: The data to be written to the process's memory 226 | 227 | :return: It returns True if succeed if not it raises an exception. 228 | """ 229 | try: 230 | write_buffer = ctypes.c_uint(value) 231 | lp_buffer = ctypes.byref(write_buffer) 232 | n_size = ctypes.sizeof(write_buffer) 233 | lp_number_of_bytes_written = ctypes.c_ulong(0) 234 | ctypes.windll.kernel32.WriteProcessMemory(self.handle, ctypes.c_void_p(lp_base_address), lp_buffer, 235 | n_size, lp_number_of_bytes_written) 236 | return True 237 | except (BufferError, ValueError, TypeError) as error: 238 | if self.handle: 239 | self.close() 240 | self.error_code = self.get_last_error() 241 | error = {'msg': str(error), 'Handle': self.handle, 'PID': self.pid, 242 | 'Name': self.name, 'ErrorCode': self.error_code} 243 | ReadWriteMemoryError(error) 244 | 245 | def writeDouble(self, lp_base_address: int, value: float) -> bool: 246 | """ 247 | Write data to the process's memory. 248 | 249 | :param lp_base_address: The process' pointer. 250 | :param value: The data to be written to the process's memory 251 | 252 | :return: It returns True if succeed if not it raises an exception. 253 | """ 254 | try: 255 | write_buffer = ctypes.c_double(value) 256 | lp_buffer = ctypes.byref(write_buffer) 257 | n_size = ctypes.sizeof(write_buffer) 258 | lp_number_of_bytes_written = ctypes.c_ulong(0) 259 | ctypes.windll.kernel32.WriteProcessMemory(self.handle, ctypes.c_void_p(lp_base_address), lp_buffer, 260 | n_size, lp_number_of_bytes_written) 261 | return True 262 | except (BufferError, ValueError, TypeError) as error: 263 | if self.handle: 264 | self.close() 265 | self.error_code = self.get_last_error() 266 | error = {'msg': str(error), 'Handle': self.handle, 'PID': self.pid, 267 | 'Name': self.name, 'ErrorCode': self.error_code} 268 | ReadWriteMemoryError(error) 269 | 270 | def writeString(self, lp_base_address: int, string: str) -> bool: 271 | """ 272 | Write data to the process's memory. 273 | 274 | :param lp_base_address: The process' pointer. 275 | :param string: The string to be written to the process's memory 276 | 277 | :return: It returns True if succeed if not it raises an exception. 278 | """ 279 | try: 280 | write_buffer = ctypes.create_string_buffer(string.encode()) 281 | lp_buffer = ctypes.byref(write_buffer) 282 | n_size = ctypes.sizeof(write_buffer) 283 | lp_number_of_bytes_written = ctypes.c_size_t() 284 | ctypes.windll.kernel32.WriteProcessMemory(self.handle, lp_base_address, lp_buffer, 285 | n_size, lp_number_of_bytes_written) 286 | return True 287 | except (BufferError, ValueError, TypeError) as error: 288 | if self.handle: 289 | self.close() 290 | self.error_code = self.get_last_error() 291 | error = {'msg': str(error), 'Handle': self.handle, 'PID': self.pid, 292 | 'Name': self.name, 'ErrorCode': self.error_code} 293 | ReadWriteMemoryError(error) 294 | 295 | def writeByte(self, lp_base_address: int, bytes: List[hex]) -> bool: 296 | """ 297 | Write data to the process's memory. 298 | :param lp_base_address: The process' pointer {don't use offsets}. 299 | :param bytes: The byte(s) to be written to the process's memory 300 | :return: It returns True if succeed if not it raises an exception. 301 | """ 302 | try: 303 | for x in range(len(bytes)): 304 | write_buffer = ctypes.c_ubyte(bytes[x]) 305 | lp_buffer = ctypes.byref(write_buffer) 306 | n_size = ctypes.sizeof(write_buffer) 307 | lp_number_of_bytes_written = ctypes.c_ulong(0) 308 | ctypes.windll.kernel32.WriteProcessMemory(self.handle, ctypes.c_void_p(lp_base_address + x), lp_buffer, 309 | n_size, lp_number_of_bytes_written) 310 | return True 311 | except (BufferError, ValueError, TypeError) as error: 312 | if self.handle: 313 | self.close() 314 | self.error_code = self.get_last_error() 315 | error = {'msg': str(error), 'Handle': self.handle, 'PID': self.pid, 316 | 'Name': self.name, 'ErrorCode': self.error_code} 317 | ReadWriteMemoryError(error) 318 | 319 | class ReadWriteMemory: 320 | """ 321 | The ReadWriteMemory Class is used to read and write to the memory of a running process. 322 | """ 323 | def __init__(self): 324 | self.process = Process() 325 | 326 | @staticmethod 327 | def set_privileges(): 328 | import win32con 329 | import win32api 330 | import win32security 331 | import ntsecuritycon 332 | from ntsecuritycon import TokenPrivileges 333 | 334 | remote_server = None 335 | token = win32security.OpenProcessToken(win32api.GetCurrentProcess(), win32con.TOKEN_ADJUST_PRIVILEGES | win32con.TOKEN_QUERY) 336 | win32security.AdjustTokenPrivileges(token, False, ((p[0], 2) if p[0] == win32security.LookupPrivilegeValue(remote_server, "SeBackupPrivilege") or p[0] == win32security.LookupPrivilegeValue(remote_server, "SeDebugPrivilege") or p[0] == win32security.LookupPrivilegeValue(remote_server, "SeSecurityPrivilege") else (p[0], p[1]) for p in win32security.GetTokenInformation(token, TokenPrivileges))) 337 | 338 | def get_process_by_name(self, process_name: str) -> "Process": 339 | """ 340 | :description: Get the process by the process executabe\'s name and return a Process object. 341 | 342 | :param process_name: The name of the executable file for the specified process for example, my_program.exe. 343 | 344 | :return: A Process object containing the information from the requested Process. 345 | """ 346 | if not process_name.endswith('.exe'): 347 | self.process.name = process_name + '.exe' 348 | 349 | process_ids = self.enumerate_processes() 350 | 351 | for process_id in process_ids: 352 | self.process.handle = ctypes.windll.kernel32.OpenProcess(PROCESS_QUERY_INFORMATION, False, process_id) 353 | if self.process.handle: 354 | image_file_name = (ctypes.c_char * MAX_PATH)() 355 | if ctypes.windll.psapi.GetProcessImageFileNameA(self.process.handle, image_file_name, MAX_PATH) > 0: 356 | filename = os.path.basename(image_file_name.value) 357 | if filename.decode('utf-8') == process_name: 358 | self.process.pid = process_id 359 | self.process.name = process_name 360 | return self.process 361 | self.process.close() 362 | 363 | raise ReadWriteMemoryError(f'Process "{self.process.name}" not found!') 364 | 365 | def get_process_by_id(self, process_id: int) -> "Process": 366 | """ 367 | :description: Get the process by the process ID and return a Process object. 368 | 369 | :param process_id: The process ID. 370 | 371 | :return: A Process object containing the information from the requested Process. 372 | """ 373 | 374 | self.process.handle = ctypes.windll.kernel32.OpenProcess(PROCESS_QUERY_INFORMATION, False, process_id) 375 | if self.process.handle: 376 | image_file_name = (ctypes.c_char * MAX_PATH)() 377 | if ctypes.windll.psapi.GetProcessImageFileNameA(self.process.handle, image_file_name, MAX_PATH) > 0: 378 | filename = os.path.basename(image_file_name.value) 379 | self.process.pid = process_id 380 | self.process.name = filename.decode('utf-8') 381 | self.process.close() 382 | return self.process 383 | else: 384 | raise ReadWriteMemoryError(f'Unable to get the executable\'s name for PID={self.process.pid}!') 385 | 386 | raise ReadWriteMemoryError(f'Process "{self.process.pid}" not found!') 387 | 388 | @staticmethod 389 | def enumerate_processes() -> list: 390 | """ 391 | Get the list of running processes ID's from the current system. 392 | 393 | :return: A list of processes ID's 394 | """ 395 | count = 32 396 | while True: 397 | process_ids = (ctypes.wintypes.DWORD * count)() 398 | cb = ctypes.sizeof(process_ids) 399 | bytes_returned = ctypes.wintypes.DWORD() 400 | if ctypes.windll.Psapi.EnumProcesses(ctypes.byref(process_ids), cb, ctypes.byref(bytes_returned)): 401 | if bytes_returned.value < cb: 402 | return list(set(process_ids)) 403 | else: 404 | count *= 2 405 | --------------------------------------------------------------------------------