xxxx(五):接受消息hook代码实战
xxxx系列的二和四分别介绍了远程dll注入代码和接受消息的地址,接下来该hook代码实战了!(注意: 下面的代码不是一次调试成功的,期间经历和几十次的异常、奔溃和重启,每次地址可能都不一样,截图是多次截取的,地址看起来可能不连贯,甚至差异巨大,但不基本的消息接受功能是ok的);
先用xxxx系列二的注入代码注入dll,从x32dbg看成功了!
按照常规思路,最开始只更改5byte,即E9+偏移地址的形式,结果原代码后面紧跟着E8,和现在的hook代码“粘连”成新的指令,直接导致后续所有的代码都产生错乱,程序直接崩掉退出,这种hook方法看来是不行的!
重新来:既然和后面的E8粘连,就要想办法断开。借鉴以往处理字符串的经验:结尾用00断开表示字符串结束了,那么指令代码怎么截断了? 当然是NOP(0x90)了!这次hook改成6个byte,前面5个byte是jmp地址,最后一个byte改成NOP,避免“粘连”,这次成功了:
也能跳转到我们自定义的代码执行,说明地址hook是成功的!
效果展示:hook到消息后应该找个界面展示出来才能看到最终的效果。我这里为了简化代码、突出hook重点,直接选择了简单粗暴的messagebox弹窗打印消息,这样做也有个缺陷:messagebox是阻塞的,必须点击确认后才能继续执行代码(也就是继续接受下一条消息),看起来感觉是有延迟。效果如下:
某个群里:有人发了消息被截获后弹窗;
点击确认后,在聊天窗显示了出来,接着马上又截获下一条消息:
点击确认后继续在聊天窗显示了出来,此时又已经收到了两条消息:
dll的代码如下:
// dllmain.cpp : 定义 DLL 应用程序的入口点。 #include "pch.h" #include <string> #include "resource.h" using namespace std; void InitWindow(HMODULE); void HookChatRecord(); //void RegisterWindow(HMODULE); std::wstring GetMsgByAddress(DWORD); void RecieveWxMesage(); //LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); void ParseMessage(); #define WM_SendTextMessage 5 //接收的文本消息结构体 struct MessageStruct { wchar_t wxid[40]; wchar_t content[MAX_PATH]; }; wchar_t tempwxid[50] = { 0 }; //存放微信ID DWORD r_esp = 0; DWORD r_eax = 0; CHAR originalCode[6] = { 0 }; /* 1、计算需要HOOK的地址;0x3CCB6B是call dword prt ds:[eax+0x8]代码地址相对于基址的偏移; 这行代码前一行是push edi,把接收到的消息指针入栈,后面才能用[[esp]]读取,所以这行代码不能动,那就只能从call dword prt ds:[eax+0x8]这行代码开始hook了; 一共覆盖5 byte的代码,除了上面这样,还覆盖了push edi和push ecx,在返回hook地址前都要补上 2、从实践看,需要hook call dword prt ds:[eax+0x8]前一行、也就是push edi,一共hook 6字节(hook地址-1),最后一个字节NOP,和后面的机器码做个隔断,避免把后面的E8一起当成新的指令 3、后面从push edi开始还原 */ //DWORD dwHookAddr = (DWORD)GetModuleHandle(L"WeChatWin.dll") + 0x3CCB6B; DWORD dwHookAddr = (DWORD)GetModuleHandle(L"WeChatWin.dll") + 0x3CCB6B-1; //返回地址 //DWORD RetAddr = dwHookAddr + 5; DWORD RetAddr = dwHookAddr + 5 + 1; BOOL APIENTRY DllMain( HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved ) { switch (ul_reason_for_call) { case DLL_PROCESS_ATTACH: MessageBoxW(NULL,L"dll加载成功",L"dll加载测试", MB_OK); //启动线程来初始化界面 CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)InitWindow, hModule, 0, NULL); break; case DLL_THREAD_ATTACH: case DLL_THREAD_DETACH: case DLL_PROCESS_DETACH: break; } return TRUE; } void InitWindow(HMODULE hModule) { //获取WeChatWin的基址 DWORD dwWeChatWinAddr = (DWORD)GetModuleHandle(L"WeChatWin.dll"); //HOOK接收消息 HookChatRecord(); //注册窗口 //RegisterWindow(hModule); } void HookChatRecord() { //组装数据 BYTE bJmpCode[6] = { 0xE9,0x00,0x00,0x00,0x00,0x90 };//最后一个是NOP,避免和后面的额E8粘连形成新的指令,导致后续所有指令错乱 //这个函数地址在dll里面,应该是相对地址:load这个dll的时候OS应该会重定位,然后RecieveWxMesage表示绝对地址了 *(DWORD*)&bJmpCode[1] = (DWORD)RecieveWxMesage - (dwHookAddr + 5); //保存当前位置的指令,在unhook的时候使用;这个dll已经被加载到xxxx的进程,所以这里得到的是目标进程的handle; ReadProcessMemory(GetCurrentProcess(), (LPVOID)dwHookAddr, originalCode, 6, 0); //覆盖指令 B9 xxxxxxxx WriteProcessMemory(GetCurrentProcess(), (LPVOID)dwHookAddr, bJmpCode, 6, 0); } //结尾处直接jmp回hook地址,没有ret,所以这里用裸函数,避免破坏堆栈平衡 __declspec(naked) void RecieveWxMesage() { //保存现场 __asm { push edi//还原第一行代码,edi保存了消息的指针 //提取esp寄存器内容,放在一个变量中 mov r_esp, esp pushad pushfd } ParseMessage(); //恢复现场 __asm { popfd popad //hook时破坏的代码都要补上,一共5byte的机器码:FF 50 80 57 51 call dword ptr ds:[eax + 0x8] push edi push ecx //跳回被HOOK指令的下一条指令 jmp RetAddr } } void ParseMessage() { //信息块的位置 DWORD** msgAddress = (DWORD**)r_esp; wstring wid = GetMsgByAddress(**msgAddress + 0x40); wstring fullmsg = GetMsgByAddress(**msgAddress + 0x68); wstring isWid = GetMsgByAddress(**msgAddress + 0x164); wstring md5 = GetMsgByAddress(**msgAddress + 0x178); MessageBoxW(NULL, (LPCWSTR)&fullmsg, (LPCWSTR)&wid, MB_OK); return;//这里一定要写return,否则卡死 } /* 根据传入的memAddress读取字符串内容 */ wstring GetMsgByAddress(DWORD memAddress) { wstring tmp; DWORD msgLength = *(DWORD*)(memAddress + 4);//每个消息下面都有2个4byte的正数保存了这个字符串的长度 if (msgLength > 0) { WCHAR* msg = new WCHAR[msgLength + 1]{ 0 }; wmemcpy_s(msg, msgLength + 1, (WCHAR*)(*(DWORD*)memAddress), msgLength + 1); tmp = msg; delete[]msg; } return tmp; }
这份代码并不完美,缺陷也比较明显:(1)应该用diaologbox显示消息 (2)原本期望在messagebox标题显示发送消息人id,结果是乱码;有些消息本身也是乱码,还要进一步调试看看问题在哪;
这个功能还可以进一步完善用来抢红包:红包本质上也是一种消息,hook函数接收到后如果检测出是红包,立即调用收红包的函数!
参考:1、https://github.com/TonyChen56/WeChatRobot/blob/master/%E6%BA%90%E7%A0%81/WeChatHelper/WeChatHelper/ChatRecord.cpp xxxxRobot