如何在Activity中获取调用者包名,以及如何通过伪造mReferrerr让Activity无法获取调用者的包名

背景

如果你写了一个Android App,该App的某些Activity是export的,那么这些Activity可以被任何其他App调起来。调起的方式大家都很熟悉,就是用startActivity。

现在有个需求,我想知道我的Activity会不会被恶意调起。如果要实现这个需求,我就要能够准确的知道调用者的包名。所以现在的问题是:

能否准确检测到调起我Activity的应用的包名

可选方案

作为一个工作了几年,有一些技术积累的Android从业者,我们凭着自己的经验,可以说出以下几个可选方案。

  1. 使用Binder.getCallingUid获取调用者的uid
  2. 调用Activity的getReferrer方法
  3. 调用Activity的getCallingPackage方法
  4. 调用Activity的getCallingActivity方法

其实上面的四个方案都是不靠谱的。下面我们逐个分析。


使用Binder.getCallingUid获取调用者的uid

该方法之所以不靠谱,是因为该方法只在Binder线程中才有效。至于什么是Binder线程,那就是另一个话题了,在此不过多说明了,只是简单的提一下。Binder线程可以认为是使用Binder通信机制的工作线程,只要一个进程使用了Binder进行通信,就会有对应的Binder线程。但是在Activity中,我们是无法touch到Binder线程的。虽然系统启动Activity的时候,是通过Binder机制调到ActivityThread中,ActivityThread中启动Activity的早期代码确实是在Binder线程中执行的,但是系统会很快将启动Activity的逻辑放到主线程中。所以我们能接触到的onCreate等生命周期方法都是在主线程中执行的。
综上所属,调用Binder.getCallingUid无效。

下面我们贴出Binder.getCallingUid方法的源码注释。

    /**
     * Return the Linux uid assigned to the process that sent you the
     * current transaction that is being processed.  This uid can be used with
     * higher-level system services to determine its identity and check
     * permissions.  If the current thread is not currently executing an
     * incoming transaction, then its own uid is returned.
     */
    public static final native int getCallingUid();

最关键的是这一句:

If the current thread is not currently executing an incoming transaction, then its own uid is returned

翻译过来就是:

如果当前线程没有在处理一个transaction,那么就返回当前进程自己的uid

这里的transaction是Binder机制中的一个术语,可以认为是一次Binder数据传输,也就是正在执行Binder任务。而只有Binder线程才会执行Binder任务,所以,又可以这么翻译:

如果当前线程不是binder线程,那么就返回当前进程自己的uid

其实话说回来,即使我们在Activity中能touch到Binder线程,即使onCreate方法是在Binder线程中执行的,我们也拿不到调用方App的uid。因为调用方启动Activity的startActivity方法,首先要通过Binder调到SystemServer中,再由SystemServer通过Binder调到我自己的App中,所以我只会获取到SystemServer进程的uid,也就是system uid 1000。


调用Activity的getReferrer方法

该方法不靠谱,是因为Referer可以被调用者更改,导致我们拿到的是伪装过的Rererrer。通过源码看一下:

/**
     * Return information about who launched this activity.  If the launching Intent
     * contains an {@link android.content.Intent#EXTRA_REFERRER Intent.EXTRA_REFERRER},
     * that will be returned as-is; otherwise, if known, an
     * {@link Intent#URI_ANDROID_APP_SCHEME android-app:} referrer URI containing the
     * package name that started the Intent will be returned.  This may return null if no
     * referrer can be identified -- it is neither explicitly specified, nor is it known which
     * application package was involved.
     *
     * <p>If called while inside the handling of {@link #onNewIntent}, this function will
     * return the referrer that submitted that new intent to the activity.  Otherwise, it
     * always returns the referrer of the original Intent.</p>
     *
     * <p>Note that this is <em>not</em> a security feature -- you can not trust the
     * referrer information, applications can spoof it.</p>
     */
    @Nullable
    public Uri getReferrer() {
        Intent intent = getIntent();
        try {
            Uri referrer = intent.getParcelableExtra(Intent.EXTRA_REFERRER);
            if (referrer != null) {
                return referrer;
            }
            String referrerName = intent.getStringExtra(Intent.EXTRA_REFERRER_NAME);
            if (referrerName != null) {
                return Uri.parse(referrerName);
            }
        } catch (BadParcelableException e) {
            Log.w(TAG, "Cannot read referrer from intent;"
                    + " intent extras contain unknown custom Parcelable objects");
        }
        if (mReferrer != null) {
            return new Uri.Builder().scheme("android-app").authority(mReferrer).build();
        }
        return null;
    }

