Python进程内存读取检测作弊代码故障排查求助(附代码)
进程内存中'exechack.cc'字符串检测代码修正方案
原代码核心问题分析
- PROCESSENTRY32结构体定义错误:原代码未正确定义进程快照所需的结构体字段,导致无法正确枚举进程ID。
- 内存区域过滤条件过窄:仅筛选
PAGE_READWRITE权限的内存区域,会遗漏存放字符串的只读内存段(如程序的只读数据区)。 - 缺少必要的错误检查:未对系统API调用的返回结果做详细错误排查,难以定位问题。
修正后的完整代码
import ctypes from ctypes import wintypes # 定义常量 PROCESS_QUERY_INFORMATION = 0x0400 PROCESS_VM_READ = 0x0010 MEM_COMMIT = 0x1000 PAGE_READONLY = 0x02 PAGE_READWRITE = 0x04 PAGE_WRITECOPY = 0x08 PAGE_EXECUTE_READ = 0x20 PAGE_EXECUTE_READWRITE = 0x40 PAGE_EXECUTE_WRITECOPY = 0x80 # 正确定义PROCESSENTRY32结构体 class PROCESSENTRY32(ctypes.Structure): _fields_ = [ ("dwSize", wintypes.DWORD), ("cntUsage", wintypes.DWORD), ("th32ProcessID", wintypes.DWORD), ("th32DefaultHeapID", wintypes.LPVOID), ("th32ModuleID", wintypes.DWORD), ("cntThreads", wintypes.DWORD), ("th32ParentProcessID", wintypes.DWORD), ("pcPriClassBase", wintypes.LONG), ("dwFlags", wintypes.DWORD), ("szExeFile", ctypes.c_char * 260) ] class MEMORY_BASIC_INFORMATION(ctypes.Structure): _fields_ = [ ("BaseAddress", ctypes.c_void_p), ("AllocationBase", ctypes.c_void_p), ("AllocationProtect", wintypes.DWORD), ("RegionSize", ctypes.c_size_t), ("State", wintypes.DWORD), ("Protect", wintypes.DWORD), ("Type", wintypes.DWORD) ] # 加载kernel32并设置函数参数类型 kernel32 = ctypes.WinDLL('kernel32', use_last_error=True) kernel32.CreateToolhelp32Snapshot.argtypes = [wintypes.DWORD, wintypes.DWORD] kernel32.CreateToolhelp32Snapshot.restype = wintypes.HANDLE kernel32.Process32First.argtypes = [wintypes.HANDLE, ctypes.POINTER(PROCESSENTRY32)] kernel32.Process32First.restype = wintypes.BOOL kernel32.Process32Next.argtypes = [wintypes.HANDLE, ctypes.POINTER(PROCESSENTRY32)] kernel32.Process32Next.restype = wintypes.BOOL kernel32.OpenProcess.argtypes = [wintypes.DWORD, wintypes.BOOL, wintypes.DWORD] kernel32.OpenProcess.restype = wintypes.HANDLE kernel32.VirtualQueryEx.argtypes = [wintypes.HANDLE, wintypes.LPCVOID, ctypes.POINTER(MEMORY_BASIC_INFORMATION), ctypes.c_size_t] kernel32.VirtualQueryEx.restype = ctypes.c_size_t kernel32.ReadProcessMemory.argtypes = [wintypes.HANDLE, wintypes.LPCVOID, wintypes.LPVOID, ctypes.c_size_t, ctypes.POINTER(ctypes.c_size_t)] kernel32.ReadProcessMemory.restype = wintypes.BOOL kernel32.CloseHandle.argtypes = [wintypes.HANDLE] kernel32.CloseHandle.restype = wintypes.BOOL def find_exechack(process_name): process_ids = [] # 创建进程快照 snapshot = kernel32.CreateToolhelp32Snapshot(0x00000002, 0) # TH32CS_SNAPPROCESS if snapshot == wintypes.HANDLE(-1).value: print(f"CreateToolhelp32Snapshot失败,错误码: {ctypes.get_last_error()}") return pe32 = PROCESSENTRY32() pe32.dwSize = ctypes.sizeof(PROCESSENTRY32) if not kernel32.Process32First(snapshot, ctypes.byref(pe32)): print(f"Process32First失败,错误码: {ctypes.get_last_error()}") kernel32.CloseHandle(snapshot) return # 枚举进程 while True: exe_name = pe32.szExeFile.decode('gbk', errors='ignore') # 适配中文进程名 if exe_name == process_name: process_ids.append(pe32.th32ProcessID) if not kernel32.Process32Next(snapshot, ctypes.byref(pe32)): # 检查是否是正常结束 if ctypes.get_last_error() != 18: # ERROR_NO_MORE_FILES print(f"Process32Next失败,错误码: {ctypes.get_last_error()}") break kernel32.CloseHandle(snapshot) # 遍历进程搜索内存 target_str = b'exechack.cc' target_len = len(target_str) for pid in process_ids: # 打开进程,申请足够权限 process_handle = kernel32.OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, False, pid) if not process_handle: print(f"打开进程{pid}失败,错误码: {ctypes.get_last_error()}") continue address = ctypes.c_void_p(0) while True: mbi = MEMORY_BASIC_INFORMATION() result = kernel32.VirtualQueryEx(process_handle, address, ctypes.byref(mbi), ctypes.sizeof(mbi)) if result == 0: break # 筛选已提交且可读的内存区域 if mbi.State == MEM_COMMIT and mbi.Protect in (PAGE_READONLY, PAGE_READWRITE, PAGE_WRITECOPY, PAGE_EXECUTE_READ, PAGE_EXECUTE_READWRITE, PAGE_EXECUTE_WRITECOPY): try: # 分配缓冲区,避免过大内存区域导致内存溢出 buffer_size = min(mbi.RegionSize, 1024 * 1024) # 限制单次读取1MB buffer = (ctypes.c_char * buffer_size)() bytes_read = ctypes.c_size_t() if kernel32.ReadProcessMemory(process_handle, mbi.BaseAddress, buffer, buffer_size, ctypes.byref(bytes_read)): # 搜索字符串,处理跨缓冲区的情况 buffer_data = buffer[:bytes_read.value] offset = 0 while offset <= len(buffer_data) - target_len: if buffer_data[offset:offset+target_len] == target_str: print(f"在进程{pid}中找到'exechack.cc',地址: {hex(mbi.BaseAddress.value + offset)}") # 找到后可选择继续搜索或退出 # break offset += 1 except MemoryError: print(f"读取进程{pid}内存区域{hex(mbi.BaseAddress.value)}时内存不足") # 移动到下一个内存区域 address.value += mbi.RegionSize kernel32.CloseHandle(process_handle) # 示例调用 if __name__ == "__main__": find_exechack("target_process.exe")
关键修改说明
- 正确定义PROCESSENTRY32结构体:按照Windows API的标准定义结构体字段,确保能正确获取进程ID和进程名。
- 放宽内存区域筛选条件:加入所有可读权限的内存类型,避免遗漏存放字符串的只读区域。
- 添加API参数类型和返回值定义:明确ctypes函数的参数和返回类型,减少调用错误。
- 优化内存读取逻辑:限制单次读取的内存大小(1MB),避免因大内存区域导致内存溢出;同时处理字符串跨缓冲区的情况。
- 完善错误处理:对每个系统API调用添加错误码检查,方便调试定位问题。
- 适配中文进程名:使用GBK编码解码进程名,避免中文进程名乱码。
内容的提问来源于stack exchange,提问作者Stanislav
相关产品推荐
相关产品推荐

