I am trying to create a memory scanner, similar to the Cheat Engine, but only for extracting information. So, given a Process ID — in this case the PID of "notepad.exe" — I would like to know which specific addresses belong to the program that I am scanning.
Trying to look for examples, I noticed someone attempting to scan every address from one point to another. But it's too slow. Then I tried to create a batch size to scan just a part of the memory.
The problem is: if the size is too short, it'll take too long still. Otherwise, if the size is too long, it's possible to lose many addresses, because even the result of ReadMemoryScan is false for the first address, the next one might be true. Here is my example.
from ctypes import wintypes
from sys import stdout
import ctypes
import psutil
write = stdout.write
import numpy as np
def get_client_pid(process_name):
for process in psutil.process_iter():
if process.name() == process_name:
return int(process.pid)
pid = get_client_pid("notepad.exe")
if pid == None:
sys.exit()
k32 = ctypes.WinDLL("kernel32", use_last_error=True)
OpenProcess = k32.OpenProcess
OpenProcess.argtypes = [wintypes.DWORD, wintypes.BOOL, wintypes.DWORD]
OpenProcess.restype = wintypes.HANDLE
ReadProcessMemory = k32.ReadProcessMemory
ReadProcessMemory.argtypes = [
wintypes.HANDLE,
wintypes.LPCVOID,
wintypes.LPVOID,
ctypes.c_size_t,
ctypes.POINTER(ctypes.c_size_t)
]
ReadProcessMemory.restype = wintypes.BOOL
GetLastError = k32.GetLastError
GetLastError.argtypes = None
GetLastError.restype = wintypes.DWORD
CloseHandle = k32.CloseHandle
CloseHandle.argtypes = [wintypes.HANDLE]
CloseHandle.restype = wintypes.BOOL
processHandle = OpenProcess(0x10, False, int(pid))
# addr = 0x0FFFFFFFFFFF
data = ctypes.c_ulonglong()
bytesRead = ctypes.c_ulonglong()
start = 0x000000000000
end = 0x7fffffffffff
batch_size = 2 ** 13
MemoryData = np.zeros(batch_size, "l")
Size = MemoryData.itemsize * MemoryData.size
Data_address = []
index = 0
for address in range(start, end, batch_size):
result = ReadProcessMemory(
processHandle,
ctypes.c_void_p(address),
MemoryData.ctypes.data,
Size, ctypes.byref(bytesRead)
)
if result: # Save the address
Data_address.extend(list(range(address, address + batch_size)))
error = GetLastError()
CloseHandle(processHandle)
I decided searching from 0x000000000000 to 0x7fffffffffff because it seems Cheat Engine scans this size. Are there things that I could do to improve the search performance?
You may use my PyMemoryEditor package for scanning the process as well as easily read and write to the process memory. This package supports Windows and Linux and works similar to the Cheat Engine, also having all scan types, allowing you to search for integers, float, booleans, bytes and strings. See the example bellow:
from PyMemoryEditor import OpenProcess, ScanTypesEnum
value_content = 9712
value_size = 4 # Amount of bytes (in this case, 4 bytes)
pytype = int # Type of the value in Python
scan_type = ScanTypesEnum.EXACT_VALUE
with OpenProcess(process_name = "notepad.exe") as process:
for address in process.search_by_value(pytype, value_size, value_content, scan_type):
print("Found address", address, "for the value", value_content)
What this method is doing internally is getting all memory regions of the process and trying to find the value in a frame of size value_size at each memory region. So you don't have to search your whole memory. Just search on the memory pages of the target process.
This method works very quick for the scan types EXACT_VALUE and NOT_EXACT_VALUE, because it implements the KMP (Knuth–Morris–Pratt) Algorithm for searching, that has complexity O(n + m) where n is the memory region size and m is the size of the value in bytes.
You may even check out the scanning progress setting the progress_information as True. This way, the method search_by_value will return the progress of the scanning for each found address.
for address, info process.search_by_value(..., progress_information = True):
template = "Address: 0x{:<10X} | Progress: {:.1f}%"
progress = info["progress"] * 100
print(template.format(address, progress))
For reading and writing process memory, you should use the methods below:
value = process.read_process_memory(address, int, 4)
new_value = value + 1
process.write_process_memory(address, int, 4, new_value)
But you can also use the method search_by_addresses(...) if you have a large number of addresses that must be read. This way, you reduce the frequency of system calls.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With