美文网首页
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