在 MAS 版本的 QQ 更新到版本 5.5.1 之后,终于支持撤回消息了(当然也就不能防撤回了)

macQQ

通过万能的 Google 查找了一下防撤回方法,没想到还真的找到了

  • 尝试一:反编译二进制文件(参考:美女驱动型程序猿

    1. 安装 [Hopper Disassembler][2]
    
    2. 用 Hopper Disassembler 打开 /Applications/QQ.app/Contents/MacOS/QQ
    
        需要注意的是,当二进制文件被打开之后不要急于操作,要等待右下角 `Working` 消失,这个时间会非常长
    
    3. 在左边 `Labels` 一栏搜索 `handleRecallNotify` 关键词,得到如下结果
    
        ![qq-bin](/usr/uploads/2017/04/qq-bin.png)
    
    4. 选中 `push rbp` 这一行,按住 `Option + A` 并输入 `ret`,之后再点击 `Assembler and Go Next` 并按 `ESC` 结束编辑,大概意思是,直接让这个函数返回,不做任何操作(为啥每次都把 rbp 看成 rbq 呢?捂脸……)
    
        ![qq-modify](/usr/uploads/2017/04/qq-modify.png)
    
    5. 按住 `Shift + Command + E` 来输出新的二进制文件,会提示是否移除原有的签名,确认即可,这个时候把新的二进制文件复制到 /Applications/QQ.app/Contents/MacOS/ 把原来的替换掉就行
    
    P.S. 经过参考链接评论区提醒,在测试发现修后的 QQ 把缓存全都放到了 ~/Documents/,本来好好的文档目录多出了一堆乱七八糟的文件,对于强迫症患者而言是绝对不能忍的
    
  • 尝试二:动态库注入 hook(参考:0xBBC

    
    1. 新建 OC 源文件 `QQUnrecall.m`
    
        ```c
        #import <Foundation/Foundation.h>
        #import <objc/runtime.h>
    
        void handleRecallNotifyIsOnline(id _i, SEL _s, void * _p, BOOL _b) {
            NSLog(@"已经阻止 QQ 撤回一条消息");
        }
    
        #pragma clang diagnostic push
        #pragma clang diagnostic ignored "-Wundeclared-selector"
    
        static void __attribute__((constructor)) initialize(void) {
            method_setImplementation(
                class_getInstanceMethod(
                    NSClassFromString(@"QQMessageRevokeEngine"),
                    @selector(handleRecallNotify:isOnline:)
                ),
                (IMP)&handleRecallNotifyIsOnline
            );
        }
    
        #pragma clang diagnostic pop
        ```
    
    2. 编译成动态库文件 `clang -dynamiclib -framework Foundation QQUnrecall.m -o libQQUnrecall.dylib`
    
    3. 使用 `export DYLD_INSERT_LIBRARIES=libQQUnrecall.dylib` 后运行 `/Applications/QQ.app/Contents/MacOS/QQ` 得到如下测试结果
    
        ![qq-unrecall-test](/usr/uploads/2017/04/qq-unrecall-test.png)
    
    4. 简单包装一下避免每次通过终端启动 QQ
    
        ```c
        #include <stdio.h>
        #include <stdlib.h>
        #include <string.h>
    
        #define QPATH "/Applications/QQ.app/Contents/MacOS/"
        #define LIBENV "DYLD_INSERT_LIBRARIES"
        #define LIBNAME "libQQUnrecall.dylib"
    
        #define HOOK "export " LIBENV "=%s" LIBNAME
        #define RUN QPATH "QQ"
        #define AND " && "
    
        #define CMD HOOK AND RUN
    
        #define M 1024
    
        char path[M], buff[M];
        unsigned long len, i;
    
        int main(int argc, char **argv) {
            strcpy(path, argv[0]);
            len = strlen(path);
            for (i = len - 1; i; i--) {
                if (path[i] == '/')
                    break ;
                path[i] = 0;
            }
            sprintf(buff, CMD, path);
            system(buff);
            return 0;
        }
        ```
    

P.S. 点击 这里 下载成品(macOS 10.12 以下版本最好自行编译,否则可能会出现问题),理论上只要撤回函数名称和参数不改,对于所有版本 QQ 都可用此方法