macOS逆向-微信小助手

前言

前一阵子入了 iOS 逆向的坑,整了个微信机器人,不过由于是用自己的证书打包,因此只能用7天,之后还得重新打包,实在麻烦。于是就拿 macOS 动刀了。

本篇主要制作 macOS 版微信的插件,实现消息防撤回的功能,从而熟悉 mac OS 插件制作,由于 (lan ai) macOS 逆向分析与 iOS 类似,且不像 iOS 有那么多的工具,因此花费的时间较多,这里暂不阐述。之后有时间再整理 iOS 逆向分析过程。

  • 基本原理: 与 iOS 注入动态库类似,通过 app 启动时调用我们注入的库,从而进行 hook。
  • 插件 GitHub 地址: WeChatPlugin-MacOS

功能

  • 消息自动回复
  • 消息防撤回
  • 远程控制
  • 微信多开
  • 第二次登录免认证
  • 置底功能(类似置顶)

远程控制:

  • 屏幕保护
  • 清空废纸篓
  • 锁屏、休眠、关机、重启
  • 退出QQ、WeChat、Chrome、Safari、所有程序
  • 网易云音乐(播放、暂停、下一首、上一首、喜欢、取消喜欢)

若想使用远程控制网易云音乐,请在“系统偏好设置 ==> 安全性与隐私 ==> 隐私 ==> 辅助功能”中添加微信


Demo 演示

  • 消息防撤回
    消息防撤回.gif

  • 自动回复
    自动回复.gif

  • 微信多开
    微信多开.gif

  • 远程控制 (测试关闭Chrome、QQ、开启屏幕保护)
    远程控制.gif

  • 免认证 & 置底
    免认证&置底


安装 & 卸载

第一次安装需要输入密码,仅是为了获取写入微信文件夹的权限

懒癌版安装(适合非程序猿)

打开应用程序-实用工具-Terminal(终端),执行以下命令并根据提示输入密码即可。

cd ~/Downloads && git clone https://github.com/TKkk-iOSer/WeChatPlugin-MacOS.git && ./WeChatPlugin-MacOS/Other/Install.sh

普通安装

  • 下载WeChatPlugin,用 Termimal 打开项目当前目录,执行 ./Other/Install.sh即可。

若想修改源码&重编译

  • 先更改微信的 owner 以获取写入微信文件夹的权限,否则会出现类似Permission denied的错误。

sudo chown -R $(whoami) /Applications/WeChat.app

Permission denied.png

  • 下载 WeChatPlugin, 用Xcode打开,先进行 Build (command + B),之后 Run (command + R)即可启动微信,此时插件注入完成。

  • 若 Error,提示找不到 Framework,先进行 Build。

安装完成

  • 登录微信,在菜单栏中看到微信小助手即安装成功。
    微信小助手.png

卸载

Terminal(终端)打开该项目,运行 ./Other/Uninstall.sh 即可


使用

  • 消息防撤回:点击开启消息防撤回或者快捷键command + t,即可开启、关闭。
  • 自动回复:点击开启自动回复或者快捷键conmand + k,将弹出自动回复设置的窗口,点击红色箭头的按钮设置开关。

若关键字为 *,则任何信息都回复;
若关键字为x|y,则 x 和 y 都回复;
若关键字或者自动回复为空,则不开启该条自动回复。
若开启正则,请确认正则表达式书写正确,在线正则表达式测试

自动回复设置.png

  • 微信多开:点击登录新微信或者快捷键command + shift + n,即可多开微信。

  • 远程控制:点击远程控制 Mac OS或者快捷键command + shift + c,即可打开控制窗口。

.png

①为选择是否开启远程控制此功能。

②为能够触发远程控制的消息内容(仅向自己发送账号有效)。


plugin 制作

创建Framework

使用 Xcode 创建 macOS 的 Cocoa Framework.

创建Cocoa Framework.png

Edit Scheme…

编辑 Scheme,在 Debug 模式下启动 WeChat。
Edit Scheme.png

choose executable.gif

添加Run Script

在 Build Phases 中添加 Run Script

add run scripe.gif

Script 内容如下

