Sketch!一次悸动的小逆向尝试
丙戌月 癸酉日
故事从一开始应该是这样的:最近一直在使用Sketch,然而呢,直到某一天弹出这个着实让我惊叹不已:
想着今天是1024专属日,于是乎上官网看看然而发现并没有限免,万年不变的价格也是出奇的合理,99美元能买到这样一款优秀到超出想象,好用到违反广告法的软件真的是赚到了。
看看还有没有其它类似的App,要不找找看有木有破解版?故事的结尾刚要如此,然而笔锋一转,情节立马一波三折,没想到用盗版软件的也会有自己破解软件的今天-_-||。因为最近刚看完了一些iOS逆向开发的书,奈何没有越狱的设备,只好在OS X上练练手。正所谓道法自然,虽平台,工具不同,但内功都是一样的。
不忍直视的弹窗
现在先让我们为这个万恶的弹窗默哀一分钟,接着就开始仔细关注分析一下有哪些入手点。仔细一看就会发现,那个welcome界面依然还在的,也就是说主程序仍然是可以使用的,只是在其上多出了一个如下图的提示注册的弹窗,获取了当前的焦点,以至于我们无法进行其他的操作。
所以我们的任务很简单啦,要么在register上做文章,弄清楚注册流程和算法(第一次我就不要不要试这个了,顿时压力好大);要么在quit按钮上做文章,使其只关闭弹窗但不退出整个程序;或者在判断是否Trial Expired
的过程中做手脚;最后,还有最简单的,但是却不优雅的,就是直接去掉弹窗就好了。
工具嘛,IDA或Hopper Disassembler按自己的喜欢就可以了,这里我就用Hopper Disassembler就可以了。开始前也可以class dump一下头文件看看,会有助于后面的分析,这里我就不用了。
那就开始咯
进入Sketch.app目录,找到Sketch二进制文件把它丢进Hopper里然后坐等分析,分析完后如下图:
Hopper
这个软件很强大,不过是收费的,但提供免费试用的,试用版的每30分钟会退出一次(咳咳,用自己来破解自己,想想就可怕)。
我们在左边搜索一下弹窗出现的相关关键字Trial
:
从字面上,我们一眼就能看出每个类每个方法的作用,其中BCTrialCountdown
这个类我猜就是负责计算剩余试用天数的,里面的trialPopUpWithNumberOfDays
方法应该就是那个不厌其烦每次弹出提示我们还有多少天过期。
而BCTrialExpiredWindowController
应该就是我们要找的弹窗的controller,有show
,quit
,visitStore
,registerLicense
等方法,嗯,没错,就是它了。这样我们就很快定位到相关函数了。
下面我们来仔细看看show
方法的汇编代码:
+[BCTrialExpiredWindowController show]:
00000001002aa6e0 push rbp ; Objective C Implementation defined at 0x100368688 (class)
00000001002aa6e1 mov rbp, rsp
00000001002aa6e4 push r15
00000001002aa6e6 push r14
00000001002aa6e8 push r13
00000001002aa6ea push r12
00000001002aa6ec push rbx
00000001002aa6ed push rax
00000001002aa6ee mov rbx, rdi
00000001002aa6f1 mov rdi, qword [ds:objc_cls_ref_BCTrialExpiredWindowController] ; objc_cls_ref_BCTrialExpiredWindowController, argument "instance" for method _objc_msgSend
00000001002aa6f8 mov rsi, qword [ds:0x1003d2178] ; @selector(alloc)
00000001002aa6ff mov r12, qword [ds:imp___got__objc_msgSend] ; imp___got__objc_msgSend
00000001002aa706 call r12 ; _objc_msgSend
00000001002aa709 mov r14, rax
00000001002aa70c mov rsi, qword [ds:0x1003d2230] ; @selector(class), argument "selector" for method _objc_msgSend
00000001002aa713 mov rdi, rbx ; argument "instance" for method _objc_msgSend
00000001002aa716 call r12 ; _objc_msgSend
00000001002aa719 mov rdi, rax ; argument "aClass" for method imp___stubs__NSStringFromClass
00000001002aa71c call imp___stubs__NSStringFromClass
00000001002aa721 mov rdi, rax ; argument "instance" for method imp___stubs__objc_retainAutoreleasedReturnValue
00000001002aa724 call imp___stubs__objc_retainAutoreleasedReturnValue
00000001002aa729 mov rbx, rax
00000001002aa72c mov rsi, qword [ds:0x1003d4100] ; @selector(initWithWindowNibName:), argument "selector" for method _objc_msgSend
00000001002aa733 mov rdi, r14 ; argument "instance" for method _objc_msgSend
00000001002aa736 mov rdx, rbx
00000001002aa739 call r12 ; _objc_msgSend
00000001002aa73c mov r14, rax
00000001002aa73f mov r13, qword [ds:imp___got__objc_release] ; imp___got__objc_release
00000001002aa746 mov rdi, rbx ; argument "instance" for method _objc_release
00000001002aa749 call r13 ; _objc_release
00000001002aa74c mov rsi, qword [ds:0x1003d4108] ; @selector(window), argument "selector" for method _objc_msgSend
00000001002aa753 mov rdi, r14 ; argument "instance" for method _objc_msgSend
00000001002aa756 call r12 ; _objc_msgSend
00000001002aa759 mov rdi, rax ; argument "instance" for method imp___stubs__objc_retainAutoreleasedReturnValue
00000001002aa75c call imp___stubs__objc_retainAutoreleasedReturnValue
00000001002aa761 mov rbx, rax
00000001002aa764 mov rsi, qword [ds:0x1003d4110] ; @selector(center), argument "selector" for method _objc_msgSend
00000001002aa76b mov rdi, rbx ; argument "instance" for method _objc_msgSend
00000001002aa76e call r12 ; _objc_msgSend
00000001002aa771 mov rdi, rbx ; argument "instance" for method _objc_release
00000001002aa774 call r13 ; _objc_release
00000001002aa777 mov rax, qword [ds:imp___got__NSApp] ; imp___got__NSApp
00000001002aa77e mov r15, qword [ds:rax]
00000001002aa781 mov rsi, qword [ds:0x1003d4108] ; @selector(window), argument "selector" for method _objc_msgSend
00000001002aa788 mov rdi, r14 ; argument "instance" for method _objc_msgSend
00000001002aa78b call r12 ; _objc_msgSend
00000001002aa78e mov rdi, rax ; argument "instance" for method imp___stubs__objc_retainAutoreleasedReturnValue
00000001002aa791 call imp___stubs__objc_retainAutoreleasedReturnValue
00000001002aa796 mov rbx, rax
00000001002aa799 mov rsi, qword [ds:0x1003d4118] ; @selector(runModalForWindow:), argument "selector" for method _objc_msgSend
00000001002aa7a0 mov rdi, r15 ; argument "instance" for method _objc_msgSend
00000001002aa7a3 mov rdx, rbx
00000001002aa7a6 call r12 ; _objc_msgSend
00000001002aa7a9 mov rdi, rbx ; argument "instance" for method _objc_release
00000001002aa7ac call r13 ; _objc_release
00000001002aa7af mov rdi, r14 ; argument "instance" for method _objc_release
00000001002aa7b2 mov rax, r13
00000001002aa7b5 add rsp, 0x8
00000001002aa7b9 pop rbx
00000001002aa7ba pop r12
00000001002aa7bc pop r13
00000001002aa7be pop r14
00000001002aa7c0 pop r15
00000001002aa7c2 pop rbp
00000001002aa7c3 jmp rax ; _objc_release
; endp
00000001002aa7c5 nop qword [cs:rax+rax+0x0]
其实也没什么可看的,看最后的一个call
,传给_objc_msgSend
的两个参数,一个是自身的实例,另一个是runModalForWindow
的selector,也就是说这里才弹出窗口:
你可以下个断点确认下,我已经确认了,也就是说,在0x1002aa7a6
处将这个call r12
置成nop
不让它调用就可以了。
这样弹窗就不会出现了。到这里,尽管目的达到了,可是感觉不怎么优雅,明明是考验开锁技巧的,却偏偏用锤子直接把锁砸开了。尽管我现在做不到写注册机这种地步,可是还是让我们把修改还原,还是再多看看吧。
代码辣么长,我想再看看
上面我们是粗鲁的破坏掉了BCTrialExpiredWindowController
的show
方法,使其失去了弹窗的作用。与其破坏,还不如不让其调用。我们找找看看是在哪调用的。
可以看到,从0x1002a8834
处开始调用BCTrialExpiredWindowController
的show
方法,往上看看这个sub procedure整体:
整体上这样看就很明白了,0x1002a87f2
到0x1002a8832
这段是判断是否有license,如果有,就跳转到0x1002a884c
处,然后返回,如果不正确,就往下到0x1002a8834
调用BCTrialExpiredWindowController
的show
方法弹窗。这样也表明了0x1002a87e7
的test是用来判断是否过期,过期的话就跳转去判断是否有license。
看懂了这里,我们就只用修改cmp r14, 0x1
这句为cmp r14, 0x0
,然后就能无限使用了,这比之前的是不是更好一些呢。
然后Shift+Cmd+E
将其保存为二进制文件,将原来的二进制文件备份,然后相应的替换即可。
至此,弹窗已去,万佛朝宗,大功告成,天下我有。
最美的永远是意外
悲剧往往发生在你双击的那一刻,因为你永远都不会知道打开的是什么。当我们双击运行看看,咦,怎么打不开,而且是闪退有木有,难道是我们打开的方式不对?
没错,真的是打开方式不对。这次我们不点击应用图标,而是进入其目录直接运行二进制文件看看:
这下就明白了。程序自己退出了,并打印了一些信息,说明程序内部有对自身进行检测,如果发现代码,签名等被修改了就强制退出。嗯,我猜应该是这样。
没办法,看来活还没干完,打开Hopper
接着干。这次,我们查找Invalid Signing
这个字符串,看是哪里输出的。
00000001001bffbf lea rdi, qword [ds:cfstring_Invalid_Signing] ; @"Invalid Signing", argument "format" for method imp___stubs__NSLog, XREF=sub_1001bfe40+357
00000001001bffc6 xor eax, eax
00000001001bffc8 call imp___stubs__NSLog
00000001001bffcd mov edi, 0xae ; argument "status" for method imp___stubs__exit
00000001001bffd2 call imp___stubs__exit
嗯,没错,就是这里。调用NSLog
输出@"Invalid Signing"
,然后就退出程序。
再来看看全部代码,下图会更直观一些:
可以看到,不管上面进行了什么操作,都要经过0x1001bff7a
这里,然后通过test r13d, r13d
判断是直接返回呢,还是跳到0x1001bffbf
这里退出呢。既然这样,我们将下面的jne 0x1001bffbf
置为nop
就可以了。
修改后保存成二进制文件,再运行就没有问题了。
最后放出链接,http://pan.baidu.com/s/1qWmXcTm
打完收工。