看到代码后,就不用多说了。因为referrer是首先从Intent中获取的,而Intent是由调用者传的,所以,调用者完全可以传入一个假的referrer。

再看这个方法的注释的最后一句,Android官方都明说了,这个方法不靠谱-_-

Note that this is not a security feature -- you can not trust the referrer information, applications can spoof it.


调用Activity的getCallingPackage方法或者getCallingActivity

这两个方法之所以不靠谱,是因为只有在调用者通过startActivityForResult来启动我的Activity的时候,我才能通过这两个方法拿到调用这的包名或者activity名。

还是直接贴一下源码和源码注释:

    /**
     * Return the name of the package that invoked this activity.  This is who
     * the data in {@link #setResult setResult()} will be sent to.  You can
     * use this information to validate that the recipient is allowed to
     * receive the data.
     *
     * <p class="note">Note: if the calling activity is not expecting a result (that is it
     * did not use the {@link #startActivityForResult}
     * form that includes a request code), then the calling package will be
     * null.</p>
     *
     * <p class="note">Note: prior to {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2},
     * the result from this method was unstable.  If the process hosting the calling
     * package was no longer running, it would return null instead of the proper package
     * name.  You can use {@link #getCallingActivity()} and retrieve the package name
     * from that instead.</p>
     *
     * @return The package of the activity that will receive your
     *         reply, or null if none.
     */
    @Nullable
    public String getCallingPackage() {
        try {
            return ActivityManager.getService().getCallingPackage(mToken);
        } catch (RemoteException e) {
            return null;
        }
    }
    /**
     * Return the name of the activity that invoked this activity.  This is
     * who the data in {@link #setResult setResult()} will be sent to.  You
     * can use this information to validate that the recipient is allowed to
     * receive the data.
     *
     * <p class="note">Note: if the calling activity is not expecting a result (that is it
     * did not use the {@link #startActivityForResult}
     * form that includes a request code), then the calling package will be
     * null.
     *
     * @return The ComponentName of the activity that will receive your
     *         reply, or null if none.
     */
    @Nullable
    public ComponentName getCallingActivity() {
        try {
            return ActivityManager.getService().getCallingActivity(mToken);
        } catch (RemoteException e) {
            return null;
        }
    }

这两个方法的注释中,都有这么一段:

     * if the calling activity is not expecting a result (that is it
     * did not use the {@link #startActivityForResult}
     * form that includes a request code), then the calling package will be
     * null.

意思就是如果不是通过startActivityForResult调起的,调用这两个方法返回null。


反射Activity的mReferrer可以获取调用者包名

除了以上的不靠谱方案,还有其他方案吗?答案是有的。那就是反射Activity的成员变量 mReferrer。mReferrer在Activity.java中的定义如下:

/*package*/ String mReferrer;

这是一个成员变量。通过这个成员变量,我们可以获取到调用者的包名。我之前并不知道这个成员变量,一次偶然的机会,通过反编译某大厂的某App,发现他们是通过这个成员变量获取调用者的包名。

我写了个ReferrerTarget的demo,确实可以拿到具体的调用者包名,代码如下:

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        String referrer = reflectGetReferrer();
        Log.i("ReferrerTest", "get referrer : " + referrer);
    }

    private String reflectGetReferrer() {
        try {
            Field referrerField =
                    Activity.class.getDeclaredField("mReferrer");
            referrerField.setAccessible(true);
            return (String)referrerField.get(this);
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
        return "";
    }
}

我在桌面上点击该demo的图标启动MainActivity,可以看到如下日志:

11-27 19:08:29.933 5877 5877 I ReferrerTest: get referrer : com.oppo.launcher

我用的oppo的手机测试的,可以看到,通过反射mReferrer这个成员变量,我们获取到调用者的包名com.oppo.launcher,也就是oppo的桌面。

