zoukankan      html  css  js  c++  java
  • 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

  • 相关阅读:
    Lucene in action 笔记 case study
    关于Restful Web Service的一些理解
    Lucene in action 笔记 analysis篇
    Lucene in action 笔记 index篇
    Lucene in action 笔记 term vector
    Lucene in action 笔记 search篇
    博客园开博记录
    数论(算法概述)
    DIV, IFRAME, Select, Span标签入门
    记一个较困难的SharePoint性能问题的分析和解决
  • 原文地址:https://www.cnblogs.com/theseventhson/p/14324562.html
Copyright © 2011-2022 走看看