CleanTool v2.3.1 权限与后台机制拆包报告#
文档日期:2026-10-12
1. 结论#
CleanTool 的后台不是单点保活,而是一组互相兜底的链路:进程启动前置 Provider → 特殊用途前台服务 → JobScheduler fallback → 15 分钟持久 Job → 15 分钟 PeriodicWork → 每日 10:00 Alarm → 开机/包更新/系统事件/Widget/FCM 再触发。主编排和 FGS 启动方法先检查通知权限/通知总开关;满足后,常驻 FGS 使用通知 ID 1,并在被销毁、任务划走或常驻通知删除时再次尝试启动。
静态代码证明“尝试”存在,不证明 Android 12L-16 与各 OEM 一定允许每次后台启动。特别是 targetSdk 36 对 FGS 启动、specialUse 类型和后台限制更严格,实际成功率属于 U。
2. 权限清单#
| 权限 | 来源 | 静态用途 | 风险/说明 | 等级 |
|---|---|---|---|---|
INTERNET |
App/SDK | 广告、Firebase、Adjust、远端配置 | 网络数据类型需抓包核验 | S1/U |
ACCESS_NETWORK_STATE |
App/SDK | 网络状态、广告、VPN判断 | 普通权限 | S1 |
ACCESS_WIFI_STATE |
App/SDK | Wi-Fi/网络工具与 SDK | 普通权限 | S1 |
READ_EXTERNAL_STORAGE |
App | 文件扫描兼容 | target 36 上已不足以覆盖现代媒体访问 | S1 |
WRITE_EXTERNAL_STORAGE maxSdk=28 |
App | 旧 Android 文件删除兼容 | 仅旧系统生效 | S1 |
MANAGE_EXTERNAL_STORAGE |
App | 全盘文件扫描、删除、FileObserver | 高敏感 All files access | S1 |
PACKAGE_USAGE_STATS |
App | 最近使用、流量/应用统计 | 必须用户到特殊访问页授权 | S1 |
BIND_NOTIFICATION_LISTENER_SERVICE(uses-permission) |
App | Manifest 同时声明通知监听 Service | 该权限本质用于保护 Service,普通 App 不能自行获得;用户需在设置授权 | S1 |
REQUEST_DELETE_PACKAGES |
App | 发起应用卸载 | 最终仍需系统/用户确认 | S1 |
FOREGROUND_SERVICE |
App | 常驻前台服务 | target 36 受严格限制 | S1 |
FOREGROUND_SERVICE_SPECIAL_USE |
App | specialUse FGS |
合规敏感;需与 Play 声明一致 | S1 |
POST_NOTIFICATIONS |
App | 常驻通知、场景提醒、FCM | Android 13+ 运行时授权 | S1 |
RECEIVE_BOOT_COMPLETED |
App | 开机/更新后重建后台链路 | 自启动敏感 | S1 |
WAKE_LOCK |
App/FCM/SDK | 后台任务/消息处理 | 实际持锁点未逐一归属 | S1/U |
AD_ID |
广告/归因 | 广告标识符 | Data safety/同意管理相关 | S1 |
ACCESS_ADSERVICES_TOPICS |
Google Ads | Privacy Sandbox Topics | SDK 合并权限 | S1 |
ACCESS_ADSERVICES_ATTRIBUTION |
Google Ads | Privacy Sandbox Attribution | SDK 合并权限 | S1 |
ACCESS_ADSERVICES_AD_ID |
Google Ads | Privacy Sandbox Ad ID | SDK 合并权限 | S1 |
com.google.android.c2dm.permission.RECEIVE |
FCM | 接收推送 | SDK 合并权限 | S1 |
| Install Referrer bind | Adjust/Ads | 安装归因 | SDK 合并权限 | S1 |
VIBRATE |
通知/SDK | 通知振动 | 普通权限 | S1 |
Play CHECK_LICENSE |
SDK | Play 许可/完整性能力 | 未发现 App 自有直接调用 | S1/U |
| Amazon Privacy Pass attestation | SDK | 广告/反欺诈 | 未发现 App 自有直接调用 | S1/U |
| 自定义 dynamic receiver signature 权限 | AndroidX | 保护动态 Receiver | 库级基础设施 | S1 |
Manifest 证据:jadx/resources/AndroidManifest.xml:15-30,67-79。
3. 用户授权路径#
| 能力 | 检查/跳转 | 证据 |
|---|---|---|
| All files access | Environment.isExternalStorageManager();MANAGE_APP_ALL_FILES_ACCESS_PERMISSION |
we.java:76-132 |
| Usage access | USAGE_ACCESS_SETTINGS + package URI |
MainActivity.java:140-162、SuccessEndActivity.java:1375-1378 |
| Notification access | ACTION_NOTIFICATION_LISTENER_SETTINGS;读取 enabled_notification_listeners |
c9.java:100、im2.java:377-404 |
| Post notifications | 运行时请求;拒绝后可跳 APP_NOTIFICATION_SETTINGS |
SplashActivity.java:182、v2.java:133-142 |
4. App 自有后台组件#
| 组件 | 触发/周期 | 行为 | 等级 |
|---|---|---|---|
WpCyBN ContentProvider |
进程创建,initOrder=100 |
初始化 keep callback、生命周期、归因并调用 dn.a() |
S1(方法部分 JADX 失败) |
gXXfik FGS |
初始化、系统事件、Job、Worker、FCM、Widget、销毁/划走 | ID 1 前台通知;API<34 返回 START_STICKY,API≥34 返回 START_NOT_STICKY |
S1 |
PsaeNNErXb JobService |
FGS start 异常 fallback,job ID 3857 |
5 秒最小延迟/7 秒 deadline;自身先前台化,再启动 FGS;15 秒后 jobFinished |
S1 |
sToBblBqZa JobService |
job ID 3432,15 分钟 |
onCreate 启动 FGS;onStartJob 再次排下一轮 | S1 |
kcTBvDgL Worker |
Unique periodic work,15 分钟(WorkManager 下限) | doWork() 调 im2.d() 并成功结束 |
S1 |
BpFZcYhmZ Receiver |
Alarm requestCode 74655;每天 10:00;type=2 / ELAPSED_REALTIME_WAKEUP |
唤醒型 Alarm 到达后启动 FGS、记录 live point、重排次日 Alarm | S1 |
ZPHDnIht Receiver |
BOOT_COMPLETED / MY_PACKAGE_REPLACED | 启动 FGS、记录 live point | S1 |
z7 动态 Receiver |
9 类系统事件 | 500ms 去抖后启动 FGS并记录 live point | S1 |
| 三个 AppWidget Provider | Widget 广播 | 每次 onReceive 都调用 im2.d() |
S1 |
NaKMPne FCM Service |
FCM 消息/token | 每次消息先调用 im2.d() |
S1 |
5. 保活链路#
5.1 启动入口#
WpCyBN.onCreate() 在 Application 之前执行。包名哈希校验通过后,它从 Manifest metadata 反射得到 KeepCallbackImp,注册 Activity lifecycle,并调用 dn.a(context)。见 WpCyBN.java:79-168。
KeepCallbackImp 的内置配置为:
- 每日 Alarm 时间:
10:00; - 周期 Job:15 分钟;
- PeriodicWork:15 分钟;
- 常驻通知 ID:
1。
5.2 FGS 与 Job fallback#
dn.a() 与 im2.d() 都先经过 f92.a(context):Android 13+ 检查 POST_NOTIFICATIONS,旧版本检查 areNotificationsEnabled()。也就是说,静态主链显示通知权限/总开关未满足时不会继续启动这组 FGS、Job、Alarm 与 PeriodicWork。权限满足后,im2.d() 在 gXXfik.f4807c=false 时调用 startForegroundService;如果该调用抛异常,才调度 job ID 3857:
- network type:none;
- charging/device idle:false;
- persisted:true;
- minimum latency:5,000ms;
- override deadline:7,000ms;
- API 33+ priority:400。
因此 PsaeNNErXb 是异常兜底,不是常规固定周期 Job。见 im2.java:69-139。
5.3 周期与自重建#
hd3.a()以 job ID3432调度sToBblBqZa,默认延迟 15 分钟、deadline 为延迟+5秒、persisted。sToBblBqZa.onCreate()调im2.d();onStartJob()再调用hd3.a(this),形成自重排。wu3.c()创建kcTBvDgL的 unique periodic work,默认 15 分钟,flex 5 分钟。wu3.b()每天 10:00 通过AlarmManager.set(type=2 / ELAPSED_REALTIME_WAKEUP, ...)安排唤醒型 Alarm;Receiver 触发后再次调用wu3.b(),形成次日重排。
注意:Alarm 使用 wakeup 类型,但 set() 仍是非 exact 调度,可能被系统合批、Doze 或 OEM 策略延迟;10:00 是请求时间,不保证准点。JobScheduler/WorkManager 同样受系统配额约束,15 分钟是请求参数,不是精确定时承诺。
5.4 被杀/划任务/删通知后的动作#
gXXfik.onDestroy():标记未运行、停止前台,然后立即im2.d()。gXXfik.onTaskRemoved():立即im2.d()。- 常驻通知
deleteIntent:API<35 使用getForegroundService,API≥35 使用getService,目标均为gXXfik。 PsaeNNErXb.onCreate():若 FGS 未运行,再调用startForegroundService(gXXfik)。
这些均是 S1“重启尝试”。Android 是否执行 deleteIntent、后台启动是否获准为 U。
5.5 系统事件拉起#
dn.a() 动态注册的 z7 监听:
SCREEN_OFFSCREEN_ONPACKAGE_REPLACEDCLOSE_SYSTEM_DIALOGSCONFIGURATION_CHANGEDPACKAGE_REMOVEDUSER_PRESENTACTION_POWER_CONNECTEDPACKAGE_ADDED
任一事件经每 action 500ms 去抖后调用 im2.d()。另有 Manifest Receiver 处理开机和自身包替换。
6. 通知监听与文件监听#
6.1 NotificationListener 的实际行为#
NotificationListener.onNotificationPosted() 对每条非 ongoing 且非 CleanTool 自身的通知执行:
- 读取来源包、标题、正文;
cancelNotification(statusBarNotification.getKey());- 异步保存/处理记录;
- 当日计数 +1;
- 计数达到 20 时尝试发出
notify_notifyClean召回通知,并把计数清零。
异步分支实际构造 NotificationItem(id=0, pkg, title, content, time) 并通过 Room DAO 写入本地数据库;写入前对 title/content 做长度截断。可见入口只排除 ongoing 通知和 CleanTool 自身,没有第三方包白名单。证据:cw.java:704-724、j32.java:18-53、dx1.java:259-285。这意味着它是“撤下其他 App 通知 + 保存其包名/标题/正文/时间”的实现,而不仅是展示统计。数据库保留/清理周期、备份范围和是否上传仍为 U。
6.2 FileObserver#
App 启动协程会递归监听 Music、Podcasts、Ringtones、Alarms、Notifications、Movies、Downloads、Documents、Pictures、DCIM、Screenshots、Camera 等公共目录。新图片触发截图/图片类提醒,新非图片文件触发 notify_new_fileA。进入销毁生命周期状态时停止所有 observer。见 as0.java:18-70、zr0.java:30-100、xe.java:188-210、kf.java:83-101。
7. FCM 与 Remote Config#
7.1 FCM#
- 自有服务:
NaKMPne extends FirebaseMessagingService。 - 新 token 同时传给 Adjust,并缓存后订阅内置 topic
tZDmW。 - 消息到达先启动/维持 FGS,再处理通知。
RemoteMessage.c()==1(Firebase 类中对应 delivered priority 为high)时随机选择 10 条本地文案之一,action 统一为notify_sub_high。- high-priority 代码可见后台、未锁屏、非横屏、距上次 live point ≥5 分钟、07:00-23:00 等判断,但实际执行体因 JADX 失败未完整恢复,记 U。
7.2 Remote Config#
已确认远控键:openTime、notifyCoolTime、day0_ad...day3_ad、VPN 广告开关、notify_interval_10m。未发现远端直接下发通知 title/body 的读取代码;当前通知文本来自本地 string 资源。线上参数值未验证。
8. 安全与合规观察#
| 发现 | 影响 | 等级 |
|---|---|---|
network_config 允许 cleartext traffic |
允许 HTTP 明文链路;是否实际使用需抓包 | S1/U |
allowBackup=true 且备份规则为空 |
可能按系统默认备份符合条件的数据 | S1/I |
FileProvider 暴露 external storage 根路径 . |
若 App 生成错误 URI/grant,分享面过宽 | S1/I |
gXXfik exported=true 且无组件权限 |
其他 App 可显式尝试启动该 Service;系统仍会施加 FGS 限制 | S1 |
NotifyRepeatReceiver、WidgetCreateReceiver exported=true 且自定义 action 无权限 |
存在外部显式/广播触发面,需做输入验证 | S1/I |
OutOpenActivity exported=true |
外部 App 可显式打开统一入口并传 action | S1 |
| NotificationListener 会取消其他 App 通知 | 用户信息可达性与合规风险高 | S1 |
NotificationListener 本地保存第三方通知的 pkg/title/content/time,且 allowBackup=true |
通知正文可能包含敏感信息;数据库是否被备份规则排除未确认 | S1/U |
specialUse FGS + 多重重启链 |
Play foreground service 声明与实际用途需严格一致 | S1/I |
9. 未验证项#
- targetSdk 36 真机上各后台入口的成功率、系统日志与电量影响。
specialUsesubtype/Play Console 申报内容。- 远端参数是否关闭部分保活或调整周期。
- FCM high-priority 失败方法中的完整动作。
- 通知记录的保留/清理周期、备份范围和是否上传。
- 明文网络、外部 Provider 与 exported 组件是否能被实际利用。