Mac 下的软件绕过激活之iMazing


#1

#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] 方法。
来阻止弹窗。


成功绕过激活。


#2

牛批啊 兄dei


#3

牛批啊 兄dei


#4

就不能直接复制到论坛里吗


#5

:joy:当时写在wiz就懒的粘贴了,正好买了VIP就一键分享了。


#6

果断复制一下啊,这样就能搜索到了,用户体验更好哇


#7

好的,收到,晚上我改下…


#8
@implementation NSObject (iMazingTweak)

__attribute__((constructor)) static void checkIfIsActivated(void) {
    Class class = objc_getClass("Activation");
    SEL selector = NSSelectorFromString(@"checkIfIsActivated");
    Method method = class_getInstanceMethod(class, selector);
    IMP imp = imp_implementationWithBlock(^(id self) {
        return YES;
    });
    class_replaceMethod(class, selector, imp, method_getTypeEncoding(method));
}

@end

可以通过编译 dylib or framework 的方式进行注入,效果更佳。

BTW, 请支持正版~


#9

ret 0x0 指令的立即数是用来修改栈用的,不是 C 里的 return 0
恰恰相反, 这个函数需要返回一个非 0 值才是已注册。直接 ret 成功是因为 rax 还保存着调用者的内容。保险起见可以多写一行 mov rax, 1

另外弹窗的部分用的是 sparkle 框架,用来实现自动更新的。如果对这个弹窗不爽可以补丁掉 Contents/Frameworks/DevMateKit.framework/DevMateKit 这个文件里的 -[DM_SUUpdater checkIfConfiguredProperly] 方法,直接 ret 即可


#10

感谢指导,晚上我会修改原文的。


#11

之前我看伪代码也有错误,这个地方是返回非0 的值即可,当时ret 0x0直接把rax返回回去,又因为rax保存的是调用的信息不可能是0 所以成功。
正确的写法应该是给rax赋值1返回。
还有那个弹窗的问题。感谢指正。


#13

我写了一个Mac注入的Xcode的模板 可以试一下
mac-framework-inject


#14

牛逼啊 兄弟