mReferrer可以被伪造

上面我们已经验证了,通过反射mReferrer可以获取到调用者的包名。但是这个包名能够确保是正确的吗?也就是说,这个字段能被伪造吗?其实是可以被伪造的。想要了解这个信息,我们就必须看源码了(在代码的海洋里遨游,我痛并快乐着,哈哈)。


我们就要看mReferrer是怎么赋值的,代码在Activity.java的attach方法中:

 final void attach(Context context, ActivityThread aThread,
            Instrumentation instr, IBinder token, int ident,
            Application application, Intent intent, ActivityInfo info,
            CharSequence title, Activity parent, String id,
            NonConfigurationInstances lastNonConfigurationInstances,
            Configuration config, String referrer, IVoiceInteractor voiceInteractor,
            Window window, ActivityConfigCallback activityConfigCallback) {
        attachBaseContext(context);

        mFragments.attachHost(null /*parent*/);

        mWindow = new PhoneWindow(this, window, activityConfigCallback);
        mWindow.setWindowControllerCallback(this);
        mWindow.setCallback(this);
        mWindow.setOnWindowDismissedCallback(this);
        mWindow.getLayoutInflater().setPrivateFactory(this);
        if (info.softInputMode != WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED) {
            mWindow.setSoftInputMode(info.softInputMode);
        }
        if (info.uiOptions != 0) {
            mWindow.setUiOptions(info.uiOptions);
        }
        mUiThread = Thread.currentThread();

        mMainThread = aThread;
        mInstrumentation = instr;
        mToken = token;
        mIdent = ident;
        mApplication = application;
        mIntent = intent;
        mReferrer = referrer;
        mComponent = intent.getComponent();
        mActivityInfo = info;
        mTitle = title;
        mParent = parent;
        mEmbeddedID = id;

attach方法中有个参数String referrer,在执行attach方法的时候,将这个参数值赋值到mReferrer成员变量:

mReferrer = referrer;

因为这个attach方法是在framework中调用的,这么看来好像没法伪造。但是作为一个喜欢刨根问底的程序员,直觉告诉我,不能太想当然。如果这个值是SystemServer中填充的,然后发送到App中的,那么可以认为无法伪造,但是如果这个值依赖于调用者的传入,那么很可能可以被伪造。这么讲的话大家可能一头雾水,如果看不明白,请跟着我的思路理代码,过程比较长,但是答案总会水落石出。

那么接下来,我们看一下attach方法的调用逻辑(因为系统代码太多,所以进行了删减,只留下关键代码)。

 private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
        ......
        ......
        
        ContextImpl appContext = createBaseContextForActivity(r);
        Activity activity = null;
        try {
            java.lang.ClassLoader cl = appContext.getClassLoader();
            activity = mInstrumentation.newActivity(
                    cl, component.getClassName(), r.intent);
            StrictMode.incrementExpectedActivityCount(activity.getClass());
            r.intent.setExtrasClassLoader(cl);
            r.intent.prepareToEnterProcess();
            if (r.state != null) {
                r.state.setClassLoader(cl);
            }
        } catch (Exception e) {
            if (!mInstrumentation.onException(activity, e)) {
                throw new RuntimeException(
                    "Unable to instantiate activity " + component
                    + ": " + e.toString(), e);
            }
        }
        ......
        ......

        try {
           
                appContext.setOuterContext(activity);
                activity.attach(appContext, this, getInstrumentation(), r.token,
                        r.ident, app, r.intent, r.activityInfo, title, r.parent,
                        r.embeddedID, r.lastNonConfigurationInstances, config,
                        r.referrer, r.voiceInteractor, window, r.configCallback);

                if (customIntent != null) {
                    activity.mIntent = customIntent;
                }
                r.lastNonConfigurationInstances = null;
                checkAndBlockForNetworkAccess();
                activity.mStartedActivity = false;
                int theme = r.activityInfo.getThemeResource();
                if (theme != 0) {
                    activity.setTheme(theme);
                }

Activity的attach方法,是在ActivityThread的performLaunchActivity方法中调用的。该方法是Activity启动流程的一部分,在该方法中,首先创建出Activity的实例,然后调用attach方法:

                activity.attach(appContext, this, getInstrumentation(), r.token,
                        r.ident, app, r.intent, r.activityInfo, title, r.parent,
                        r.embeddedID, r.lastNonConfigurationInstances, config,
                        r.referrer, r.voiceInteractor, window, r.configCallback);

从这里我们看到,attach方法调用的时候,参数String referrer对应的是传入的r.referrer,这里的r是ActivityThread的performLaunchActivity方法的参数ActivityClientRecord r。 那么我们继续追踪ActivityClientRecord r是在哪里传过来的,r.referrer是怎么来的。接下来,我们要看ActivityThread的performLaunchActivity方法是在哪里调用的。

经过查看源码,可以看到ActivityThread的performLaunchActivity方法是在ActivityThread的handleLaunchActivity方法中调用的:

private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent, String reason) {
        ......
        ......
        
        Activity a = performLaunchActivity(r, customIntent);
        ......
        ......

ActivityThread的handleLaunchActivity方法是在ActivityThread.H的handleMessage中调用的。这个ActivityThread.H是个Handler,负责将Binder线程中接收到的任务,转到主线程中去执行,这也是为什么四大组件的生命周期方法都是在主线程执行的原因:

 public void handleMessage(Message msg) {
            if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what));
            switch (msg.what) {
                case LAUNCH_ACTIVITY: {
                    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStart");
                    final ActivityClientRecord r = (ActivityClientRecord) msg.obj;

                    r.packageInfo = getPackageInfoNoCheck(
                            r.activityInfo.applicationInfo, r.compatInfo);
                    handleLaunchActivity(r, null, "LAUNCH_ACTIVITY");
                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                } break;

从这里我们可以追溯到 ActivityClientRecord r是放在Message的obj中带过来的:

final ActivityClientRecord r = (ActivityClientRecord) msg.obj;

接下来,我们看一下使用H这个Handler发送LAUNCH_ACTIVITY消息的地方,该代码还是在ActivityThread中,发送消息的具体方法为ActivityThread.ApplicationThread的scheduleLaunchActivity方法:

        @Override
        public final void scheduleLaunchActivity(Intent intent, IBinder token, int ident,
                ActivityInfo info, Configuration curConfig, Configuration overrideConfig,
                CompatibilityInfo compatInfo, String referrer, IVoiceInteractor voiceInteractor,
                int procState, Bundle state, PersistableBundle persistentState,
                List<ResultInfo> pendingResults, List<ReferrerIntent> pendingNewIntents,
                boolean notResumed, boolean isForward, ProfilerInfo profilerInfo) {

            updateProcessState(procState, false);

            ActivityClientRecord r = new ActivityClientRecord();

            r.token = token;
            r.ident = ident;
            r.intent = intent;
            r.referrer = referrer;
            r.voiceInteractor = voiceInteractor;
            r.activityInfo = info;
            r.compatInfo = compatInfo;
            r.state = state;
            r.persistentState = persistentState;

            r.pendingResults = pendingResults;
            r.pendingIntents = pendingNewIntents;

            r.startsNotResumed = notResumed;
            r.isForward = isForward;

            r.profilerInfo = profilerInfo;

            r.overrideConfig = overrideConfig;
            updatePendingConfiguration(curConfig);

            sendMessage(H.LAUNCH_ACTIVITY, r);
        }

这里说一下这个ApplicationThread,他是一个Binder对象,用于接收从SystemServer发送过来的Binder任务。scheduleLaunchActivity方法上有个String referrer,在scheduleLaunchActivity中,创建了ActivityClientRecord r,并将String referrer赋值给r.referrer:

ActivityClientRecord r = new ActivityClientRecord();
......
r.referrer = referrer;

但是到了这里,我们没有办法在ActivityThread中跟踪scheduleLaunchActivity的调用了,因为上面说了ActivityThread.ApplicationThread是一个Binder对象(具体来说,是一个Binder服务对象,或者叫Binder实体对象)。调用它的地方,在SystemServer中,因为是SystemServer通知当前App去启动一个Activity的(对Activity启动流程不熟的朋友,可以在网上搜一些资料)。接下来,我们在SystemServer中找调用ActivityThread.ApplicationThread.scheduleLaunchActivity的地方。是在ActivityStackSupervisor的realStartActivityLocked方法中:

    final boolean realStartActivityLocked(ActivityRecord r, ProcessRecord app,
            boolean andResume, boolean checkConfig) throws RemoteException {
            ......
            ......
            app.thread.scheduleLaunchActivity(new Intent(r.intent), r.appToken,
                        System.identityHashCode(r), r.info,
                        // TODO: Have this take the merged configuration instead of separate global
                        // and override configs.
                        mergedConfiguration.getGlobalConfiguration(),
                        mergedConfiguration.getOverrideConfiguration(), r.compat,
                        r.launchedFromPackage, task.voiceInteractor, app.repProcState, r.icicle,
                        r.persistentState, results, newIntents, !andResume,
                        mService.isNextTransitionForward(), profilerInfo);

这里的 app.thread是一个IApplicationThread对象,可以认为是ActivityThread.ApplicationThread的Binder客户端,调用app.thread.scheduleLaunchActivity,就会调到ActivityThread.ApplicationThread.scheduleLaunchActivity。app.thread.scheduleLaunchActivity的r.launchedFromPackage就对应ActivityThread.ApplicationThread.scheduleLaunchActivity方法的String referrer参数。这里的r.launchedFromPackage中的r是一个ActivityRecord对象,不要和ActivityThread中的ActivityClientRecord对象搞混了。 ActivityRecord在SystemServer(或者说ActivityManagerService,其实ActivityManagerService是SystemServer中的一个服务,专门管理应用程序的进程和四大组件的)中就对应App中的一个Activity对象。

总结一下以上的流程,ActivityManagerService中ActivityRecord的launchedFromPackage,就是Activity中的mReferrer。launchedFromPackage在ActivityRecord中的定义如下:

final String launchedFromPackage; // always the package who started the activity.

所以我们接下来要继续追踪ActivityRecord的launchedFromPackage是从哪里来的。

launchedFromPackage是在ActivityRecord的构造方法中传入的。

    ActivityRecord(ActivityManagerService _service, ProcessRecord _caller, int _launchedFromPid,
            int _launchedFromUid, String _launchedFromPackage, Intent _intent, String _resolvedType,
            ActivityInfo aInfo, Configuration _configuration,
            ActivityRecord _resultTo, String _resultWho, int _reqCode,
            boolean _componentSpecified, boolean _rootVoiceInteraction,
            ActivityStackSupervisor supervisor, ActivityOptions options,
            ActivityRecord sourceRecord) {
        service = _service;
        appToken = new Token(this);
        info = aInfo;
        launchedFromPid = _launchedFromPid;
        launchedFromUid = _launchedFromUid;
        launchedFromPackage = _launchedFromPackage;

构造ActivityRecord的地方,是在ActivityStarter的startActivity方法中:

    private int startActivity(IApplicationThread caller, Intent intent, Intent ephemeralIntent,
            String resolvedType, ActivityInfo aInfo, ResolveInfo rInfo,
            IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,
            IBinder resultTo, String resultWho, int requestCode, int callingPid, int callingUid,
            String callingPackage, int realCallingPid, int realCallingUid, int startFlags,
            ActivityOptions options, boolean ignoreTargetSecurity, boolean componentSpecified,
            ActivityRecord[] outActivity, TaskRecord inTask) {
            ......
            ......
            ActivityRecord r = new ActivityRecord(mService, callerApp, callingPid, callingUid,
                callingPackage, intent, resolvedType, aInfo, mService.getGlobalConfiguration(),
                resultRecord, resultWho, requestCode, componentSpecified, voiceSession != null,
                mSupervisor, options, sourceRecord);
                
            ......
            ......

这里的callingPackage,就对应ActivityRecord的launchedFromPackage。 接下来我们就追踪callingPackage的来源。

调用startActivity的是ActivityStarter的startActivityLocked方法:

    int startActivityLocked(IApplicationThread caller, Intent intent, Intent ephemeralIntent,
            String resolvedType, ActivityInfo aInfo, ResolveInfo rInfo,
            IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,
            IBinder resultTo, String resultWho, int requestCode, int callingPid, int callingUid,
            String callingPackage, int realCallingPid, int realCallingUid, int startFlags,
            ActivityOptions options, boolean ignoreTargetSecurity, boolean componentSpecified,
            ActivityRecord[] outActivity, TaskRecord inTask, String reason) {

        if (TextUtils.isEmpty(reason)) {
            throw new IllegalArgumentException("Need to specify a reason.");
        }
        mLastStartReason = reason;
        mLastStartActivityTimeMs = System.currentTimeMillis();
        mLastStartActivityRecord[0] = null;

        mLastStartActivityResult = startActivity(caller, intent, ephemeralIntent, resolvedType,
                aInfo, rInfo, voiceSession, voiceInteractor, resultTo, resultWho, requestCode,
                callingPid, callingUid, callingPackage, realCallingPid, realCallingUid, startFlags,
                options, ignoreTargetSecurity, componentSpecified, mLastStartActivityRecord,
                inTask);

ActivityStarter的startActivityLocked方法是在ActivityStarter的startActivityMayWait中调用的:

    final int startActivityMayWait(IApplicationThread caller, int callingUid,
            String callingPackage, Intent intent, String resolvedType,
            IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,
            IBinder resultTo, String resultWho, int requestCode, int startFlags,
            ProfilerInfo profilerInfo, WaitResult outResult,
            Configuration globalConfig, Bundle bOptions, boolean ignoreTargetSecurity, int userId,
            TaskRecord inTask, String reason) {
            ......
            ......
            int res = startActivityLocked(caller, intent, ephemeralIntent, resolvedType,
                    aInfo, rInfo, voiceSession, voiceInteractor,
                    resultTo, resultWho, requestCode, callingPid,
                    callingUid, callingPackage, realCallingPid, realCallingUid, startFlags,
                    options, ignoreTargetSecurity, componentSpecified, outRecord, inTask,
                    reason);

ActivityStarter的startActivityMayWait方法,是在ActivityManagerService的startActivityAsUser中调用的:

    public final int startActivityAsUser(IApplicationThread caller, String callingPackage,
            Intent intent, String resolvedType, IBinder resultTo, String resultWho, int requestCode,
            int startFlags, ProfilerInfo profilerInfo, Bundle bOptions, int userId) {
        enforceNotIsolatedCaller("startActivity");
        userId = mUserController.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(),
                userId, false, ALLOW_FULL_ONLY, "startActivity", null);
        // TODO: Switch to user app stacks here.
        return mActivityStarter.startActivityMayWait(caller, -1, callingPackage, intent,
                resolvedType, null, null, resultTo, resultWho, requestCode, startFlags,
                profilerInfo, null, null, bOptions, false, userId, null, "startActivityAsUser");
    }

代码流程又长了,总结一下:

ActivityManagerService.startActivityAsUser中的callingPackage,就是ActivtyRecord中的launchedFromPackage,就是ActivityThread中的ActivityClientRecord.referrer,就是Activity中的mReferrer。

所以,要追踪Activity中的mReferrer的来源,我们要继续追踪ActivityManagerService.startActivityAsUser中的callingPackage参数。

但是到了这里,我们没有办法继续在ActivityManagerService中追踪callingPackage的来源。因为ActivityManagerService的startActivityAsUser方法,是一个Binder方法。调用它的地方,在app中。以上面的demo(ReferrerTarget)为例:

我在桌面上点了ReferrerTarget图标,桌面会首先调用到ActivityManagerService中,ActivityManagerService再负责调起ReferrerTarget的MainActivity。

上面的代码流程,是ActivityManagerService调起ReferrerTarget的MainActivity的流程。现在我们要继续分析之前的那一步,也就是桌面调ActivityManagerService的步骤。因为是桌面在调用ActivityManagerService的startActivityAsUser方法的时候,传入的callingPackage这个参数。而这个参数,又经过复杂的流程,转成的Activity的mReferrer。

分析是怎么调到ActivityManagerService的startActivityAsUser方法的,其实也就是分析startActivity的执行流程。

我们先看Activity的startActivit方法:

    public void startActivity(Intent intent) {
        this.startActivity(intent, null);
    }
    public void startActivity(Intent intent, @Nullable Bundle options) {
        if (options != null) {
            startActivityForResult(intent, -1, options);
        } else {
            // Note we want to go through this call for compatibility with
            // applications that may have overridden the method.
            startActivityForResult(intent, -1);
        }
    }

可以看到,我们调用startActivity的时候,并没有传入callingPackage参数,但是当调到ActivityManagerService的startActivityAsUser方法的时候,却出现了callingPackage参数,所以,肯定是调用流程中间的某一步,加入了这个参数。

我们继续分析startActivity的调用流程,startActivity会调用startActivityForResult。

    public void startActivityForResult(@RequiresPermission Intent intent, int requestCode) {
        startActivityForResult(intent, requestCode, null);
    }
public void startActivityForResult(@RequiresPermission Intent intent, int requestCode,
            @Nullable Bundle options) {
        if (mParent == null) {
            options = transferSpringboardActivityOptions(options);
            Instrumentation.ActivityResult ar =
                mInstrumentation.execStartActivity(
                    this, mMainThread.getApplicationThread(), mToken, this,
                    intent, requestCode, options);
            if (ar != null) {
                mMainThread.sendActivityResult(
                    mToken, mEmbeddedID, requestCode, ar.getResultCode(),
                    ar.getResultData());
            }
            if (requestCode >= 0) {
                // If this start is requesting a result, we can avoid making
                // the activity visible until the result is received.  Setting
                // this code during onCreate(Bundle savedInstanceState) or onResume() will keep the
                // activity hidden during this time, to avoid flickering.
                // This can only be done when a result is requested because
                // that guarantees we will get information back when the
                // activity is finished, no matter what happens to it.
                mStartedActivity = true;
            }

            cancelInputsAndStartExitTransition(options);
            // TODO Consider clearing/flushing other event sources and events for child windows.
        } else {
            if (options != null) {
                mParent.startActivityFromChild(this, intent, requestCode, options);
            } else {
                // Note we want to go through this method for compatibility with
                // existing applications that may have overridden it.
                mParent.startActivityFromChild(this, intent, requestCode);
            }
        }
    }

startActivityForResul会调用Instrumentation的execStartActivity方法。这里请注意一下调用Instrumentation的execStartActivity方法的时候传入的第一个参数this,也就是当前的Activity。我们看一下Instrumentation的execStartActivity方法:

public ActivityResult execStartActivity(
            Context who, IBinder contextThread, IBinder token, Activity target,
            Intent intent, int requestCode, Bundle options) {
        android.util.SeempLog.record_str(377, intent.toString());
        IApplicationThread whoThread = (IApplicationThread) contextThread;
        
        ......
        ......
        
        try {
            intent.migrateExtraStreamToClipData();
            intent.prepareToLeaveProcess(who);
            int result = ActivityManager.getService()
                .startActivity(whoThread, who.getBasePackageName(), intent,
                        intent.resolveTypeIfNeeded(who.getContentResolver()),
                        token, target != null ? target.mEmbeddedID : null,
                        requestCode, 0, null, options);
            checkStartActivityResult(result, intent);
        } catch (RemoteException e) {
            throw new RuntimeException("Failure from system", e);
        }
        return null;
    }

上一步传入的this,是当前发起startActivity调用的activity实例。具体一点,如果从桌面上点击了一个图标,桌面从startActivity开始启动一个app(如我的demo ReferrerTarget),那么这个this就是桌面。

execStartActivity中,调用ActivityManager.getService().startActivity,就会通过Binder机制,调用到ActivityManagerService的startActivityAsUser方法,这里传入的第二个参数who.getBasePackageName(),就对应ActivityManagerService的startActivityAsUser方法的callingPackage参数。

这里总结一下:

这个who.getBasePackageName(), 就是ctivityManagerService的startActivityAsUser方法的callingPackage参数,就是被调起的Activity中的mReferrer。

追踪到这里,我们终于追踪到被调起的Activity中的mReferrer是从哪里来的。

我们再继续分析who.getBasePackageName(),根据上面的分析,我们知道这who,就是发起startActivity调用的当前Activity。(如果在桌面点击图标启动ReferrerTaget,可以认为这里的who就是桌面Activity)

当分析到这里,我们就可以得出结论,如果getBasePackageName可以伪装,那么被调起Activity的mReferrer就能伪装。

那么我们看下getBasePackageName能不能伪装呢? 看下getBasePackageName的源码:

    public String getBasePackageName() {
        return mBase.getBasePackageName();
    }

这里要注意,getBasePackageName是在Activity的父类ContextWrapper中的,并且是public的,说明我们在自己的Activity中可以覆盖该方法。

如果我们覆盖了这个方法,返回假的包名,那么在被调起Activity中获取mReferrer的时候,就会获取这个伪造的假包名。


验证mReferrer可以被伪造

下面我们写个demo验证一下上面的结论。上面已经写了一个demo叫做ReferrerTarget,我们再写一个ReferrerCaller,在ReferrerCaller中调起ReferrerTarget。

public class ReferrerCallerActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_referrer_caller);
        findViewById(R.id.startReferrerTarget).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Log.i("ReferrerTest", "start ReferrerTarget from ReferrerCaller");
                Intent intent = new Intent();
                intent.setComponent(new ComponentName("com.zhangjg.referrertest.target",
                        "com.zhangjg.referrertest.target.MainActivity"));
                startActivity(intent);
            }
        });
    }
}

