admin管理员组

文章数量:1540647

    近期,发现在部分Android手机调起其他应用时,会弹出一个系统弹窗拦截调起,需要用户二次确认。经过内部众测,发现绝大多数是小米手机,而且跟Android版本没有直接关系,猜测是MIUI某次升级引入的功能。这篇文章,我们从MIUI系统源码分析下该弹窗。

    我有一系列Android逆向相关的文章,这是我今年六月份关于反编译腾讯新闻去开屏广告的逆向实战文章,也欢迎大家阅读:

Android逆向实战 - 腾讯新闻去开屏广告_apktool去广告_一个玩游戏的程序猿的博客-CSDN博客文章浏览阅读2.3k次,点赞6次,收藏17次。上次反编译一个工具类app去广告失败,原因是使用了360加固,回编译后无法启动。一般来讲,大厂的app考虑到性能、兼容性、包体积等,通常不用加固。因此,本次我们选一个大一些的app-腾讯新闻。写在前面:本篇博客仅用来研究和学习。如有侵权,请联系我删除,谢谢。_apktool去广告https://blog.csdn/qq_21154101/article/details/130409016?spm=1001.2014.3001.5501

目录

一、背景

二、确定弹窗来源

三、反编译手机管家

四、framework代码分析 

1、获取framework

2、miui-services.jar分析

3、miui-framework.jar分析

五、疑似拦截白名单


一、背景

    最近,发现手机在调起淘宝或京东时,有时会弹窗让二次确认。尤其是双11期间,各大厂商app都在疯狂投放阿里、京东等电商app的广告。跟往年大家吐槽一不小心就跳到其他app不一样,今年我们发现很多手机会进行拦截,尤其是小米手机。在这里给小米点个赞,我也是忠实的小米用户。现象如下图:

二、确定弹窗来源

    当弹出该弹窗时,通过mCurrentFocus去确定来源:

adb shell
dumpsys window|grep Focus

    如下: 

