一、前言

目前有很多的业务模块提供了Deeplink服务,Deeplink简略来说就是对外部利用提供入口。

针对不同的跳入类型,app可能会抉择提供不统一的服务,这个时候就须要对外部跳入的利用进行辨别。一般来讲,咱们会应用反射来调用Acticity中的mReferrer字段来获取跳转起源的包名。

具体代码如下;

/** * 通过反射获取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被伪造,轻则业务逻辑出错,重则造成经济损失,针对这种状况,有没有方法找到一种较为平安的起源获取办法呢?

这就须要对mReferrer的起源进行一次剖析。上面咱们来进行一次mReferrer起源的另类源码剖析。之所以说另类,是因为这次会大量应用调试伎俩来逆向进行源码剖析。

二、mReferrer从哪里来

2.1 搜寻mReferrer,起源回溯

应用搜寻性能来搜寻Activity类中的mReferrer;应用 Find Usages 性能来查找mReferrer字段。

在Activity的Attach办法中对mReferrer做了赋值。

2.2 应用断点调试跟踪调用栈

咱们在Attach办法上增加断点,通过断点来跟踪Attach的调用;

红框中就是Attach的调用门路,该调用栈在主线程中执行;从调用栈中看出Attach是ActivityThread.performLaunchActivity调用的。

performLaunchActivity调用Attach时传入的是r的referrer参数,r是一个ActivityClientRecord对象。

咱们进一步找到ActivityClientRecord中对referrer赋值的中央,就是ActivityClientRecord的构造函数。

在构造函数中增加断点,查看调用栈;

发现ActivityClientRecord在LaunchActivityItem的execute中被实例化,并且传入的是LaunchActivityItem的mReferrer。

LaunchActivityItem的mReferrer是在setValues办法中赋值的,咱们须要通过调试来看setValues是被谁调用的。当咱们应用惯例形式断点查看setValues的调用方时,咱们会发现这样一种状况。

阐明LaunchActivityItem在本地过程中,是一个被序列化后反序列化生成的对象。

在Activity中,序列化对象传输通常是应用binder来实现的,而binder的服务端是在System过程中。这里实现了反序列化,那么在远端的binder服务中肯定有序列化的过程。咱们能够在System过程中调试这个断点,应该就是序列化的过程。

2.3 断点调试

对System过程调试的形式也比较简单;

  • step1:下载安装Android自带的X86模拟器(留神肯定要装置google api版本,play版本不反对调试system过程)。
  • step2:在调试的时候抉择System过程。

通过调试,咱们找到赋值堆栈(留神这里堆栈显示的过程曾经是Binder过程了)。

咱们依据这个堆栈的批示,一步一步的跟进,这里须要留神一下,咱们在查看调试堆栈的时候,只须要关注类名和办法名就能够了,不必刻意去关注堆栈中的行号,因为行号不肯定精确。如果调试过程中发现差别太大,能够尝试更换一个模拟器版本。

这里跟进到ActivityStackSupervisor的realStartActivityLoacked办法。

在ActivityStackSupervisor中,咱们发现这个参数是由r.LaunchedFromPackage的来的,这个r是ActivityRecord,查找LaunchedFromPackage的赋值的中央,最终找到ActivityRecord的初始化办法。

2.4 对象实例化过程

在初始化办法中增加断点进行堆栈调试;

跟着堆栈一步一步的看,到了ActivityStarter的execute办法外面,这里能够看到package的起源是mRequest.callingPackage。

通过搜寻Request的callingPackage对象对的Vaule write,mRequest.callingPackage的起源是ActivityStarter的setCallingPackage办法,肯定是调用了setCallingPackage办法来实现了callingPackage内容的注入。

再看上一步骤中的堆栈,调用该办法的是ActivityTaskManagerService的startActivity办法;startActivity在构建时应用setCallingPackage传入了package。与咱们之前的猜想是统一的。

剖析到这里曾经靠近假相了。

2.5 近程服务Binder调用的剖析

咱们都晓得ActivityTaskManagerService是一个近程服务,从它工作的过程就可以看进去,是一个binder过程。因为ActivityTaskManagerService extends IActivityTaskManager.Stub,那咱们就要去找IActivityTaskManager.Stub被近程调用的中央。

要想找他近程调用的中央,咱们就要先找到IActivityTaskManager.Stub是如何被调用方拿到的。

全局搜寻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<IActivityTaskManager> IActivityTaskManagerSingleton =            new Singleton<IActivityTaskManager>() {                @Override                protected IActivityTaskManager create() {                    final IBinder b = ServiceManager.getService(Context.ACTIVITY_TASK_SERVICE);                    //这里生成了近程调用对象                    return IActivityTaskManager.Stub.asInterface(b);                }            }; }

也就是说通过ActivityTaskManager.getService()办法能够拿到IActivityTaskManager.Stub的近程调用句柄。

于是ActivityTaskManagerService的startActivity办法调用的写法应该是ActivityTaskManager.getService().startActivity,下一步的打算是找到这个办法调用的中央 。

2.6 万能的搜寻并不万能

依照失常的思路,咱们会再来应用搜寻性能在这个在线源码网站上搜寻一下ActivityTaskManager.getService().startActivity。

搜寻不到?这里肯定要留神,因为startActivity办法外面有很多参数,很可能代码被换行,一旦被换行,搜寻ActivityTaskManager.getService().startActivity就不能搜到了。

搜寻也不是万能的,咱们还是思考加断点试试。

那么断点应该加在哪里呢?咱们是否能够将断点加在ActivityTaskManagerService的startActivity上呢?

答案是不行,如果你尝试去在一个binder过程调用(近程服务调用 )的办法下面增加断点。那么你只会失去如下调用栈。

很显然调用栈间接指向了 binder远端,这不是咱们想要的调用栈。咱们晓得,调用startActivity的源码肯定是ActivityTaskManager.getService().startActivity。

而这行代码肯定是在App的过程中调用的,属于binder的客户端调用,因而咱们试着在getService()下面加一个断点试试。这里加了断点之后也要留神一下,因为这个时候的startActivity应该是攻打方调用的,也就是调起Deeplink的利用调用的。

所以。咱们须要对Deeplink的发起方进行调试。咱们能够写一个Demo来进行调试。

点击按钮来发动Deeplink,而后进行断点,这个时候就能找到如下堆栈。

点击下一步断点(Step Over)刚好就是ActivityTaskManager.getService().startActivity的办法调用。

于是咱们失去如下调用栈;

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办法来实现的。who就是context,也就是咱们的Activity。

到这里就能够确认 mReferrer其实就是应用context的getBasePackageName()来实现的。

三、如何防止包名被伪造

3.1 关注PID和Uid

如何来避免PackageName被伪造呢?

在咱们调试ActivityRecord的时候,咱们发现ActivityRecord的属性中还有PID和Uid;

只有拿到这个Uid,咱们就能够依据Uid调用packageManager的办法来获取对应Uid的报名。

3.2 调研Uid是否有伪造的可能性

上面就是要验证一下Uid是否有被伪造的可能了。调试查找Uid的起源,在ActivityRecord的初始化办法中断点查看callingUid的起源。

咱们发现 这个Uid其实是在ActivityStarter外面应用Binder.getCallingUid失去的。Binder过程可不是利用层面能够干预的了,咱们能够放心大胆的应用这个Uid,不必放心被伪造,剩下的就是如何应用Uid获取PackageName了。

3.3 应用Uid置换PackageName

咱们检索代码,发现ActivityTaskManagerService恰好提供了获取Uid的办法。

所以咱们须要拿到ActivityTaskManagerService援用,搜寻IActivityTaskManager.Stub。

ActivityTaskManager是无奈在app层援用的(是一个hide的类,但其实也是有方法的,大家能够本人去摸索一下)。

咱们持续查找;

最终发现ActivityManager提供了这么一个办法来获取ActivityTaskManagerService,然而很可怜,getTaskService是一个黑名单办法,被禁止调用。

最初咱们发现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来调用就能够了,代码如下(留神不同的零碎的版本可能代码并不一样)。

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是不是就十拿九稳了呢?这外面是否还有其余玄机?这里先卖个关子,小伙伴们能够在评论区讨论一下。

四、总结

mReferrer很容易通过重写context的getBasePackageName()被伪造,在应用时肯定要小心。通过ActivityManageService获取的Uid是无奈被伪造的,能够思考应用Uid来转换PackageName。

作者:vivo互联网客户端团队-Chen Long