其中 app_name为要注入的app名称,framework_name`为插件名称。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#!/bin/bash
# 要注入的的app
app_name="WeChat"
# 此framework名字
framework_name="WeChatPlugin"
app_bundle_path="/Applications/${app_name}.app/Contents/MacOS"
app_executable_path="${app_bundle_path}/${app_name}"
app_executable_backup_path="${app_executable_path}_backup"
framework_path="${app_bundle_path}/${framework_name}.framework"
# 备份WeChat原始可执行文件
if [ ! -f "$app_executable_backup_path" ]
then
cp "$app_executable_path" "$app_executable_backup_path"
fi
cp -r "${BUILT_PRODUCTS_DIR}/${framework_name}.framework" ${app_bundle_path}
# 注入动态库
./insert_dylib --all-yes "${framework_path}/${framework_name}" "$app_executable_backup_path" "$app_executable_path"

其中insert_dylib来源于github(与iOS的insert_dylib不同)

创建 main.mm

创建 main.mm 文件,添加构造方法。

main.mm.png

此时,一运行,即可执行initalize中的方法,并启动微信。

因此,我们就可以在这里愉快的进行hook!!!

注意

  • 若 Error,提示无权限,请对 WeChat 赋予权限。
    sudo chmod -R 777 /Applications/WeChat.app
  • 若 Error,提示找不到 Framework,先进行 Build。

愉快的 hook (以撤回消息为例)

创建 NSObject 分类

新建 NSObject 分类,加入类方法+(void)hookWeChat;并在 main.mm 中执行该方法。之后所有的 hook 都可以在该类方法中进行。

1
2
3
4
5
6
#import "WeChat+hook.h"

static void __attribute__((constructor)) initialize(void) {
NSLog(@"++++++++ WeChatPlugin loaded ++++++++");
[NSObject hook_WeChat];
}

寻找注入点

首先使用class-dump,dump 出微信的头文件信息。(如何使用请左转iOS 逆向 - 微信 helloWorld)
因为在 iOS 中,微信撤回的函数为- (void)onRevokeMsg:(id)arg1;因此,我们在微信的头文件中搜索该方法,最终在MessageService.h中找到。

runtime 登场

到这里就要开始进行 hook 了,在+(void)hookWeChat;中进行methodExchange
MessageService- (void)onRevokeMsg:(id)arg1;方法实现替换成NSObject- (void)hook_onRevokeMsg:(id)msg方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
+ (void)hookWeChat {
// 微信撤回消息
Method originalMethod = class_getInstanceMethod(objc_getClass("MessageService"), @selector(onRevokeMsg:));
Method swizzledMethod = class_getInstanceMethod([self class], @selector(hook_onRevokeMsg:));
if(originalMethod && swizzledMethod) {
method_exchangeImplementations(originalMethod, swizzledMethod);
}
}

- (void)hook_onRevokeMsg:(id)msg {
NSLog(@"=== TK-LOG-msg = %@===",msg);
[self hook_onRevokeMsg:msg];
}

验证

由于是使用 Xcode,就不用像 iOS 逆向那样只能用 lldb 调试了。可以在- (void)hook_onRevokeMsg:(id)msg中打个断点,然后撤回消息看是否会触发。结果证明该方法确实是微信消息撤回的处理方法。

使用 Hopper Disassembler

接着我们在- (void)hook_onRevokeMsg:(id)msg中直接return就可以了。
然而这时候看不到到底是撤回了哪一条信息。我们可以在用户撤回的时候将下面的内容改成”拦截 xx 的一条撤回消息:xxxx”。

撤回.png

这时候就要使用神器 Hopper Disassembler.用hopper Disassembler 进行分析,分析- (void)onRevokeMsg:(id)arg1;的实现。(分析过程与 iOS 类似,这里暂不阐述)
最终得到了主要的代码实现。(完整代码在工程中)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
MessageService *msgService = [[objc_getClass("MMServiceCenter") defaultCenter] getService:objc_getClass("MessageService")];
MessageData *revokeMsgData = [msgService GetMsgData:session svrId:[newmsgid integerValue]];
MessageData *newMsgData = ({
MessageData *msg = [[objc_getClass("MessageData") alloc] initWithMsgType:0x2710];
[msg setFromUsrName:revokeMsgData.toUsrName];
[msg setToUsrName:revokeMsgData.fromUsrName];
[msg setMsgStatus:4];
[msg setMsgContent:newMsgContent];
[msg setMsgCreateTime:[revokeMsgData msgCreateTime]];
[msg setMesLocalID:[revokeMsgData mesLocalID]];

msg;
});

[msgService AddLocalMsg:session msgData:newMsgData];

效果

点击菜单栏-帮助-开启消息防撤回,当好友撤回消息是可以看到提示。
消息防撤回.gif

小结

最终我们得到了拥有消息防撤回与自动回复的 macOS 版微信,虽然整个过程挺简单的,但主要目标是为了熟悉了如何制作 macOS 插件的过程,这样以后就可以给 macOS 上的 app 增加点小功能了。

由于本人还只是个逆向新手,难免会有所疏漏,还请大牛们指正。
本项目仅供学习参考。

参考

如何愉快地在Mac上刷朋友圈

听说你想请我喝下午茶?😏
------------------ 我是有底线的 ------------------