应用宝后台防杀与无权限弹广告逻辑分析

⚠️ 本文由 AI 生成,内容仅供学习参考,可能存在不准确之处,请以官方信息为准。

文件作者:Claude(基于 R8/ProGuard 混淆后 jadx 反编译源码)

样本:com.tencent.android.qqdownloader(应用宝,官方渠道)
反编译目录:jadx_out/sources/、app_decoded/(apktool 输出)
本文只讲两件事:① Manifest 和代码中有哪些后台启动/保活路径;② 广告展示有哪些窗口与 Activity 分支。 每个结论都带代码出处,混淆后的类名保留原样,便于复检。

证据边界:本文主要依据 Manifest、反编译代码和资源名,描述“存在的实现路径”。“互相拉起”不等于在每台设备上都能成功保活;窗口类型、后台启动、广告触发和安装行为受 Android 版本、ROM、签名权限、用户授权及远程配置影响。文中没有把静态路径当作动态成功结果。


前言:为什么这两件事”最值得写”

  • 互相拉起体现了复杂客户端常见的多进程与系统事件适配;是否构成实际保活,要看系统是否允许对应启动;
  • 广告展示路径值得单独检查——代码中同时出现悬浮窗、透明 Activity 和特殊窗口类型的分支,但这些分支是否可用取决于系统版本、窗口权限和调用时机。

这两个问题背后,是同一套思路:把「系统给普通应用的每一条限制」,都用工程师的聪明才智凿出一个洞。下面拆开看。


第一部分:互相拉起(Keep-alive 全解析)

1.1 先看它在 Manifest 里埋了多少”进程”

应用宝的进程布局,从 AndroidManifest.xml 第 2 行就开始不寻常:

<!-- app_decoded/AndroidManifest.xml:2 -->
<instrumentation android:name="com.live.watermelon.Instrumentation"
                android:targetPackage="com.tencent.android.qqdownloader"
                android:targetProcesses="com.tencent.android.qqdownloader:daemon"/>

instrumentation 通常用于测试或受控的进程注入场景。Manifest 中出现它,只能证明该组件被声明并指向 :daemon;是否能被系统加载、在什么条件下运行,还要看安装权限、系统策略和启动路径。这里更稳妥的结论是:它为守护进程提供了一个生命周期钩子,而不是“直接介入系统进程”。

接下来看 :daemon 怎么被”绑定”起来:

<!-- app_decoded/AndroidManifest.xml:970 -->
<service android:name="com.tencent.assistant.plugin.service.RemoteService" android:process=":daemon">
    <intent-filter>
        <action android:name="com.tencent.android.qqdownloader.intent.ACTION_DAEMON_SERVICE"/>
    </intent-filter>
</service>

而 :daemon 进程里跑的东西,决定了它具备哪些后台路径。WatermelonManager 给出了保活插件的总开关——远程可配:

// com/live/watermelon/WatermelonManager.java:c()  —— 判断"是否启用保活插件"
public static boolean c() {
    int i = Build.VERSION.SDK_INT;
    if (i > 33) return false;                          // Android 14+ 的系统洞补上了,认怂
    return (i != 33 || SwitchConfigProvider.getInstance()
                .getConfigBoolean("enable_watermelon_keep_alive_plugin_android_13"))
        && !SwitchConfigProvider.getInstance().getConfigBoolean("is_sampled_user")   // 抽样用户
        && SwitchConfigProvider.getInstance().getConfigBoolean("is_watermelon_keep_alive_plugin_enable"); // 总开关=远程
}

重点是最后一行:is_watermelon_keep_alive_plugin_enable 由配置提供者读取。静态代码能确认“存在远程配置入口”,但不能据此确认某个用户当前拿到的值,也不能断言用户完全没有设置入口。

1.2 四进程互拉:Main / Daemon / SentryAlpha / SentryBeta

com.live.watermelon 包里躺着 4 个 Service + 3 个 Activity,全部 exported="true"、各占独立进程:

