Transmit for mac x86/arm dylib 补丁注入通杀
本帖最后由 Vvvvvoid 于 2024-3-17 16:22 编辑## 关于
- Transmit (https://www.panic.com/transmit/#download)
Transmit 是一种功能强大的FTP/SFTP/WebDAV客户端软件,是一个Mac OS X平台上设计的文件传输软件。
它由Panic(一家以软件工具为主的公司)开发和维护,是一款非常受欢迎且易于使用的软件,而且被广泛认为是Mac OS X平台上最好的文件传输客户端之一。
今天我们来基于 dylib 注入来搞一下。
## 环境/软件
- MacOS arm/x8
- IDA/Hopper
- xcode
- 注入工具: https://github.com/tyilo/insert_dylib
- hook 框架: https://github.com/jmpews/Dobby
## 分析
进入程序后, 右上角有使用弹框;>>>> 还剩 x 天。立即购买!
全局搜索扫描目录, 发现是 PanicAppKit 程序下的引用的字符串
```
/Applications/Transmit.app/Contents/Frameworks/PanicAppKit.framework/Versions/A/PanicAppKit
// PCTitleBarTrialView
"Expired. Buy now! " = "已过期。立即购买! ";
"1 day left. Buy now! " = "还剩 1 天。立即购买! ";
"%lu days left. Buy now! " = "还剩 %lu 天。立即购买! ";
__cfstring:00000000000D6598 dq offset aLuDaysLeftBuyN ; "%lu days left. Buy now! "
__cstring:00000000000B2205 aLuDaysLeftBuyN db '%lu days left. Buy now! ',0
```
但是查看不到有具体的方法引用, 这个时候偶然看到有一个注释 “PCTitleBarTrialView”, 猜测这几个字断都是该类下的;
可以看到PanicAppKit 中 export 导出了 “PCTitleBarTrialView” , 并且主程序中也 import 导入了该类
在主程序中搜索 “PCTitleBarTrialView”, 并且查看引用, 追踪到了这个函数 -
```
__text:00000001000DDED8 loc_1000DDED8: ; CODE XREF: -+1C↑j
__text:00000001000DDED8 ; -+21↑j
__text:00000001000DDED8 mov r13, cs:selRef_trialTitleBarViewController
__text:00000001000DDEDF mov rdi, rbx ; id
__text:00000001000DDEE2 mov rsi, r13 ; SEL
__text:00000001000DDEE5 call cs:_objc_msgSend_ptr
__text:00000001000DDEEB mov rdi, rax ; id
__text:00000001000DDEEE call _objc_retainAutoreleasedReturnValue
__text:00000001000DDEF3 mov r12, rax
__text:00000001000DDEF6 mov rdi, rax ; id
__text:00000001000DDEF9 call cs:_objc_release_ptr
__text:00000001000DDEFF test r12, r12
__text:00000001000DDF02 jnz loc_1000DE072
__text:00000001000DDF08 mov rdi, cs:classRef_PCTitleBarTrialViewController ; Class
```
查看假码, 这里可以清晰的看到通过 TRTrialStatus 函数获取试用的剩余天数,之后调用 PCTitleBarTrialView 来显示;
```
void __cdecl -(TRDocument *self, SEL a2)
{
unsigned int v3; // r15d
int v4; // eax
id v5; // rax
id v6; // r12
id v7; // rax
id v8; // r12
id v9; // rax
id v10; // r12
id v11; // rax
id v12; // r13
id v13; // rax
id v14; // rax
id v15; // r13
id v16; // rax
id v17; // r13
id v18; // rax
id v19; // r15
void *v20; // rdi
id v21; // rax
id v22; // rbx
id v23; // rax
id v24; // r15
id v25; // rax
id v26; // r14
id v27; //
v3 = 0;
v4 = TRTrialStatus(self, a2);
if ( v4 == -5 || v4 == -1 || v4 != 9999 && (v3 = v4, v4 > 0) )
{
v5 = objc_msgSend(self, "trialTitleBarViewController");
v6 = objc_retainAutoreleasedReturnValue(v5);
objc_release(v6);
if ( !v6 )
{
v7 = objc_alloc(&OBJC_CLASS___PCTitleBarTrialViewController);
v8 = objc_msgSend(v7, "initWithTrialDaysLeft:", v3);
objc_msgSend(self, "setTrialTitleBarViewController:", v8);
objc_release(v8);
v9 = objc_msgSend(self, "trialTitleBarViewController");
v10 = objc_retainAutoreleasedReturnValue(v9);
objc_msgSend(v10, "setWarningThreshold:", 5LL);
objc_release(v10);
v11 = objc_msgSend(self, "trialTitleBarViewController");
v12 = objc_retainAutoreleasedReturnValue(v11);
v13 = objc_msgSend(v12, "view");
v27 = objc_retainAutoreleasedReturnValue(v13);
objc_release(v12);
v14 = objc_msgSend(NSApp, "delegate");
v15 = objc_retainAutoreleasedReturnValue(v14);
objc_msgSend(v27, "setTarget:", v15);
objc_release(v15);
objc_msgSend(v27, "setAction:", "showRegistrationWindow:");
v16 = objc_msgSend(self, "window");
v17 = objc_retainAutoreleasedReturnValue(v16);
v18 = objc_msgSend(self, "trialTitleBarViewController");
LODWORD(v10) = v3;
v19 = objc_retainAutoreleasedReturnValue(v18);
objc_msgSend(v17, "addTitlebarAccessoryViewController:", v19);
v20 = v19;
v3 = (unsigned int)v10;
objc_release(v20);
objc_release(v17);
objc_release(v27);
}
v21 = objc_msgSend(self, "trialTitleBarViewController");
v22 = objc_retainAutoreleasedReturnValue(v21);
objc_msgSend(v22, "setDaysLeft:", v3);
objc_release(v22);
}
else
{
v23 = objc_msgSend(self, "trialTitleBarViewController");
v24 = objc_retainAutoreleasedReturnValue(v23);
objc_release(v24);
if ( v24 )
{
v25 = objc_msgSend(self, "trialTitleBarViewController");
v26 = objc_retainAutoreleasedReturnValue(v25);
objc_msgSend(v26, "removeFromParentViewController");
objc_release(v26);
objc_msgSend(self, "setTrialTitleBarViewController:", 0LL);
}
}
}
__text:00000001000DDEB3 call _TRTrialStatus
__text:00000001000DDEB8 cmp eax, 0FFFFFFFBh
__text:00000001000DDEBB jz short loc_1000DDED8
```
这块有一个判断 如果 天数为 9999, 则不会执行, 我么通过修改 eax , 即 函数 TRTrialStatus 返回值来验证下
9999的 16 进制为 0x270f, 修改 eax 后可以看到成功去除了 弹框显示, 这样我们就找到关键点了, 就可以打补丁了;
接下来我们来 hook TRTrialStatus 函数;
```
int (*hook_TRTrialStatus_ori)(void);
int hook_TRTrialStatus(void){
return 9999;
};
// 根据特征吗来获取函数地址,之后用 dobby 来 hook
#if defined(__arm64__) || defined(__aarch64__)
NSString *searchMachineCode = @"F6 57 BD A9 F4 4F 01 A9 FD 7B 02 A9 FD 83 00 91 15 0C ?? ?? B5 72 ?? ?? A0 02 40 F9";
#elif defined(__x86_64__)
NSString *searchMachineCode = @"55 48 89 E5 41 57 41 56 41 55 41 54 53 50 4C 8B ?? ?? ?? ?? ?? 49 8B 3E";
#endif
// 通常来讲, _dyld_get_image_header 获取base 地址的时候 默认获取第1个 image 即为主程序的,
// 但是 Transmit 在debug的情况下的主程序会排在了后面呢...,所以这块用 imageName 来获取
int imageIndex = ;
intptr_t _hook_TRTrialStatus = ;
DobbyHook((void *)_hook_TRTrialStatus, (void *)hook_TRTrialStatus, (void *)&hook_TRTrialStatus_ori);
```
之后打好补丁, 重启, 发现有一个检查更新的报错;
全局扫描搜索, 发现是程序 Sparkle 中的,
```
/Applications/Transmit.app/Contents/Frameworks/Sparkle.framework/Versions/B/Sparkle
000000000003b354 db "Unable to Check For Updates", 0 ; DATA XREF=cfstring_Unable_to_Check_For_Updates
```
查看引用, 最终定位到了函数 SPUStandardUpdaterController startUpdater 中, 这里就不细看更新失败的原因了;
这块因为是 objc 的原生函数, 可以直接用 method_exchangeImplementations 机制来 hook
```
-:
- (void)hk_startUpdater {
NSLog(@">>>>>> Swizzled hk_startUpdater method called");
NSLog(@">>>>>> self.className : %@", self.className);
}
- (void)hk_terminateExpiredTrialTimerDidFire:(id)arg1{
NSLog(@">>>>>> Swizzled hk_terminateExpiredTrialTimerDidFire method called");
NSLog(@">>>>>> self.className : %@", self.className);
}
[MemoryUtils hookInstanceMethod:
objc_getClass("SPUStandardUpdaterController")
originalSelector:NSSelectorFromString(@"startUpdater")
swizzledClass:
swizzledSelector:NSSelectorFromString(@"hk_startUpdater")
];
[MemoryUtils hookInstanceMethod:
objc_getClass("TransmitDelegate")
originalSelector:NSSelectorFromString(@"terminateExpiredTrialTimerDidFire:")
swizzledClass:
swizzledSelector:NSSelectorFromString(@"hk_terminateExpiredTrialTimerDidFire:")
];
```
之后打好补丁,重启, 拿下
功能基本拿下, 接下来乘热打铁, hook license
由 license 窗口的 关键字 PCLicense 定位到了 showLicense 函数
查看 showLicense 函数假码,下面有个关键地方
```
rax = ;
rax = ;
rbx = rax;
r15 = [ retain];
rax = ;
rax = ;
;
;
;
;
rax = ;
rax = ;
var_30 = [ retain];
;
if (var_30 != 0x0) {
r12 = objc_alloc_init(@class(NSDateFormatter));
rax = ;
rax = ;
;
;
;
rbx = [ retain];
rax = ;
rax = ;
;
;
;
;
}
```
得出结论, licnese 信息存在 standardUserDefaults文件中,
我们的 dylib 加载后直接写入即可
```
NSUserDefaults *defaults= ;
;
;
;
```
## Quick Start
项目已经打包 github,可以直接用 xcode 打开 :
https://github.com/marlkiller/dylib_dobby_hook
```
git clone https://github.com/marlkiller/dylib_dobby_hook.git
cd script
sudo sh auto_hack.sh
```
## About ME
失业俩个月了。
北京/深圳/成都 JAVA 求职 QAQ 老哥这个东西很好用,常用的DevUtils, AirBuddy, Transmit通过 brew 安装的都能注入补丁, Navicat Premium 得从 App Store 安装才能注入。 IStat 无论如何都注入不了{:1_937:} 我前端现在都干保安了
感谢楼主分享 感谢分享{:1_921:} geyihan 发表于 2024-3-14 20:01
我前端现在都干保安了
没看懂,是种软件吗 感谢分享 学习了,mark一下 大佬还是很牛的,吓的我都不敢离职了 spd97 发表于 2024-3-15 16:48
大佬还是很牛的,吓的我都不敢离职了
裁员是很难避免的 T.T