某音乐软件在原生Pixel被拦截自启动后导致系统NFC无限崩溃

某音乐软件在原生Pixel被拦截自启动后导致系统NFC无限崩溃

本文代码基于Android 12

起因

在调试Pixel的时候,发现每次重启,国内某音乐软件的播放通知栏就会显示在锁屏上,按照以前的逻辑,这应该是接收开机广播拉起的进程,但调查之后却发现事实并没有那么简单,接下来让我们一起去看看这个软件的自启方式(不知道google issue tracker上面有没有对应的issue,希望google能尽早修复这个问题吧)。

某音乐软件如何自启

一般遇到这些应用自启的问题,先从ProcessList#startProcessLocked方法入手,因为所有应用进程创建都会经过这里,把AOSP代码导入Android Studio中,在这个方法打一个断点,当命中断点的时候,输出堆栈log,我们来看看输出的堆栈

05-09 01:34:41.891  3174  3442 E ContentPane: startProcess: name=com.xxxxxx.xxmusic app=null knownToBeDead=true thread=null pid=-1
05-09 01:34:41.891  3174  3442 E ContentPane: java.lang.Throwable: ContentPane
05-09 01:34:41.891  3174  3442 E ContentPane:   at com.android.server.am.ProcessList.startProcessLocked(ProcessList.java:2492)
05-09 01:34:41.891  3174  3442 E ContentPane:   at com.android.server.am.ActivityManagerService.startProcessLocked(ActivityManagerService.java:2686)
05-09 01:34:41.891  3174  3442 E ContentPane:   at com.android.server.am.ActiveServices.bringUpServiceLocked(ActiveServices.java:3845)
05-09 01:34:41.891  3174  3442 E ContentPane:   at com.android.server.am.ActiveServices.bindServiceLocked(ActiveServices.java:2819)
05-09 01:34:41.891  3174  3442 E ContentPane:   at com.android.server.am.ActivityManagerService.bindIsolatedService(ActivityManagerService.java:12003)
05-09 01:34:41.891  3174  3442 E ContentPane:   at android.app.IActivityManager$Stub.onTransact(IActivityManager.java:2606)
05-09 01:34:41.891  3174  3442 E ContentPane:   at com.android.server.am.ActivityManagerService.onTransact(ActivityManagerService.java:2498)
05-09 01:34:41.891  3174  3442 E ContentPane:   at android.os.Binder.execTransactInternal(Binder.java:1179)
05-09 01:34:41.891  3174  3442 E ContentPane:   at android.os.Binder.execTransact(Binder.java:1143)

一般接收开机广播起来的话都是从BroadcastQueue调用过来,但是可以看到这个进程是通过ActiveServices#bindServiceLocked起来的,也就是应用或者系统调用startServicebindService的时候,发现没有相应进程,而调用ProcessList#startProcessLocked拉起相应组件的进程。

接下来,就是看看是谁拉起的进程,我们接着在ActivityManagerService#bindIsolatedService方法,通过Binder.getCallingPidBinder.getCallingUid都打印出来。

05-09 01:34:41.691  3174  3442 D ContentPane: service is Intent { act=android.nfc.cardemulation.action.HOST_APDU_SERVICE cmp=com.xxxxxx.xxmusic/com.xxxxxx.xx.plugin.appbrand.jsapi.nfc.hce.HCEService } callingPackage is com.android.nfc uid=1027 pid=4043

发现是nfc通过action拉起的服务,此时的我明白了一些问题,原来是这个音乐软件利用系统服务或者系统应用初始化时或者执行某些流程时需要绑定一些第三方应用的action的特性,来把自己的进程拉起来。

如何通过ifw拦截某音乐软件启动

接下来问题就很好解决了,一切的component或者aciton都可以被IntentFirewall拦截下来,只需要编写一些简单的规则,就可以禁止该component或者action的启动,不了解IntentFirewall的同学可以看IFW 是什么,而规则的编写可以在github上搜索规则的关键字component-filter或者intent-filter,它们分别代表通过组件过滤启动、通过action过滤启动。而我就编写了一个规则

<rules>
    <service block="true" log="true">
        <component-filter name="com.xxxxxx.xxmusic/com.xxxxxx.xx.plugin.appbrand.jsapi.nfc.hce.HCEService" />
    </service>
</rules>

这个规则的意思就是告诉AMS如果有组件名长这样的,就把它拦截下来不允许启动,把这个写好的规则保存为xml并push到路径data/system/ifw下,因为IntentFirewall通过FileObserver监测这个文件夹文件的这些操作,所以规则是马上生效的。

private static final int MONITORED_EVENTS = FileObserver.CREATE|FileObserver.MOVED_TO|
        FileObserver.CLOSE_WRITE|FileObserver.DELETE|FileObserver.MOVED_FROM;