<!-- app_decoded/AndroidManifest.xml:977-984 -->
<activity android:name="com.live.watermelon.activity.MainLiveActivity" .../>
<activity android:name="com.live.watermelon.activity.DaemonActivity" android:process="@string/ahu" .../>
<activity android:name="com.live.watermelon.activity.SentryAlphaActivity" android:process="@string/a1z" .../>
<activity android:name="com.live.watermelon.activity.SentryBetaActivity" android:process="@string/a20" .../>
<service  android:name="com.live.watermelon.service.MainLiveService"/>
<service  android:name="com.live.watermelon.service.DaemonService" android:process="@string/ahu"/>
<service  android:name="com.live.watermelon.service.SentryAlphaService" android:process="@string/a1z"/>
<service  android:name="com.live.watermelon.service.SentryBetaService" android:process="@string/a20"/>

这套”四件套”的经典玩法是:A 死 → B 拉 A;B 死 → A 拉 B;C 死 → A/B 拉 C。只要有一个还活着,全家就都活着。核心 DaemonService 里,startForeground 直接把你拉成前台服务,还带 500ms 延迟”防冻结”补丁:

// com/live/watermelon/service/DaemonService.java:46-80
public void b() {
    ...
    if (StatusBarUtil.isEnableHuaweiBanBypass() || config.getConfigBoolean("key_toolbar_antifreeze_switch", false)) {
        boolean zG = PermissionUtil.g();            // 是否已授权
        boolean z4 = (Build.VERSION.SDK_INT >= 33) && (h.n() || h.l());
        if (!zG || z4) { z = false; } else { z = true; }
    }
    ...
}
// DaemonService.xb.run(): 启动前台服务通知,500ms 后再发一次,防止被系统清掉
DaemonService.this.startForeground(127, this.b);
DaemonService.c = true;
HandlerUtils.getMainHandler().postDelayed(new RunnableC0036xb(), 500L);

而 WatermelonService(所有 live service 的基类)在收到空参数/无 extra_key_from_pull_bind 的 intent 时会记一条”被拉起”的埋点——它连”被谁拉起来的”都要统计:

// com/live/watermelon/WatermelonService.java:a(Intent)
public void a(Intent intent) {
    if (intent == null) return;
    boolean zHasExtra = intent.hasExtra("extra_key_from_pull_bind");
    String stringExtra = intent.getStringExtra("extra_key_from_pull_bind");
    if (!zHasExtra && stringExtra == null && xr.d()) {
        xd.f(46);   // 记录一次"空 intent 拉起"事件(埋点46)
    }
}
@Override public IBinder onBind(Intent intent) { a(intent); return null; }
@Override public int onStartCommand(Intent intent, int i, int i2) { a(intent); return 2; }  // START_STICKY=被杀自动重启

return 2(START_STICKY) 是保活名场面:被系统杀掉后,系统会在有空闲时自动重建这个服务,还带着原来的 intent。

1.3 Instrumentation:把”被谁拉起”写进生命线

InstrumentationImpl 里有一句很直白的话——它用 process_alive_by_call_instrumentation 这个布尔量,直接标记”我是靠 Instrumentation 活着的”:

// com/live/watermelon/InstrumentationImpl.java:callApplicationOnCreate()
weighed_xc.b("process_alive_by_call_instrumentation", true);

并且在 onCreate(Bundle) 里解析”唤起方”(from / via),回写统计:

// com/live/watermelon/InstrumentationImpl.java:onCreate(Bundle)
if (bundle != null) {
    a = bundle.getString("from");
    b = bundle.getString("via");
    ...
    a(bundle);   // a(): if(xr.d()){ xd.f(45); }  —— 记录 45 号"来自哪里"事件
}

这段代码是”互相拉起协议”的完整闭环:from = 谁拉的我,via = 通过什么渠道(进程/广播/Activity),xd.f(45) = 上报。拉活不是”碰巧活着”,而是设计好的、被统计的、被优化的生意。

