如何在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中做了校驗,不允許僞造包名。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章