mCurrentFocus=Window{c16c09e u0 com.miui.securitycenter/com.miui.wakepath.ui.ConfirmStartActivity}
  mFocusedApp=ActivityRecord{83602ab u0 com.miui.securitycenter/com.miui.wakepath.ui.ConfirmStartActivity} t5345}
      mPreferredTopFocusableRootTask=Task{5d40ca1 #5345 type=standard A=1000:com.miui.wakepath.ui.ConfirmStartActivit U=0 visible=true visibleRequested=true mode=fullscreen translucent=true sz=1}
      mLastFocusedRootTask=Task{5d40ca1 #5345 type=standard A=1000:com.miui.wakepath.ui.ConfirmStartActivit U=0 visible=true visibleRequested=true mode=fullscreen translucent=true sz=1}
    mFocusedWindow=Window{c16c09e u0 com.miui.securitycenter/com.miui.wakepath.ui.ConfirmStartActivity}
  mTopFocusedDisplayId=0

    可以发现,弹窗来源于com.miui.securitycenter/com.miui.wakepath.ui.ConfirmStartActivity,securitycenter也就是小米手机的手机管家。 

三、反编译手机管家

    下载手机管家app,反编译。在这里我用的最新的版本(v8.6.2.231116,也就是2023年11月16号的版本)。反编译的步骤在这不细说了,反编译后,搜索ConfirmStartActivity:

     

    看到如下代码: 

    第一部分代码是从intent中获取调起app的pkgname和被调起app的pkgname以及UserId等其他信息,这些信息获取后,在r0这个判断中有用到。看下r0这个方法,就是拿上面拿到的包名和UserId等做了一系列的判断:

 private boolean r0() {
        PackageInfo n10;
        PackageInfo n11;
        if (Build.VERSION.SDK_INT <= 33) {
            return false;
        }
        boolean z10 = false;
        for (UserHandle userHandle : ((UserManager) getSystemService("user")).getUserProfiles()) {
            if (userHandle.hashCode() == this.f18556g) {
                z10 = true;
            }
        }
        if (z10) {
            Object d10 = sf.e.d(f18552n, this, "getLaunchedFromUid", null, new Object[0]);
            return !(d10 == null || p1.b(((Integer) d10).intValue()) == this.f18557h) || (n10 = x0.n(this.mAppContext, this.f18554e)) == null || p1.b(n10.applicationInfo.uid) != p1.b(this.f18557h) || (n11 = x0.n(this.mAppContext, this.f18555f)) == null || p1.b(n11.applicationInfo.uid) != p1.b(this.f18558i) || this.f18553d == null;
        }
        return true;
    }

    在AndroidManifest文件中看,该Activity的配置了一个ACTION:CHECK_ALLOW_START_ACTIVITY,顾名思义:检验是否允许启动Activity。

   

    继续看,看到了弹窗的方法i0,而且其两个按钮文案的命名是"deny"和"accept",因此高度怀疑就是dp调起拦截弹窗代码:

    但是该弹窗代码只是构建弹窗的部分UI,并没有在ConfirmStartActivity中看到其调用的地方,看下其父类,发现在父类AlertActivity的onCreate中调用了i0方法: 

    而l0方法中调用了dialog.show(),该方法是在子类ConfirmStartActivity中k0调用的,k0又是在父类如上的onCreate方法最后调用的:

    也就是说,弹窗是在ConfirmStartActivity的onCreate中展现的。如果f10185c这个变量是true,那就return不出弹窗。f10185c这个变量在哪赋值的呢?又是在ConfirmStartActivity的j0里面赋值的,大家可以往上翻一下j0的代码。

四、framework代码分析 

1、获取framework

    通过公司内部众测我们发现,该弹窗只有在MIUI才有,目前没发现是MIUI的哪个版本有的,现在是猜测需要MIUI版本 > XXX并且手机管家版本大于XXX,二者缺一不可。

    我们看下framework的代码,在这里把framework和system_ext下的framework代码一并pull下来:

adb pull /system/framework

adb pull /system/system_ext/framework

    MIUI系统,system_ext下的framework代码主要是小米自己魔改的framework,比较经典的就是miui-framework.jar和miui-services.jar:

2、miui-services.jar分析

    直接反编译miui-services.jar,搜索ConfirmStartActivity:

    打开ConfirmStartHelper类看下,这里并没有看到启动ConfirmStartActivity的入口代码,但是看到了在isAllowStartCurrentActivity方法中用到手机管家,ACTION和ConfirmStartActivity包名类名进行判断的逻辑:

    isAllowStartCurrentActivity方法比较清晰:

  • 如果Uid不是1000才需要进行相关的判断,否则直接放行。1000代表的是MIUI系统应用(也就是MIUI对自己的应用默认开后门
  • 如果不是系统应用,判断Action是CONFIRM_START_ACTIVITY_ACTION,return false,不放行。
  • Intent的来源是手机管家并且Activity是ConfirmStartActivity,那就直接return false,不放行。

    可以看到,在miui-services.jar包中,相关的逻辑也就到此为止了。再搜索isAllowStartCurrentActivity也看不到更多的信息。因此,更多的逻辑可能还是在其他的framework代码里。(注意:以下篇幅也是经常在miui-services.jar和miui-framework.jar来回折腾

3、miui-framework.jar分析

    反编译miui-framework.jar,搜索CHECK_ALLOW_START_ACTIVITY:

    MiuiIntent中定义了Intent的一些Action,在这里不截图分析。看SecurityManager类中的调用,主要就在getCheckStartActivityIntent这个方法。这个方法比较长,第一部分是通过Action是PICK还是SEND来做挑选图库图片还是发送图片的判断,然后通过buildStartIntent方法去构建Intent:

    第二部分则是我们调研的场景:

规则大致如下:

  • 如果被调起的app已经启动,则return null,也就是不进行拦截。
  • 如果调起者为系统app(uid<10000)并且 IFAAFaceManager.ERR_FACE_LOCKED!=0,放行。
  • 如果调起者不为系统app(uid>=10000),但是被调起者为系统app并且 IFAAFaceManager.ERR_FACE_LOCKED!=0,放行。
  • 如果调起者与被调起者同包名,放行。
  • 否则,通过checkAllowStartActivity方法判断,判断通过放行。

    mService是SecurityManager的一个变量,可以看到是ISecurityManager,我们看下这个接口,这接口里面有两个实现类Proxy和Stub(这里就是用的代理模式),二者都继承了Binder,并且实现了ISecurityManager接口: 

    我们知道Binder是用来跨进程通信的,在这里简单说下Binder通信机制: 

(1)服务端创建对应Binder实例对象,接收来自客户端的请求,同时,将自身的Binder注册到ServiceManager。

(2)Binder驱动中创建对应mRemote对象。

(3)客户端想和服务端通信,通过ServiceManager查找到服务端的Binder,然后Binder驱动将对应的mRemote对象返回,至此已经建立完通信。

(4)在建立完毕通信之后,客户端通过调用mRemote对象的transact()方法,将数据发送到服务端,然后挂起自己等待回复。服务端收到数据后在onTransact()方法进行处理后将结果返回给客户端,客户端收到数据,重新拉起线程,至此进程间通信完毕。

    继续看下去Proxy类中checkAllowStartActivity的实现:

    看Stub类的实现:

    上面我们也提到了,Stub继承了Binder,该调用是在onTransact方法中:

     所以,以上代码简单整理一下:

(1)客户端在_data写入了调起者和被调起者的包名、uid等信息,这些信息通过mRemote对象的transact()方法传递给服务端

(2)服务端处理后返回给客户端,客户端通过_replay.readBoolean去获取判断结果,决定是否放行。

五、疑似拦截白名单

    Stub是一个抽象类,其实现类SecurityManagerService在miui-services.jar中。这个类里面有个AccessController对象,看其作用,其中有几个方法例如filterIntentLocked,skipActivity等:

     进一步看checkAccessControlPassLockedCore方法的调用链,可以看到是如下两个方法:

    我们进一步看AccessController这个类,看到了疑似拦截的白名单代码。首先,有一套本地写死的白名单,如下:

    其次,还提供了服务端更新白名单的逻辑: 

    看下mSkipList的使用,在filterIntentLocked方法中,主要就是判断packageName是否在白名单中: 

     咱们一开始其实是根据Intent的Action - CHECK_ALLOW_START_ACTIVITY来跟踪的,定位到了getCheckStartActivityIntent方法,其调用来源在这如下代码这。上面的这个拦截白名单的使用并没有跟我们的场景关联起来,而是在另一种场景,其调用的入口如下图的最后一行代码:

    因此,该白名单大概率不是调起三方应用的那个场景,而是其他的场景。 试想一下,针对调起三方应用弹窗的场景:如果小米对支付宝加白放行,没对微信或抖音放行,那么不会引起公愤吗?所以,我认为这必然不是该场景。

 最后,分享一款免费看热门电影和电视剧的app,仅限Android:

本文标签: 三方实战系统androidmiui