Vvvvvoid 发表于 2024-1-16 23:26

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

本帖最后由 Vvvvvoid 于 2024-1-20 18:52 编辑

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

## 环境
- ida|hopper
- xcode 15
- TablePlus Version 5.8.2 (528)

## 分析 x86

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



我们先从这里入手。

hopper 打开, 字符串搜索, 找到如下:
```
0000000100731c70         db         "Free Trial limited 2 tabs", 0   
```

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




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

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


下好断点后, 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 这个函数。;



函数太长, 我们直接看 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 都有哪些属性.



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



具体 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 = [ 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 = ;;
    }
    return rbx;
}

main(){
    sub_10014AF90New(1,2,3,4);
    intptr_t _sub_10014AF90 = + 0x10014AF90;
    DobbyHook(_sub_10014AF90, sub_10014AF90New, (void *)&sub_10014AF90Ori);   
}

```


之后 dylib 注入, 重启程序, 发现 license 信息是有了, 但是还是有弹框..而且 程序界面依然有 free trial 字样



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

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



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

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



这代码可写的太好了, 短小精干, 通俗易懂,(看起来像是校验格式的, 第三个参数肯能是设备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 = + 0x100059E70;
    DobbyHook(_sub_100059E70, sub_100059E70New, (void *)&sub_100059E70Ori);   
}


```
hook ret 1 后 软件基本可用了, 但是还有一些小 bug..

然后我们在稍微多看看 0x100059E70 这个函数;

这个函数 0x100059E70 是教研 deviceId 的,简单跟踪下 可以获取到真实的 设备id ,





在 函数 100059E70 中 ,device id 来自 mov rsi , ,
获取到对象地址之后, 通过反射, 可以获取到 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();
    NSLog(@">>>>>> Class: %@", className);

    // %@ 格式说明符将其作为对象进行输出。在此情况下,NSLog 将会调用对象的 description 方法来获取其字符串表示形式,并将其输出到控制台。
    NSLog(@">>>>>> className.description: %@", address);
    NSString *objectDescription = ;
    NSLog(@">>>>>> Object Description: %@", objectDescription);

    // 获取对象的属性与值
    unsigned int count;
    objc_property_t *properties = class_copyPropertyList(, &count);

    for (unsigned int i = 0; i < count; i++) {
      objc_property_t property = properties;
      NSString *propertyName = ;

      id propertyValue = ;
      NSLog(@">>>>>> Property: %@, Value: %@", propertyName, propertyValue);
    }

    free(properties);
}
>>>>>> Class: Swift.__StringStorage
>>>>>> className.description: ee4f1d1890b4eb49a5a4d7f195ca8b67
>>>>>> deviceId: ee4f1d1890b4eb49a5a4d7f195ca8b67
```
或者也可以直接 对象地址+0x20的 偏移来获取 设备 ID
```
NSString *deviceId = ;

```

这里获取到 我本机 设备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 ; real device id
      uintptr_t *ptr = (uintptr_t *)(arg2 + 0x28);
      // NSLog(@"ptr: %#lx", ptr);
      void * addressPtr = (void *) *ptr;
      NSString * deviceId = ;
      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,                            ; 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






## Release

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

sdieedu 发表于 2024-1-17 00:52

可以可以啊大佬

Vvvvvoid 发表于 2024-1-17 17:48

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

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

设备id 不对, 可能会有一些奇怪的问题, 比如 sql 会添加莫名其妙的引号...

1rten 发表于 2024-1-16 23:31

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

Paky 发表于 2024-1-17 09:22

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

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

我一直在疑惑对象的私有属性,硬取的话是什么后果

Vvvvvoid 发表于 2024-1-17 11:47

KirchoffNZ 发表于 2024-1-17 11:04
我一直在疑惑对象的私有属性,硬取的话是什么后果

这个 LicenseModel 里的几个都是正常的成员变量.
因为 我们的项目 只有 头文件, 没有实现,
但是注入之后内存是有这个类的实现的, 可以
利用放射机制, 根据属性名 方法名 来执行获取
页: [1] 2 3 4
查看完整版本: MacOS TablePlus dylib注入 HOOK x86/arm 双插 完美破解