首页
社区
课程
招聘
[原创]2025企业微信4.1.36.6011使用内联汇编导出所有的界面XML文件
发表于: 2025-4-21 18:18 8650

[原创]2025企业微信4.1.36.6011使用内联汇编导出所有的界面XML文件

2025-4-21 18:18
8650

之前[原创]2025企业微信4.1.36.6011调试Duilib获取界面XML实现了在调试器里面获取 XML
但是很不方便 毕竟企业微信的文件太多了 一个一个手动处理不现实
经过几天的学习 学会了用 inline hook(内联汇编) 修改指令 直接内容到文件里

按照上次文章的方法 找到 CDialogBuilder::LoadContent 方法所在的地方
图片描述
往上几行 有一个 LoadXml 方法 入参是文件名称 出参是文件内容
正好都是我们要的数据 我们可以替换这个指令

这里贴一下原始的字节码 我们需要修改的就是 E8 F724F8FF 这个 call <duilib.public 指令

然后找个AI写一个 inline hook 的模板代码 稍微修改一下

这里讲一下几个地址的获取方法

找到对应地址 然后右键 复制 选中 RVA(相对虚拟地址)
图片描述
原始地址 选中那个 call 然后 双击一下 就可以跳到对应的地址 然后复制一下 RVA
图片描述
这里原始地址也可以直接代码读取然后写入 暂时不会 就先手写了

在 DLL 的 DLL_PROCESS_ATTACH 部分执行一下 inline_hook

然后我们生成一下 DLL 注入一下 看看效果
可以看到 E8 F724F8FF 被替换成了 E9 376DB8F6

先写一个裸函数 然后用字节码保存一下相关的指针地址 然后执行保存的方法 然后跳回去

!!!注意!!!
!!!注意!!!
!!!注意!!!
这里 x32dbg 要打开一下 入口断点 不然等启动后注入 会缺一部分文件
用 x32dbg 启动企业微信 然后在入口断点停下 然后注入 dll

可以看一下 ret_hook 的汇编代码

然后按 F9 继续运行 去目录下面看一下 会生成很多文件
图片描述

项目属性 C++20标准 不使用预编译头 Unicode 字符集

最近一直在整收发消息 然后发现企业微信是多线程的

暂时还没搞清楚具体怎么实现的 大概可以找到是哪里发出去和哪里收回来的
但是栈上面找不到具体的消息内容 只能找到用户ID
我猜可能是传递了对象 所以找不到
希望大佬们可以指点一下

