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

posted @ 2021-02-02 19:39  第七子007  阅读(782)  评论(0编辑  收藏  举报