本练习对象为Exces,一款小工具软件。下载地址:http://excesapp.com。作者本人其实已经描述了如何破解Exces,只是文中方法是暴力破解。笔者认为稍显简单粗暴——技不止于此!因此,尝试从真正注册license入手,实现完美破解!
Exces是如何注册的呢?运行Exces,见下图,可以看到注册license方法。在Preferences弹出框中,拖入一个有效的license文件。
那这个license文件是怎么样的呢?我们在Hopper中分析。
0x1 代码分析
在Hopper中打开Exces,找到verifyLicenseFile:方法。
; ================ B E G I N N I N G O F P R O C E D U R E ================
; Variables:
; arg_8: 16
; _cmd: 12
; self: 8
; var_1C: -28
; var_34: -52
; var_38: -56
; var_3C: -60
; var_40: -64
; var_44: -68
; var_48: -72
-[SSExcesAppController verifyLicenseFile:]:
000051fc push ebp ; Objective C Implementation defined at 0x14ef8 (instance method)
000051fd mov ebp, esp
000051ff push edi
00005200 push esi
00005201 push ebx
00005202 sub esp, 0x3c
00005205 mov eax, dword [ebp+arg_8]
00005208 mov edi, dword [ebp+self]
0000520b mov dword [ebp+var_1C], eax
0000520e mov dword [esp+0x48+var_40], eax
00005212 mov eax, dword [objc_msg_verifyPath_] ; @selector(verifyPath:)
00005217 mov dword [esp+0x48+var_48], edi ; argument #1 for method imp___jump_table__objc_msgSend
0000521a mov dword [esp+0x48+var_44], eax ; argument #2 for method imp___jump_table__objc_msgSend
0000521e call imp___jump_table__objc_msgSend
00005223 test eax, eax
00005225 je loc_530d
0000522b mov eax, dword [objc_msg_alloc] ; @selector(alloc)
00005230 mov dword [esp+0x48+var_44], eax ; argument #2 for method imp___jump_table__objc_msgSend
00005234 mov eax, dword [cls_NSTask] ; cls_NSTask
00005239 mov dword [esp+0x48+var_48], eax ; argument #1 for method imp___jump_table__objc_msgSend
0000523c call imp___jump_table__objc_msgSend
00005241 mov edx, dword [objc_msg_init] ; @selector(init)
00005247 mov dword [esp+0x48+var_44], edx ; argument #2 for method imp___jump_table__objc_msgSend
0000524b mov dword [esp+0x48+var_48], eax ; argument #1 for method imp___jump_table__objc_msgSend
0000524e call imp___jump_table__objc_msgSend
00005253 mov dword [esp+0x48+var_40], 0x12418 ; @"/bin/cp"
0000525b mov ebx, eax
0000525d mov eax, dword [objc_msg_setLaunchPath_] ; @selector(setLaunchPath:)
00005262 mov dword [esp+0x48+var_48], ebx ; argument #1 for method imp___jump_table__objc_msgSend
00005265 mov dword [esp+0x48+var_44], eax ; argument #2 for method imp___jump_table__objc_msgSend
00005269 call imp___jump_table__objc_msgSend
0000526e mov eax, dword [objc_msg_stringByExpandingTildeInPath] ; @selector(stringByExpandingTildeInPath)
00005273 mov esi, dword [cls_NSArray] ; cls_NSArray
00005279 mov dword [esp+0x48+var_48], 0x123b8 ; @"~/Library/Preferences/com.seosoft.exces.licence.excli", argument #1 for method imp___jump_table__objc_msgSend
00005280 mov dword [esp+0x48+var_44], eax ; argument #2 for method imp___jump_table__objc_msgSend
00005284 call imp___jump_table__objc_msgSend
00005289 mov dword [esp+0x48+var_34], 0x0
00005291 mov dword [esp+0x48+var_38], eax
00005295 mov eax, dword [ebp+var_1C]
00005298 mov dword [esp+0x48+var_40], 0x12428 ; @"-r"
000052a0 mov dword [esp+0x48+var_3C], eax
000052a4 mov eax, dword [objc_msg_arrayWithObjects_] ; @selector(arrayWithObjects:)
000052a9 mov dword [esp+0x48+var_48], esi ; argument #1 for method imp___jump_table__objc_msgSend
000052ac mov dword [esp+0x48+var_44], eax ; argument #2 for method imp___jump_table__objc_msgSend
000052b0 call imp___jump_table__objc_msgSend
000052b5 mov dword [esp+0x48+var_40], eax
000052b9 mov eax, dword [objc_msg_setArguments_] ; @selector(setArguments:)
000052be mov dword [esp+0x48+var_48], ebx ; argument #1 for method imp___jump_table__objc_msgSend
000052c1 mov dword [esp+0x48+var_44], eax ; argument #2 for method imp___jump_table__objc_msgSend
000052c5 call imp___jump_table__objc_msgSend
000052ca mov eax, dword [objc_msg_launch] ; @selector(launch)
000052cf mov dword [esp+0x48+var_48], ebx ; argument #1 for method imp___jump_table__objc_msgSend
000052d2 mov dword [esp+0x48+var_44], eax ; argument #2 for method imp___jump_table__objc_msgSend
000052d6 call imp___jump_table__objc_msgSend
000052db mov ebx, dword [edi+0xc]
000052de mov byte [edi+0x2c], 0x1
000052e2 mov eax, dword [objc_msg_selectedToolbarItem] ; @selector(selectedToolbarItem)
000052e7 mov dword [esp+0x48+var_48], ebx ; argument #1 for method imp___jump_table__objc_msgSend
000052ea mov dword [esp+0x48+var_44], eax ; argument #2 for method imp___jump_table__objc_msgSend
000052ee call imp___jump_table__objc_msgSend
000052f3 mov dword [ebp+arg_8], eax
000052f6 mov eax, dword [objc_msg_toolbarHit_] ; @selector(toolbarHit:)
000052fb mov dword [ebp+self], ebx
000052fe mov dword [ebp+_cmd], eax
00005301 add esp, 0x3c
00005304 pop ebx
00005305 pop esi
00005306 pop edi
00005307 leave
00005308 jmp imp___jump_table__objc_msgSend
; endp
loc_530d:
0000530d add esp, 0x3c ; CODE XREF=-[SSExcesAppController verifyLicenseFile:]+41
00005310 pop ebx
00005311 pop esi
00005312 pop edi
00005313 leave
00005314 ret
; endp
其中,关键点为调用的verifyPath:方法;如果verifyPath方法通过,便将License文件cp(拷贝) 到~/Library/Preferences/com.seosoft.exces.licence.excli。由此得到 一点提示,即licence 文件为excli后缀。
接下来看verifyPath:方法。
00005315 push ebp ; Objective C Implementation defined at 0x14eec (instance method)
00005316 mov ebp, esp
00005318 push edi
00005319 push esi
0000531a push ebx
0000531b sub esp, 0x1c
0000531e mov eax, dword [objc_msg_string] ; @selector(string)
00005323 mov edi, dword [ebp+self]
00005326 mov dword [esp+0x28+var_24], eax ; argument #2 for method imp___jump_table__objc_msgSend
0000532a mov eax, dword [cls_NSMutableString] ; cls_NSMutableString
0000532f mov dword [esp+0x28+var_28], eax ; argument #1 for method imp___jump_table__objc_msgSend
00005332 call imp___jump_table__objc_msgSend
00005337 mov dword [esp+0x28+var_20], 0x12438 ; @"0xE6242CBE8E7686CB4AFB143FAF6D"
0000533f mov ebx, eax
00005341 mov eax, dword [objc_msg_appendString_] ; @selector(appendString:)
00005346 mov dword [esp+0x28+var_28], ebx ; argument #1 for method imp___jump_table__objc_msgSend
00005349 mov dword [esp+0x28+var_24], eax ; argument #2 for method imp___jump_table__objc_msgSend
0000534d call imp___jump_table__objc_msgSend
00005352 mov eax, dword [objc_msg_appendString_] ; @selector(appendString:)
00005357 mov dword [esp+0x28+var_20], 0x12448 ; @"6B7805475B6D856812"
0000535f mov dword [esp+0x28+var_28], ebx ; argument #1 for method imp___jump_table__objc_msgSend
00005362 mov dword [esp+0x28+var_24], eax ; argument #2 for method imp___jump_table__objc_msgSend
00005366 call imp___jump_table__objc_msgSend
0000536b mov eax, dword [objc_msg_appendString_] ; @selector(appendString:)
00005370 mov dword [esp+0x28+var_20], 0x12458 ; @"9"
00005378 mov dword [esp+0x28+var_28], ebx ; argument #1 for method imp___jump_table__objc_msgSend
0000537b mov dword [esp+0x28+var_24], eax ; argument #2 for method imp___jump_table__objc_msgSend
0000537f call imp___jump_table__objc_msgSend
00005384 mov eax, dword [objc_msg_appendString_] ; @selector(appendString:)
00005389 mov dword [esp+0x28+var_20], 0x12458 ; @"9"
00005391 mov dword [esp+0x28+var_28], ebx ; argument #1 for method imp___jump_table__objc_msgSend
00005394 mov dword [esp+0x28+var_24], eax ; argument #2 for method imp___jump_table__objc_msgSend
00005398 call imp___jump_table__objc_msgSend
0000539d mov eax, dword [objc_msg_appendString_] ; @selector(appendString:)
000053a2 mov dword [esp+0x28+var_20], 0x12468 ; @"1D6547D406"
000053aa mov dword [esp+0x28+var_28], ebx ; argument #1 for method imp___jump_table__objc_msgSend
000053ad mov dword [esp+0x28+var_24], eax ; argument #2 for method imp___jump_table__objc_msgSend
000053b1 call imp___jump_table__objc_msgSend
000053b6 mov eax, dword [objc_msg_appendString_] ; @selector(appendString:)
000053bb mov dword [esp+0x28+var_20], 0x12478 ; @"A2C26"
000053c3 mov dword [esp+0x28+var_28], ebx ; argument #1 for method imp___jump_table__objc_msgSend
000053c6 mov dword [esp+0x28+var_24], eax ; argument #2 for method imp___jump_table__objc_msgSend
000053ca call imp___jump_table__objc_msgSend
000053cf mov eax, dword [objc_msg_appendString_] ; @selector(appendString:)
000053d4 mov dword [esp+0x28+var_20], 0x12488 ; @"3"
000053dc mov dword [esp+0x28+var_28], ebx ; argument #1 for method imp___jump_table__objc_msgSend
000053df mov dword [esp+0x28+var_24], eax ; argument #2 for method imp___jump_table__objc_msgSend
000053e3 call imp___jump_table__objc_msgSend
000053e8 mov eax, dword [objc_msg_appendString_] ; @selector(appendString:)
000053ed mov dword [esp+0x28+var_20], 0x12488 ; @"3"
000053f5 mov dword [esp+0x28+var_28], ebx ; argument #1 for method imp___jump_table__objc_msgSend
000053f8 mov dword [esp+0x28+var_24], eax ; argument #2 for method imp___jump_table__objc_msgSend
000053fc call imp___jump_table__objc_msgSend
00005401 mov eax, dword [objc_msg_appendString_] ; @selector(appendString:)
00005406 mov dword [esp+0x28+var_20], 0x12498 ; @"BE7F9494B188D12EE391D7A"
0000540e mov dword [esp+0x28+var_28], ebx ; argument #1 for method imp___jump_table__objc_msgSend
00005411 mov dword [esp+0x28+var_24], eax ; argument #2 for method imp___jump_table__objc_msgSend
00005415 call imp___jump_table__objc_msgSend
0000541a mov eax, dword [objc_msg_appendString_] ; @selector(appendString:)
0000541f mov dword [esp+0x28+var_20], 0x124a8 ; @"9C9E84B05"
00005427 mov dword [esp+0x28+var_28], ebx ; argument #1 for method imp___jump_table__objc_msgSend
0000542a mov dword [esp+0x28+var_24], eax ; argument #2 for method imp___jump_table__objc_msgSend
0000542e call imp___jump_table__objc_msgSend
00005433 mov eax, dword [objc_msg_appendString_] ; @selector(appendString:)
00005438 mov dword [esp+0x28+var_20], 0x124b8 ; @"8"
00005440 mov dword [esp+0x28+var_28], ebx ; argument #1 for method imp___jump_table__objc_msgSend
00005443 mov dword [esp+0x28+var_24], eax ; argument #2 for method imp___jump_table__objc_msgSend
00005447 call imp___jump_table__objc_msgSend
0000544c mov eax, dword [objc_msg_appendString_] ; @selector(appendString:)
.....
.....
代码太长,看伪代码就比较清晰了。
void * -[SSExcesAppController verifyPath:](void * self, void * _cmd, void * arg_8) {
edi = self;
ebx = [NSMutableString string];
[ebx appendString:@"0xE6242CBE8E7686CB4AFB143FAF6D"];
[ebx appendString:@"6B7805475B6D856812"];
[ebx appendString:@"9"];
[ebx appendString:@"9"];
[ebx appendString:@"1D6547D406"];
[ebx appendString:@"A2C26"];
[ebx appendString:@"3"];
[ebx appendString:@"3"];
[ebx appendString:@"BE7F9494B188D12EE391D7A"];
[ebx appendString:@"9C9E84B05"];
[ebx appendString:@"8"];
[ebx appendString:@"8"];
[ebx appendString:@"742EAE04CFE6831F338"];
[ebx appendString:@"B2D"];
[ebx appendString:@"9"];
[ebx appendString:@"9"];
[ebx appendString:@"E4AC7202CE86FEDDF170EF6AE"];
[ebx appendString:@"1"];
[ebx appendString:@"D"];
[ebx appendString:@"D"];
[ebx appendString:@"50DC8A8EDE47CA3EC1B11B86BED"];
[ebx appendString:@"945AB07989D"];
[ebx appendString:@"B"];
[ebx appendString:@"B"];
[ebx appendString:@"A634511AE20B8ADFC"];
[ebx appendString:@"501D130BE"];
[ebx appendString:@"4"];
[ebx appendString:@"4"];
[ebx appendString:@"EDF1DC6129A929A12A8"];
[ebx appendString:@"506739EE47871476D5"];
esi = [[AquaticPrime aquaticPrimeWithKey:ebx] dictionaryForLicenseFile:arg_8];
if (([[esi objectForKey:@"Expires"] isEqualToString:@"never"] == 0x0) && ([[NSCalendarDate calendarDate] isGreaterThan:[NSCalendarDate dateWithString:[esi objectForKey:@"Expires"] calendarFormat:@"%d/%m/%Y"]] != 0x0)) {
esi = 0x0;
}
else {
if (*(edi + 0xc) == 0x0) {
*(edi + 0xc) = [SSExcesPrefController new];
}
[*(edi + 0xc) setLicenseDictionary:esi];
}
eax = esi;
return eax;
}
从代码可以看出,先拼接出Key,因此这个Key是写死在APP里面的。然后调用[AquaticPrime aquaticPrimeWithKey],通过dictionaryForLicenseFile方法从license文件中读出Expires字段。如果Expires为never或者Expires定义的日期在当前日期后面,则校验通过。所以,看来验证的逻辑比较简单。但如何伪装一份license让APP接受,那就要看AquaticPrime调用的aquaticPrimeWithKey方法。
0x2 AquaticPrime Framework
AquaticPrime是什么?Google一下,就知道AquaticPrime为Mac OS X上流行的利用RSA加密算法进行license验证的Framework。大概原理是生成RSA公钥、私钥对,用私钥加密生成license文件。公钥则放APP里,用来解密和检验license。由于RSA具有非对称性,使用私钥加密的字符,配对的公钥(即上述拼接的Key)才能解密,并且从公钥无法得到私钥,所以具备很好的保密性。由于APP无法知道私钥,无从知道license怎么来的,也无法解密任意的license。看到这里,似乎非暴力破解此APP是无望了。开始笔者也是这么认为的。还好,AquaticPrime为开源软件,在github找到AquaticPrimeFramework源代码,看到了希望的曙光。
0x3 破解AquaticPrime
AquaticPrimeFramework源码提供了生成license文件的APP,可以生成出一对RSA钥匙和对应的license文件。所以,破解的思路就是自制一份license文件和RSA公、私钥,然后将APP中的公钥替换为自制公钥 。
(1)生成license和RSA公、私钥
如下图。根据代码分析,license中的Expires字段设为never,后缀名为excli。
(2)使用自制的公钥替换APP公钥
这里使用了Hex Fiend二进制编辑工具。用Hex Fiend打开APP,找到公钥字符串,全部替换为自制的公钥。如下二图。
替换前:
替换后:
(3)修改verifyPath方法,导入自制的公钥
在Hopper中编辑verifyPath方法,只保留第一个objc_msg_appendString_ 0x12438方法,然后在0x12438定义的字符串中将length设为0x102。即一次性将公钥导入,省去混淆公钥的过程(代码太长,省略部分)。
...
...
0000531e mov eax, dword [objc_msg_string] ; @selector(string)
00005323 mov edi, dword [ebp+8]
00005326 mov dword [esp+4], eax ; argument #2 for method imp___jump_table__objc_msgSend
0000532a mov eax, dword [cls_NSMutableString] ; cls_NSMutableString
0000532f mov dword [esp], eax ; argument #1 for method imp___jump_table__objc_msgSend
00005332 call imp___jump_table__objc_msgSend
00005337 mov dword [esp+8], 0x12438 ; @"0xDC24E2A5B7C87320A6663704670BC09E4B6AE5301D40CDCCE5F79259AAD0765A4BB5ED02ABBECCC6323852E44ACB97BA6D9AEEFE7AE420AD2991803853D76741F5118C36878E0A5C1067A2705788D1A5145430D3BA4B37469BF2F48A5EE210529A238ED86296B4563EBA56D23BC15D7980586079F58A83C24E0B73BE4336DE…"
0000533f mov ebx, eax
00005341 mov eax, dword [objc_msg_appendString_] ; @selector(appendString:)
00005346 mov dword [esp], ebx ; argument #1
00005349 mov dword [esp+4], eax ; argument #2
0000534d call imp___jump_table__objc_msgSend
00005352 mov eax, dword [objc_msg_appendString_] ; @selector(appendString:)
00005357 mov dword [esp+8], 0x12448 ; @"09E4B6AE5301D40CDCCE5F79259AAD0765A4BB5ED02ABBECCC6323852E44ACB97BA6D9AEEFE7AE420AD2991803853D76741F5118C36878E0A5C1067A2705788D1A5145430D3BA4B37469BF2F48A5EE210529A238ED86296B4563EBA56D23BC15D7980586079F58A83C24E0B73BE4336DE959EE47871476D5"
0000535f mov dword [esp], ebx ; argument #1
00005362 mov dword [esp+4], eax ; argument #2
00005366 nop dword [eax+eax]
0000536b mov eax, dword [objc_msg_appendString_] ; @selector(appendString:)
00005370 mov dword [esp+8], 0x12458 ; @"E5F79259AAD0765A4BB5ED02ABBECCC6323852E44ACB97BA6D9AEEFE7AE420AD2991803853D76741F5118C36878E0A5C1067A2705788D1A5145430D3BA4B37469BF2F48A5EE210529A238ED86296B4563EBA56D23BC15D7980586079F58A83C24E0B73BE4336DE959EE47871476D5"
00005378 mov dword [esp], ebx ; argument #1
0000537b mov dword [esp+4], eax ; argument #2
0000537f nop dword [eax+eax]
00005384 mov eax, dword [objc_msg_appendString_] ; @selector(appendString:)
00005389 mov dword [esp+8], 0x12458 ; @"E5F79259AAD0765A4BB5ED02ABBECCC6323852E44ACB97BA6D9AEEFE7AE420AD2991803853D76741F5118C36878E0A5C1067A2705788D1A5145430D3BA4B37469BF2F48A5EE210529A238ED86296B4563EBA56D23BC15D7980586079F58A83C24E0B73BE4336DE959EE47871476D5"
00005391 mov dword [esp], ebx ; argument #1
00005394 mov dword [esp+4], eax ; argument #2
00005398 nop dword [eax+eax]
0000539d mov eax, dword [objc_msg_appendString_] ; @selector(appendString:)
000053a2 mov dword [esp+8], 0x12468 ; @"F79259AAD0765A4BB5ED02ABBECCC6323852E44ACB97BA6D9AEEFE7AE420AD2991803853D76741F5118C36878E0A5C1067A2705788D1A5145430D3BA4B37469BF2F48A5EE210529A238ED86296B4563EBA56D23BC15D7980586079F58A83C24E0B73BE4336DE959EE47871476D5"
000053aa mov dword [esp], ebx ; argument #1
000053ad mov dword [esp+4], eax ; argument #2
...
...
0x4 程序验证
将自制的license文件拖入Exces,验证成功!:)!