#0x00前言
iMazing是一款Mac下管理手机的工具。
本次过程使用工具:Hopper,文本编辑器。
对于那些不熟悉Mac下软件逆向的同学,作者画了一张脑图给你们参考。
#0x01初步查看软件
从官网下载试用版,发现试用版和正式版的区别挺多的。
首先先判断这个软件的字符串标签都是从哪里来的,我们把系统语言设置为中文,这个软件打开发现他是中文,然后再将系统语言设置为英文,这个软件打开就是英文。这说明这个软件的语言是适配系统语言的,但是一般适配系统语言的字符串标签要么存在Resources目录,要么存到二进制文件里边去,iMazing这个软件他就写到了Resources目录。
en.lproj是存放英语字符串的文件夹,zh.lproj是存放中文字符串的文件夹。在非英语语言的文件夹下一般只有两个strings文件
在en.lproj文件夹下有一个readme.txt告诉了我们GenericLabels.strings这个文件存放的是可以复用的字符串标签,那么猜测一下iMazingLabels.strings存储的是不可复用字符串标签。
这个时候我们既然知道了他的中文字符串在哪里,我们就可以检索他的中文字符串来找一找这个软件的一个关键的突破点。
我们找到了这个关键字iMazingNotActivated_Title这个应该表示的是未激活窗口的一个标题。
#0x01开始反编译
使用Hopper载入iMazing的二进制文件,等它分析完后,在搜索iMazingNotActivated_Title这个字符串。
找到后,在右下角双击Is Referenced By下面的第一个地址来查看引用。
那个NSString是CFStringRef转换成的,我们可以不管它的转换,继续在右下角双击Is Referenced By下面的第一个地址来查看引用。
这个时候我们追到了MessageViewController类的displayRegistrationAlert方法。
void -[MessageViewController displayRegistrationAlert](void * self, void * _cmd) {
r15 = self;
r12 = *_objc_msgSend;
r14 = [[self mainWindowController] window];
if (r14 != 0x0) {
r13 = (r12)((r12)(@class(NSAlert), @selector(new)), @selector(autorelease));
(r12)(r13, @selector(setMessageText:), (r12)((r12)(@class(LocalizationManager), @selector(sharedInstance)), @selector(localizedStringForClass:key:), (r12)(r15, @selector(class)), @"iMazingNotActivated_Title"));
(r12)(r13, @selector(setInformativeText:), (r12)((r12)(@class(LocalizationManager), @selector(sharedInstance)), @selector(localizedStringForClass:key:), (r12)(r15, @selector(class)), @"iMazingNotActivated_Info"));
(r12)(r13, @selector(setAlertStyle:), 0x0);
(r12)(r13, @selector(addButtonWithTitle:), (r12)((r12)(@class(LocalizationManager), @selector(sharedInstance)), @selector(localizedStringForClass:key:), (r12)(r15, @selector(class)), @"OK_UserReply"));
(r12)(r13, @selector(beginSheetModalForWindow:completionHandler:), r14, ^ {/* block implemented at sub_1001102ae */ } });
(r12)(**_NSApp, @selector(runModalForWindow:), r14);
}
return;
}
发现这个方法只是单纯的构造了一个弹窗,并没什么用。。
那再找一个字符串好了。。
这回设定为Deactivate字符串。
找到了这个deactivateLicense:
最终追踪到了
ApplicationDelegate validateMenuItem:
菜单项这里
char -[ApplicationDelegate validateMenuItem:](void * self, void * _cmd, void * arg2) {
r15 = _cmd;
r12 = self;
r14 = [arg2 retain];
rbx = [[**_NSApp modalWindow] retain];
[rbx release];
if (rbx == 0x0) {
var_38 = r15;
rbx = [[r12 mainWindowController] retain];
r15 = [rbx exiting];
[rbx release];
if (r15 != 0x0) {
rbx = 0x0;
}
else {
r15 = *_objc_msgSend;
rbx = [[r12 mainWindowController] retain];
rdx = [r14 action];
rax = [rbx respondsToSelector:rdx];
var_30 = r14;
[rbx release];
rbx = r15;
r15 = [[r12 mainWindowController] retain];
if (rax != 0x0) {
r13 = rbx;
rdx = var_38;
rbx = [r15 respondsToSelector:rdx];
[r15 release];
if (rbx != 0x0) {
r14 = [(r13)(r12, @selector(mainWindowController), rdx) retain];
rbx = (r13)(r14, @selector(validateMenuItem:), var_30);
rdi = r14;
r14 = var_30;
[rdi release];
}
else {
rbx = 0x1;
r14 = var_30;
}
}
else {
r14 = [(rbx)(r15, @selector(currentContentViewController), rdx) retain];
rbx = (rbx)(r14, @selector(respondsToSelector:), (rbx)(var_30, @selector(action), rdx));
[r14 release];
[r15 release];
if (rbx != 0x0) {
r14 = [[r12 mainWindowController] retain];
rbx = [[r14 currentContentViewController] retain];
rdx = var_38;
r15 = [rbx respondsToSelector:rdx];
[rbx release];
[r14 release];
if (r15 != 0x0) {
r14 = [[r12 mainWindowController] retain];
r15 = [[r14 currentContentViewController] retain];
rbx = [r15 validateMenuItem:var_30];
[r15 release];
rdi = r14;
r14 = var_30;
[rdi release];
}
else {
rbx = 0x1;
r14 = var_30;
}
}
else {
r14 = var_30;
rbx = 0x1;
if (((((((([r14 action] != @selector(showHelp:)) && ([r14 action] != @selector(showAboutWindow:))) && ([r14 action] != @selector(showAlternateAboutWindow:))) && ([r14 action] != @selector(showPreferencesWindow:))) && ([r14 action] != @selector(showSendFeedbackOrBugReport:))) && ([r14 action] != @selector(socialVisitFacebook:))) && ([r14 action] != @selector(socialVisitTwitter:))) && ([r14 action] != @selector(socialVisitGoogle:))) {
if ([r14 action] != @selector(socialShareFacebook:)) {
if ([r14 action] != @selector(socialShareTwitter:)) {
if ([r14 action] != @selector(socialShareGoogle:)) {
if ([r14 action] != @selector(showBuyScreen:)) {
if ([r14 action] != @selector(openRetrieveLicensePage:)) {
rbx = [r14 action] == @selector(deactivateLicense:) ? 0x1 : 0x0;
}
}
}
}
}
}
}
}
}
}
else {
rbx = 0x0;
}
[r14 release];
rax = sign_extend_64(rbx);
return rax;
}
这时候我们看到它经过了很多的判断才调用了(deactivateLicense:)这个方法,我们看一下这个方法是那个类的,双击方法名即可。
void -[ApplicationDelegate deactivateLicense:](void * self, void * _cmd, void * arg2) {
rbx = [[Activation sharedInstance] retain];
[rbx Pv8iwIVja9yAv];
[rbx release];
return;
}
sharedInstance是 单例模式不用管,重点看一下Pv8iwIVja9yAv这个方法,这个方法来自Activation类
void -[Activation Pv8iwIVja9yAv](void * self, void * _cmd) {
rax = *_NSApp;
[*rax Pv8iwIVja9yAv];
[self checkActivation];
return;
}
调用的checkActivation方法好像是检查是否激活的。
void -[Activation checkActivation](void * self, void * _cmd) {
r14 = self;
if ([self checkIfIsActivated] != 0x0) {
[IronSource reportConversionToIronSource:@"purchase"];
}
else {
rbx = [[Preferences sharedInstance] retain];
r15 = [rbx firstLaunch];
[rbx release];
if (r15 == 0x0) {
intrinsic_movsd(xmm0, *qword);
[r14 performSelector:@selector(showDialog) withObject:0x0 afterDelay:r8];
}
}
return;
}
好像我们只要将Activation checkIfIsActivated这个方法让他为 YES 就可以成功绕过激活。
char -[Activation checkIfIsActivated](void * self, void * _cmd) {
r13 = sub_100024440(0x0);
if (r13 != 0x0) {
r14 = sub_100027860();
if (r14 != 0x0) {
r15 = [CFDictionaryGetValue(r14, *0x100241aa0) retain];
if (r15 != 0x0) {
r12 = [[Preferences sharedInstance] retain];
[r12 setRegistrationEmail:r15];
[r12 release];
}
CFRelease(r14);
[r15 release];
}
}
rbx = [[DDNAConfig sharedInstance] retain];
[rbx setAppRegistered:sign_extend_64(r13)];
[rbx release];
rax = sign_extend_64(r13);
return rax;
}
直接把Activation checkIfIsActivated这个方法
mov rax, 0x1
ret
保存测试一下。
打开后发现果然没有让我激活了。但是会弹一个窗。
一开始我以为,这个弹窗的原因是我们修改了它的二进制文件,它签名校验失败了,但是并不影响正常使用。
后来经过@ChiChou 的指正弹窗是 sparkle 框架,用来实现自动更新的。
可以patch /Frameworks/DevMateKit.framework/DevMateKit 这个文件里的 -[DM_SUUpdater checkIfConfiguredProperly] 方法。
来阻止弹窗。
成功绕过激活。