<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Android Activity Deeplink啟動來源獲取源碼分析

          共 7806字,需瀏覽 16分鐘

           ·

          2021-11-13 16:37

          作者:vivo互聯(lián)網(wǎng)客戶端團隊-Chen Long


          一般來講,Android開發(fā)人員會使用反射調(diào)用Acticity中的mReferrer字段來獲取跳轉(zhuǎn)來源的包名,但是這種方案是否真的安全呢?我們從源碼中來尋找答案。


          • 前言

          • mReferrer從哪里來

            • 搜索mReferrer,來源回溯

            • 使用斷點調(diào)試跟蹤調(diào)用棧

            • 斷點調(diào)試

            • 對象實例化過程

            • 遠(yuǎn)程服務(wù)Binder調(diào)用分析

            • 萬能的搜索并不萬能

          • 如何避免包名被偽造

            • 關(guān)注Pid和Uid

            • 調(diào)研Uid是否有偽造的可能性

            • 使用Uid置換PackageName

          • 總結(jié)


          一、前言



          目前有很多的業(yè)務(wù)模塊提供了Deeplink服務(wù),Deeplink簡單來說就是對外部應(yīng)用提供入口。


          針對不同的跳入類型,app可能會選擇提供不一致的服務(wù),這個時候就需要對外部跳入的應(yīng)用進行區(qū)分。一般來講,我們會使用反射來調(diào)用Acticity中的mReferrer字段來獲取跳轉(zhuǎn)來源的包名。


          具體代碼如下;

          /** * 通過反射獲取referrer * @return */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 "";}


          但是mReferrer有沒有被偽造的可能呢?


          一旦mReferrer被偽造,輕則業(yè)務(wù)邏輯出錯,重則造成經(jīng)濟損失,針對這種情況,有沒有辦法找到一種較為安全的來源獲取方法呢?


          這就需要對mReferrer的來源進行一次分析。下面我們來進行一次mReferrer來源的另類源碼分析。之所以說另類,是因為這次會大量使用調(diào)試手段來逆向進行源碼分析。


          二、mReferrer從哪里來


          2.1 搜索mReferrer,來源回溯


          使用搜索功能來搜索Activity類中的mReferrer;使用 Find Usages 功能來查找mReferrer字段。



          在Activity的Attach方法中對mReferrer做了賦值。


          2.2?使用斷點調(diào)試跟蹤調(diào)用棧


          我們在Attach方法上添加斷點,通過斷點來跟蹤Attach的調(diào)用;



          紅框中就是Attach的調(diào)用路徑,該調(diào)用棧在主線程中執(zhí)行;從調(diào)用棧中看出Attach是ActivityThread.performLaunchActivity調(diào)用的。



          performLaunchActivity調(diào)用Attach時傳入的是r的referrer參數(shù),r是一個ActivityClientRecord對象。


          我們進一步找到ActivityClientRecord中對referrer賦值的地方,就是ActivityClientRecord的構(gòu)造函數(shù)。



          在構(gòu)造函數(shù)中添加斷點,查看調(diào)用棧;



          發(fā)現(xiàn)ActivityClientRecord在LaunchActivityItem的execute中被實例化,并且傳入的是LaunchActivityItem的mReferrer。


          LaunchActivityItem的mReferrer是在setValues方法中賦值的,我們需要通過調(diào)試來看setValues是被誰調(diào)用的。當(dāng)我們使用常規(guī)方式斷點查看setValues的調(diào)用方時,我們會發(fā)現(xiàn)這樣一種情況。



          說明LaunchActivityItem在本地進程中,是一個被序列化后反序列化生成的對象。


          在Activity中,序列化對象傳輸通常是使用binder來完成的,而binder的服務(wù)端是在System進程中。這里實現(xiàn)了反序列化,那么在遠(yuǎn)端的binder服務(wù)中一定有序列化的過程。我們可以在System進程中調(diào)試這個斷點,應(yīng)該就是序列化的過程。


          2.3 斷點調(diào)試


          對System進程調(diào)試的方式也比較簡單;


          • step1:下載安裝Android自帶的X86模擬器(注意一定要安裝google api版本,play版本不支持調(diào)試system進程)。

          • step2:在調(diào)試的時候選擇System進程。



          通過調(diào)試,我們找到賦值堆棧(注意這里堆棧顯示的進程已經(jīng)是Binder進程了)。



          我們根據(jù)這個堆棧的指示,一步一步的跟進,這里需要注意一下,我們在查看調(diào)試堆棧的時候,只需要關(guān)注類名和方法名就可以了,不用刻意去關(guān)注堆棧中的行號,因為行號不一定準(zhǔn)確。如果調(diào)試過程中發(fā)現(xiàn)差異太大,可以嘗試更換一個模擬器版本。


          這里跟進到ActivityStackSupervisor的realStartActivityLoacked方法。



          在ActivityStackSupervisor中,我們發(fā)現(xiàn)這個參數(shù)是由r.LaunchedFromPackage的來的,這個r是ActivityRecord,查找LaunchedFromPackage的賦值的地方,最終找到ActivityRecord的初始化方法。


          2.4 對象實例化過程


          在初始化方法中添加斷點進行堆棧調(diào)試;



          跟著堆棧一步一步的看,到了ActivityStarter的execute方法里面,這里可以看到package的來源是mRequest.callingPackage。



          通過搜索Request的callingPackage對象對的Vaule write,mRequest.callingPackage的來源是ActivityStarter的setCallingPackage方法,一定是調(diào)用了setCallingPackage方法來實現(xiàn)了callingPackage內(nèi)容的注入。



          再看上一步驟中的堆棧,調(diào)用該方法的是ActivityTaskManagerService的startActivity方法;startActivity在構(gòu)建時使用setCallingPackage傳入了package。與我們之前的猜測是一致的。



          分析到這里已經(jīng)接近真相了。


          2.5 遠(yuǎn)程服務(wù)Binder調(diào)用的分析


          我們都知道ActivityTaskManagerService是一個遠(yuǎn)程服務(wù),從它工作的進程就可以看出來,是一個binder進程。因為ActivityTaskManagerService extends IActivityTaskManager.Stub,那我們就要去找IActivityTaskManager.Stub被遠(yuǎn)程調(diào)用的地方。


          要想找他遠(yuǎn)程調(diào)用的地方,我們就要先找到IActivityTaskManager.Stub是如何被調(diào)用方拿到的。


          全局搜索IActivityTaskManager.Stub或者

          搜索IActivityTaskManager.Stub.asInterface,

          這里為了方便使用了在線的Android源碼搜索平臺。



          我們在ActivityTaskManager中找到如下代碼;

          @TestApi@SystemService(Context.ACTIVITY_TASK_SERVICE)public class ActivityTaskManager {     ActivityTaskManager(Context context, Handler handler) {    }     /** @hide */    public static IActivityTaskManager getService() {        return IActivityTaskManagerSingleton.get();    }     @UnsupportedAppUsage(trackingBug = 129726065)    private static final Singleton IActivityTaskManagerSingleton =            new Singleton() {                @Override                protected IActivityTaskManager create() {                    final IBinder b = ServiceManager.getService(Context.ACTIVITY_TASK_SERVICE);                    //這里生成了遠(yuǎn)程調(diào)用對象                    return IActivityTaskManager.Stub.asInterface(b);                }            }; }


          也就是說通過ActivityTaskManager.getService()方法可以拿到IActivityTaskManager.Stub的遠(yuǎn)程調(diào)用句柄。


          于是ActivityTaskManagerService的startActivity

          方法調(diào)用的寫法應(yīng)該是

          ActivityTaskManager.getService().startActivity,下一步的計劃是找到這個方法調(diào)用的地方 。


          2.6 萬能的搜索并不萬能


          按照正常的思路,我們會再來使用搜索功能在這個

          在線源碼網(wǎng)站上搜索一下

          ActivityTaskManager.getService().startActivity。


          搜索不到?這里一定要注意,因為startActivity方法里面有很多參數(shù),很可能代碼被換行,一旦被換行,搜索

          ActivityTaskManager.getService().startActivity就不能搜到了。


          搜索也不是萬能的,我們還是考慮加斷點試試。


          那么斷點應(yīng)該加在哪里呢?我們是否可以將斷點加在ActivityTaskManagerService的startActivity上呢?


          答案是不行,如果你嘗試去在一個binder進程調(diào)用(遠(yuǎn)程服務(wù)調(diào)用 )的方法上面添加斷點。那么你只會得到如下調(diào)用棧。



          很顯然調(diào)用棧直接指向了 binder遠(yuǎn)端,這不是我們想要的調(diào)用棧。我們知道,調(diào)用startActivity的源碼一定是

          ActivityTaskManager.getService().startActivity。


          而這行代碼一定是在App的進程中調(diào)用的,屬于binder的客戶端調(diào)用,因此我們試著在getService()上面加一個斷點試試。這里加了斷點之后也要注意一下,因為這個時候的startActivity應(yīng)該是攻擊方調(diào)用的,也就是調(diào)起Deeplink的應(yīng)用調(diào)用的。


          所以。我們需要對Deeplink的發(fā)起方進行調(diào)試。我們可以寫一個Demo來進行調(diào)試。



          點擊按鈕來發(fā)起Deeplink,然后進行斷點,這個時候就能找到如下堆棧。



          點擊下一步斷點(Step Over)剛好就是

          ActivityTaskManager.getService().startActivity的方法調(diào)用。



          于是我們得到如下調(diào)用棧;

          ContextImpl.startActivty()Instrumentation.execStartActivity()ActivityTaskManager.getService()                    .startActivity(whoThread, who.getBasePackageName(), intent,                     intent.resolveTypeIfNeeded(who.getContentResolver()),                     token, target != null ? target.mEmbeddedID : null,                     requestCode, 0, null, options);


          這邊就找到了 可以看到,callingPackage正是使用getBasePackageName方法來實現(xiàn)的。who就是context,也就是我們的Activity。


          到這里就可以確認(rèn) mReferrer其實就是使用context的getBasePackageName()來實現(xiàn)的。


          三、如何避免包名被偽造


          3.1?關(guān)注PID和Uid


          如何來防止PackageName被偽造呢?


          在我們調(diào)試ActivityRecord的時候,我們發(fā)現(xiàn)ActivityRecord的屬性中還有PID和Uid



          只要拿到這個Uid,我們就可以根據(jù)Uid調(diào)用packageManager的方法來獲取對應(yīng)Uid的報名。


          3.2 調(diào)研Uid是否有偽造的可能性


          下面就是要驗證一下Uid是否有被偽造的可能了。調(diào)試查找Uid的來源,在ActivityRecord的初始化方法中斷點查看callingUid的來源。



          我們發(fā)現(xiàn) 這個Uid其實是在ActivityStarter里面使用Binder.getCallingUid得到的。Binder進程可不是應(yīng)用層面可以干涉的了,我們可以放心大膽的使用這個Uid,不用擔(dān)心被偽造,剩下的就是如何使用Uid獲取PackageName了。


          3.3 使用Uid置換PackageName


          我們檢索代碼,發(fā)現(xiàn)ActivityTaskManagerService恰好提供了獲取Uid的方法。



          所以我們需要拿到ActivityTaskManagerService引用,搜索IActivityTaskManager.Stub。



          ActivityTaskManager是無法在app層引用的(是一個hide的類,但其實也是有辦法的,大家可以自己去探索一下)。


          我們繼續(xù)查找;



          最終發(fā)現(xiàn)ActivityManager提供了這么一個方法來獲取ActivityTaskManagerService,但是很不幸,getTaskService是一個黑名單方法,被禁止調(diào)用。


          最后我們發(fā)現(xiàn)ActivityTaskManagerService的getLaunchedFromUid方法其實是被ActivityManageService包裝了一下的。

          public class ActivityManagerService extends IActivityManager.Stub        implements Watchdog.Monitor, BatteryStatsImpl.BatteryCallback {      @VisibleForTesting    public ActivityTaskManagerService mActivityTaskManager;      @Override    public boolean updateConfiguration(Configuration values) {        return mActivityTaskManager.updateConfiguration(values);    }     @Override    public int getLaunchedFromUid(IBinder activityToken) {        return mActivityTaskManager.getLaunchedFromUid(activityToken);    }     public String getLaunchedFromPackage(IBinder activityToken) {        return mActivityTaskManager.getLaunchedFromPackage(activityToken);    } }


          所以可以使用ActivityManageService來調(diào)用就可以了,代碼如下(注意不同的系統(tǒng)的版本可能代碼并不一樣)。

          private String reRealPackage() {    try {        Method getServiceMethod = ActivityManager.class.getMethod("getService");        Object sIActivityManager = getServiceMethod.invoke(null);        Method sGetLaunchedFromUidMethod = sIActivityManager.getClass().getMethod("getLaunchedFromUid", IBinder.class);        Method sGetActivityTokenMethod = Activity.class.getMethod("getActivityToken");        IBinder binder = (IBinder) sGetActivityTokenMethod.invoke(this);        int uid = (int) sGetLaunchedFromUidMethod.invoke(sIActivityManager, binder);        return getPackageManager().getPackagesForUid(uid)[0];    } catch (Exception e) {        e.printStackTrace();    }    return "null";}


          使用Uid來置換PackageName是不是就萬無一失了呢?這里面是否還有其他玄機?這里先賣個關(guān)子,小伙伴們可以在評論區(qū)討論一下。


          四、總結(jié)


          mReferrer很容易通過重寫context的getBasePackageName()被偽造,在使用時一定要小心。通過ActivityManageService獲取的Uid是無法被偽造的,可以考慮使用Uid來轉(zhuǎn)換PackageName。


          技術(shù)交流,歡迎加我微信:ezglumes ,拉你入技術(shù)交流群。

          私信領(lǐng)取相關(guān)資料

          推薦閱讀:

          音視頻開發(fā)工作經(jīng)驗分享 || 視頻版

          OpenGL ES 學(xué)習(xí)資源分享

          開通專輯 | 細(xì)數(shù)那些年寫過的技術(shù)文章專輯

          NDK 學(xué)習(xí)進階免費視頻來了

          你想要的音視頻開發(fā)資料庫來了

          推薦幾個堪稱教科書級別的 Android 音視頻入門項目

          覺得不錯,點個在看唄~

          瀏覽 60
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  韩日精品在线观看 | 欧美一区二区三曲的 | www.高清无码 | 久久久久无码精品亚洲日韩 | 欧美成人午夜精品免费 |