javascript:;
程序设计缘由,对运行程序指定模块中的特定字节进行替换
程序实现的功能
读取系统列表程序,获取程序下所有模块,通过在内存中搜索匹配,实现对模块中汇编代码的修改。
程序设置使用qwen-code、glm、gpt-5mini进行代码实现。
打包后程序下载地址:
https://wwdc.lanzouo.com/iZeZn39ojkbc
程序源码:
import tkinter as tk
import tkinter.font as tkfont
import customtkinter as ctk
from tkinter import messagebox, scrolledtext, Listbox, Scrollbar
import psutil
import pymem
import pymem.process
import pymem.thread
import re
import os
class FixedListDropdown(ctk.CTkFrame):
"""固定高度的选择弹出框,使用原生 tk.Listbox + Scrollbar,支持鼠标滚轮。
提供简易兼容接口:configure(values=...), set(value), get(), bind(event, callback)
并支持传入 command=callback
"""
def __init__(self, master, values=None, max_display_items=8, width=200, height=22, command=None, dropdown_font=None, **kwargs):
super().__init__(master, **kwargs)
self._values = values or []
self.max_display_items = max_display_items
self.command = command
self.var = tk.StringVar()
# dropdown_font 可以是一个字体元组,如 ("Consolas", 11)
if dropdown_font is None:
dropdown_font = ("Consolas", 11)
self._font = tkfont.Font(font=dropdown_font)
# 显示当前值的按钮样式
self.display = ctk.CTkButton(self, textvariable=self.var, anchor="w", width=width, height=height,
command=self._toggle_dropdown, fg_color="#1f1f1f")
self.display.pack(fill="x", expand=True)
self._popup = None
def configure(self, **kwargs):
# 允许通过 configure(values=[...]) 更新选项
if 'values' in kwargs:
self._values = kwargs.pop('values') or []
# 允许设置宽/height等属性(不完全支持)
return super().configure(**kwargs)
def set(self, value):
self.var.set(value)
def get(self):
return self.var.get()
def bind(self, sequence=None, func=None, add=None):
# 绑定到内部 display 按钮
# customtkinter/tkinter 对 bind 的 add 参数要求不同,只有 '+' 或 True 表示追加绑定
try:
if add in ('+', True):
return self.display.bind(sequence, func, add)
else:
return self.display.bind(sequence, func)
except TypeError:
# 降级处理:直接调用不带 add
return self.display.bind(sequence, func)
def _toggle_dropdown(self):
if self._popup and tk.Toplevel.winfo_exists(self._popup):
self._close_popup()
else:
self._open_popup()
def _open_popup(self):
# 创建弹出窗口并在其中放置 Listbox + Scrollbar
self._popup = tk.Toplevel(self)
self._popup.wm_overrideredirect(True)
# 计算位置:在 display 下方
# 通过 display 的位置与宽度来设置 popup 的位置与宽度,确保一致
self.display.update_idletasks()
x = self.display.winfo_rootx()
y = self.display.winfo_rooty() + self.display.winfo_height()
listbox_height = min(len(self._values), self.max_display_items) or self.max_display_items
# 估算单行高度并计算总高度
line_h = max(1, self._font.metrics('linespace'))
popup_height = listbox_height * line_h + 4
popup_width = max(100, self.display.winfo_width())
self._popup.geometry(f"{popup_width}x{popup_height}+{x}+{y}")
frame = tk.Frame(self._popup, bg="#2b2b2b")
frame.pack(fill="both", expand=True)
# Scrollbar
scrollbar = Scrollbar(frame)
scrollbar.pack(side="right", fill="y")
# 使用与 display 宽度一致的 listbox,并设置字体
self._listbox = Listbox(frame, height=listbox_height, yscrollcommand=scrollbar.set, activestyle='none', selectmode='browse', font=self._font)
self._listbox.pack(side="left", fill="both", expand=True)
scrollbar.config(command=self._listbox.yview)
# 填充选项
# 先清空(防止重复)再填充
self._listbox.delete(0, tk.END)
for v in self._values:
self._listbox.insert(tk.END, v)
# 绑定选择事件
self._listbox.bind('>', self._on_select)
# 鼠标滚轮支持
self._listbox.bind('', lambda e: self._listbox.yview_scroll(int(-1*(e.delta/120)), 'units'))
# 点击外部关闭
self._popup.bind('', lambda e: self._close_popup())
self._popup.focus_force()
def _on_select(self, event):
try:
idx = self._listbox.curselection()
if not idx:
return
value = self._listbox.get(idx)
self.set(value)
if callable(self.command):
try:
self.command(value)
except Exception:
pass
finally:
self._close_popup()
def _close_popup(self):
try:
if self._popup:
self._popup.destroy()
except Exception:
pass
self._popup = None
class MemoryPatchTool:
def __init__(self, root):
self.root = root
self.root.title("内存热补丁工具")
self.root.geometry("850x600")
# 设置CustomTkinter主题
ctk.set_appearance_mode("dark")
ctk.set_default_color_theme("blue")
# 设置选项
self.show_system_processes = tk.BooleanVar(value=True)
self.pm = None
self.process_id = None
self.process_list_data = [] # 存储进程数据
self.module_list_data = [] # 存储模块数据
self.create_widgets()
# 在窗口创建后绑定鼠标滚轮事件
self.root.after(100, self.setup_mousewheel)
def create_widgets(self):
# 主框架
main_frame = ctk.CTkFrame(self.root)
main_frame.pack(fill="both", expand=True, padx=6, pady=6)
# 标题(更紧凑)
# title_label = ctk.CTkLabel(main_frame, text="内存热补丁工具", font=("Arial", 16, "bold"))
# title_label.pack(pady=6)
# 创建水平布局框架 - 设置、进程选择、模块选择并列
horizontal_frame = ctk.CTkFrame(main_frame)
horizontal_frame.pack(fill="x", padx=6, pady=4)
# 设置区域
settings_frame = ctk.CTkFrame(horizontal_frame)
settings_frame.pack(side="left", fill="both", expand=True, padx=5, pady=5)
settings_label = ctk.CTkLabel(settings_frame, text="设置:", font=("Arial", 12, "bold"))
settings_label.pack(anchor="w", padx=5, pady=5)
# 显示系统进程选项
self.show_system_processes_checkbox = ctk.CTkCheckBox(
settings_frame,
text="显示系统进程",
variable=self.show_system_processes,
command=self.on_settings_change,
font=("Arial", 10)
)
self.show_system_processes_checkbox.pack(anchor="w", padx=10, pady=5)
# 进程选择区域
process_frame = ctk.CTkFrame(horizontal_frame)
process_frame.pack(side="left", fill="both", expand=True, padx=5, pady=5)
process_label = ctk.CTkLabel(process_frame, text="进程选择:", font=("Arial", 12, "bold"))
process_label.pack(anchor="w", padx=5, pady=5)
# 进程下拉列表容器
process_combo_frame = ctk.CTkFrame(process_frame)
process_combo_frame.pack(fill="x", padx=5, pady=5)
# 进程选择(使用固定高度弹出列表)
self.process_combobox = FixedListDropdown(
process_combo_frame,
values=[],
max_display_items=10, # 最多显示10个项目
width=300,
height=22,
dropdown_font=("Consolas", 14),
command=self.on_process_selected
)
self.process_combobox.pack(side="left", padx=4, pady=4)
# 刷新进程按钮
self.refresh_btn = ctk.CTkButton(
process_combo_frame,
text="刷新",
command=self.refresh_processes,
width=60,
height=25
)
self.refresh_btn.pack(side="left", padx=5, pady=5)
# 显示已选择的进程
self.selected_process_label = ctk.CTkLabel(process_frame, text="未选择进程", font=("Arial", 10))
self.selected_process_label.pack(anchor="w", padx=5, pady=2)
# 模块选择区域
module_frame = ctk.CTkFrame(horizontal_frame)
module_frame.pack(side="left", fill="both", expand=True, padx=5, pady=5)
module_label = ctk.CTkLabel(module_frame, text="模块列表:", font=("Arial", 12, "bold"))
module_label.pack(anchor="w", padx=5, pady=5)
# 模块下拉列表容器
module_combo_frame = ctk.CTkFrame(module_frame)
module_combo_frame.pack(fill="x", padx=5, pady=5)
# 模块选择(使用固定高度弹出列表)
self.module_combobox = FixedListDropdown(
module_combo_frame,
values=[],
max_display_items=10,
width=200,
height=22,
dropdown_font=("Consolas", 14),
command=self.on_module_selected
)
self.module_combobox.pack(side="left", padx=4, pady=4)
# 刷新模块按钮
self.refresh_module_btn = ctk.CTkButton(
module_combo_frame,
text="刷新",
command=self.refresh_modules,
state="disabled",
width=60,
height=25
)
self.refresh_module_btn.pack(side="left", padx=5, pady=5)
# 搜索参数区域
search_frame = ctk.CTkFrame(main_frame)
search_frame.pack(fill="x", padx=10, pady=5)
search_label = ctk.CTkLabel(search_frame, text="搜索和替换参数:", font=("Arial", 12, "bold"))
search_label.pack(anchor="w", padx=5, pady=5)
# 原始字节输入
original_frame = ctk.CTkFrame(search_frame)
original_frame.pack(fill="x", padx=5, pady=5)
original_label = ctk.CTkLabel(original_frame, text="原始字节 (hex):", font=("Arial", 10))
original_label.pack(anchor="w")
self.original_entry = ctk.CTkEntry(original_frame, placeholder_text="例如: 90 90 90 90 或 90909090", height=25)
self.original_entry.pack(fill="x", padx=5, pady=5)
# 替换字节输入
replace_frame = ctk.CTkFrame(search_frame)
replace_frame.pack(fill="x", padx=5, pady=5)
replace_label = ctk.CTkLabel(replace_frame, text="替换字节 (hex):", font=("Arial", 10))
replace_label.pack(anchor="w")
self.replace_entry = ctk.CTkEntry(replace_frame, placeholder_text="例如: C3 或 任何其他字节序列", height=25)
self.replace_entry.pack(fill="x", padx=5, pady=5)
# 操作按钮
action_frame = ctk.CTkFrame(search_frame)
action_frame.pack(fill="x", padx=5, pady=10)
self.search_btn = ctk.CTkButton(
action_frame,
text="搜索字节模式",
command=self.search_bytes,
width=80,
height=25
)
self.search_btn.pack(side="left", padx=5)
self.replace_btn = ctk.CTkButton(
action_frame,
text="替换字节模式",
command=self.replace_bytes,
state="disabled",
width=80,
height=25
)
self.replace_btn.pack(side="left", padx=5)
# 结果显示区域
result_frame = ctk.CTkFrame(main_frame)
result_frame.pack(fill="both", expand=True, padx=10, pady=5)
result_label = ctk.CTkLabel(result_frame, text="结果:", font=("Arial", 12, "bold"))
result_label.pack(anchor="w", padx=5, pady=5)
# 创建文本框显示结果
self.result_text = scrolledtext.ScrolledText(
result_frame,
width=60,
height=8,
bg="#2b2b2b",
fg="white",
font=("Consolas", 11)
)
self.result_text.pack(fill="both", expand=True, padx=5, pady=5)
# 初始化进程列表
self.refresh_processes()
def refresh_processes(self):
"""刷新进程列表"""
current_values = []
self.process_list_data = []
try:
processes = []
for proc in psutil.process_iter(['pid', 'name']):
try:
# 检查是否显示系统进程
if not self.show_system_processes.get():
# 简单判断系统进程:位于System32目录下
try:
proc_exe = proc.exe()
if "system32" in proc_exe.lower() or "syswow64" in proc_exe.lower():
continue
except (psutil.AccessDenied, psutil.NoSuchProcess):
pass
except (psutil.AccessDenied, psutil.NoSuchProcess):
# 如果无法获取exe路径,则根据名称判断
system_names = ["svchost.exe", "dllhost.exe", "conhost.exe", "taskhostw.exe",
"wininit.exe", "winlogon.exe", "services.exe", "lsass.exe",
"smss.exe", "csrss.exe", "dwm.exe", "explorer.exe"]
if proc.info['name'].lower() in [name.lower() for name in system_names]:
continue
processes.append((proc.info['pid'], proc.info['name']))
current_values.append(f"{proc.info['pid']:>8} : {proc.info['name']}")
# 按名称排序
processes.sort(key=lambda x: x[1].lower())
current_values.sort(key=lambda x: x.split(":")[1].strip().lower())
self.process_list_data = processes
self.process_combobox.configure(values=current_values)
if current_values:
self.process_combobox.set(current_values[0])
except Exception as e:
messagebox.showerror("错误", f"无法获取进程列表: {str(e)}")
def on_settings_change(self):
"""当设置改变时刷新进程列表"""
self.refresh_processes()
def on_process_selected(self, choice):
"""进程选择回调函数 - 自动选择进程"""
self.select_process()
def on_module_selected(self, choice):
"""模块选择回调函数 - 自动处理模块选择"""
pass # 可以在这里添加模块选择后的逻辑
def bind_mousewheel(self, widget):
"""为组件绑定鼠标滚轮事件"""
def _on_mousewheel(event):
# 将滚轮滚动传递给内部 Listbox(如果存在)
try:
lb = getattr(widget, '_listbox', None)
if lb:
lb.yview_scroll(int(-1*(event.delta/120)), "units")
except Exception:
pass
# 绑定到显示按钮(FixedListDropdown 的 display)和 listbox(如果已创建)
try:
if hasattr(widget, 'display'):
widget.display.bind("", _on_mousewheel)
except Exception:
pass
try:
lb = getattr(widget, '_listbox', None)
if lb:
lb.bind("", _on_mousewheel)
except Exception:
pass
def setup_mousewheel(self):
"""设置鼠标滚轮支持"""
try:
# 为进程下拉列表绑定鼠标滚轮
self.bind_mousewheel(self.process_combobox)
# 为模块下拉列表绑定鼠标滚轮
self.bind_mousewheel(self.module_combobox)
# 设置下拉列表的固定高度和滚动
self.setup_dropdown_scroll()
except Exception as e:
print(f"设置鼠标滚轮时出错: {e}")
def setup_dropdown_scroll(self):
"""设置下拉列表的滚动行为"""
try:
# 获取下拉列表的内部Listbox组件并设置其高度
def setup_combobox_dropdown(combobox):
# 当下拉列表打开时,设置其高度
def on_dropdown_open():
try:
lb = getattr(combobox, '_listbox', None)
if lb:
lb.configure(height=min(combobox.max_display_items, 10))
except Exception:
pass
# 绑定打开事件到 display 按钮
combobox.bind("[B]", lambda e: combobox.after(100, on_dropdown_open))
# 为两个下拉列表设置滚动
setup_combobox_dropdown(self.process_combobox)
setup_combobox_dropdown(self.module_combobox)
except Exception as e:
print(f"设置下拉列表滚动时出错: {e}")
def select_process(self):
"""选择进程"""
try:
# 获取选中的进程
selected_text = self.process_combobox.get()
if not selected_text or not self.process_list_data:
messagebox.showwarning("警告", "请从进程列表中选择一个进程")
return
# 解析选中的进程信息
pid = None
name = None
for i, (p_pid, p_name) in enumerate(self.process_list_data):
if selected_text.startswith(f"{p_pid:>8} : {p_name}"):
pid = p_pid
name = p_name
break
if pid is None or name is None:
messagebox.showerror("错误", "无法解析选中的进程信息")
return
self.process_id = pid
self.pm = pymem.Pymem(pid)
# 更新标签
self.selected_process_label.configure(text=f"已选择: {name} ({pid})")
self.result_text.insert(tk.END, f"[+] 已连接到进程: {name} ({pid})\n")
self.replace_btn.configure(state="normal")
self.refresh_module_btn.configure(state="normal")
# 清空模块列表
self.module_combobox.configure(values=[])
self.module_combobox.set("")
self.module_list_data = []
# 自动刷新模块列表
self.refresh_modules()
except Exception as e:
messagebox.showerror("错误", f"无法连接到进程: {str(e)}")
self.replace_btn.configure(state="disabled")
self.refresh_module_btn.configure(state="disabled")
def refresh_modules(self):
"""刷新模块列表"""
if not self.pm or not self.process_id:
messagebox.showerror("错误", "请先选择一个进程")
return
module_values = []
self.module_list_data = []
try:
modules = list(self.pm.list_modules())
for module in modules:
module_name = module.name.decode('utf-8') if isinstance(module.name, bytes) else module.name
module_values.append(module_name)
self.module_list_data.append(module)
self.module_combobox.configure(values=module_values)
if module_values:
self.module_combobox.set(module_values[0])
self.result_text.insert(tk.END, f"[+] 已加载 {len(modules)} 个模块\n")
except Exception as e:
messagebox.showerror("错误", f"无法获取模块列表: {str(e)}")
def hex_to_bytes(self, hex_string):
"""将十六进制字符串转换为字节"""
# 移除所有空格
hex_string = hex_string.replace(" ", "")
# 如果长度是奇数,在前面加0
if len(hex_string) % 2 != 0:
hex_string = "0" + hex_string
# 转换为字节
return bytes.fromhex(hex_string)
def search_bytes(self):
"""搜索字节模式"""
if not self.pm or not self.process_id:
messagebox.showerror("错误", "请先选择一个进程")
return
try:
original_hex = self.original_entry.get().strip()
if not original_hex:
messagebox.showerror("错误", "请输入要搜索的原始字节")
return
# 转换原始字节
search_bytes = self.hex_to_bytes(original_hex)
# 获取模块信息(如果指定了模块)
selected_module_text = self.module_combobox.get()
addresses = []
# 使用在模块列表中选择的模块
if selected_module_text and self.module_list_data:
try:
# 查找选中的模块
selected_module = None
for i, module in enumerate(self.module_list_data):
module_name = module.name.decode('utf-8') if isinstance(module.name, bytes) else module.name
if module_name == selected_module_text:
selected_module = module
break
if selected_module:
base_address = selected_module.lpBaseOfDll
module_size = selected_module.SizeOfImage
# 在模块中搜索
module_name = selected_module.name.decode('utf-8') if isinstance(selected_module.name, bytes) else selected_module.name
self.result_text.insert(tk.END, f"[+] 在模块 {module_name} 中搜索...\n")
self.result_text.insert(tk.END, f" 基址: 0x{base_address:X}\n")
self.result_text.insert(tk.END, f" 大小: {module_size} 字节\n")
# 读取模块内存
try:
memory_data = self.pm.read_bytes(base_address, module_size)
# 查找所有匹配项
start = 0
while True:
pos = memory_data.find(search_bytes, start)
if pos == -1:
break
addresses.append(base_address + pos)
start = pos + 1
except Exception as e:
self.result_text.insert(tk.END, f"[!] 读取模块内存时出错: {str(e)}\n")
else:
self.result_text.insert(tk.END, "[!] 未找到选中的模块\n")
return
except Exception as e:
self.result_text.insert(tk.END, f"[!] 处理选中模块时出错: {str(e)}\n")
return
else:
# 在整个进程中搜索(这里简化处理,只在主模块中搜索)
self.result_text.insert(tk.END, "[+] 在进程内存中搜索...\n")
try:
# 获取主模块
process_handle = pymem.process.open(self.process_id)
main_module = pymem.process.base_module(process_handle)
base_address = main_module.lpBaseOfDll
module_size = main_module.SizeOfImage
# 关闭句柄
pymem.process.close_handle(process_handle)
# 读取内存
try:
memory_data = self.pm.read_bytes(base_address, module_size)
# 查找所有匹配项
start = 0
while True:
pos = memory_data.find(search_bytes, start)
if pos == -1:
break
addresses.append(base_address + pos)
start = pos + 1
except Exception as e:
self.result_text.insert(tk.END, f"[!] 读取进程内存时出错: {str(e)}\n")
except Exception as e:
self.result_text.insert(tk.END, f"[!] 获取进程基址时出错: {str(e)}\n")
# 显示结果
if addresses:
self.result_text.insert(tk.END, f"[+] 找到 {len(addresses)} 个匹配项:\n")
for addr in addresses[:20]: # 只显示前20个
# 尝试读取周围的字节以提供更多上下文
try:
context_bytes = self.pm.read_bytes(addr-4, 8)
self.result_text.insert(tk.END, f" 0x{addr:X}: {context_bytes.hex().upper()}\n")
except:
self.result_text.insert(tk.END, f" 0x{addr:X}\n")
if len(addresses) > 20:
self.result_text.insert(tk.END, f" ... 还有 {len(addresses) - 20} 个匹配项\n")
self.found_addresses = addresses
else:
self.result_text.insert(tk.END, "[-] 未找到匹配项\n")
except Exception as e:
self.result_text.insert(tk.END, f"[!] 搜索过程中发生错误: {str(e)}\n")
self.result_text.see(tk.END)
self.result_text.update()
def replace_bytes(self):
"""替换字节模式"""
if not self.pm or not self.process_id:
messagebox.showerror("错误", "请先选择一个进程")
return
try:
original_hex = self.original_entry.get().strip()
replace_hex = self.replace_entry.get().strip()
if not original_hex:
messagebox.showerror("错误", "请输入要搜索的原始字节")
return
if not replace_hex:
messagebox.showerror("错误", "请输入要替换成的字节")
return
# 转换字节
search_bytes = self.hex_to_bytes(original_hex)
replace_bytes = self.hex_to_bytes(replace_hex)
# 检查长度是否匹配
if len(search_bytes) != len(replace_bytes):
# 如果替换字节较短,则重复填充
if len(replace_bytes) 0:
self.result_text.insert(tk.END, f"[+] 成功替换了 {replaced_count} 处字节\n")
else:
self.result_text.insert(tk.END, "[-] 未找到匹配项进行替换\n")
except Exception as e:
self.result_text.insert(tk.END, f"[!] 替换过程中发生错误: {str(e)}\n")
self.result_text.see(tk.END)
self.result_text.update()
def main():
root = ctk.CTk()
app = MemoryPatchTool(root)
root.mainloop()
if __name__ == "__main__":
main()
pyproject文件内容
[project]
name = "hook-api"
version = "0.1.0"
description = "内存热补丁工具 - 对目标进程中指定模块的内存进行字节模式搜索和替换"
requires-python = ">=3.10"
dependencies = [
"psutil>=5.9.0",
"pymem>=1.10.0",
"customtkinter>=5.2.0",
]
[project.scripts]
memory-patch-tool = "memory_patch_tool:main"