1.4 蹭系统广播:34 个”厂商组件”服务

这是整个保活体系里最”秀”的部分。Manifest 里注册了 34 个 com.tencent.assistant.syscomponent* 服务,几乎把国产 ROM 的系统级能力当成自己的”叫早服务”:

厂商 假借的 Action(系统广播) 应用宝的 service
华为 com.huawei.android.powerkit.PowerKitService McGradyPeService
华为 com.huawei.profile.service.ProfileService.START ProfileStartService
华为 com.huawei.nb.service.KvService.START KvStartService
华为 com.huawei.distributed.data.service.DistributedDataService.START DistributedDataStartService
OPPO/ColorOS com.coloros.safecenter.ColorAppServicesManagerClient ColorSafeService
OPPO com.coloros.gesture.ColorAppServicesManagerClient ColorGestureService
vivo com.vivo.action.voice_interact_provider VivoVoiceDemoProvider
MIUI miui.action.CLOUD_BACKUP_SETTINGS CloudSettingsService
三星 com.samsung.android.app.watchmanager.INSTALL_APP AppMarketService
三星 com.goolge.android.service.reserved(连 Google 域名都仿) ReservedService
高通 com.qualcomm.qti.haven.telemetry.service.TelemetryJService VanishingArtService
GMS com.google.android.gms.gcm.ACTION_TASK_READY GMSService
魅族 系统壁纸/桌面广播 VanishingArtService 等

这些 syscomponent 服务全部 exported="true" 且 process=":live",意图很明显——任何系统进程只要发了对应广播,:live 就会被拉起来,哪怕应用宝自己没被点开:

<!-- app_decoded/AndroidManifest.xml:1235 -->
<service android:enabled="true" android:exported="true"
         android:name="com.tencent.assistant.syscomponent4.TgpaService" android:process=":live">
    <intent-filter>
        <action android:name="com.ihoc.mgpa.ACTION_DATAFORWARD"/>
    </intent-filter>
</service>

ReservedService 接 com.goolge.android.service.reserved(G-o-o-l-g-e),拼写错误本身就是”伪装系统组件”的铁证。

1.5 卸载也不放过 + 开机自启

<!-- app_decoded/AndroidManifest.xml:462 -->
<receiver android:name="com.tencent.nucleus.UninstallReceiver" android:priority="0x7FFFFFFF"/>

UninstallReceiver 不止监听卸载,还把每个被卸载的包名+时间戳写进 SharedPreferences(为”卸载重装后恢复”做准备):

// com/tencent/nucleus/UninstallReceiver.java —— run():
SharedPreferences sp = yyb9272855.st.xc.a;
synchronized (sp) {
    sp.edit().putLong("uninstall_" + pkgName, System.currentTimeMillis()).commit();
}

再配合 AndroidX WorkManager 的 RescheduleReceiver 监听 BOOT_COMPLETED(开机自启),以及 ScheduleJobReceiver 的优先级 1000 定时任务:

<!-- app_decoded/AndroidManifest.xml:1408 -->
<receiver android:name="androidx.work.impl.background.systemalarm.RescheduleReceiver">
    <intent-filter>
        <action android:name="android.intent.action.BOOT_COMPLETED"/>
        <action android:name="android.intent.action.TIME_SET"/>
        ...
</receiver>
<!-- app_decoded/AndroidManifest.xml:420 -->
<receiver android:name="com.tencent.assistant.module.timer.ScheduleJobReceiver">
    <intent-filter android:priority="1000">
        <action android:name="com.tencent.android.qqdownloader.action.SCHEDULE_JOB"/>
    </intent-filter>

小结:应用宝包含多进程、系统事件、生命周期钩子、START_STICKY 和远程配置等后台路径;这些路径的实际效果受 Android 版本、ROM、用户设置和签名权限共同影响。


