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

  • 相关阅读:
    Python 日志处理(三) 日志状态码分析、浏览器分析
    Python 日志处理(二) 使用正则表达式处理Nginx 日志
    mongodb关联查询 和spring data mongodb
    redis实现分布式锁
    springboot使用过滤器和拦截器
    springboot使用schedule定时任务
    fastjson格式化输出内容
    logback-spring.xml
    spring data jpa封装specification实现简单风格的动态查询
    spring data jpa自定义baseRepository
  • 原文地址:https://www.cnblogs.com/theseventhson/p/14324562.html
Copyright © 2011-2022 走看看