尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

WeChatICU-ForMac源码解析(三):fishhook重绑定NSHomeDirectory与企业微信多开绕过单例检测的秘密

WeChatICU-ForMac源码解析(三):fishhook重绑定NSHomeDirectory与企业微信多开绕过单例检测的秘密 WeChatICU-ForMac源码解析三fishhook重绑定NSHomeDirectory与企业微信多开绕过单例检测的秘密【免费下载链接】WeChatICU-ForMacMac版企业微信消息防撤回, 聊天会话去水印项目地址: https://gitcode.com/gh_mirrors/we/WeChatICU-ForMacWeChatICU-ForMac 是一款面向 Mac 版企业微信的开源小助手主打消息防撤回和聊天会话去水印两大功能。在系列前两篇里我们了解了它如何用 ObjC 方法交换Method Swizzling拦截撤回消息和水印开关。这一篇进入最硬核的部分作者如何用 Facebook 开源的fishhook重绑定 C 函数NSHomeDirectory以及它是如何绕过企业微信的单例检测实现多开登录的。先花 1 分钟把源码拉下来想对照源码阅读的话执行下面命令即可建议检出历史完整的 master 分支git clone https://gitcode.com/gh_mirrors/we/WeChatICU-ForMac.git整个项目的核心代码不到 300 行全部集中在WeChatICU/WeChatICU/目录下文件作用WeChatICU/WeChatICU/main.m入口dylib 加载即自动执行 HookWeChatICU/WeChatICU/NSObjectICUHook.m全部 Hook 逻辑防撤回、去水印、多开WeChatICU/WeChatICU/fishhook.c、fishhook.hFacebook 的 C 函数重绑定库WeChatICU/install.sh一键安装脚本含注入原理WeChatICU/WeChatICU/WeworkHeaders.h企业微信内部类的头文件声明一、注入原理回顾为什么 C 函数交换不能靠 Swizzling回顾WeChatICU/install.sh的安装动作它做了三件事把企业微信原始可执行文件备份为企业微信_backup用自带的insert_dylib工具把编译好的WeChatICU.framework注入到企业微信可执行文件的加载列表中重启企业微信后dylib 随主程序一起被加载。加载后的第一秒WeChatICU/WeChatICU/main.m中的构造函数就自动触发了所有 Hookstatic void __attribute__((constructor)) initialize(void) { [NSObject hook]; }__attribute__((constructor))是 GCC/Clang 的特性函数会在 dylib 加载后、main函数之前自动执行。这就是整个小助手的开机即生效魔法。但这里有个关键问题NSHomeDirectory()、NSSearchPathForDirectoriesInDomains()是 C 函数不是 ObjC 方法。ObjC 运行时提供的method_exchangeImplementations只能交换类方法对 C 函数的调用无能为力——它们在编译后变成了二进制指令里的符号引用。要改写 C 函数的调用目标需要操作 Mach-O 二进制里的绑定表bind/stub 表这正是 fishhook 的拿手好戏。二、fishhook 是什么给 C 函数改签名fishhook 是 Facebook 开源的轻量级库本仓库自带了一份副本见WeChatICU/WeChatICU/fishhook.c。它的工作原理可以通俗理解为macOS 的可执行文件里每次调用外部 C 函数并不是直接跳转而是先经过一个跳板stub由动态链接器 dyld 决定最终地址。fishhook 把这些跳板的指针悄悄改写成你自己的函数同时把原来的函数指针留给你方便先执行原版再执行我的版本。核心 API 只有一个在WeChatICU/WeChatICU/fishhook.h中声明int rebind_symbols(struct rebinding rebindings[], size_t rebindings_nel);其中struct rebinding就是三元组{ 要改写的函数名, 你的替身函数, 原函数指针存放地址 }。项目里对它的调用只有短短 4 行却同时改写了 2 个函数WeChatICU/WeChatICU/NSObjectICUHook.mrebind_symbols((struct rebinding[2]) { {NSSearchPathForDirectoriesInDomains, swizzled_NSSearchPathForDirectoriesInDomains, (void *) original_NSSearchPathForDirectoriesInDomains}, {NSHomeDirectory, swizzled_NSHomeDirectory, (void *) original_NSHomeDirectory} }, 2);下面逐个拆解这两个替身函数在搞什么鬼。三、重绑定 NSHomeDirectory把家目录偷梁换柱先看替身函数的完整实现非常简洁NSString *swizzled_NSHomeDirectory(void) { return [NSString stringWithFormat:%/Library/Containers/com.tencent.WeWorkMac/Data, original_NSHomeDirectory()]; }它先调用真正的NSHomeDirectory()拿到用户家目录比如/Users/zhangsan再拼上一个沙盒容器路径返回/Users/zhangsan/Library/Containers/com.tencent.WeWorkMac/Data为什么必须这么干因为企业微信是一个 App Sandbox 沙盒应用。它真正的配置、缓存、日志全部存放在自己的沙盒目录~/Library/Containers/com.tencent.WeWorkMac/Data里而不是散落在用户家目录各处。注入场景下的问题在于注入的 dylib 与企业微信自身代码对家目录在哪的预期可能不一致。一旦某些路径计算落在沙盒外企业微信就会去错误的位置找配置——轻则读取失败重则多开实例之间互相踩坑、配置错乱。作者的做法是釜底抽薪只要企业微信以及注入代码通过NSHomeDirectory()取家目录一律劫持成沙盒路径。后续所有基于家目录拼接的读写操作自然就都落到了企业微信真正存数据的地方。四、改写 NSSearchPathForDirectoriesInDomains批量路径替换NSHomeDirectory只管一个函数但 Foundation 里还有一类批量返回目录的函数最典型的就是NSSearchPathForDirectoriesInDomains用来查 Library、Application Support 等系统标准目录。如果只改前者不改后者路径照样会漏到沙盒外。替身函数的策略很直白先拿到原版返回的所有路径把其中凡是包含用户家目录的部分统统替换成沙盒路径NSArrayNSString * *swizzled_NSSearchPathForDirectoriesInDomains(NSSearchPathDirectory directory, NSSearchPathDomainMask domainMask, BOOL expandTilde) { NSMutableArrayNSString * *paths [original_NSSearchPathForDirectoriesInDomains(directory, domainMask, expandTilde) mutableCopy]; NSString *sandBoxPath [NSString stringWithFormat:%/Library/Containers/com.tencent.WeWorkMac/Data, original_NSHomeDirectory()]; [paths enumerateObjectsUsingBlock:^(NSString *filePath, NSUInteger idx, BOOL *_Nonnull stop) { NSRange range [filePath rangeOfString:original_NSHomeDirectory()]; if (range.length 0) { NSMutableString *newFilePath [filePath mutableCopy]; [newFilePath replaceCharactersInRange:range withString:sandBoxPath]; paths[idx] newFilePath; } }]; return paths; }两个 C 函数一起被重绑定后效果是整个进程里所有取家目录 / 取标准目录的出口都被统一收口到沙盒路径计算从此只有一种答案。五、多开之谜如何绕过企业微信的单例检测到这里你可能会问改了路径和目录跟多开有什么关系其实多开的秘密藏在另外两个方法交换里同在WeChatICU/WeChatICU/NSObjectICUHook.m。1. 让企业微信看不见其他实例正常 Mac 应用多开第二个实例时往往会通过NSRunningApplication.runningApplicationsWithBundleIdentifier:查询我这个 App 是不是已经在跑了。企业微信Bundle ID 为com.tencent.WeWorkMac同样依赖这个单例检测——查到了正在运行的实例就直接退出或只激活第一个窗口。小助手对这个方法做了一次交换 (NSArray *)hook_runningApplicationsWithBundleIdentifier:(NSString *)arg1 { if ([arg1 isEqualToString:com.tencent.WeWorkMac]) { return []; // 查询自己告诉你没有别的实例 } return [self hook_runningApplicationsWithBundleIdentifier:arg1]; }当企业微信拿自己的 Bundle ID 去查询时直接返回空数组——骗过单例检测第二个、第三个实例就能照常启动。再配合前面把家目录收口到沙盒多个实例的配置读写互不干扰多开登录就此成立。2. 用 _backup 文件骗过完整性自校验还记得安装脚本把原始可执行文件备份成了企业微信_backup吗真正在运行的企业微信已经被insert_dylib改过多了一条 dylib 加载指令。如果企业微信在启动时校验自己的可执行文件就会发现我被人动过手脚。作者的应对同样聪明——交换NSBundle的executablePath方法- (NSString *)hook_executablePath { NSString *executablePath [self hook_executablePath]; if ([executablePath hasSuffix:企业微信]) { executablePath [executablePath stringByAppendingString:_backup]; } return executablePath; }当企业微信通过NSBundle询问我的可执行文件在哪时答案被悄悄指向了从未被修改过的原始备份文件企业微信_backup。完整性校验读到的自然是一份干干净净的二进制检查顺利放行。顺带一提安装时的备份动作就在WeChatICU/install.sh中cp ${APP_EXECUTABLE_PATH} ${APP_EXECUTABLE_BACKUP_PATH}卸载脚本WeChatICU/uninstall.sh则会删掉注入的 framework 并还原备份文件。六、完整 Hook 清单一览把整篇讲到的手法汇总成一张表方便你对照源码#目标手法目的1WEWMessage.isRevokeObjC 方法交换消息防撤回2WEWConversation.isConversationSupportWaterMarkObjC 方法交换聊天会话去水印3NSHomeDirectoryfishhook 重绑定家目录收口到沙盒4NSSearchPathForDirectoriesInDomainsfishhook 重绑定标准目录批量改写到沙盒5NSRunningApplication查询方法ObjC 方法交换绕过单例检测实现多开6NSBundle.executablePathObjC 方法交换用_backup原件骗过完整性自校验所有方法交换都通过hookMethod这个工具函数完成底层就是method_exchangeImplementations目标类的定义则来自WeChatICU/WeChatICU/WeworkHeaders.h中对WEWConfigService等内部类的声明。功能开关防撤回 / 去水印由菜单栏小助手菜单控制状态持久化在NSUserDefaults中。七、小结一套组合拳的启示WeChatICU-ForMac 用不到 300 行代码给我们上了一堂完整的macOS 动态注入课insert_dylib负责进门把 dylib 写进可执行文件的加载列表构造函数负责落子dylib 加载即自动布下全部 HookObjC 方法交换负责改行为防撤回、去水印、单例检测、完整性校验fishhook负责改地基连 C 函数级别的路径计算都能重定向。对初学者的最大启发是ObjC 运行时和 C 动态链接器是 macOS 上两套独立的可改写面。想 Hook 方法用method_exchangeImplementations想 Hook C 函数就得动用 fishhook 这类操作绑定表的工具。理解了这个分界线你就读懂了这个项目最核心的设计。下一节可以延伸阅读 fishhook 对dyld共享缓存的适配细节以及insert_dylib修改 Mach-O 加载命令的具体过程。【免费下载链接】WeChatICU-ForMacMac版企业微信消息防撤回, 聊天会话去水印项目地址: https://gitcode.com/gh_mirrors/we/WeChatICU-ForMac创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表