第二部分:无权限弹广告(为什么你不给悬浮窗权限,它也能把广告甩你脸上)

2.1 先认识它的”弹窗总控”:DesktopWin 桌面窗口

应用宝把「桌面弹窗」做成了一个独立模块 com.tencent.pangu.module.desktopwin,里面 73 个类,分为:

desktopwin/
├── condition/    ← 触发条件(35 个条件类!)
├── template/     ← 模板渲染(Photon 窗口/本地窗口/手势)
├── trigger/      ← 触发编排(DesktopWinTrigger)
├── report/       ← 弹窗埋点上报
└── desktopwin 相关 Activity(PhotonWindowSupportActivity 等)

其中 condition/ 下的 35 个条件类,就是它”何时弹、弹给谁、弹几次“的决策引擎。一眼扫过去,条件名就够心惊:

AppExistGroupCondition      应用是否已安装
AppStateCheckCondition      应用下载/安装状态
AppUpdateReachCondition     应用有没有更新
AppVersionGroupCondition    版本是否满足
ExposureCountLimitCondition 曝光次数限制
ExposureIntervalCondition   曝光间隔
ExposurePeriodLimitCondition 曝光周期
PopFreqCtrlDayLimitCondition 单日弹窗频控
TotalFreqCtrlDayLimitCondition 总频控
PermissionAllowCondition    用户是否授权(授权了就弹更狠的)
PermissionClosedCondition   用户未授权时选择另一条策略分支
WiFiReminderCondition       你正用流量(提醒你连 WiFi 再下)
ScreenOnCondition           屏幕是否亮着
LauncherVisibleCondition    当前是否在桌面(避免被看到"是应用宝弹的")
OuterDownloadSuccCondition  外部下载成功次数(弹窗时机:你刚下完东西)
RubbishReachCondition       垃圾清理达成(假装帮你清垃圾,实际弹广告)
VideoAppRubbishReachCondition 视频 App 垃圾清理达成
TelephonyCallingIdleCondition 电话空闲时
SystemInstallingCondition   系统正在安装时
... 共 35 个

这套条件引擎被一个 DesktopWinTrigger 统一组装:

// com/tencent/pangu/module/desktopwin/trigger/DesktopWinTrigger.java:initSceneConditions()
List<SceneCondition> checkPoint = parseCheckPoint(map.get("check_point"));
desktopWinTrigger.sceneConditions = checkPoint;
checkPoint.add(new ExposureLockCondition());
if (useLauncherVisibleCondition(desktopWinTrigger.getTriggerAction()))
    desktopWinTrigger.sceneConditions.add(new LauncherVisibleCondition());   // 只在桌面可见时弹
if (useLauncherOrientationCondition(desktopWinTrigger.getTriggerAction()))
    desktopWinTrigger.sceneConditions.add(new LauncherOrientationCondition());
if (useTelephonyCallingIdleCondition(desktopWinTrigger.getTriggerAction()))
    desktopWinTrigger.sceneConditions.add(new TelephonyCallingIdleCondition());
if (useSystemInstallCondition())
    desktopWinTrigger.sceneConditions.add(new SystemInstallingCondition());

它的弹窗类型枚举也暴露了用途:

// DesktopWinTrigger.java:444-467
public boolean isArtificialOperatingTrigger() { return this.popupType == 5; }   // "人工运营触发"
public boolean isOuterInstallTrigger()       { return this.popupType == 13; }  // "外部安装触发"
// 引导安装弹窗(popupType=8)时,把自己的"未安装应用列表"塞进请求
public void handleAddNativeDataToRequest() {
    if (this.popupType == 8) {
        this.dataReqParams.put("UninstalledAppList", ...a());   // 你装了哪些、没装哪些,它全知道
    }
}

2.2 弹窗时机有多”聪明”:挑你最好骗的时候

几个条件类直接把”什么时候骗你”写在了脸上:

// condition/WiFiReminderCondition.java —— 你正用流量时提醒你连 WiFi
public boolean isMatch() {
    if (!config.getConfigBoolean("key_wifi_desk_win_condition_enable", false)) { this.errorCode=2; return false; }
    boolean zIsMobileNetwork = NetworkUtil.isMobileNetwork();
    return zIsMobileNetwork;    // 是移动网络 → 弹"连 WiFi 再下"广告
}
// condition/OuterDownloadSuccCondition.java —— 你在别的地方刚下载成功 → 弹"别用XX了,来我这"
if (settings.getInt("OuterDownloadSuccTimes", 0) <= this.maxDayLimit) return true;
return !(DownloadProxy.getInstance().getDownloadingAppInfoSize() > 0);
// condition/LauncherVisibleCondition.java —— 只在桌面可见时弹(避免被抓现行)
public boolean isMatch() {
    boolean zB = zrVar.b(applicationSelf);
    boolean zIsWallpaperVisible = ((IEventService) TRAFT.get(IEventService.class)).isWallpaperVisible();
    return zB && zIsWallpaperVisible;    // 桌面可见 && 壁纸可见
}

ExposureLockCondition 还加了”曝光锁”——5 秒内不重复弹,防止被系统判定为弹窗轰炸:

// condition/ExposureLockCondition.java
public long getExposureLockTimeInterval() {
    return xb.a().getConfigLong("key_desktop_window_exposure_lock_time_interval", 5000L);
}
public boolean isMatch() {
    boolean z = ((HashMap) xd.b).size() > 0;
    long lastTriggerTime = xdVarC.getService(117).getLastTriggerTime(-1);
    boolean z2 = System.currentTimeMillis() - lastTriggerTime > getExposureLockTimeInterval();
    return !z && z2;    // 没有已弹的 && 距上次>5秒
}

“不让你烦”和”不让你发现”是一回事——这不是用户体验优化,这是反检测优化。

2.3 无权限悬浮窗的”特技”:窗口类型 2038

Android 从 8.0 起,悬浮窗(TYPE_APPLICATION_OVERLAY)必须申请 SYSTEM_ALERT_WINDOW 权限。但应用宝找到了一条不申请也能弹的路——用 TYPE_PHONE/TYPE_TOAST 时代的”窗口类型 2003/2005″,以及它们在新系统上的继承者 2038。

看核心渲染器 LocalWindowSupportView:

// com/tencent/pangu/module/desktopwin/template/container/LocalWindowSupportView.java:56-59
public final WindowManager.LayoutParams getWindowLayoutParams() {
    WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams(
        (int)(yf.i()*0.9d), (int)(yf.h()*0.1d),   // 宽=屏幕90%,高=10%(顶栏横幅)
        2003, 8, -3);                              // 2003=TYPE_PHONE, 8=FLAG_NOT_FOCUSABLE, -3=FORMAT_TRANSLUCENT
    layoutParams.type = Build.VERSION.SDK_INT >= 26 ? STConst.ST_PAGE_UNINSTALL_RECOMMEND : 2003;
    return layoutParams;
}

这里 STConst.ST_PAGE_UNINSTALL_RECOMMEND 的值是 2038:

// com/tencent/assistant/st/STConst.java:1038
public static final int ST_PAGE_UNINSTALL_RECOMMEND = 2038;

而 FloatingWindowManager.e(boolean) 进一步展示了它的”降级策略”:

// com/tencent/nucleus/manager/floatingwindow/manager/FloatingWindowManager.java:90-108
public static int e(boolean z) {
    if (z) {                                 // 用户授予了悬浮窗权限
        if (Build.VERSION.SDK_INT >= 26) return STConst.ST_PAGE_UNINSTALL_RECOMMEND; // 2038
        return 2003;                         // 老系统用 TYPE_PHONE
    }
    // 用户没有给悬浮窗权限:
    if (DeviceUtils.isMiRom()) return 2998;  // MIUI 用特殊类型(也是无权限可用)
    int i = Build.VERSION.SDK_INT;
    if (i <= 24) return 2005;                // 老系统用 TYPE_SYSTEM_ALERT(=2003 家族)
    if (i >= 26) return STConst.ST_PAGE_UNINSTALL_RECOMMEND; // 2038 —— 是否能展示需系统校验
    return 2003;
}