点击按钮,启动ReferrerTarget,可以看到如下日志:

11-27 21:59:31.986 11392 11392 I ReferrerTest: start ReferrerTarget from ReferrerCaller
11-27 21:59:32.249 11463 11463 I ReferrerTest: get referrer : com.zhangjg.referrertest.referrercaller

可以看到,在ReferrerTarget中通过反射mReferrer,可以检测到调用者是ReferrerCaller(包名com.zhangjg.referrertest.referrercaller)。

那么,我们在ReferrerCallerActivity中实现getBasePackageName方法,在该方法中返回假包名:

public class ReferrerCallerActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_referrer_caller);
        findViewById(R.id.startReferrerTarget).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Log.i("ReferrerTest", "start ReferrerTarget from ReferrerCaller");
                Intent intent = new Intent();
                intent.setComponent(new ComponentName("com.zhangjg.referrertest.target",
                        "com.zhangjg.referrertest.target.MainActivity"));
                startActivity(intent);
            }
        });
    }

    public String getBasePackageName() {
        return "fake_package";
    }
}

再次点击ReferrerCallerActivity中的按钮,启动ReferrerTarget,可以看到日志如下:

11-27 22:09:18.665 15196 15196 I ReferrerTest: start ReferrerTarget from ReferrerCaller
11-27 22:09:18.777 15510 15510 I ReferrerTest: get referrer : fake_package

