吾爱破解 - LCG - LSG |安卓破解|病毒分析|www.52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 4333|回复: 32
收起左侧

[MacOS逆向] MacOS TablePlus dylib注入 HOOK x86/arm 双插 完美破解

  [复制链接]
Vvvvvoid 发表于 2024-1-16 23:26
本帖最后由 Vvvvvoid 于 2024-1-20 18:52 编辑

前记

TablePlus 是我很喜欢的 macos 平台的数据库工具...
今天我们来基于 dylib 注入来分析一下。

环境

  • ida|hopper
  • xcode 15
  • TablePlus Version 5.8.2 (528) [x86/arm 通杀]  

分析 x86

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

limit.png

我们先从这里入手。

hopper 打开, 字符串搜索, 找到如下:  

0000000100731c70         db         "Free Trial limited 2 tabs", 0   

x 一下, 有四个引用, 我们找一个相对调用链路图短的 分析。
就第二个吧。
2.png
3.png

切换到假码模式, 我们看到, 这块有个关键判断
4.png
qword_1008daaf0 == 0x0 的话 ,会弹出 Tab 限制的对话框
聪明的同学可能想到了, 直接写入内存 0x1,  但是 , 我试过了, 不行的..
qword_1008daaf0 这个引用是一个对象,对象的属性取不到程序会出异常奔溃 !!

00000001008daaf0         db  0x00 ; '.' 

换个工具, 我们用 ida 打开, 对该内存地址下一个写入断点 (之所以换工具, 是因为我不知道怎么用 hopper 下写入断点)
write.png

下好断点后, 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

函数太长, 我们直接看 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

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

具体 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": @"marlkiller@voidm.com",
        @"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

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

然后: 下断,弹框,断下来了

10.png

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

接着我们分析下函数 sub_100059E70 的 代码:

11.png

这代码可写的太好了, 短小精干, 通俗易懂,(看起来像是校验格式的, 第三个参数肯能是设备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

在 函数 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 < count; i++) {
        objc_property_t property = properties[i];
        NSString *propertyName = [NSString stringWithUTF8String:property_getName(property)];

        id propertyValue = [object valueForKey:propertyName];
        NSLog(@">>>>>> 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

Release

项目已经打包 github,可以直接用 xcode 打开 :
https://github.com/marlkiller/dylib_dobby_hook
目录:

  • libs:  项目依赖的开源 dobby 库
  • release:  build 后的成品
  • script:  里面有个 hack.sh, 可以直接sudo sh 执行一键注入脚本
  • tools: insert_dylib 开源注入工具

免费评分

参与人数 10威望 +2 吾爱币 +110 热心值 +10 收起 理由
eijop252023 + 1 + 1 热心回复!
Hmily + 2 + 100 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
来阻 + 1 + 1 非常感谢您的文章
小朋友呢 + 2 + 1 用心讨论,共获提升!
马潇洒 + 1 + 1 用心讨论,共获提升!
spd97 + 1 + 1 用心讨论,共获提升!
sunkoto + 1 + 1 谢谢@Thanks!
PerfectFJH + 1 + 1 用心讨论,共获提升!
jgs + 1 + 1 谢谢@Thanks!
woyucheng + 1 + 1 谢谢@Thanks!

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

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

第二个 hook 是教研 deviceId 的 ,
自己跟一下, 可以获取到本设备的 deviceId
把正确的 deviceId 放到 license 里, deviceid 正确的话 ,
第二个 hook 也没必要存在了

设备id 不对, 可能会有一些奇怪的问题, 比如 sql 会添加莫名其妙的引号...
 楼主| Vvvvvoid 发表于 2024-1-17 11:47
KirchoffNZ 发表于 2024-1-17 11:04
我一直在疑惑对象的私有属性,硬取的话是什么后果

这个 LicenseModel 里的几个都是正常的成员变量.
因为 我们的项目 只有 头文件, 没有实现,
但是注入之后内存是有这个类的实现的, 可以
利用放射机制, 根据属性名 方法名 来执行获取
1rten 发表于 2024-1-16 23:31
sdieedu 发表于 2024-1-17 00:52
可以可以啊大佬
Paky 发表于 2024-1-17 09:22
感慨技术贴的坛友,坚持不懈的努力和学习,不断的提高和进步,就是吾爱的强大所在
ansenmo 发表于 2024-1-17 09:40
感谢大佬分享
anywn9999 发表于 2024-1-17 09:51
支持一下,慢慢学习
u2000 发表于 2024-1-17 10:29
嚯~这个厉害.学习学习
Bruce_HD 发表于 2024-1-17 10:45
感谢分享。看一看,瞧一瞧。
welldown 发表于 2024-1-17 10:52
新人进来学习了
KirchoffNZ 发表于 2024-1-17 11:04
我一直在疑惑对象的私有属性,硬取的话是什么后果
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则 警告:本版块禁止灌水或回复与主题无关内容,违者重罚!

快速回复 收藏帖子 返回列表 搜索

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2024-4-29 04:29

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表