但在这里,我们只有重启才能复现现象,所以我们重启手机,果然该音乐应用没有启动,没有显示在锁屏界面上,ps进程也没发现该音乐应用的进程启动,说明我们的规则是生效了。

拦截某音乐软件启动后手机莫名发热

这时候我就没有继续看该应用的自启问题,转而去看其他应用的自启问题,但过了一会发现手机一直发热,电量也在快速往下掉,我打印event log发现nfc应用一直在crash且重新创建进程,然后我执行adb logcat -s AndroidRuntime发现这个crash堆栈一直在刷

05-09 05:03:38.341  1619  1619 E AndroidRuntime: java.lang.SecurityException: Not allowed to bind to service Intent { act=android.nfc.cardemulation.action.HOST_APDU_SERVICE cmp=com.xxxxxx.xxmusic/com.xxxxxx.xx.plugin.appbrand.jsapi.nfc.hce.HCEService }
05-09 05:03:38.341  1619  1619 E AndroidRuntime:        at android.app.ContextImpl.bindServiceCommon(ContextImpl.java:1984)
05-09 05:03:38.341  1619  1619 E AndroidRuntime:        at android.app.ContextImpl.bindServiceAsUser(ContextImpl.java:1919)
05-09 05:03:38.341  1619  1619 E AndroidRuntime:        at android.content.ContextWrapper.bindServiceAsUser(ContextWrapper.java:829)
05-09 05:03:38.341  1619  1619 E AndroidRuntime:        at com.android.nfc.cardemulation.HostEmulationManager.bindPaymentServiceLocked(HostEmulationManager.java:382)
05-09 05:03:38.341  1619  1619 E AndroidRuntime:        at com.android.nfc.cardemulation.HostEmulationManager.lambda$onPreferredPaymentServiceChanged$0$HostEmulationManager(HostEmulationManager.java:118)
05-09 05:03:38.341  1619  1619 E AndroidRuntime:        at com.android.nfc.cardemulation.HostEmulationManager$$ExternalSyntheticLambda0.run(Unknown Source:4)
05-09 05:03:38.341  1619  1619 E AndroidRuntime:        at android.os.Handler.handleCallback(Handler.java:938)
05-09 05:03:38.341  1619  1619 E AndroidRuntime:        at android.os.Handler.dispatchMessage(Handler.java:99)
05-09 05:03:38.341  1619  1619 E AndroidRuntime:        at android.os.Looper.loopOnce(Looper.java:201)
05-09 05:03:38.341  1619  1619 E AndroidRuntime:        at android.os.Looper.loop(Looper.java:288)
05-09 05:03:38.341  1619  1619 E AndroidRuntime:        at android.app.ActivityThread.main(ActivityThread.java:7839)
05-09 05:03:38.341  1619  1619 E AndroidRuntime:        at java.lang.reflect.Method.invoke(Native Method)
05-09 05:03:38.341  1619  1619 E AndroidRuntime:        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(Runt

通过这个堆栈可以看出,因为无法启动这个服务,所以在ContextImpl中抛出了SecurityException异常,而且com.android.nfc本来是一个persist应用,所以会一直创建进程,一直crash,一直杀进程,这样手机不发热才怪呢。。

frameworks/base/core/java/android/app/ContextImpl.java

private boolean bindServiceCommon(Intent service, ServiceConnection conn, int flags,
            String instanceName, Handler handler, Executor executor, UserHandle user) {
        // ...
        try {
            // ...
            int res = ActivityManager.getService().bindIsolatedService(
                mMainThread.getApplicationThread(), getActivityToken(), service,
                service.resolveTypeIfNeeded(getContentResolver()),
                sd, flags, instanceName, getOpPackageName(), user.getIdentifier());
            if (res < 0) {
                throw new SecurityException(
                        "Not allowed to bind to service " + service);
            }
            return res != 0;
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

修复Nfc无限崩溃问题

发现这个问题后,我马上删除了ifw的规则文件,但这样又无法管控住这个应用的自启动,我尝试转换思路,只要我在nfc代码中catch住这个SecurityException异常,那是不是nfc就不会crash了,通过堆栈,我们很快定位到HostEmulationManager.java这个文件的报错,我们看下报错的代码

376      void bindPaymentServiceLocked(int userId, ComponentName service) {
377          unbindPaymentServiceLocked();
378  
379          Intent intent = new Intent(HostApduService.SERVICE_INTERFACE);
380          intent.setComponent(service);
381          mLastBoundPaymentServiceName = service;
382          if (mContext.bindServiceAsUser(intent, mPaymentConnection,
383                  Context.BIND_AUTO_CREATE | Context.BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS,
384                  new UserHandle(userId))) {
385            mPaymentServiceBound = true;
386          } else {
387              Log.e(TAG, "Could not bind (persistent) payment service.");
388          }
389      }

可以看到382行代码,正是我们一直crash的地方,执行着bindService的操作,但没有catch SecurityException的代码,所以当可以找到组件但无法启动这个service的时候,这里就会发生crash。有趣的是,我在这个类HostEmulationManager.java发现另外一个方法,这里会对bindServiceAsUser进行try catch,但看方法名后缀是IfNeeded,这在系统源码的一些约定俗成的规则代表可能执行也可能不执行,所以用try catch进行异常的捕捉。

Messenger bindServiceIfNeededLocked(ComponentName service) {
    if (mPaymentServiceName != null && mPaymentServiceName.equals(service)) {
        Log.d(TAG, "Service already bound as payment service.");
        return mPaymentService;
    } else if (mServiceName != null && mServiceName.equals(service)) {
        Log.d(TAG, "Service already bound as regular service.");
        return mService;
    } else {
        Log.d(TAG, "Binding to service " + service);
        unbindServiceIfNeededLocked();
        Intent aidIntent = new Intent(HostApduService.SERVICE_INTERFACE);
        aidIntent.setComponent(service);
        try {
            mServiceBound = mContext.bindServiceAsUser(aidIntent, mConnection,
                    Context.BIND_AUTO_CREATE | Context.BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS,
                    UserHandle.CURRENT);
            if (!mServiceBound) {
                Log.e(TAG, "Could not bind service.");
            }
        } catch (SecurityException e) {
            Log.e(TAG, "Could not bind service due to security exception.");
        }
        return null;
    }
}

而我的workaround则是模仿这个方法,对bindServiceAsUser进行try catch,但没仔细分析如果这个PaymentService不绑定的话会对系统nfc有多大的影响,但这个音乐软件没安装之前,也没有对应action的PaymentService,我猜对nfc影响应该不大。

void bindPaymentServiceLocked(int userId, ComponentName service) {
    unbindPaymentServiceLocked();

    Intent intent = new Intent(HostApduService.SERVICE_INTERFACE);
    intent.setComponent(service);
    mLastBoundPaymentServiceName = service;
    try {
        mPaymentServiceBound = mContext.bindServiceAsUser(intent, mPaymentConnection,
                Context.BIND_AUTO_CREATE | Context.BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS,
                new UserHandle(userId));
        if (!mPaymentServiceBound) {
            Log.e(TAG, "Could not bind service.");
        }
    } catch (SecurityException e) {
        Log.e(TAG, "Could not bind (persistent) payment service due to security exception.");
    }
}

修改后,我重新编译了Nfc的系统App,push到Nfc的扫描路径下、把规则重新push到路径data/system/ifw后重启,某音乐软件没有自启,且Nfc也不再崩溃了。

某音乐软件如何让自己被Nfc拉起

这里简单分析下音乐软件如何让自己被Nfc拉起,通过dumpsys package,发现以下信息

Service Resolver Table:
  Non-Data Actions:
      android.nfc.cardemulation.action.HOST_APDU_SERVICE:
        13200b3 com.xxxxxx.xxmusic/com.xxxxxx.xx.plugin.appbrand.jsapi.nfc.hce.HCEService filter bf30670 permission android.permission.BIND_NFC_SERVICE
          Action: "android.nfc.cardemulation.action.HOST_APDU_SERVICE"
          Category: "android.intent.category.DEFAULT"

通过在AndroidManifest.xml中把自己的服务HCEServiceAction: "android.nfc.cardemulation.action.HOST_APDU_SERVICE"进行绑定,用户重启手机的时候,nfc进程也会启动,nfc进程启动的时候会寻找android.nfc.cardemulation.action.HOST_APDU_SERVICE对应的组件进行绑定,这样就给第三方应用进程拉起来了。

说在后面

对于上面这样的情况,如果是普通用户,对这个音乐软件的启动根本毫无办法,就算通过ifw拦截了该组件,如果不修改nfc应用的代码,nfc进程无限崩溃,电量肉眼可见地掉下来,这也是不可接受的,而nfc系统软件是不可卸载的,因为是persist应用,自然也不能通过更新解决(修改过系统源码的话当我没说),可以说这个自启方法让我见识到了App自启的"决心",和ROM厂商在防止App自启上与App的斗智斗勇,不得不说,这个自启方法,如果不是手上有源码可以编译或者root的情况下(root可以删除nfc),基本就是无解的,国内软件生态还需继续加油。。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,684评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,143评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,214评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,788评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,796评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,665评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,027评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,679评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,346评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,664评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,766评论 1 331
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,412评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,015评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,974评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,073评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,501评论 2 343

推荐阅读更多精彩内容