美文网首页
PC端VX消息防撤回 之 DLL注入HOOK(二)

PC端VX消息防撤回 之 DLL注入HOOK(二)

作者: Sharkchilli | 来源:发表于2020-07-13 14:12 被阅读0次

    前言

    上一章中介绍了vx的逆向分析,现在我们就要进行编码了,我的思路如下:

    • 使用远程注入DLL方式将我们的代码注入到VX中
    • 再在DLL中使用Inline hook hook上一章中找到的关键函数
    • 最后在我们的hook代码中做业务处理

    所以这里我使用了两个项目:

    DLL注入

    #include <iostream>
    #include "stdlib.h"
    #include <tchar.h>
    #include <Windows.h>
    
    bool Inject(DWORD dwId, char * szPath)//参数1:目标进程PID  参数2:DLL路径
    {
        //一、在目标进程中申请一个空间
    
    
        /*
        【1.1 获取目标进程句柄】
        参数1:想要拥有的进程权限(本例为所有能获得的权限)
        参数2:表示所得到的进程句柄是否可以被继承
        参数3:被打开进程的PID
        返回值:指定进程的句柄
        */
        HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwId);
    
    
        /*
        【1.2 在目标进程的内存里开辟空间】
        参数1:目标进程句柄
        参数2:保留页面的内存地址,一般用NULL自动分配
        参数3:欲分配的内存大小,字节单位
        参数4:MEM_COMMIT:为特定的页面区域分配内存中或磁盘的页面文件中的物理存储
        参数5:PAGE_READWRITE 区域可被应用程序读写
        返回值:执行成功就返回分配内存的首地址,不成功就是NULL
        */
        LPVOID pRemoteAddress = VirtualAllocEx(
            hProcess,
            NULL,
            1,
            MEM_COMMIT,
            PAGE_READWRITE
        );
    
        //二、 把dll的路径写入到目标进程的内存空间中
    
        DWORD dwWriteSize = 0;
        /*
        【写一段数据到刚才给指定进程所开辟的内存空间里】
        参数1:OpenProcess返回的进程句柄
        参数2:准备写入的内存首地址
        参数3:指向要写的数据的指针(准备写入的东西)
        参数4:要写入的字节数(东西的长度+0/)
        参数5: 返回值。返回实际写入的字节
        */
        WriteProcessMemory(hProcess,
            pRemoteAddress,
            szPath,
            strlen(szPath) * 1 + 1,
            &dwWriteSize);
    
    
        //三、 创建一个远程线程,让目标进程调用LoadLibrary
    
        /*
        参数1:该远程线程所属进程的进程句柄
        参数2:一个指向 SECURITY_ATTRIBUTES 结构的指针, 该结构指定了线程的安全属性
        参数3:线程栈初始大小,以字节为单位,如果该值设为0,那么使用系统默认大小
        参数4:在远程进程的地址空间中,该线程的线程函数的起始地址(也就是这个线程具体要干的活儿)
        参数5:传给线程函数的参数(刚才在内存里开辟的空间里面写入的东西)
        参数6:控制线程创建的标志。0(NULL)表示该线程在创建后立即运行
        参数7:指向接收线程标识符的变量的指针。如果此参数为NULL,则不返回线程标识符
        返回值:如果函数成功,则返回值是新线程的句柄。如果函数失败,则返回值为NULL
        */
        HANDLE hThread = CreateRemoteThread(
            hProcess,
            NULL,
            0,
            (LPTHREAD_START_ROUTINE)LoadLibrary,
            pRemoteAddress,
            NULL,
            NULL
        );
        if (hThread == NULL) {
            printf("CreateRemoteThread fail:%d\n", (int)hThread);
        }
        else
        {
            printf("hThread:%d\n", (int)hThread);
        }
    
        WaitForSingleObject(hThread, -1); //当句柄所指的线程有信号的时候,才会返回
    
        /*
        四、 【释放申请的虚拟内存空间】
        参数1:目标进程的句柄。该句柄必须拥有 PROCESS_VM_OPERATION 权限
        参数2:指向要释放的虚拟内存空间首地址的指针
        参数3:虚拟内存空间的字节数
        参数4:MEM_DECOMMIT仅标示内存空间不可用,内存页还将存在。
               MEM_RELEASE这种方式很彻底,完全回收。
        */
        VirtualFreeEx(hProcess, pRemoteAddress, 1, MEM_DECOMMIT);
        return 0;
    }
    
    
    int _tmain(int argc, _TCHAR * argv[])
    {
        //这里是在目标进程的路径 所以我们暂时使用绝对路径
        char wStr[] = "D:\\source\\repos\\SharkDll\\Debug\\SharkDll.dll";
    
        DWORD dwId = 0;
    
        //参数1:(NULL
        //参数2:目标窗口的标题
        //返回值:目标窗口的句柄
        //HWND hCalc = FindWindow("TfrmMain", NULL);
        HWND hCalc = FindWindow(NULL,"微信");
        //HWND hCalc = FindWindow("IEFrame", NULL);
        printf("目标窗口的句柄为:%d\n", (int)hCalc);
    
        DWORD dwPid = 0;
    
        //参数1:目标进程的窗口句柄
        //参数2:把目标进程的PID存放进去
        DWORD dwRub = GetWindowThreadProcessId(hCalc, &dwPid);
        printf("目标窗口的进程PID为:%d\n", dwPid);
        //return 0;
        //参数1:目标进程的PID
        //参数2:想要注入DLL的路径
        Inject(dwPid, wStr);
    
        return 0;
    }
    

    这段代码是网上的注释写的非常清楚了我稍微说明一下:

    • OpenProcess - 用于打开要寄生的目标进程。
    • VirtualAllocEx/VirtualFreeEx - 用于在目标进程中分配/释放内存空间。
    • WriteProcessMemory - 用于在目标进程中写入要加载的DLL名称。
    • CreateRemoteThread - 远程加载DLL的核心内容,用于控制目标进程调用API函数。
    • LoadLibrary - 目标进程通过调用此函数来加载DLL。

    在此我只给出了简要的函数说明,关于函数的详细功能和介绍请参阅MSDN。
    注意:dll路径我为了方便测试使用了绝对路径,请注意修改

    HOOK

    为了有操作界面我使用了MFC的DLL工程
    项目代码我都上传到git上了,我们这看看关键代码

    先看看我们要使用到的hook函数
    hook.cpp

    /**
    * 安装Hook
    * hookAddr 内存地址
    * backCode 备份的地址
    * HookImpl 跳转到的函数,该函数为裸函数
    */
    int StartHook5(DWORD hookAddr, BYTE backCode[5], void(*FuncBeCall)()) {
        DWORD jmpAddr = (DWORD)FuncBeCall - (hookAddr + 5);
    
        BYTE jmpCode[5];
        *(jmpCode + 0) = 0xE9;
        *(DWORD *)(jmpCode + 1) = jmpAddr;
        HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, NULL, GetCurrentProcessId());
        //备份被替换的
        if (ReadProcessMemory(hProcess, (LPVOID)hookAddr, backCode, 5, NULL) == 0) {
            return -1;
        }
        //写入jmp指令
        if (WriteProcessMemory(hProcess, (LPVOID)hookAddr, jmpCode, 5, NULL) == 0) {
            return -1;
        }
    
        return 0;
    }
    
    

    这段hook代码是网上的,写的并不是很清楚我说明一下
    参数一:关键call的地址
    参数二:保存原来call的函数地址
    参数三:是hook后的函数

    调用完此函数后可以将
    call WeChatWi.56BA6F9A
    修改为call _OnRevock
    即修改为我们的业务函数

    现在看看如何调用这个hook函数
    SharkDllMenuDialog.cpp

    
    //我们计算得到的相对地址,通过获得WeChatWin.dll的基址就可以得到本次运行的虚拟地址
    //调用关键call的相对地址
    #define REVOCK_CALL_RVA 0x52d358
    //关键call的相对地址
    #define REVOCK_CALL_TARGET_RVA 0x11d6f9a
    
    
    //关键call调用时的地址
    DWORD revockCallVA = 0;
    //原来的函数调用
    //55EFD358    E8 3D9CCA00     call WeChatWi.56BA6F9A
    DWORD revockCallTargetVA = 0;
    //原来的函数调用的下一句,调回的一句
    //55EFD35D    8B07            mov eax,dword ptr ds:[edi]
    DWORD revockCallJmpBackVA = 0;
    
    DWORD wechatWinAddr = 0;
    BYTE backCode[5];
    
    HWND m_dialog_hwnd;
    
    
    // SharkDllMenuDialog 消息处理程序
    CEdit* pBoxOne;
    
    void SharkDllMenuDialog::OnBnClickedOk()
    {
        // TODO: 在此添加控件通知处理程序代码
        //CDialogEx::OnOK();
        // 开启消息防撤回
        CreateThread(NULL, 0, LPTHREAD_START_ROUTINE(DlgThread), 0, 0, NULL);
        //将编辑框保存到pBoxOne
        pBoxOne = (CEdit*)sharkDialog->GetDlgItem(IDC_EDIT1);
    }
    
    
    //定义用于连接的字符串
    CString back_msg = NULL;
    CString back_split = CString(L"\r\n");
    CString back_msginfo = CString(L"撤回消息为:");
    CString back_tips = NULL;
    
    
    
    void OnRevock(DWORD esp) {
        wchar_t *tips = *(wchar_t **)(esp + 0x4);
        wchar_t *msg = *(wchar_t **)(esp + 0x0);
        if (NULL != tips) {
            //WCHAR buffer[0x8192];
            char buff2[0x8192];
            //因为hook的位置会有多次调用,所以需要判断tips和msg是否是我们需要的关键信息
            wchar_t* pos = wcsstr(tips, L"<revokemsg>");
            wchar_t* pos2 = wcsstr(msg, L"<revokemsg>");
            //如果需要的话我们把撤回的消息显示在编辑框中
            if (pos2==NULL && pos != NULL && NULL != msg) {
                CString temp_tip(tips);
                CString temp_msg(msg);
    
                //排除空信息
                if (temp_msg.IsEmpty() || temp_msg.GetLength() == 0 || !temp_msg) {
                    return;
                }
                //排除重复的信息
                if (back_msg == temp_msg && back_tips == temp_tip) {
                    return;
                }
                //下面就是以下字符串处理了
                back_msg = CString(msg);
                back_tips = CString(tips);
    
                //设置信息
                CString last_result;
                pBoxOne->GetWindowText(last_result);
                last_result = last_result + back_tips + back_split + back_msginfo + back_msg + back_split;
    
                pBoxOne->SetWindowText(last_result);
    
            }
        }
    
    }
    DWORD tEsp = 0;
    _declspec(naked) void _OnRevock() {
        //调用我们的业务函数前,请先保存现场
        __asm {
            mov tEsp, esp
            pushad
        }
        OnRevock(tEsp);
        //以下是调用原来的函数,执行正常的流程
        __asm {
            popad
            call revockCallTargetVA
            jmp revockCallJmpBackVA
        }
    }
    
    
    //开启线程进行hook,不开线程会卡死
    void DlgThread(HMODULE hInstance) {
        //hook开始
        MessageBoxA(NULL, "DLL Attached!\n", "Game Hacking", MB_OK | MB_TOPMOST);
    
        //得到WeChatWin.dll的基址
        wechatWinAddr = GetWxModuleAddress();
        //计算出关键call调用时的地址
        revockCallVA = wechatWinAddr + REVOCK_CALL_RVA;
        //计算出关键call的地址
        revockCallTargetVA = wechatWinAddr + REVOCK_CALL_TARGET_RVA;
        //指向了 55EFD35D    8B07            mov eax,dword ptr ds:[edi]
        //这里是通关计算得到调用call后的下一条指令,并没有使用到我们上一章的相对地址,因为上条指令刚好是5个字节所以+5
        revockCallJmpBackVA = revockCallVA + 5;
        StartHook5(wechatWinAddr + REVOCK_CALL_RVA, backCode, _OnRevock);
    
    }
    

    完整的代码可以到我的Github上看到。
    上面代码的具体做了以下事情:
    点击按钮时开启一个线程进行hook(这里不开线程会卡死),再获得WeChatWin.dll的基址在加上我们上一章得到的相对地址就可以计算出,调用关键call的地址和关键call的地址,以及他们的下一条汇编指令的地址,从而进行hook。将调用关键call的汇编改为_OnRevock函数,在_OnRevock中调用完业务处理OnRevock函数后,我们需要执行原来的流程也就是call revockCallTargetVA和jmp revockCallJmpBackVA。

    运行

    首先运行微信并且登录
    运行WechatTest.exe
    弹出以下窗口,这个窗口是属于微信的:


    image.png

    使用火绒剑可以看到我们的dll成功的注入了


    image.png
    点击防撤回开启,出现提示
    image.png
    现在进行使用微信发送并撤回一条消息:
    image.png image.png

    消息成功的被我们拦截到了

    尾言

    其实界面之类的可以做的更好看,也可以减少hook函数的调用次数,这些都需要去对微信进行进一步的逆向分析,找到界面有关的call函数,和分析其栈的变化情况。运用的技术点分别是远程注入inline hook,在注入和hook方便我也是只略知一二这里我就不对其做详细的说明了,感兴趣的朋友可以查找其相关资料。
    有了此文章即使微信进行更新,只要按照文章获得相应的相对地址并在代码中修改即可~

    参考文章

    PC微信逆向--实现消息防撤回
    详细解读:远程线程注入DLL到PC版微信

    相关文章

      网友评论

          本文标题:PC端VX消息防撤回 之 DLL注入HOOK(二)

          本文链接:https://www.haomeiwen.com/subject/dzfacktx.html