准备工作
喝水不忘挖井人,感谢恩师 @QiuChenly
- IDA 8.3 或 IDA 7.7
- Hopper Disassembler 5.13.5
- Sublime Text 或 VS Code
本文分析的是 Apple Silicon(Arm) 下的二进制文件,x86同理,望周知
开始分析
本来想搜一下这个弹出框的,后来发现关了之后程序不会退出,那就暂时先不处理它
通过初步使用,发现在清理的时候弹出 "完整版评估期已到期"
找到界面上的关键字,在Hopper里面找到对应的地址
可以发现,前两个引用是同一个函数,我们点第一个进去,跳转到地址100334ecc
先埋个伏笔,记住程序入口地址100014e34
打开LLDB lldb /Applications/MacCleaner\ Pro\ 3.app
运行程序,并给程序入口下个断点,埋个伏笔
br s -a 100014e34
接着,对我们刚才根据字符串搜索到的地址 100334ecc
进行断点
br s -a 100334ecc
输入 r
重启,程序会断在入口点
输入 c
继续运行
重复我们的清理步骤,触发断点
输入 bt
查看堆栈,堆栈如下
(lldb) bt
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 2.1
* frame #0: 0x0000000100334ecc MacCleaner Pro 3`___lldb_unnamed_symbol22333 + 120
frame #1: 0x000000010034f290 MacCleaner Pro 3`___lldb_unnamed_symbol22790 + 88
frame #2: 0x00000001003494c8 MacCleaner Pro 3`___lldb_unnamed_symbol22757 + 1116
frame #3: 0x000000010019e750 MacCleaner Pro 3`___lldb_unnamed_symbol11841 + 480
frame #4: 0x000000010007ae50 MacCleaner Pro 3`___lldb_unnamed_symbol5191 + 68
frame #5: 0x000000018c27df44 AppKit`-[NSApplication(NSResponder) sendAction:to:from:] + 460
frame #6: 0x000000018c27dd48 AppKit`-[NSControl sendAction:to:] + 72
frame #7: 0x000000018c27dc8c AppKit`__26-[NSCell _sendActionFrom:]_block_invoke + 100
frame #8: 0x000000018c27dbb4 AppKit`-[NSCell _sendActionFrom:] + 204
frame #9: 0x000000018c27dad8 AppKit`-[NSButtonCell _sendActionFrom:] + 88
frame #10: 0x000000018c27b108 AppKit`NSControlTrackMouse + 1480
frame #11: 0x000000018c27ab14 AppKit`-[NSCell trackMouse:inRect:ofView:untilMouseUp:] + 144
frame #12: 0x000000018c27a9cc AppKit`-[NSButtonCell trackMouse:inRect:ofView:untilMouseUp:] + 488
frame #13: 0x000000018c279ea0 AppKit`-[NSControl mouseDown:] + 448
frame #14: 0x00000001003ea0ec MacCleaner Pro 3`___lldb_unnamed_symbol28251 + 76
frame #15: 0x000000018c278c6c AppKit`-[NSWindow(NSEventRouting) _handleMouseDownEvent:isDelayedEvent:] + 3472
frame #16: 0x000000018c2043ec AppKit`-[NSWindow(NSEventRouting) _reallySendEvent:isDelayedEvent:] + 364
frame #17: 0x000000018c2040ac AppKit`-[NSWindow(NSEventRouting) sendEvent:] + 284
frame #18: 0x000000018c8c2f60 AppKit`-[NSApplication(NSEventRouting) sendEvent:] + 1604
frame #19: 0x000000018c5135bc AppKit`-[NSApplication _handleEvent:] + 60
frame #20: 0x000000018c0cc3a0 AppKit`-[NSApplication run] + 512
frame #21: 0x000000018c0a3640 AppKit`NSApplicationMain + 880
frame #22: 0x000000018849d0e0 dyld`start + 2360
(lldb)
我们对#1, #2, #3, #4进行分析
堆栈 #1 分析
frame #1: 0x000000010034f290 MacCleaner Pro 3`___lldb_unnamed_symbol22790 + 88
复制地址 0x000000010034f290
,打开IDA后,按 g
输入地址进行跳转
跳转后部分伪代码如下
if ( result ) {
v5 = v4;
v9 = result;
v29 = sub_100341690();
if ( (a2 & 1) != 0 ) {
v11 = sub_100334E54(v10 != 0); // <-------- #1 跳转到这里
} else {
v30 = 0LL;
sub_100351E9C(&v30);
v11 = sub_100335164(v30, *(v4 + 56), v12 != 0);
}
从伪代码中发现,若 (a2 & 1) != 0
则走 #1
该处判断汇编如下
__text:000000010034F280 TBZ W26, #0, loc_10034F2B4
给地址 10034F280
进行下断点,尝试修改跳转逻辑
br s -a 10034F280
打印出 W26
的值为1
(lldb) po $w26
1
尝试修改为0
(lldb) po $w26 = 0
<nil>
(lldb) c
修改无果,发现弹窗依旧,继续往 #2
进行分析
堆栈 #2 分析
frame #2: 0x00000001003494c8 MacCleaner Pro 3`___lldb_unnamed_symbol22757 + 1116
复制地址 0x00000001003494c8
,打开IDA后,按 g
输入地址进行跳转
跳转后部分伪代码如下
if ( a1 || sub_100351860(v16) < 2u ) {
v17 = 1LL;
} else {
sub_10034F238(a6, 1, 0LL, 0LL);
v17 = 0LL; // <-------- #2 跳转到这里
}
return a7(v17);
稍微有点意思了,从伪代码可以知道,最终 v17 = 0
,a7(0)
也就是说,弹框时,v17 = 0
,进入函数 sub_100351860
看看
__int64 sub_100351860() {
_QWORD v1[4]; // [xsp+0h] [xbp-40h] BYREF
__int64 v2[4]; // [xsp+20h] [xbp-20h] BYREF
sub_100351B88(0xD000000000000014LL, 0x8000000100517F90LL);
sub_1003365A0(v2, v1);
if ( !v1[3] ) return 0LL;
sub_1003365E8(v1);
v2[0] = 0LL;
sub_100351E9C(v2);
if ( v2[0] > 0 ) return 1LL;
else return 2LL;
}
发现该函数的返回值有三个,分别为
结合刚才的伪代码得出如下结论
当返回值为 2
时,v17 = 0
,弹框
我们可以大胆猜测一下,当返回值为 0
或者 1
时,v17 = 1
是不是就不弹框呢
与其大胆猜测,不如动手出真知
#2
伪代码判断汇编如下
__text:0000000100349160 CMP W8, #2
对地址 100349160
进行断点
br s -a 0x100349160
此时打印 W8
的值可以看到为 2
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 4.1
frame #0: 0x0000000100349160 MacCleaner Pro 3`___lldb_unnamed_symbol22757 + 244
MacCleaner Pro 3`___lldb_unnamed_symbol22757:
-> 0x100349160 <+244>: cmp w8, #0x2
0x100349164 <+248>: b.hs 0x1003494b0 ; <+1092>
0x100349168 <+252>: mov w0, #0x1
0x10034916c <+256>: ldp x20, x8, [sp, #0x18]
Target 0: (MacCleaner Pro 3) stopped.
(lldb) po $w8
2
那就尝试把逻辑修改为不成立,即将 W8
修改为 0
或 1
,这里,我们修改为 1
(lldb) po $w8 = 1
1
继续运行
哦?好像找到点了吗?当我们尝试修改 W8
的值为 0
或 1
时,程序并没有弹窗,而是进入了清除阶段,这是好事
那么到这里,我们确定 sub_100351860
返回 0
或 1
可以影响功能的使用,那到这里就结束了吗
堆栈 #3 分析
frame #3: 0x000000010019e750 MacCleaner Pro 3`___lldb_unnamed_symbol11841 + 480
复制地址 0x000000010019e750
,打开IDA后,按 g
输入地址进行跳转
跳转后的函数如下,比较长,这里比较重要
void *sub_10019E570() {
v1 = v0;
if ( *(v0 + 104) == 1 ) return sub_10019D664(v0);
if ( qword_100703BE0 != -1LL ) swift_once(&qword_100703BE0, sub_1001AC77C);
v3 = qword_100747BE8;
v4 = objc_retainAutoreleasedReturnValue(objc_msgSend(*(v0 + 80), "view"));
v5 = objc_retainAutoreleasedReturnValue(objc_msgSend(v4, "window"));
objc_release(v4);
v6 = OBJC_IVAR____TtC16MacCleaner_Pro_322BaseFeaturesController_licenseManager;
v7 = *(*(v3 + OBJC_IVAR____TtC16MacCleaner_Pro_322BaseFeaturesController_licenseManager) + 40LL);
v8 = *(*v7 + 200LL);
swift_retain(v7);
v9 = swift_retain_n(v1, 2LL);
LOBYTE(v8) = v8(v9);
swift_release(v7);
if ( (v8 - 1) < 3u || (v10 = *(*(v3 + v6) + 48LL),
v11 = *(*v10 + 104LL),
v12 = swift_retain(v10),
LODWORD(v11) = v11(v12),
swift_release(v10),
v11 == 1) )
{
sub_10019D664(v1);
goto LABEL_8;
}
if ( NSApp ) {
v14 = objc_retainAutoreleasedReturnValue(objc_msgSend(NSApp, "mainWindow"));
if ( v14 ) {
v15 = v14;
v16 = objc_retainAutoreleasedReturnValue(objc_msgSend(v14, "nk_topSheetSelfIfNoSheets"));
objc_release(v15);
(*(**(v3 + v6) + 304LL))(
0LL,
0x6E5765766F6D6552LL,
0xEC0000006E744264LL,
0xD00000000000002FLL,
0x80000001004E0D80LL,
v16,
sub_10019F1A8,
v1);
objc_release(v17); // <-------- #3 跳转到这里
swift_release_n(v1, 2LL);
result = v16;
goto LABEL_9;
}
LABEL_8:
swift_release_n(v1, 2LL);
result = v13;
LABEL_9:
objc_release(result);
return result;
}
result = swift_release(v1);
__break(1u);
return result;
}
对函数进行初步分析,我们可以得知,#3
位于伪代码 objc_release(v17);
处
而想要走到这里
if ( (v8 - 1) < 3u || (v10 = *(*(v3 + v6) + 48LL),
v11 = *(*v10 + 104LL),
v12 = swift_retain(v10),
LODWORD(v11) = v11(v12),
swift_release(v10),
v11 == 1))
{
sub_10019D664(v1);
goto LABEL_8;
}
该判断就不应该成立,否则,程序无法走到 #3
处
继续往上看伪代码,在这之前
v7 = *(*(v3 + OBJC_IVAR____TtC16MacCleaner_Pro_322BaseFeaturesController_licenseManager) + 40LL);
v8 = *(*v7 + 200LL);
swift_retain(v7);
v9 = swift_retain_n(v1, 2LL);
LOBYTE(v8) = v8(v9);
swift_release(v7);
该段对应的汇编如下
__text:000000010019E610 LDR X20, [X8,#0x28]
__text:000000010019E614 LDR X8, [X20]
__text:000000010019E618 LDR X22, [X8,#0xC8]
__text:000000010019E61C MOV X0, X20
__text:000000010019E620 BL _swift_retain
__text:000000010019E624 MOV X0, X19
__text:000000010019E628 MOV W1, #2
__text:000000010019E62C BL _swift_retain_n
__text:000000010019E630 BLR X22
__text:000000010019E634 MOV X22, X0
__text:000000010019E638 MOV X0, X20
__text:000000010019E63C BL _swift_release
通过对该段汇编进行断点并打印相关信息得出如下结果
(lldb) po $x8
NKLicenseManager.NKLicenseManager
(lldb) po $x20
NKLicenseManager.LicenseStateStorage
由上述可得
X8
为 NKLicenseManager.LicenseStateStorage
[X8, #0xC8]
为 NKLicenseManager.LicenseStateStorage
下的某个属性
通过对[X8, #0xC8]
进行打印得出
(lldb) p/x $x8 + 0xc8
(unsigned long) 0x0000000100729060
IDA中跳转到该地址
发现一个关键函数sub_10033F0FC
,但在这之前,我们先计算一下$x8的地址
已知 $x8 + 0xc8 = 0x0000000100729060
可得 $x8 = 0x0000000100728f98
跳转到地址 0x0000000100728f98
其实上面已经猜到了 [X8, #0xC8]
为 NKLicenseManager.LicenseStateStorage
下的某个属性
这里只是验证
结合接下来的伪代码和汇编可知
将 W8
减 1
后的值跟 -1
进行 &
运算,再将结果和 3
进行对比
if ( (v8 - 1) < 3u || (v10 = *(*(v3 + v6) + 48LL),
v11 = *(*v10 + 104LL),
v12 = swift_retain(v10),
LODWORD(v11) = v11(v12),
swift_release(v10),
v11 == 1))
{
sub_10019D664(v1);
goto LABEL_8;
}
__text:000000010019E640 SUB W8, W22, #1
__text:000000010019E644 AND W8, W8, #0xFF
__text:000000010019E648 CMP W8, #3
此时 W8
的最终值为 255,比 3
大,所以,该处逻辑没走通,所以弹框
为什么 W8
的最终值为 255?
该处可通过对地址 10019E648
进行断点打印,不再赘述
综上所属,虽然在 #2
的堆栈分析中,我们知道函数 sub_100351860
的返回值 0
或 1
可以影响功能使用,但考虑到 #3
先运行于 #2
,所以函数sub_10033F0FC
的返回值会影响判断逻辑,从而影响 #2
的执行
而当我们并非完整版时,sub_10033F0FC
的返回值为 0,即 W8
为 0,所以,这时候,我们只需要将函数 sub_10033F0FC
的返回值修改为 1 即可达到解锁目的
还记得一开始我们埋下的伏笔吗
LLDB中输入 r
重启程序,程序断下来后,我们对函数 sub_10033F0FC
的返回值进行修改,该处可通过以下方法进行修改,本文仅使用LLDB进行临时修改测试
输入
memory write 10033F0FC 20 00 80 D2 C0 03 5F D6
回车后,输入c
继续运行程序
可以看到,原本左下角的绿色按钮 "解锁完整版" 就不存在了,测试一下功能
可以看到功能也是正常的,到这里,我们就完成了对功能的解锁分析
真的结束了吗,好像并没有,还记得启动的时候还有一个小弹窗吗
《您的免费试用已结束》
免费试用弹窗分析
搜索对应字符串
到Hopper进行字符串搜索
进入函数 sub_100345a08
跳转地址为 100345a2c
,老规矩断点,看堆栈
(lldb) bt
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
* frame #0: 0x0000000100345a2c MacCleaner Pro 3`___lldb_unnamed_symbol22677 + 36
frame #1: 0x0000000100335300 MacCleaner Pro 3`___lldb_unnamed_symbol22334 + 412
frame #2: 0x000000010034f2d8 MacCleaner Pro 3`___lldb_unnamed_symbol22790 + 160
frame #3: 0x00000001003502a8 MacCleaner Pro 3`___lldb_unnamed_symbol22815 + 492
frame #4: 0x0000000100217b88 MacCleaner Pro 3`___lldb_unnamed_symbol14926 + 236
frame #5: 0x0000000100153758 MacCleaner Pro 3`___lldb_unnamed_symbol10053 + 672
frame #6: 0x0000000100153860 MacCleaner Pro 3`___lldb_unnamed_symbol10054 + 100
frame #7: 0x00000001888f0500 CoreFoundation`__CFNOTIFICATIONCENTER_IS_CALLING_OUT_TO_AN_OBSERVER__ + 148
frame #8: 0x000000018898462c CoreFoundation`___CFXRegistrationPost_block_invoke + 88
frame #9: 0x0000000188984574 CoreFoundation`_CFXRegistrationPost + 440
frame #10: 0x00000001888bf16c CoreFoundation`_CFXNotificationPost + 764
frame #11: 0x00000001899b3f50 Foundation`-[NSNotificationCenter postNotificationName:object:userInfo:] + 88
frame #12: 0x000000018c0e08ac AppKit`-[NSApplication _postDidFinishNotification] + 284
frame #13: 0x000000018c0e065c AppKit`-[NSApplication _sendFinishLaunchingNotification] + 172
frame #14: 0x000000018c0deba4 AppKit`-[NSApplication(NSAppleEventHandling) _handleAEOpenEvent:] + 504
frame #15: 0x000000018c0de7a0 AppKit`-[NSApplication(NSAppleEventHandling) _handleCoreEvent:withReplyEvent:] + 492
frame #16: 0x00000001899dc374 Foundation`-[NSAppleEventManager dispatchRawAppleEvent:withRawReply:handlerRefCon:] + 316
frame #17: 0x00000001899dc168 Foundation`_NSAppleEventManagerGenericHandler + 80
frame #18: 0x000000018f866dc0 AE`___lldb_unnamed_symbol866 + 1624
frame #19: 0x000000018f8666e8 AE`___lldb_unnamed_symbol865 + 44
frame #20: 0x000000018f85fcf8 AE`aeProcessAppleEvent + 488
frame #21: 0x0000000192ed22d4 HIToolbox`AEProcessAppleEvent + 68
frame #22: 0x000000018c0d91dc AppKit`_DPSNextEvent + 1440
frame #23: 0x000000018c8c3eec AppKit`-[NSApplication(NSEventRouting) _nextEventMatchingEventMask:untilDate:inMode:dequeue:] + 716
frame #24: 0x000000018c0cc37c AppKit`-[NSApplication run] + 476
frame #25: 0x000000018c0a3640 AppKit`NSApplicationMain + 880
frame #26: 0x000000018849d0e0 dyld`start + 2360
那么我们重点关注 #1
到 #6
经过分析,在 #4
的时候,发现非常熟悉的代码
frame #4: 0x0000000100217b88 MacCleaner Pro 3`___lldb_unnamed_symbol14926 + 236
复制地址 0x0000000100217b88
,打开IDA后,按 g
输入地址进行跳转
部分伪代码如下
if ( qword_1007039F8 != -1 ) swift_once(&qword_1007039F8, sub_1000AE644);
v0 = qword_10070BC80;
v1 = OBJC_IVAR____TtC16MacCleaner_Pro_322BaseFeaturesController_licenseManager;
v2 = qword_100703BE0;
v3 = objc_retain(qword_10070BC80);
if ( v2 != -1 ) {
swift_once(&qword_100703BE0, sub_1001AC77C);
v1 = OBJC_IVAR____TtC16MacCleaner_Pro_322BaseFeaturesController_licenseManager;
}
v4 = *(*(qword_100747BE8 + v1) + 40LL);
v5 = *(*v4 + 200LL);
再看看汇编
__text:0000000100217AF0 88 29 00 90 ADRP X8, #qword_100747BE8@PAGE
__text:0000000100217AF4 17 F5 45 F9 LDR X23, [X8,#qword_100747BE8@PAGEOFF]
__text:0000000100217AF8 E8 6A 78 F8 LDR X8, [X23,X24]
__text:0000000100217AFC 14 15 40 F9 LDR X20, [X8,#0x28]
__text:0000000100217B00 88 02 40 F9 LDR X8, [X20]
__text:0000000100217B04 18 65 40 F9 LDR X24, [X8,#0xC8]
__text:0000000100217B08 E0 03 14 AA MOV X0, X20
这跟刚才我们分析了半天的功能解锁有什么区别
由 #4
的地址跳转到上述代码的下半部分其中一行
objc_release(v3);
而 v3
的来源是
v3 = objc_retain(qword_10070BC80);
结合汇编代码
__text:0000000100217AE4 F6 03 00 AA MOV X22, X0
可得,X0
为
(lldb) po $x0
<MacCleaner_Pro_3.MainWindow: 0x1401061d0>
综上所述,此时主窗口已经加载,这里没有可检查的必要,我们继续回溯到 #5
frame #5: 0x0000000100153758 MacCleaner Pro 3`___lldb_unnamed_symbol10053 + 672
通过跳转地址后发现
if ( qword_100703CF8 != -1 ) swift_once(&qword_100703CF8, sub_1002979F8);
if ( qword_100703B58 != -1 ) swift_once(&qword_100703B58, sub_1001740C0);
if ( qword_100703BE0 != -1 ) swift_once(&qword_100703BE0, sub_1001AC77C);
sub_100217A9C();
swift_release(v34); // <-------- #5 跳转到这里
注意到这里#5
上的一个函数 sub_100217A9C
#4
的地址值为 100217b88
#5
的地址值为 100153758
而这个sub_100217A9C
刚好夹在#4
和#5
之间,你觉得这真的没有问题吗
那我们就关注一下这个函数 sub_100217A9C
对其进行 交叉引用查看,发现有个非常熟悉的东西
void __cdecl -[BaseFeaturesController onAppDidFinishLaunching](
_TtC16MacCleaner_Pro_322BaseFeaturesController *self,
SEL a2)
{
_TtC16MacCleaner_Pro_322BaseFeaturesController *v2; // x19
v2 = objc_retain(self);
sub_100217A9C();
objc_release(v2);
}
怎么这里也在执行这个函数?那很难令人不怀疑,进入这个函数后,前半部分懒得赘述了,直接看后半部分
v10 = sub_10017F754(v8);
v12 = v11;
(*(*v13 + 376LL))('hcnuaL', '\xE6\0\0\0\0\0\0\0', v10, v11, v0, 1LL);
objc_release(v3);
return swift_bridgeObjectRelease(v12);
看起来感觉是在启动某些东西
此时,有两个选择
(*(*v13 + 376LL))('hcnuaL', '\xE6\0\0\0\0\0\0\0', v10, v11, v0, 1LL);
-
直接找到上述位置修改二进制nop掉
-
把函数sub_100217A9C
整个ret掉
我的选择是2
,毕竟我没看到这个函数有什么作用
memory write 100217A9C 20 00 80 D2 C0 03 5F D6
这时,我们发现
弹窗没有了,功能也是正常的,到此,我们就完成了对 MacCleaner Pro的学习分析
总结
入口点
br s -a 100014e34
单独解锁功能,不去除弹框,不去除 "解锁完整版" 按钮
memory write 100351860 20 00 80 D2 C0 03 5F D6
# 或者
memory write 100351860 00 00 80 D2 C0 03 5F D6
移除 "解锁完整版" 按钮并解锁功能
memory write 10033F0FC 20 00 80 D2 C0 03 5F D6
移除 "您的免费试用已结束" 弹窗
memory write 100217A9C 20 00 80 D2 C0 03 5F D6