声明&工具
[ol]
观前提示:MacBook是我缴获的美军物资。
[/ol]
IDA在论坛资源区下载,AppCode不会用可以直接用XCode。
Frida不会用可以百度,TypeScript看不懂可以百度。
insert_dylib源代码下载地址:
https://github.com/Tyilo/insert_dylib
注入我用的是rd_route劫持库.
h文件和c文件下载(后面用到):
https://github.com/rodionovd/rd_route
注意:
楼主没写过macOS App,也不会写。以下发表的所有Hook理解也是根据百度搜索到的资料理解的,如果有理解错误的地方请指正。
本文所述任何观点与公布的文件仅供参考学习Decompiler Assembler技术使用,任何人不应该将破解后的完整App文件进行非法传播以破坏软件公司的利益,读者引起的任何法律责任作者与52pojie论坛概不负责。阅读即表示读者同意以上协议。
要成品的翻到最后。文件都提供了,用法也在附件上面写了,我是真无语,非得写明白了你才知道。
直入主题
第一步 寻找关键点
开幕雷击。
16748811151832.jpg (676.65 KB, 下载次数: 0)
下载附件
2023-1-28 14:27 上传
作者这里用的是CMM的4.12.3中文版
16748811482279.jpg (68.81 KB, 下载次数: 0)
下载附件
2023-1-28 14:27 上传
二话不说先打开IDA搜isAppActivated。
16748813905720.jpg (128.75 KB, 下载次数: 0)
下载附件
2023-1-28 14:27 上传
可以看到有三条符合条件的函数。
我们就不去看setIsAppActivated这种函数了,没有意义。
那么我们怎么确定哪个函数是有实际作用的呢?
16748814887641.jpg (192.19 KB, 下载次数: 0)
下载附件
2023-1-28 14:27 上传
第一个结果其实就已经很可疑了,又是sub函数又是result的。
但我们看下另外两个:
16748815335399.jpg (123.66 KB, 下载次数: 0)
下载附件
2023-1-28 14:27 上传
就是简单的返回了app是否激活,所以我们直接看第一个结果就行,因为这两个函数没有参考意义。
我们分析一下这个伪代码:
char __cdecl -[CMMacPawAccountActivationManager isAppActivated](CMMacPawAccountActivationManager *self, SEL a2)
{
char result; // al
__int64 v3; // [rsp+8h] [rbp-8h]
v3 = -100LL;
result = sub_10036BC40(&v3, a2);
if ( !result )
result = sub_1004D9788(1LL, 0LL) == 9;
return result;
}
可以看到result是一个0或者1的返回值,因为!result表示如果result是0那么条件成立进行二次对比是否为9sub_1004D9788(1LL, 0LL) == 9.如果和9相等那就返回1,也就是Activated。
所以我们看下sub_10036BC40是干嘛的:
16748819168829.jpg (260.22 KB, 下载次数: 0)
下载附件
2023-1-28 14:27 上传
老实说其实我也没看懂他在做什么。但是我看到“code”之类的我就估计是访问网络或者读取本地保存的授权订阅文件进行正版检查,然后返回result是1还是0.
最后我在函数名上按下x看了一下引用:
16748820135319.jpg (305.52 KB, 下载次数: 0)
下载附件
2023-1-28 14:27 上传
这么多引用,我们已经可以不用猜了,这个函数就是关键点。
只要爆破掉这个函数,CMM就破掉了。
第二步 Frida快速验证
16748822630838.jpg (164.16 KB, 下载次数: 0)
下载附件
2023-1-28 14:27 上传
代码如图所示。
Utils源码在“AirBuddy2 2.6.3 的dylib注入方案(2)”文章中有释出,大家可以去自己复制下来。
然后使用frida-compile把ts文件编译为_index.js以供frida注入使用。
frida-compile index.ts -o _index.js -w
python:
def launchApp(image, js="_index.js"):
pid = frida.spawn(image)
frida.resume(pid)
os.system(f"frida -p {pid} -l {js} --debug --runtime=v8")
launchApp(
"/Applications/CleanMyMac-X.app/Contents/MacOS/CleanMyMac-X"
)
我们准备就绪后,按下F5启动App。
16748826310963.jpg (49 KB, 下载次数: 0)
下载附件
2023-1-28 14:27 上传
由于我们要注入内存,所以输入密码获得权限。
16748827267503.jpg (715.73 KB, 下载次数: 0)
下载附件
2023-1-28 14:27 上传
破解完成。
清理垃圾时也不会无限请求获得权限和磁盘访问权限了。
所以我们不破坏原始文件签名的做法是对的,如果你修改了就会无限权限请求,烦不胜烦。
到这里其实我们已经破解完了,但是我不能忍受激活信息是空白。
我也要和国外的TNT Team一样加入我的个人信息,套皮成为Vtuber拯救世界!
那么这里信息怎么改呢?
先看下效果。
16748830839643.jpg (72.24 KB, 下载次数: 0)
下载附件
2023-1-28 14:27 上传
这里其实没什么特别的,就是拿到控件实例,然后setText就行了。
怎么找到设置信息的位置呢?
老样子暴力搜索:
16748832860631.jpg (21.43 KB, 下载次数: 0)
下载附件
2023-1-28 14:28 上传
16748833309894.jpg (1.13 MB, 下载次数: 0)
下载附件
2023-1-28 14:28 上传
搜索到了,在MacPawAccount.framework里面。
/* Text field values */
"TextFieldValue_DeviceSlotPending" = "待确定";
"TextFieldValue_Email" = "电子邮件地址:";
"TextFieldValue_ExpirationDate" = "到期日期:";
"TextFieldValue_ExpirationDate_Never" = "从不";
那么key就是TextFieldValue_Email和TextFieldValue_ExpirationDate辣。
我们IDA打开/Applications/CleanMyMac-X.app/Contents/Frameworks/MacPawAccount.framework/Versions/A/MacPawAccount二进制文件搜一下:
16748834342037.jpg (231.91 KB, 下载次数: 0)
下载附件
2023-1-28 14:28 上传
首先打开字符串表,然后搜索:TextFieldValue_Email
Find it,at 0x72B7B
16748835061443.jpg (41.94 KB, 下载次数: 0)
下载附件
2023-1-28 14:28 上传
疯狂查找引用:
16748835553742.jpg (543.21 KB, 下载次数: 0)
下载附件
2023-1-28 14:28 上传
找到啦!
我们进去看一下他是怎么操作:
注意62-68行:
16748836265034.jpg (1.09 MB, 下载次数: 0)
下载附件
2023-1-28 14:28 上传
首先获取到了控件引用:
v16 = ((__int64 (__fastcall *)(MPAActivationInfoViewController *, const char *))objc_msgSend)(
self,
"customerInfoTitleLabel");
由于IDA对ObjectC并不是很友好(已加入脆鲨教育名单
我翻译一下:
v16 = objc_msgSend(self,"customerInfoTitleLabel");
这下都看懂了吧?
还没看懂?
v16 = self.customerInfoTitleLabel();
这下懂了吧?
我为什么这么翻译呢?这里就是一个新的知识点啦:
在ObjectC中,所有的对象成员调用都会被转化为objc_msgSend函数形式的调用。所以我们就知道他就是调用了这个函数得到窗口控件实例。
那么我们同理可得:
v20 = ((__int64 (__fastcall *)(MPAActivationInfoViewController *, const char *))objc_msgSend)(
self,
"dateInfoTitleLabel");
v21 = objc_retainAutoreleasedReturnValue(v20);
((void (__fastcall *)(__int64, const char *, __int64))objc_msgSend)(v21, "setStringValue:", v19);
拿到控件实例后,调用实例的setStringValue:方法,后面为啥有:呢?这是OC经典特性,表示有一个参数。如果是setStringValue:size:就是表示有两个参数。
但是这是“customerInfoTitleLabel”而不是“customerInfoValueLabel”控件,这个“customerInfoTitleLabel”是设置标题的文字“电子邮件地址:”的中文,而不是电子邮件信息的框。所以我们还要往下看。
翻到函数最底下赫然发现:
16748851272995.jpg (59.32 KB, 下载次数: 0)
下载附件
2023-1-28 14:28 上传
这下没跑了,updateUIForCustomer:licenseValidationResult:这个函数显然是更新UI信息来的。
搜一下进去看看:
16748851792594.jpg (719.25 KB, 下载次数: 0)
下载附件
2023-1-28 14:28 上传
他做了什么事情?获取用户的电子邮件,然后设置到TextField中。
v5 = objc_msgSend(v4, "email");
v6 = objc_retainAutoreleasedReturnValue(v5);
v7 = v6;
if ( !v6 )
v7 = &stru_7F4D8;
v33 = self;
v8 = -[MPAActivationInfoViewController customerInfoValueLabel](self, "customerInfoValueLabel");
v9 = objc_retainAutoreleasedReturnValue(v8);
objc_msgSend(v9, "setStringValue:", v7);
翻译一下:
v5 = v4.email;
v6 = v5;
v7 = v6;
v8 = -[MPAActivationInfoViewController customerInfoValueLabel](self, "customerInfoValueLabel");
v9 = v8;
v9.setStringValue(v7);
等价于:
v8 = -[MPAActivationInfoViewController customerInfoValueLabel](self, "customerInfoValueLabel");
v8.setStringValue(v4.email);
同理可得到期时间的编辑框为:
v29 = -[MPAActivationInfoViewController dateInfoValueLabel](v27, "dateInfoValueLabel");
v30 = objc_retainAutoreleasedReturnValue(v29);
objc_msgSend(v30, "setStringValue:", v34);
等价于:
v29 = -[MPAActivationInfoViewController dateInfoValueLabel](self, "dateInfoValueLabel");
v29.setStringValue(v4.date);
所以,我们要改文字,那就先hook这个函数,然后在函数执行结束的时候拿到控件实例设置我们的文字即可。
那我们直接看一下frida中怎么实现呢?
注意这个函数钩子挂在“MacPawAccount”中,别忘了哦。
函数挂钩0xBC77是我个人习惯,因为我习惯直接钩内存地址:
16748855538388.jpg (197.96 KB, 下载次数: 0)
下载附件
2023-1-28 14:28 上传
HookApp("MacPawAccount", (hook, getPointer, getClass, appBaseAddress, tools) => {
hook(getPointer(0xBC77), (ths, retv) => {
let obj = new ObjC.Object(ths.self);
let customerInfoValueLabel = obj.customerInfoValueLabel();
let dateInfoValueLabel = obj.dateInfoValueLabel();
customerInfoValueLabel["- setStringValue:"](ObjC.classes.NSString.stringWithString_("[email protected]/forum.php?mod=viewthread&tid=1705872"));
dateInfoValueLabel["- setStringValue:"](ObjC.classes.NSString.stringWithString_("永不过期 V2 K'ed by 秋城落叶 仅供学习交流 禁止转卖/传播/牟利"));
}, (ths, args) => {
ths.self = args[0];
})
})
我们在函数入口处先持有一个self引用:
ths.self = args[0];
在ObjectC中,args 0 1 分别是对象实例 对象selector指针即(SEL),是系统预留的两个参数。从参数2开始才是参数1.
然后在函数执行结尾的时候我们挂上钩子:
let obj = new ObjC.Object(ths.self);
用new ObjC.Object把变量转为Object实例方便我们开始调用函数。
调用函数时候注意要遵守OC规定"- functionalName:"
前面规定"- "后面如果有参数要加上":"
let customerInfoValueLabel = obj.customerInfoValueLabel();
let dateInfoValueLabel = obj.dateInfoValueLabel();
拿到控件后我们设置自定义的文字:
customerInfoValueLabel["- setStringValue:"](ObjC.classes.NSString.stringWithString_("[email protected]/forum.php?mod=viewthread&tid=1705872"));
dateInfoValueLabel["- setStringValue:"](ObjC.classes.NSString.stringWithString_("永不过期 V2 K'ed by 秋城落叶 仅供学习交流 禁止转卖/传播/牟利"));
保存重启app。
16748844434904.jpg (573.97 KB, 下载次数: 0)
下载附件
2023-1-28 14:28 上传
这结果就很酷,很符合我对科技的想象。Awesome!
第三步 使用ObjectC Dylib注入进App持久化破解
在这里我除了使用rd_route对内存地址挂钩之外,我还使用ObjectC Method Swizzing进行OC Runtime 函数劫持。
它的原理是假定有函数A,B。
A函数的实现位于地址A1,B位于B1.
在OC运行时中,A1和B1分别是A和B的IMP指针,保存于系统运行时中。
我们只需要在load函数中先劫持两个函数的IMP指针,交换一下他们的IMP指针地址即可实现A执行B的方法 B执行A的方法,从而实现Hook。
老样子打开AppCode新建项目,复制rd_route,import几个系统库。
//
// test1.m
// test1
//
// Created by 秋城落叶 on 2023/1/11.
//
//
#import "test1.h"
#import
#import
@implementation test1
static IMP originalFun = NULL;
//声明类内部变量
//变量参数定义
//updateUIForCustomer:licenseValidationResult:
- (void)updateUIForCustomer:(id)arg1 licenseValidationResult:(id)arg2 {
//拿到IMP指针先定义函数结构
void (*updateUIForCustomer)(id, SEL, id, id) = (void *) originalFun;//首先强制转换为函数
updateUIForCustomer(self, @selector(updateUIForCustomer:licenseValidationResult:), arg1, arg2);//开始函数调用成功
NSLog(@"======= QiuChenly licenseValidationResult called");
NSLog(@"res = %@", arg1);
NSLog(@"res = %@", arg2);//用%@打印一切数据对象。顺便还能展示指针的数据类型是什么
//引入SwiftUI库直接拿到指针所属对象
NSTextField *(*customerInfoValueLabel)(id, SEL) = (void *) class_getMethodImplementation(NSClassFromString(@"MPAActivationInfoViewController"), NSSelectorFromString(@"customerInfoValueLabel"));
NSTextField *(*dateInfoValueLabel)(id, SEL) = (void *) class_getMethodImplementation(NSClassFromString(@"MPAActivationInfoViewController"), NSSelectorFromString(@"dateInfoValueLabel"));//声明方法结构和实现体
NSTextField *customerInfoValueLabels = customerInfoValueLabel(self, NSSelectorFromString(@"customerInfoValueLabel"));
NSTextField *dateInfoValueLabels = dateInfoValueLabel(self, NSSelectorFromString(@"dateInfoValueLabel"));//传入self和SEL调用函数
NSLog(@"NSTextField = %@", customerInfoValueLabels);
[customerInfoValueLabels setStringValue:@"[email protected]/forum.php?mod=viewthread&tid=1705872"];//直接调用函数方法设置字符串
[dateInfoValueLabels setStringValue:@"永不过期 K'ed by 秋城落叶 仅供学习交流 禁止转卖/传播/牟利"];
}
BOOL (*sub_10036BC40_org)(int*) = NULL;
//定义一个swift函数用于替换 可选是否static静态
static BOOL sub_10036BC40_hook(int* a1) {
BOOL ret = sub_10036BC40_org(0);//调用原函数
NSLog(@"======= QiuChenly load sub_10036BC40_hook called original return value is %d, args1 = %p", ret, a1);
return 0x1;
}
void CleanMyMacHook() {
intptr_t sub_10036BC40 = _dyld_get_image_vmaddr_slide(0) + 0x10036BC40;//获取第0个镜像基地址 加上偏移地址
rd_route((void *) sub_10036BC40, sub_10036BC40_hook, (void **) &sub_10036BC40_org);
NSLog(@"======= QiuChenly load sub_10036BC40 success ======= %p - %p", &sub_10036BC40, &sub_10036BC40_hook);
NSLog(@"======= QiuChenly load MPAActivationInfoViewController =======");
Method oMPAActivationInfoViewController = class_getInstanceMethod(NSClassFromString(@"MPAActivationInfoViewController"), NSSelectorFromString(@"updateUIForCustomer:licenseValidationResult:"));
IMP nMPAActivationInfoViewController = class_getMethodImplementation([InlineInjectPlugin class], @selector(updateUIForCustomer:licenseValidationResult:));
//IMP函数实现和Selector的极限拉扯
originalFun = method_setImplementation(oMPAActivationInfoViewController, nMPAActivationInfoViewController);//设置新的IMP实现到系统中
}
+ (void)load {
CleanMyMacHook();
}
@end
编译,得到dylib文件,使用insert_dylib进行注入即可完成持久化破解。
注意:最好把dylib注入补丁也放在app里面,否则移动app就无法读取到dylib文件导致破解失败。
把编译出来的文件注入到第三方库中:
sudo
sudo insert_dylib libInlineInjectPlugin.dylib /Applications/CleanMyMac-X.app/Contents/Frameworks/Announcements.framework/Versions/A/Announcements /Applications/CleanMyMac-X.app/Contents/Frameworks/Announcements.framework/Versions/A/Announcements
附录 下面是用Frida Hook的脚本
import { HookApp, log } from "./Utils.js";
HookApp("CleanMyMac-X", (hook, getPointer, getClass, appBaseAddress, tools) => {
//CMMacPawAccountActivationManager_isAppActivated
hook(getPointer(0x36BC40), (ths, retv) => {
retv.replace(ptr(1))
})
})
//MacPawAccount
HookApp("MacPawAccount", (hook, getPointer, getClass, appBaseAddress, tools) => {
hook(getPointer(0xBC77), (ths, retv) => {
let obj = new ObjC.Object(ths.self);
let customerInfoValueLabel = obj.customerInfoValueLabel();
let dateInfoValueLabel = obj.dateInfoValueLabel();
customerInfoValueLabel["- setStringValue:"](ObjC.classes.NSString.stringWithString_("[email protected]/forum.php?mod=viewthread&tid=1705872"));
dateInfoValueLabel["- setStringValue:"](ObjC.classes.NSString.stringWithString_("永不过期 V2 K'ed by 秋城落叶 仅供学习交流 禁止转卖/传播/牟利"));
}, (ths, args) => {
ths.self = args[0];
log("0xBC77参数", args[2], args[3])
})
})
编译好的学习文件
激活补丁
libInlineInjectPlugin.dylib.zip
(10.31 KB, 下载次数: 2, 售价: 1 CB吾爱币)
2023-1-28 14:24 上传
点击文件名下载附件
售价: 1 CB吾爱币 [记录]
[购买]
下载积分: 吾爱币 -1 CB
我编译好的注入工具,你们也可以自己通过源码编译。
insert_dylib.zip
(8.47 KB, 下载次数: 1, 售价: 1 CB吾爱币)
2023-1-28 14:24 上传
点击文件名下载附件
售价: 1 CB吾爱币 [记录]
[购买]
下载积分: 吾爱币 -1 CB
别忘了关SIP。
Refs&Credit
[ol]
[/ol]