MacCleaner Pro 分析学习
# 准备工作
喝水不忘挖井人,感谢恩师 [@QiuChenly](https://www.52pojie.cn/home.php?mod=space&uid=653608)
- 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` 查看堆栈,堆栈如下
```bash
(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`- + 460
frame #6: 0x000000018c27dd48 AppKit`- + 72
frame #7: 0x000000018c27dc8c AppKit`__26-_block_invoke + 100
frame #8: 0x000000018c27dbb4 AppKit`- + 204
frame #9: 0x000000018c27dad8 AppKit`- + 88
frame #10: 0x000000018c27b108 AppKit`NSControlTrackMouse + 1480
frame #11: 0x000000018c27ab14 AppKit`- + 144
frame #12: 0x000000018c27a9cc AppKit`- + 488
frame #13: 0x000000018c279ea0 AppKit`- + 448
frame #14: 0x00000001003ea0ec MacCleaner Pro 3`___lldb_unnamed_symbol28251 + 76
frame #15: 0x000000018c278c6c AppKit`- + 3472
frame #16: 0x000000018c2043ec AppKit`- + 364
frame #17: 0x000000018c2040ac AppKit`- + 284
frame #18: 0x000000018c8c2f60 AppKit`- + 1604
frame #19: 0x000000018c5135bc AppKit`- + 60
frame #20: 0x000000018c0cc3a0 AppKit`- + 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` 输入地址进行跳转
跳转后部分伪代码如下
```c
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` 进行下断点,尝试修改跳转逻辑
```bash
br s -a 10034F280
```
打印出 `W26` 的值为1
```bash
(lldb) po $w26
1
```
尝试修改为0
```bash
(lldb) po $w26 = 0
<nil>
(lldb) c
```
修改无果,发现弹窗依旧,继续往 `#2` 进行分析
## 堆栈 #2 分析
```bash
frame #2: 0x00000001003494c8 MacCleaner Pro 3`___lldb_unnamed_symbol22757 + 1116
```
复制地址 `0x00000001003494c8`,打开IDA后,按 `g` 输入地址进行跳转
跳转后部分伪代码如下
```c
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` 看看
```c
__int64 sub_100351860() {
_QWORD v1; // BYREF
__int64 v2; // BYREF
sub_100351B88(0xD000000000000014LL, 0x8000000100517F90LL);
sub_1003365A0(v2, v1);
if ( !v1 ) return 0LL;
sub_1003365E8(v1);
v2 = 0LL;
sub_100351E9C(v2);
if ( v2 > 0 ) return 1LL;
else return 2LL;
}
```
发现该函数的返回值有三个,分别为
- 0
- 1
- 2
结合刚才的伪代码得出如下结论
当返回值为 `2` 时,`v17 = 0`,弹框
我们可以大胆猜测一下,当返回值为 `0` 或者 `1` 时,`v17 = 1` 是不是就不弹框呢
与其大胆猜测,不如动手出真知
`#2` 伪代码判断汇编如下
```
__text:0000000100349160 CMP W8, #2
```
对地址 `100349160` 进行断点
```bash
br s -a 0x100349160
```
此时打印 `W8` 的值可以看到为 `2`
```bash
* 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,
Target 0: (MacCleaner Pro 3) stopped.
(lldb) po $w8
2
```
那就尝试把逻辑修改为不成立,即将 `W8` 修改为 `0` 或 `1`,这里,我们修改为 `1`
```bash
(lldb) po $w8 = 1
1
```
继续运行
哦?好像找到点了吗?当我们尝试修改 `W8` 的值为 `0` 或 `1` 时,程序并没有弹窗,而是进入了清除阶段,这是好事
那么到这里,我们确定 `sub_100351860` 返回 `0` 或 `1` 可以影响功能的使用,那到这里就结束了吗
## 堆栈 #3 分析
```bash
frame #3: 0x000000010019e750 MacCleaner Pro 3`___lldb_unnamed_symbol11841 + 480
```
复制地址 `0x000000010019e750`,打开IDA后,按 `g` 输入地址进行跳转
跳转后的函数如下,比较长,这里比较重要
```c
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);` 处
而想要走到这里
```c
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`处
继续往上看伪代码,在这之前
```c
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,
__text:000000010019E614 LDR X8,
__text:000000010019E618 LDR X22,
__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
```
通过对该段汇编进行断点并打印相关信息得出如下结果
```bash
(lldb) po $x8
NKLicenseManager.NKLicenseManager
```
```bash
(lldb) po $x20
NKLicenseManager.LicenseStateStorage
```
由上述可得
- `X8` 为 `NKLicenseManager.LicenseStateStorage`
- `` 为 `NKLicenseManager.LicenseStateStorage` 下的某个属性
通过对``进行打印得出
```bash
(lldb) p/x $x8 + 0xc8
(unsigned long) 0x0000000100729060
```
IDA中跳转到该地址
发现一个关键函数`sub_10033F0FC`,但在这之前,我们先计算一下$x8的地址
已知 `$x8 + 0xc8 = 0x0000000100729060`
可得 `$x8 = 0x0000000100728f98`
跳转到地址 `0x0000000100728f98`
其实上面已经猜到了 `` 为 `NKLicenseManager.LicenseStateStorage` 下的某个属性
这里只是验证
结合接下来的伪代码和汇编可知
将 `W8` 减 `1` 后的值跟 `-1` 进行 `&` 运算,再将结果和 `3` 进行对比
```c
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进行临时修改测试
- FridaHook
- 静态注入
- 修改二进制
输入
```bash
memory write 10033F0FC 20 00 80 D2 C0 03 5F D6
```
回车后,输入`c`继续运行程序
可以看到,原本左下角的绿色按钮 "解锁完整版" 就不存在了,测试一下功能
可以看到功能也是正常的,到这里,我们就完成了对功能的解锁分析
真的结束了吗,好像并没有,还记得启动的时候还有一个小弹窗吗
**《您的免费试用已结束》**
# 免费试用弹窗分析
搜索对应字符串
到Hopper进行字符串搜索
进入函数 `sub_100345a08`
跳转地址为 `100345a2c`,老规矩断点,看堆栈
```bash
(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`- + 88
frame #12: 0x000000018c0e08ac AppKit`- + 284
frame #13: 0x000000018c0e065c AppKit`- + 172
frame #14: 0x000000018c0deba4 AppKit`- + 504
frame #15: 0x000000018c0de7a0 AppKit`- + 492
frame #16: 0x00000001899dc374 Foundation`- + 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`- + 716
frame #24: 0x000000018c0cc37c AppKit`- + 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` 输入地址进行跳转
部分伪代码如下
```c
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,
__text:0000000100217AF8 E8 6A 78 F8 LDR X8,
__text:0000000100217AFC 14 15 40 F9 LDR X20,
__text:0000000100217B00 88 02 40 F9 LDR X8,
__text:0000000100217B04 18 65 40 F9 LDR X24,
__text:0000000100217B08 E0 03 14 AA MOV X0, X20
```
这跟刚才我们分析了半天的功能解锁有什么区别
由 `#4` 的地址跳转到上述代码的下半部分其中一行
```c
objc_release(v3);
```
而 `v3` 的来源是
```c
v3 = objc_retain(qword_10070BC80);
```
结合汇编代码
```
__text:0000000100217AE4 F6 03 00 AA MOV X22, X0
```
可得,`X0` 为
```bash
(lldb) po $x0
<MacCleaner_Pro_3.MainWindow: 0x1401061d0>
```
综上所述,此时主窗口已经加载,这里没有可检查的必要,我们继续回溯到 `#5`
```bash
frame #5: 0x0000000100153758 MacCleaner Pro 3`___lldb_unnamed_symbol10053 + 672
```
通过跳转地址后发现
```c
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`
对其进行 交叉引用查看,发现有个非常熟悉的东西
```c
void __cdecl -(
_TtC16MacCleaner_Pro_322BaseFeaturesController *self,
SEL a2)
{
_TtC16MacCleaner_Pro_322BaseFeaturesController *v2; // x19
v2 = objc_retain(self);
sub_100217A9C();
objc_release(v2);
}
```
怎么这里也在执行这个函数?那很难令人不怀疑,进入这个函数后,前半部分懒得赘述了,直接看后半部分
```c
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);
```
看起来感觉是在启动某些东西
此时,有两个选择
```c
(*(*v13 + 376LL))('hcnuaL', '\xE6\0\0\0\0\0\0\0', v10, v11, v0, 1LL);
```
1. 直接找到上述位置修改二进制nop掉
2. 把函数`sub_100217A9C`整个ret掉
我的选择是`2`,毕竟我没看到这个函数有什么作用
```bash
memory write 100217A9C 20 00 80 D2 C0 03 5F D6
```
这时,我们发现
弹窗没有了,功能也是正常的,到此,我们就完成了对 MacCleaner Pro的学习分析
# 总结
入口点
```bash
br s -a 100014e34
```
单独解锁功能,不去除弹框,不去除 "解锁完整版" 按钮
```bash
memory write 100351860 20 00 80 D2 C0 03 5F D6
# 或者
memory write 100351860 00 00 80 D2 C0 03 5F D6
```
移除 "解锁完整版" 按钮并解锁功能
```bash
memory write 10033F0FC 20 00 80 D2 C0 03 5F D6
```
移除 "您的免费试用已结束" 弹窗
```bash
memory write 100217A9C 20 00 80 D2 C0 03 5F D6
```
经验证,利用该分析方法,还适用于Nektony旗下的App
1. MacCleaner Pro
2. App Cleaner & Uninstaller
3. Duplicate File Finder
4. Disk Space Analyzer 本帖最后由 diaosini 于 2024-3-2 19:07 编辑
同样是M1芯片,经过复现 br s -a 100334ecc 一直无法断,咋回事
```
$ lldb /Applications/MacCleaner\ Pro\ 3.app
(lldb) target create "/Applications/MacCleaner Pro 3.app"
Current executable set to '/Applications/MacCleaner Pro 3.app' (arm64).
(lldb) br s -a 100014e34
Breakpoint 1: address = 0x0000000100014e34
(lldb) br s -a 100334ecc
Breakpoint 2: address = 0x0000000100334ecc
(lldb)
``` 本帖最后由 wwbhl 于 2024-10-19 20:07 编辑
直接memory write 100173038 写让这个函数sub_100173038 返回 1 处理不掉弹窗。图片里断点间 nop 掉可以解决问题,伪代码如下,请教一下如何用 frida 处理它,或者 使用memory write(已解决)附上答案和截图memory write 10017336c 1F 20 03
D5 学习了一下 牛,学习学习。 大佬,学习一下。厉害了 好久没看文章了,写的不错。 xiaohe 太强了,收获很大,感谢发帖 大师兄牛逼 在ARM64架构中(例如在iOS或者某些Android设备上),这串数据对应的汇编指令是:
20 00 80 D2 对应 mov x0, #0x1,意思是将寄存器 x0 的值设置为1。
C0 03 5F D6 对应 ret,表示函数返回指令 谢谢分享这么实在的文章 牛X,学习了