可以看到ReferrerTarget中通过反射mReferrer,获取到的是伪造的包名。

所以这里可以得出结论:

通过在调用方Activity中实现getBasePackageName方法,在该方法中返会假包名,那么这个Activity在调起目标Activity的时候,在目标Activity中无法通过反射mReferrer获取正确的调用者包名,也就是说mReferrer是可以伪造的。

同样的道理:

通过在调用方Service中实现getBasePackageName方法,在该方法中返会假包名,那么这个Servcie在调起目标Activity的时候,在目标Activity中无法通过反射mReferrer获取正确的调用者包名,也就是说mReferrer是可以伪造的。

在Service中伪造getBasePackageName也是验证过的,这里就不放代码了。原理和在Activity中伪造getBasePackageName是一样的。在Service中调用startActivity的源码,请读者自己分析一下。

最后还有一点需要说明,有些厂商是禁止通过getBasePackageName伪造调用着包名的,如oppo。因为在oppo上运行demo的时候,发现ReferrerTarget没有启动起来,有如下日志:

11-27 22:07:08.201  1044  2288 I OppoAppStartupManager: InterceptInfo fake_package  com.zhangjg.referrertest.target  com.zhangjg.referrertest.target.MainActivity  com.zhangjg.referrertest.referrercaller  true

可以确认oppo的系统在OppoAppStartupManager中做了校验,不允许伪造包名。

  • 8
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值