大神论坛

找回密码
快速注册
查看: 135 | 回复: 0

[macOS] 对MacOS TablePlus数据库 dylib注入 HOOK x86/arm 逆向破解 附pj源码

主题

帖子

0

积分

初入江湖

UID
689
积分
0
精华
威望
0 点
违规
大神币
68 枚
注册时间
2023-10-14 10:57
发表于 2024-01-17 22:31
本帖最后由 dhdajun 于 2024-01-17 22:31 编辑

前记

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

环境

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

分析 x86

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

我们先从这里入手。

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

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

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

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

qword_1008daaf0 == 0x0 的话 ,会弹出 Tab 限制的对话框
聪明的同学可能想到了, 直接写入内存 0x1,  但是 , 我试过了, 不行的..
qword_1008daaf0 这个引用是一个对象,对象的属性取不到程序会出异常奔溃 !!

00000001008daaf0         db  0x00 ; '.'

换个工具, 我们用 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 代码,如下

注意 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 字样

好消息是, 还是有弹框, 我们可以回到最初的
“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 = [Constant getBaseAddr:0] + 0x100059E70;
DobbyHook(_sub_100059E70, sub_100059E70New, (void *)&sub_100059E70Ori);
}

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

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

这里获取到 我本机 设备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==@""){
uint64_t _rsi = (arg2 + 0x28);
// NSLog(@"_rsi: %p", _rsi);
NSString * addressString = [MemoryUtils readMachineCodeStringAtAddress:(_rsi) length:(8)];
NSArray<NSString *> *byteStrings = [addressString componentsSeparatedByString:@" "];
NSMutableArray<NSString *> *reversedByteStrings = [NSMutableArray arrayWithCapacity:byteStrings.count];

for (NSInteger i = byteStrings.count - 1; i >= 0; i--) {
[reversedByteStrings addObject:byteStrings[i]];
}

NSString *reversedAddressString = [reversedByteStrings componentsJoinedByString:@""];

unsigned long long address = strtoull([reversedAddressString UTF8String], NULL, 16);
void *addressPtr = (void *)address;

// 获取真实的 deviceId
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

后记

有空研究下特征码这个玩意, 这样就可以不用写死 hook 地址了, .......(其实我懒得写, 我更希望大家提 pull request 给我).
dobby hook 原先也懒得写, 只是因为问人无果, 资料也不多, 只能自己研究了..


下方隐藏内容为本帖所有文件或源码下载链接:

游客你好,如果您要查看本帖隐藏链接需要登录才能查看, 请先登录

返回顶部