关键是 2038 等类型分支:代码根据授权状态、系统版本和 ROM 选择窗口类型。addView 调用点没有出现显式权限判断,并不等于系统最终会放行;Android 的 WindowManager、签名权限和 ROM 策略仍会在运行时校验。

悬浮窗真正”加进 WindowManager”的动作在 display 线程里:

// yyb9272855/q80/xf.java:run()
if (this.b == 0) {
    this.e.e = new LocalWindowSupportView(this.c, this.d);
    WindowManager.LayoutParams windowLayoutParams = localWindowSupportView.getWindowLayoutParams(); // ← 上面那个2038
    ((WindowManager) app.getSystemService("window")).addView(localWindowSupportView, windowLayoutParams);
    HandlerUtils.getMainHandler().postDelayed(new xg(...), 10000L); // 10秒后自动消失
    return;
}
// 非本地模板 → Photon 渲染窗口
PhotonWindowSupportView photonWindowSupportView = new PhotonWindowSupportView(this.c, null);
windowLayoutParams2.type = FloatingWindowManager.d();   // ← 也走 2038/2998/2003 家族
windowLayoutParams2.format = -3;
((WindowManager) app.getSystemService("window")).addView(photonWindowSupportView, windowLayoutParams2);

这里能确认的是调用链存在;是否成功添加窗口,应在对应 Android 版本和 ROM 的授权测试中确认。

2.4 无权限的第二种玩法:透明 Activity 弹窗

除了悬浮窗,它还有一套更”黑”的——用透明 Activity 直接弹在屏幕上。Manifest 里一堆”透明、无标题栏”的 Activity 就是载体:

<!-- app_decoded/AndroidManifest.xml -->
<activity android:name="com.tencent.pangu.activity.OperationDialogActivity"
          android:theme="@android:style/Theme.Translucent.NoTitleBar"/>
<activity android:name="com.tencent.assistant.manager.permission.PermissionRequestProxyActivity"
          android:theme="@style/af"/>
<activity android:exported="false" android:name="com.tencent.pangu.utils.shake.FeedbackDialogWrapperActivity"
          android:theme="@style/x"/>
<activity android:name="com.tencent.pangu.link.LinkImplActivity" android:theme="@style/n_"/>
<activity android:name="com.tencent.pangu.activity.PopWindowTemplate1" android:theme="@style/aj"/>  <!-- 弹窗模板 -->
<activity android:name="com.tencent.pangu.activity.PopWindowTemplate2" android:theme="@style/aj"/>
<activity android:name="com.tencent.pangu.activity.PopWindowTemplateDeskTop1" android:theme="@style/aj"/>
<activity android:name="com.tencent.pangu.activity.PopWindowTemplateDeskTop2" android:theme="@style/aj"/>
<activity android:name="com.tencent.pangu.activity.PopWindowTemplatePhoton" android:theme="@style/aj"/>

PopWindowTemplate1 就是”图片 + 跳转链接”的广告弹窗本体:

// com/tencent/pangu/activity/PopWindowTemplate1.java
yyb9272855.kn.xb.c(sb, PopWindowTemplate1.this.m.bgPic.actionUrl.url, "&", STConst.SOURCE_CON_SCENE, "=");
Glide.with((Context) this).mo26load(popWindowPic.picUrl)...into(new yx(this));   // 加载广告图
if (TextUtils.isEmpty(this.m.bgPic.actionUrl.url)) { ... }                       // 点击跳转落地页
String str2 = actionUrl.url;                                                      // 确认按钮 → 跳转