70027863 | 50                       | push eax
70027864 | E8 F724F8FF              | call <duilib.public: static class std::basic_string<wchar_t, struct std::char_traits<wchar_t>, class std::allocator<wchar_t>> __cdecl DuiLib::CResManager::LoadXML(wchar_t const *, int)>
70027869 | 83C4 0C                  | add esp,0xC
7002786C | 8365 FC 00               | and dword ptr ss:[ebp-0x4],0x0
70027863 | 50                       | push eax
70027864 | E8 F724F8FF              | call <duilib.public: static class std::basic_string<wchar_t, struct std::char_traits<wchar_t>, class std::allocator<wchar_t>> __cdecl DuiLib::CResManager::LoadXML(wchar_t const *, int)>
70027869 | 83C4 0C                  | add esp,0xC
7002786C | 8365 FC 00               | and dword ptr ss:[ebp-0x4],0x0
// 生成跳转代码
void GenerateJmpCode(DWORD value, BYTE *byteArray)
{
    byteArray[0] = 0xE9;                       // 跳转指令 JMP E9
    byteArray[1] = (value & 0x000000FF);       // 最低位字节
    byteArray[2] = (value & 0x0000FF00) >> 8;  // 第二字节
    byteArray[3] = (value & 0x00FF0000) >> 16; // 第三字节
    byteArray[4] = (value & 0xFF000000) >> 24; // 最高位字节
}
void inline_hook()
{
    DWORD baseAddress = reinterpret_cast<DWORD>(GetModuleHandle(L"DuiLib.dll")); // 获取模块基址
    DWORD TargetAddress = (DWORD)ret_hook;                                       // 目标地址(自己定义的执行代码位置)
    BeginAddress = baseAddress + 0xE7864;                                        // 起始地址
    BackAddress = baseAddress + 0xE7869;                                         // 返回地址
    OriginAddress = baseAddress + 0x69D60;                                       // 原始Call地址
 
    std::wstring message = std::format(L"BaseAddress: {:x} BeginAddress: {:x} BackAddress: {:x} OriginAddress: {:x}", //
                                       baseAddress, BeginAddress, BackAddress, OriginAddress);
 
    MessageBoxW(NULL, message.c_str(), L"注入", MB_OK);
 
    // 读取保存原来的字节码
    ReadProcessMemory(GetCurrentProcess(), (LPCVOID)BeginAddress, (LPVOID)&OriginBytes, 5, NULL);
 
    BYTE jmpCode[5];
    GenerateJmpCode(TargetAddress - BeginAddress - 5, jmpCode); // 目标地址 - 当前地址 - 5
 
    // 写入JMP跳转字节码
    WriteProcessMemory(GetCurrentProcess(), (LPVOID)BeginAddress, (LPVOID)&jmpCode, 5, NULL);
}
// 生成跳转代码
void GenerateJmpCode(DWORD value, BYTE *byteArray)
{
    byteArray[0] = 0xE9;                       // 跳转指令 JMP E9
    byteArray[1] = (value & 0x000000FF);       // 最低位字节
    byteArray[2] = (value & 0x0000FF00) >> 8;  // 第二字节
    byteArray[3] = (value & 0x00FF0000) >> 16; // 第三字节
    byteArray[4] = (value & 0xFF000000) >> 24; // 最高位字节
}
void inline_hook()
{
    DWORD baseAddress = reinterpret_cast<DWORD>(GetModuleHandle(L"DuiLib.dll")); // 获取模块基址
    DWORD TargetAddress = (DWORD)ret_hook;                                       // 目标地址(自己定义的执行代码位置)
    BeginAddress = baseAddress + 0xE7864;                                        // 起始地址
    BackAddress = baseAddress + 0xE7869;                                         // 返回地址
    OriginAddress = baseAddress + 0x69D60;                                       // 原始Call地址
 
    std::wstring message = std::format(L"BaseAddress: {:x} BeginAddress: {:x} BackAddress: {:x} OriginAddress: {:x}", //
                                       baseAddress, BeginAddress, BackAddress, OriginAddress);
 
    MessageBoxW(NULL, message.c_str(), L"注入", MB_OK);
 
    // 读取保存原来的字节码
    ReadProcessMemory(GetCurrentProcess(), (LPCVOID)BeginAddress, (LPVOID)&OriginBytes, 5, NULL);
 
    BYTE jmpCode[5];
    GenerateJmpCode(TargetAddress - BeginAddress - 5, jmpCode); // 目标地址 - 当前地址 - 5
 
    // 写入JMP跳转字节码
    WriteProcessMemory(GetCurrentProcess(), (LPVOID)BeginAddress, (LPVOID)&jmpCode, 5, NULL);
}
BOOL APIENTRY DllMain(HMODULE hModule,
                      DWORD ul_reason_for_call,
                      LPVOID lpReserved)
{
    switch (ul_reason_for_call)
    {
    case DLL_PROCESS_ATTACH:
    {
        // 附加的时候进行 hook
        saveXmlPath = L"D:\\repos\\wxworkdebug";
        inline_hook();
        break;
    }
    case DLL_THREAD_ATTACH:
    case DLL_THREAD_DETACH:
        break;
    case DLL_PROCESS_DETACH:
    {
        // 卸载的时候恢复原来的代码
        WriteProcessMemory(GetCurrentProcess(), (LPVOID)BeginAddress, (LPVOID)&OriginBytes, 5, NULL);
        break;
    }
    }
    return TRUE;
}
BOOL APIENTRY DllMain(HMODULE hModule,
                      DWORD ul_reason_for_call,
                      LPVOID lpReserved)
{
    switch (ul_reason_for_call)
    {
    case DLL_PROCESS_ATTACH:
    {
        // 附加的时候进行 hook
        saveXmlPath = L"D:\\repos\\wxworkdebug";
        inline_hook();
        break;
    }
    case DLL_THREAD_ATTACH:
    case DLL_THREAD_DETACH:
        break;
    case DLL_PROCESS_DETACH:
    {
        // 卸载的时候恢复原来的代码
        WriteProcessMemory(GetCurrentProcess(), (LPVOID)BeginAddress, (LPVOID)&OriginBytes, 5, NULL);
        break;
    }
    }
    return TRUE;
}
70027863 | 50                       | push eax
70027864 | E9 376DB8F6              | jmp <wxworkxmlexport.void __cdecl ret_hook(void)>
70027869 | 83C4 0C                  | add esp,0xC
7002786C | 8365 FC 00               | and dword ptr ss:[ebp-0x4],0x0
70027863 | 50                       | push eax
70027864 | E9 376DB8F6              | jmp <wxworkxmlexport.void __cdecl ret_hook(void)>
70027869 | 83C4 0C                  | add esp,0xC
7002786C | 8365 FC 00               | and dword ptr ss:[ebp-0x4],0x0
inline __declspec(naked) void ret_hook()
{
    DWORD xmlNameAddress;
    DWORD xmlAddress;
    // 原程序代码务必要执行
    __asm
    {
         mov xmlNameAddress, ecx; // 保存入参 也就是 xml 的名称
         call OriginAddress; //执行原始的 LoadXml 的那个Call
         mov xmlAddress, eax; // 保存出参 也就是 xml 的内容
    }
 
    saveXml(xmlNameAddress, xmlAddress); // 这里执行一下自定义的保存方法
 
    __asm
    {
         jmp BackAddress; // 跳回原来的逻辑 继续执行
    }
}
inline __declspec(naked) void ret_hook()
{
    DWORD xmlNameAddress;
    DWORD xmlAddress;
    // 原程序代码务必要执行
    __asm
    {
         mov xmlNameAddress, ecx; // 保存入参 也就是 xml 的名称
         call OriginAddress; //执行原始的 LoadXml 的那个Call

传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!!

收藏
点赞 ・43
打赏
分享
最新回复 (32)
雪    币: 0
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
2 楼
666666
2025-4-22 14:20
0
雪    币: 2040
活跃值: (2430)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
3 楼
ida搜  presendnewmessage   即可定位到发消息
2025-4-22 16:49
0
雪    币: 2868
活跃值: (2986)
能力值: ( LV5,RANK:60 )
在线值:
发帖
回帖
粉丝
4 楼
2025-4-22 19:43
0
雪    币: 217
活跃值: (2132)
能力值: ( LV3,RANK:25 )
在线值:
发帖
回帖
粉丝
5 楼
不错啊
2025-4-23 14:39
0
雪    币: 56
活跃值: (71)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
6 楼
厉害大佬
2025-4-23 21:46
0
雪    币: 2265
活跃值: (2172)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
7 楼
FANTASYING ida搜 presendnewmessage 即可定位到发消息
这个我之前找到过 然后消息内容的引用地址也找到过 好像固定在 0019F114 但是直接改这个内容好像没用 感觉是赋值给某个对象 然后传递到发送线程了 之前根据 sqlite 去追踪 数据库执行的SQL 倒是找出来了 不过还是没啥头绪 怎么构造发送的消息体
2025-4-24 14:15
0
雪    币: 244
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
8 楼
m
2025-4-24 16:39
0
雪    币: 2040
活跃值: (2430)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
9 楼
MiaoWoo 这个我之前找到过 然后消息内容的引用地址也找到过 好像固定在 0019F114 但是直接改这个内容好像没用 感觉是赋值给某个对象 然后传递到发送线程了 之前根据 sqlite 去追踪 数据库执行的SQ ...
他参数全是对象,向上分析,去找pb构造,即可构造出参数拉~
2025-4-24 19:41
0
雪    币: 67
活跃值: (2456)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
10 楼
谢谢如此好的分析
2025-5-4 16:49
0
雪    币: 200
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
11 楼
谢谢分享
2025-5-4 17:37
0
雪    币: 398
活跃值: (2805)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
12 楼
谢谢分享
2025-5-8 18:54
0
雪    币: 200
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
13 楼
你的帖子非常有用,感谢分享!
2025-5-13 16:35
0
雪    币: 135
活跃值: (3055)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
14 楼
不错
2025-6-5 19:38
0
雪    币: 5116
活跃值: (3822)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
15 楼
你的帖子非常有用,感谢分享!
2025-6-5 19:42
0
雪    币: 200
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
16 楼
感谢分享
2025-6-11 15:58
0
雪    币: 6413
活跃值: (5201)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
17 楼
啥时候出期发送消息的CALL的帖子
2025-7-18 15:15
0
雪    币: 1925
活跃值: (2487)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
18 楼
看看主题
2025-7-21 10:51
0
雪    币: 2265
活跃值: (2172)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
19 楼
linghaien 啥时候出期发送消息的CALL的帖子
最近估计没啥时间 之前那个项目后来停掉了 就没研究了
2025-8-4 13:59
0
雪    币: 205
活跃值: (303)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
20 楼
好贴,有用
2025-8-7 14:47
0
雪    币: 200
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
21 楼
学习
2025-9-15 18:17
0
雪    币: 96
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
22 楼
学习
2025-9-17 05:20
0
雪    币: 0
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
23 楼
学习
2025-10-24 23:48
0
雪    币: 0
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
24 楼
1111
2025-10-26 11:06
0
雪    币: 10
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
25 楼
啥时候出期发送消息的CALL的帖子
2025-11-23 15:19
0
游客
登录 | 注册 方可回帖
返回