Tweak With Mac QQ I

1,583 阅读4分钟
原文链接: mp.weixin.qq.com

前言

我最终还是把毒手伸向了 Mac 应用,只是觉得他们不够好用,但又不能指望找到对应的产品经理提建议,等排期,于是只能自己写 Tweak 。

起因是这样的,Mac QQ 在一次版本更新中,移除了一个类似于 Alfred 的功能,叫做 Swifty。这个功能确实大多数人都不用,毕竟有更好用的 alfred,但是去寻找聊天人的时候。Swifty 有无可比拟的优势,比如我想找 H4x 童鞋聊天,仅需双击 ctrl 后,输入 H4 回车即可。

而现在只能,cmd + shift + z 唤起 QQ,cmd + f 唤起搜索框,输入 h4,回车,用的超级不连贯。我就心想能不能把对应的接口拿出来,给 Alfred 调用。(别说 AppleScript,那个实在是太鬼畜)

初步分析

Mac 应用,干坏事比 iOS 轻松非常多。因为不需要蛋疼证书问题,也没有烦人的 ASLR,更不用砸壳, lldb、class-dump 都可以直接糊上去,但如何找到对应的位置,还是需要下一番功夫的。

Class-dump 出来关于 Search 的东西有一些,但其中的方法还是挺多的,于是就打算 lldb 挂上去瞅瞅,结果发现了一个有趣的现象:

每当我搜索的时候,打一个字符,命令行中就多了一行 NSLog,于是我可以在 NSLog 上下断点,看看是哪个函数打印出来的就好了嘛。

br s -F "NSLog"

果然是 NSLog,bt 看一下堆栈发现有门,但是符号是 unnamed,我们需要魔改一下二进制文件。

杨君大法好

杨君同学前一段开源了一个伟大的项目,restore-symbol,可以把符号通过 class-dump 解析出来,再塞回去。并且配合 IDA Pro 的 Python 脚本,连 Block 的符号都可以还原回去。

跑完了之后,你会发现,无法运行。主要原因是 QQ 是有签名的,需要干掉签名。

用这个命令行工具 https://github.com/steakknife/unsign,去掉签名即可。

动态调试

我们再次给 NSLog 下断点,发现符号出现了。

ContactSearcherInter中的这个方法

- (void)Query:(long long)arg1 Contacts:(id)arg2 WithKey:(id)arg3;

直接干掉 NSLog 断点,给这个参数下断点:

br dis 1
br s -S "Query:Contacts:WithKey:"

打印三个参数,可以发现

  • Query 传入一个 magic number 10
  • Contacts 传入一个空的 NSMutableArray
  • Key 传入 String,为搜索的 Key

执行完了之后,我们打印断点中获取到的 NSMutableArrary 的地址,发现传入的数组就是最终的结果。

看起来搜索 API 搞定了,之后需要研究如何找到这个 Class 的内存地址。

经过 grep,发现:

  • ContactSearchInter 是 MQSearchViewController 的属性
  • MQSearchViewController 是MQAIOWindowController2 的属性
  • MQAIOWindowController2 是 MQAIOWindow2 的 windowConroller
  • MQAIOWindow2 可以通过 [NSApplication sharedApplication].windows 取到

拼图完整了。

如何通信?

这是个非常好的问题,我 Alfred 是单独的一个进程,QQ 是单独的一个进程。

我该如何做进程间通信呢。

方案一:监听文件目录

直接跑个 线程去监控一个约定好的文件修改,收到参数写回去。

这个方法,快槽猛,但是总是让有洁癖的人无法接受。

方案二:notify_post

iOS、 macOS 有个大地图炮的 Notification,叫做 notify_post,全局的。但是有个缺点是:这货是没法传参数的,纯粹的 SendMessage。

所以就成了 Alfred 发送 Notification,QQ 捕获到了之后,写个文件 Notification 回去。Alfred 收到 Notification,读文件。

比第一个方法好很多。但是觉得还是蠢。

方案三:用 macOS 进程间通信 API

看了眼 NSXPCConnection 的 API,晕了,不想碰了。

方案四:开一个 Server

这是我最后选择的方案,虽说 tweak 部分代码变多了。但是对于未来想做 QQ bot 的话方便很多。

方案确定,开始写代码

有了上述的分析之后,后面的事情就轻车熟路了。

找到 ContactSearchInter

调用 API

这里需要注意下,因为传入参数大于两个,没法用 NSObject 内置的

performSelector:<#(SEL)#> withObject:<#(id)#> withObject:<#(id)#>

毕竟参数不够长, 需要用 NSInvocation

对数据进行清洗

因为返回的并不是 NSString 类型,是对应的 Class, Group、Discuss、Buddy。而且还有 NSNull 在捣乱,所以要洗一下数据。

封装成 Server 端 API

塞入 QQ

yolo QQ libQQHook.dylib

完成

对比一下:

后记

这是 Tweak QQ 的第一篇文章,这次一改往日的风格,记录下来的是我思考的过程,而我具体怎么做的一带而过,应该对大家帮助更大些吧?不过文章中很多具体操作的基础都没有写详细,因为我公众号之前都写过了,就不再描述了。

下一步会把唤起 QQ 和切换到对应聊天窗口搞好,记得戳下方的二维码订阅哦。