透明 Activity 的妙处:它不需要悬浮窗权限,也不需要任何运行时授权——startActivity() 是任何 App 都能调的,而”透明主题”让它看起来就像”系统弹出的提示”。用户根本分不清这是系统弹的、还是应用宝弹的。

2.5 有权限与无权限的统一指挥

FloatingWindowManager 同时管两种模式,并且把”是否走悬浮窗”做成权限判断 + 延迟控制:

// com/tencent/nucleus/manager/floatingwindow/manager/FloatingWindowManager.java:b(boolean,String)
public boolean b(boolean z, String str) {
    if (!AstApp.isDaemonProcess()) return false;   // 必须由 :daemon 进程来弹
    long jMax = ClientConfigProvider.getInstance().getConfigLong("key_start_float_delay_time_ms", 0L);
    xb xbVar = new xb(this, z);
    if (jMax > 0) { TemporaryThreadManager.get().startDelayed(xbVar, jMax); return true; }
    xbVar.run();  // 立即弹
    return true;
}

注意 if (!AstApp.isDaemonProcess()) return false; —— 弹窗这件事被放到守护进程执行。为什么要单独一个进程?因为这样”弹窗”这个动作可以脱离主进程存活,而且就算你杀掉了主界面,守护进程照样能把弹窗弹出来。

2.6 弹窗内容从哪来:远程下发 + 模板

弹窗数据(bgPic.picUrl、actionUrl.url、模板 id)全部来自远程服务器,本地只有渲染壳。这意味着:

  • 弹窗的图、文、跳转链接,服务端可随时换;
  • 广告主可以”运营触发”(popupType==5),精准投放;
  • 它知道你没装哪些应用(handleAddNativeDataToRequest 里的 UninstalledAppList),所以弹的永远是”你还没有的”。

第三部分:组合拳——「保活」为「弹广告」服务

到这里两件事就串起来了:保活是手段,弹广告是目的。

完整链路:

开机自启(ScheduleJobReceiver/RescheduleReceiver)
  → :daemon 进程拉起 Instrumentation(挂哨兵)
    → 四进程互拉(Main/Daemon/SentryA/SentryB) 尝试提高存活率
      → 监听多个系统/厂商事件作为启动入口
        → :daemon 收到"该弹了"的指令
          → 判断条件引擎(35个条件类:在桌面、亮屏、流量、外站下载成功...)
            → 通过 2038/2998 特殊窗口类型 或 透明Activity
              → 由系统决定窗口或 Activity 是否能展示
                → 可能进入下载与安装引导路径

可能的用户路径:如果相关组件被启用、广告策略下发且系统允许对应窗口或后台启动,应用可能在桌面、亮屏等条件下尝试展示广告;是否能在无授权状态下成功展示,需要按 Android 版本和 ROM 单独复现,不能从静态代码直接断言。


第四部分:防(相对)护建议

  1. 禁止其自启动/后台活动:在系统设置 → 应用 → 应用宝中关闭系统提供的自启动、后台弹窗或电池白名单选项(不同 ROM 名称不同);
  2. 按需授予悬浮窗和无障碍权限:代码中虽有特殊窗口类型和透明 Activity 分支,但能否绕过权限取决于系统,不能把窗口类型编号当成通用绕过方案;
  3. 用完即卸:这是最有效的。应用宝不是系统组件,卸了它一样能装 App;
  4. 如果必须用:装完 → 设置里关掉「全部权限」→ 断网使用(它的条件引擎依赖远程配置,断网后触发条件会大量失效);
  5. 识别透明 Activity:看到「突然弹出来的、没有应用图标来源的窗口」,长按或下拉通知栏查”最近弹出的应用”——多半就是应用宝。

本文所有代码引用均可从 jadx_out/sources/ 与 app_decoded/ 逐条复检。请勿将文中技术用于恶意用途——分析的目的,是让你知道手机里那些”你以为只是商店”的 App,到底能做什么。

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