赞 | 0 |
VIP | 2 |
好人卡 | 27 |
积分 | 1 |
经验 | 26327 |
最后登录 | 2019-10-13 |
在线时间 | 953 小时 |
Lv1.梦旅人
- 梦石
- 0
- 星屑
- 115
- 在线时间
- 953 小时
- 注册时间
- 2007-4-25
- 帖子
- 805
|
@yangff
先FindWindow抓句柄
然后SetWindowLong抓WndProc
对于属于外部线程的窗口,不能直接通过 SetWindowLong 替换窗口过程,普通情况下只有调用 SetWindowLong 的线程本身创建的窗口才能被替换。换句话说就是只能线程自己替换自己的窗口过程。RM 编辑器的窗口属于外部线程,所以直接调用肯定是不行的。
在必须事件驱动架构,且不修改 RPG Maker 程序本身的情况下,这个问题大概有三种解法:
1、非局部钩子
2、DLL 注入
3、直接的代码注入
第一种方法最安全,但局限性也最大——不同类型的钩子能拦截到的消息类型也不同,对于单个窗口来说,拦截能力不如直接替换窗口过程函数。第三种方法最自由,但最危险,需要考虑的地方也最多,最麻烦。下面用的是利用 CreateRemoteThread 和 LoadLibrary 这两者参数匹配的特性实现的 DLL 注入——因为 LoadLibrary 的签名和线程过程函数的签名匹配,所以 LoadLibrary 可以直接当做线程来用。在 LoadLibrary 成功映射 DLL 到目标进程地址空间之后,我们编写的 DllMain 就会被调用,代码注入就完成了。
关键步骤:
1、编写包含了 DllMain (共享库入口点)以及窗口过程函数的 hook.dll,无需导出任何函数;
2、编写另一个用于注入的 inject.dll,找到 RPG Maker 的窗口(EnumWindows);
3、通过窗口句柄打开一个 RPG Maker 的线程句柄,并请求必要的权限(OpenProcess);
4、在 RPG Maker 的地址空间中远程分配一段内存,用来保存 `hook.dll' 这个模块名(VirtualAllocEx);
5、将 `hook.dll' 写入该段内存(WriteProcessMemory);
6、创建远程线程,执行 `LoadLibrary("hook.dll");'的过程(CreateRemoteThread);
8、等待线程结束,回收垃圾(WaitForSingleObject、VirtualFreeEx、CloseHandle)。
之所以需要 4、5 步是因为 LoadLibrary 的参数是一个字符串指针,它指向的必须是局部于目标进程(在目标进程地址空间中)的字符串,所以需要远程分配内存。
inject.c:- #include <Windows.h>
- enum
- {
- LIB_PATH_SIZE = 9,
- BUF_SIZE = 128
- };
- #define CHECK(EXPR, MSG) do \
- { \
- if (!(EXPR)) { \
- MessageBox(0, MSG, "", MB_OK); \
- return; \
- } \
- } while (0)
- BOOL CALLBACK
- enum_windows_proc(HWND hwnd, LPARAM lparam)
- {
- char buf[BUF_SIZE];
- GetWindowText(hwnd, buf, BUF_SIZE);
- if (strstr(buf, "RPG Maker") == NULL)
- return TRUE;
- GetClassName(hwnd, buf, BUF_SIZE);
- if (strncmp(buf, "Afx:00400000:b:00010003:00000006:", 33) != 0)
- return TRUE;
- *((HWND*) lparam) = hwnd;
- return FALSE;
- }
- void WINAPI
- hook_map_tree(void)
- {
- HMODULE kernel32;
- HANDLE process;
- HANDLE thread;
- HWND window;
- DWORD process_id;
- LPTHREAD_START_ROUTINE load_library;
- void* lib_path;
- BOOL result;
- EnumWindows(enum_windows_proc, (LPARAM) &window);
- GetWindowThreadProcessId(window, &process_id);
- kernel32 = GetModuleHandle("kernel32");
- load_library = (LPTHREAD_START_ROUTINE)
- GetProcAddress(kernel32, "LoadLibraryA");
- process = OpenProcess(PROCESS_VM_OPERATION | PROCESS_VM_WRITE |
- PROCESS_VM_OPERATION | PROCESS_CREATE_THREAD |
- PROCESS_QUERY_INFORMATION, FALSE, process_id);
- CHECK(process != 0, "OpenProcess failed");
- lib_path = VirtualAllocEx(process, NULL, LIB_PATH_SIZE, MEM_COMMIT,
- PAGE_READWRITE);
- CHECK(lib_path != 0, "VirtualAllocEx failed");
- result = WriteProcessMemory(process, lib_path, "hook.dll", LIB_PATH_SIZE,
- NULL);
- CHECK(result != 0, "WriteProcessMemory failed");
- thread = CreateRemoteThread(process, NULL, 0, load_library, lib_path, 0,
- NULL);
- CHECK(thread != 0, "CreateRemoteThread failed");
- WaitForSingleObject(thread, INFINITE);
- VirtualFreeEx(process, lib_path, LIB_PATH_SIZE, MEM_RELEASE);
- CloseHandle(thread);
- CloseHandle(process);
- }
复制代码 导出 hook_map_tree。
hook.c:
- #include <windows.h>
- #include <Commctrl.h>
- #include <stdio.h>
- #include <string.h>
- WNDPROC original_proc;
- LRESULT CALLBACK
- window_proc(HWND window, UINT msg, WPARAM wparam, LPARAM lparam)
- {
- if (msg == WM_NOTIFY) {
- if (((NMHDR*) lparam)->code == TVN_SELCHANGING)
- {
- char buf[128];
- NMTREEVIEW* tv = (NMTREEVIEW*) lparam;
- sprintf(buf, "Map id:%d", tv->itemNew.lParam);
- MessageBox(GetForegroundWindow(), buf, "Hook", MB_OK);
- }
- }
- return CallWindowProc(original_proc, window, msg, wparam, lparam);
- }
- enum
- {
- BUF_SIZE = 128
- };
- BOOL CALLBACK
- enum_windows_proc(HWND hwnd, LPARAM lparam)
- {
- char buf[BUF_SIZE];
- GetWindowText(hwnd, buf, BUF_SIZE);
- if (strstr(buf, "RPG Maker") == NULL)
- return TRUE;
- GetClassName(hwnd, buf, BUF_SIZE);
- if (strncmp(buf, "Afx:00400000:b:00010003:00000006:", 33) != 0)
- return TRUE;
- *((HWND*) lparam) = hwnd;
- return FALSE;
- }
- HWND
- find_window(void)
- {
- HWND window;
- EnumWindows(enum_windows_proc, (LPARAM) &window);
- window = FindWindowEx(window, NULL, "AfxMDIFrame80s", "");
- window = FindWindowEx(window, NULL, "AfxMDIFrame80s", "");
- //window = FindWindowEx(window, NULL, "SysTreeView32", "");
- return window;
- }
- BOOL WINAPI
- DllMain(HINSTANCE instance, DWORD reason, LPVOID reserved)
- {
- if (reason == DLL_PROCESS_ATTACH)
- original_proc = (WNDPROC) SetWindowLong(find_window(), GWL_WNDPROC,
- (LONG) window_proc);
- return TRUE;
- }
复制代码 Tree-View 控件在选择项改变时会发送通知消息给父窗口,所以在 hook.c 里面替换 SysTreeView32 的父窗口 AfxMDIFrame80s 的窗口过程函数,并处理 WM_NOTIFY 就行了。地图的编号保存在 NMTREEVIEW#itemNew#lParam 中。这里拦截的是 TVN_SELCHANGING 通知消息,是在选择项即将改变时发送给父窗口的,所以 NMTREEVIEW#itemNew 就描述了改变后的新选项的信息。
先保证 RM 的 SysTreeView32 已经被创建,然后就可以在 Ruby 中调用:
hook_map_tree = Win32API.new('inject', 'hook_map_tree', 'v', 'v')
hook_map_tree.call
[/code]
目前的实现是选项改变时弹出地图编号。
我这里没有 RMVX,止于测试 RMXP,在搜索窗口那里可能有所不同,适当调整一下窗口标题、窗口类名就行了。inject.c 只需要找到 RM 编辑器的主窗口(因为创建窗口的线程是统一的),但 hook.c 需要找到 SysTreeView32 的父窗口。
附上两个预编译的 DLL:
Project1.zip
(40.23 KB, 下载次数: 31)
|
评分
-
查看全部评分
|