MacOS TablePlus dylib注入 HOOK x86/arm 双插 完美破解

查看 72|回复 10
作者:Vvvvvoid   
前记
TablePlus 是我很喜欢的 macos 平台的数据库工具...
今天我们来基于 dylib 注入来分析一下。
环境
  • ida|hopper
  • xcode 15
  • TablePlus Version 5.8.2 (528) [x86/arm 通杀]  

    分析 x86
    首先软件的免费版本,是有很多限制的,
    比如 只能打开俩个 tab 页  


    limit.png (234.46 KB, 下载次数: 1)
    下载附件
    2024-1-16 23:16 上传

    我们先从这里入手。
    hopper 打开, 字符串搜索, 找到如下:  
    0000000100731c70         db         "Free Trial limited 2 tabs", 0   
    x 一下, 有四个引用, 我们找一个相对调用链路图短的 分析。
    就第二个吧。


    2.png (103.88 KB, 下载次数: 2)
    下载附件
    2024-1-16 23:16 上传



    3.png (554.7 KB, 下载次数: 0)
    下载附件
    2024-1-16 23:16 上传

    切换到假码模式, 我们看到, 这块有个关键判断


    4.png (118.82 KB, 下载次数: 1)
    下载附件
    2024-1-16 23:16 上传

    qword_1008daaf0 == 0x0 的话 ,会弹出 Tab 限制的对话框
    聪明的同学可能想到了, 直接写入内存 0x1,  但是 , 我试过了, 不行的..
    qword_1008daaf0 这个引用是一个对象,对象的属性取不到程序会出异常奔溃 !!
    00000001008daaf0         db  0x00 ; '.'
    换个工具, 我们用 ida 打开, 对该内存地址下一个写入断点 (之所以换工具, 是因为我不知道怎么用 hopper 下写入断点)


    write.png (121.47 KB, 下载次数: 1)
    下载附件
    2024-1-16 23:17 上传

    下好断点后, run 运行
    __text:00000001002FA1F1                 mov     rbp, rsp
    __text:00000001002FA1F4                 call    sub_10014AF90
    __text:00000001002FA1F9                 mov     cs:qword_1008DAAF0, rax
    __text:00000001002FA200                 pop     rbp
    __text:00000001002FA201                 retn
    可以看到, 执行函数 sub_10014AF90 返回 rax, 然后 赋值给了qword_1008daaf0  ;
    我们分析下 sub_10014AF90 这个函数。;


    5.png (488.06 KB, 下载次数: 1)
    下载附件
    2024-1-16 23:16 上传

    函数太长, 我们直接看 return, 由前面以知, 返回0是会弹框, 所以我们忽略掉 176 216 行的 return 0;
    着重看后面的   return v38;
    v38  = v36.initWithDictionary
    v36 =  objc_allocWithZone(&OBJC_CLASS___LicenseModel); // 这一行实例化 LicenseModel 对象,很关键 !!
      v36 = objc_allocWithZone(&OBJC_CLASS___LicenseModel);
      v37 = (void *)_sSD10FoundationE19_bridgeToObjectiveCSo12NSDictionaryCyF(
                      v35,
                      &_ss11AnyHashableVN,
                      (char *)&_sypN + 8,
                      &_ss11AnyHashableVSHsWP);
      swift_bridgeObjectRelease(v35);
      v38 = objc_msgSend(v36, "initWithDictionary:", v37);
      objc_release(v37);
      v39 = _sSS10FoundationE19_bridgeToObjectiveCSo8NSStringCyF(0x2E676E6964616F4CLL, 0xEA00000000002E2ELL);
      objc_msgSend(v38, "setUpdatesAvailableUntil:", v39);
      sub_100025820(v65);
      v40 = *(void (__fastcall **)(char *, __int64))(v72 + 8);
      v41 = v73;
      v40(v69, v73);
      v40(v68, v41);
      sub_1000255A0(v67, v70);
      sub_1000255A0(InitializedObjCClass, v66);
      swift_bridgeObjectRelease(v64);
      ((void (__fastcall *)(__int64))objc_release)(v39);
      return v38;
    简单了解了下, initWithDictionary 是将 map 转换成对象, 我们用 hopper 把程序的 header 文件导出来, 看看 LicenseModel 都有哪些属性.


    6.png (330.63 KB, 下载次数: 0)
    下载附件
    2024-1-16 23:16 上传

    这就简单明白了, 我们直接搞个函数,把 LicenseModel hook 过去
    hook 之前 先把这几个用到的头文件扔到我们项目里


    7.png (135.75 KB, 下载次数: 1)
    下载附件
    2024-1-16 23:16 上传



    8.png (110.55 KB, 下载次数: 1)
    下载附件
    2024-1-16 23:16 上传

    具体 HOOK 代码,如下
    (具体的 hook 实现参考帖子 https://www.52pojie.cn/thread-1880510-1-1.html) :
    注意 rbx 也就是 licenseModel 我写成了常量, 防止内存泄漏, 而且程序启动时,会先执行一边该函数 来生成 rbx 对象常量, 之后在 hook  
    const LicenseModel *rbx;
    int (*sub_10014AF90Ori)();
    id sub_10014AF90New(int arg0, int arg1, int arg2, int arg3){
        LicenseModel *r12 = [[NSClassFromString(@"LicenseModel") alloc] init];
        NSDictionary *propertyDictionary = @{
            @"sign": @"fuckSign",
            @"email": @"[email protected]",
            @"deviceID": @"fuckDeviceId",
            @"purchasedAt": @"2999-01-16",
            @"nextChargeAt": @(9999999999999), // Replace with the actual double value
            @"updatesAvailableUntil": @"2999-01-16" // Replace with the actual value
        };
        if (rbx==nil){
            rbx = [r12 initWithDictionary:propertyDictionary];;
        }
        return rbx;
    }
    main(){
        sub_10014AF90New(1,2,3,4);
        intptr_t _sub_10014AF90 = [Constant getBaseAddr:0] + 0x10014AF90;
        DobbyHook(_sub_10014AF90, sub_10014AF90New, (void *)&sub_10014AF90Ori);   
    }
    之后 dylib 注入, 重启程序, 发现 license 信息是有了, 但是还是有弹框..而且 程序界面依然有 free trial 字样


    9.png (304.09 KB, 下载次数: 0)
    下载附件
    2024-1-16 23:16 上传

    好消息是, 还是有弹框, 我们可以回到最初的
    “Free Trial limited 2 tabs”
    地方继续分析  
    然后: 下断,弹框,断下来了


    10.png (250.98 KB, 下载次数: 0)
    下载附件
    2024-1-16 23:16 上传

    可以看到 这里还有一个判断,
    test    bl, 1 ,  // 如果 bl 不等于 1, 则直接跳弹框
    bl 等于 ebx 等于 eax 等于 函数 sub_100059E70 的返回值
    接着我们分析下函数 sub_100059E70 的 代码:


    11.png (212.67 KB, 下载次数: 1)
    下载附件
    2024-1-16 23:16 上传

    这代码可写的太好了, 短小精干, 通俗易懂,(看起来像是校验格式的, 第三个参数肯能是设备id 或者激活码之类的? 有空可以在研究研究)
    别管他啥逻辑, 我们直接 hook 返回 1 试试
    // 前端的 hook, 我就懒得在贴在这了
    int (*sub_100059E70Ori)();
    bool sub_100059E70New(int arg0, int arg1, int arg2, int arg3, int arg4){
    return 0x1;
    }
    main(){
        intptr_t _sub_100059E70 = [Constant getBaseAddr:0] + 0x100059E70;
        DobbyHook(_sub_100059E70, sub_100059E70New, (void *)&sub_100059E70Ori);   
    }
    hook ret 1 后 软件基本可用了, 但是还有一些小 bug..
    然后我们在稍微多看看 0x100059E70 这个函数;
    这个函数 0x100059E70 是教研 deviceId 的,  简单跟踪下 可以获取到真实的 设备id ,


    1.png (189.16 KB, 下载次数: 0)
    下载附件
    2024-1-17 11:41 上传

    在 函数 100059E70 中 ,device id 来自 mov rsi , [rdx+0x28],
    获取到对象地址之后, 通过反射, 可以获取到 class , 以及类的属性
    可以看到对象为 __StringStorage, 对象的 description 为设备 ID ,
    如果是 用 lldb 调试的话,  也可以直接 po rsi  来获取对象的 description, 即设备 id
    + (void)inspectObjectWithAddress:(void *)address {
        id object = (__bridge id)address;
        // LicenseModel *license = (__bridge LicenseModel *)addressPtr;
        // 获取对象的十六进制地址
        uintptr_t ptrValue = (uintptr_t)address;
        NSLog(@">>>>>> Address: 0x%lx", ptrValue);
        // 获取对象的类名
        NSString *className = NSStringFromClass([object class]);
        NSLog(@">>>>>> Class: %@", className);
        // %@ 格式说明符将其作为对象进行输出。在此情况下,NSLog 将会调用对象的 description 方法来获取其字符串表示形式,并将其输出到控制台。
        NSLog(@">>>>>> className.description: %@", address);
        NSString *objectDescription = [object description];
        NSLog(@">>>>>> Object Description: %@", objectDescription);
        // 获取对象的属性与值
        unsigned int count;
        objc_property_t *properties = class_copyPropertyList([object class], &count);
        for (unsigned int i = 0; i >>>>> Property: %@, Value: %@", propertyName, propertyValue);
        }
        free(properties);
    }
    >>>>>> Class: Swift.__StringStorage
    >>>>>> className.description: ee4f1d1890b4eb49a5a4d7f195ca8b67
    >>>>>> deviceId: ee4f1d1890b4eb49a5a4d7f195ca8b67
    或者也可以直接 对象地址+0x20  的 偏移来获取 设备 ID
    NSString *deviceId = [NSString stringWithCString:addressPtr+0x20 encoding:NSUTF8StringEncoding];
    这里获取到 我本机 设备id 为 : 88548e5a38eeee04e89c5621ba04bc7e
    而 第一个 hook 随便写了一个 fuckDeviceId, 我们替换成 88548e5a38eeee04e89c5621ba04bc7e
    如果 设备 id 正确的话, 第二个 hook 其实就没必要 hook 了;
    当然写死 deviceId 的话, 别人用的时候 还是会出问题;
    设备 id 不匹配, 可能会有一些奇怪的问题, 比如 sql 会添加莫名其妙的引号...
    这里我们稍微改改我们 hook 100059E70 函数的时候, 获取真实的deviceId ,然后赋值给我们的 licenseModel, 然后 在执行 100059E70Ori
    bool sub_100059E70New(uint64_t arg0, uint64_t arg1, uint64_t arg2, uint64_t arg3, uint64_t arg4){
        if (_rbx!=nil && _rbx.deviceID==@""){
            // mov        rsi, qword [rdx+0x28] ; real device id
            uintptr_t *ptr = (uintptr_t *)(arg2 + 0x28);
            // NSLog(@"ptr: %#lx", ptr);
            void * addressPtr = (void *) *ptr;
            NSString * deviceId = [MemoryUtils readStringAtAddress:(addressPtr+0x20)];
            NSLog(@"deviceId: %@", deviceId);
            _rbx.deviceID =deviceId;
        }
        return sub_100059E70Ori(arg0,arg1,arg2,arg3,arg4);
    }
    然后继续 build, dylib 注入, 启动~~
    ouho~ 成功拿下, 你妈再也不用担心你买不起 APP 了
    分析 arm
    弟弟起的比我早, 可能在催我搞 arm 版了。
    过程不多说了, 与 x86 一样 , 关键函数如下:
    arm 的 bl 约等于 x86 的 call, x0寄存器 约等于 x86 的 rax.
    arm 版 hook 的函数分别为 sub_100131360, sub_100050ea0
    0000000100131ec8         bl         sub_100131360                               ; sub_100131360, CODE XREF=sub_100131978+1512
    0000000100131ecc         mov        x21, x0
    ...
    0000000100131f14         str        x21, [x8, #0xd70]                           ; qword_10086ad70
    0000000100217134         bl         sub_100050ea0                               ; sub_100050ea0
    0000000100217138         mov        x23, x0
    ...
    0000000100217154         tbz        w23, 0x0, loc_1002172dc
    后记
    01-18:
    把论坛大佬写的特征码工具, 集成进来了,
    @QiuChenly
    https://github.com/QiuChenlyOpenSource/SearchHexCodeInFile
    5.8.x 版本通杀应该没问题~  
    俩个函数特征码如下:
    arm:
    FC 6F BA A9 FA 67 01 A9 F8 5F 02 A9 F6 57 03 A9 F4 4F 04 A9 FD 7B 05 A9 FD 43 01 91 FF 03 02 D1 60
    F8 5F BC A9 F6 57 01 A9 F4 4F 02 A9 FD 7B 03 A9 FD C3 00 91 56 08 40 F9 36
    x86:
    55 48 89 E5 41 57 41 56 41 55 41 54 53 48 81 EC 98 00 00 00 48 8D 3D
    55 48 89 E5 41 57 41 56 41 55 41 54 53 50 4C 8B 62 10 4D 85 E4 74


    12.png (489.24 KB, 下载次数: 1)
    下载附件
    2024-1-16 23:16 上传

    Release
    项目已经打包 github,可以直接用 xcode 打开 :
    https://github.com/marlkiller/dylib_dobby_hook
    目录:
  • libs:  项目依赖的开源 dobby 库
  • release:  build 后的成品
  • script:  里面有个 hack.sh, 可以直接sudo sh 执行一键注入脚本
  • tools: insert_dylib 开源注入工具

    下载次数, 函数

  • sdieedu   

    可以可以啊大佬
    Vvvvvoid
    OP
      


    spd97 发表于 2024-1-17 17:36
    谢谢分享,可是破解后使用tab补全会碰到自动加引号的问题,头大啊

    第二个 hook 是教研 deviceId 的 ,
    自己跟一下, 可以获取到本设备的 deviceId
    把正确的 deviceId 放到 license 里, deviceid 正确的话 ,
    第二个 hook 也没必要存在了
    设备id 不对, 可能会有一些奇怪的问题, 比如 sql 会添加莫名其妙的引号...
    1rten   

    感谢大佬,学习一下解决思路
    Paky   

    感慨技术贴的坛友,坚持不懈的努力和学习,不断的提高和进步,就是吾爱的强大所在
    anywn9999   

    支持一下,慢慢学习
    u2000   

    嚯~这个厉害.学习学习
    Bruce_HD   

    感谢分享。看一看,瞧一瞧。
    welldown   

    新人进来学习了
    KirchoffNZ   

    我一直在疑惑对象的私有属性,硬取的话是什么后果
    您需要登录后才可以回帖 登录 | 立即注册

    返回顶部