之前[原创]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:
{
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:
{
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;
call OriginAddress;
mov xmlAddress, eax;
}
saveXml(xmlNameAddress, xmlAddress);
__asm
{
jmp BackAddress;
}
}
inline __declspec(naked) void ret_hook()
{
DWORD xmlNameAddress;
DWORD xmlAddress;
__asm
{
mov xmlNameAddress, ecx;
call OriginAddress;
传播安全知识、拓宽行业人脉——看雪讲师团队等你加入!