关于在GDI的CreatePen函数地址处挂载跳转钩子的代码咨询
函数指针Hook的Zig代码修正与风险说明
核心问题分析
你的代码目标是替换内存中指定地址的函数指针,将其指向自定义钩子函数,同时保留原函数调用逻辑。当前Zig代码存在多处语法和逻辑错误,以下是具体问题和修正方案:
1. 指针类型错误
原代码中FunctionAddress被声明为*u32,但x64系统下函数指针是8字节(u64),应改为*volatile u64(volatile防止编译器优化读写操作)。
2. 错误的跳转指令写法
原代码试图用[2]u8存储跳转指令,但实际上我们是直接替换函数指针的值,不需要机器码指令——直接将钩子函数的地址写入目标内存地址即可。
3. DllMain返回类型错误
Windows的DllMain要求返回BOOL(即c_int),而非Zig的错误联合类型!bool。
4. WriteFile参数不完整
WriteFile需要指定写入字节数,且应使用标准常量STD_OUTPUT_HANDLE而非硬编码的-11。
5. 未保存原函数指针的正确方式
原代码中ActualFunction的赋值方式错误,应先读取目标地址的原始函数指针,再替换为钩子地址。
修正后的Zig代码
const std = @import("std"); const windows = std.os.windows; // 目标地址:存储CreatePen函数指针的内存位置 const FunctionAddress: *volatile u64 = @ptrFromInt(0x142c46108); // 原函数指针,后续在DllMain中初始化 var ActualFunction: fn(c_int, c_int, windows.DWORD) ?anyopaque = undefined; pub export fn DllMain(handle: ?anyopaque, reason: windows.DWORD, reserved: ?anyopaque) callconv(windows.WINAPI) windows.BOOL { if (reason != windows.DLL_PROCESS_ATTACH) { return windows.TRUE; } // 1. 保存原函数指针 ActualFunction = @ptrFromInt(FunctionAddress.*); // 2. 获取钩子函数地址 const hook_address = windows.kernel32.GetProcAddress(handle, "CreatePenHook") orelse { std.log.err("Failed to get CreatePenHook address", .{}); return windows.FALSE; }; const hook_address_int: u64 = @intFromPtr(hook_address); // 3. 修改内存保护为可读写 var old_protect: windows.DWORD = undefined; const protect_success = windows.kernel32.VirtualProtect( FunctionAddress, @sizeOf(u64), windows.PAGE_EXECUTE_READWRITE, &old_protect ); if (protect_success == 0) { std.log.err("VirtualProtect failed: {d}", .{windows.kernel32.GetLastError()}); return windows.FALSE; } // 4. 写入钩子函数地址到目标指针位置 FunctionAddress.* = hook_address_int; // 5. 恢复原内存保护(可选但推荐) _ = windows.kernel32.VirtualProtect( FunctionAddress, @sizeOf(u64), old_protect, &old_protect ); return windows.TRUE; } pub export fn CreatePenHook(arg1: c_int, arg2: c_int, arg3: windows.DWORD) callconv(windows.WINAPI) ?anyopaque { // 输出日志到控制台 const msg = "Created Pen\n"; const stdout_handle = windows.kernel32.GetStdHandle(windows.STD_OUTPUT_HANDLE) orelse { std.log.err("Failed to get stdout handle", .{}); return ActualFunction(arg1, arg2, arg3); }; var bytes_written: windows.DWORD = undefined; _ = windows.kernel32.WriteFile( stdout_handle, msg.ptr, @intCast(windows.DWORD, msg.len), &bytes_written, null ); // 调用原函数 return ActualFunction(arg1, arg2, arg3); }
关键风险提示
- 目标地址有效性:必须确保
0x142c46108确实是存储CreatePen函数指针的有效地址,若地址错误会导致程序崩溃或非法内存访问。 - 内存保护权限:若目标地址所在内存页无法修改(如系统只读区域),VirtualProtect会失败,需确认该地址的可写性。
- 线程安全:若多个线程同时访问该函数指针,替换操作可能导致竞态条件,建议添加同步机制(如临界区)。
- 原函数调用一致性:确保钩子函数的参数、调用约定(
WINAPI即stdcall)与原函数完全一致,否则会导致栈损坏。 - DllMain限制:在DllMain中避免执行复杂操作(如控制台输出),部分场景下可能导致死锁,建议将钩子初始化移至专门的导出函数。
内容的提问来源于stack exchange,提问作者Nazar
相关产品推荐
相关产品推荐

