一、前言

目前有很多的业务模块提供了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

Android Activity Deeplink启动来源获取源码分析的更多相关文章

  1. Flink的Job启动TaskManager端(源码分析)

    前面说到了  Flink的JobManager启动(源码分析)  启动了TaskManager 然后  Flink的Job启动JobManager端(源码分析)  说到JobManager会将转化得到 ...

  2. Android应用层View绘制流程与源码分析

    1  背景 还记得前面<Android应用setContentView与LayoutInflater加载解析机制源码分析>这篇文章吗?我们有分析到Activity中界面加载显示的基本流程原 ...

  3. [Android实例] Scroll原理-附ScrollView源码分析

    想象一下你拿着放大镜贴很近的看一副巨大的清明上河图, 那放大镜里可以看到的内容是很有限的, 而随着放大镜的上下左右移动,就可以看到不同的内容了 android中手机屏幕就相当于这个放大镜, 而看到的内 ...

  4. [Android实例] Scroll原理-附ScrollView源码分析 (转载)

    想象一下你拿着放大镜贴很近的看一副巨大的清明上河图, 那放大镜里可以看到的内容是很有限的, 而随着放大镜的上下左右移动,就可以看到不同的内容了 android中手机屏幕就相当于这个放大镜, 而看到的内 ...

  5. 转 Android的消息处理机制(图+源码分析)——Looper,Handler,Message

    作为一个大三的预备程序员,我学习android的一大乐趣是可以通过源码学习google大牛们的设计思想.android源码中包含了大量的设计模式,除此以外,android sdk还精心为我们设计了各种 ...

  6. 【转】android的消息处理机制(图+源码分析)——Looper,Handler,Message

    原文地址:http://www.cnblogs.com/codingmyworld/archive/2011/09/12/2174255.html#!comments 作为一个大三的预备程序员,我学习 ...

  7. android的消息处理机制(图+源码分析)——Looper,Handler,Message

    android源码中包含了大量的设计模式,除此以外,android sdk还精心为我们设计了各种helper类,对于和我一样渴望水平得到进阶的人来说,都太值得一读了.这不,前几天为了了解android ...

  8. 【Android 界面效果45】ViewPager源码分析

    ViewPager概述: Layout manager that allows the user to flip left and right through pages of data. You s ...

  9. spring mvc 启动过程及源码分析

    由于公司开源框架选用的spring+spring mvc + mybatis.使用这些框架,网上都有现成的案例:需要那些配置文件.每种类型的配置文件的节点该如何书写等等.如果只是需要项目能够跑起来,只 ...

随机推荐

  1. ch_nginx.sh

    #!/bin/bash counter=`ps -ef |grep nginx |grep -v grep | wc -l` if [ $counter = 0 ];then service ngin ...

  2. React Native之新架构中的Turbo Module实现原理分析

    有段时间没更新博客了,之前计划由浅到深.从应用到原理,更新一些RN的相关博客.之前陆续的更新了6篇RN应用的相关博客(传送门),后边因时间问题没有继续更新.主要是平时空余时间都用来帮着带娃了,不过还是 ...

  3. openGauss X ShardingSphere,分布式方案的另一种最佳实践

    Apache ShardingSphere 持续助力于 openGauss 分布式数据库能力的构建.openGauss 数据库自 2020 年 6 月开源以来,受到了业界的广泛关注,现已吸引众多伙伴. ...

  4. captcha_trainer 验证码识别-训练 使用记录

    captcha_trainer 验证码识别-训练 使用记录 在爬数据的时候,网站出现了验证码,那么我们就得去识别验证码了.目前有两种方案 接入打码平台(花钱,慢) 自己训练(费时,需要GPU环境,快) ...

  5. UnboundLocalError: local variable 'range' referenced before assignment

    1. 报错信息 UnboundLocalError: local variable 'range' referenced before assignment 2. 代码 class Car(): &q ...

  6. [no code][scrum meeting] Alpha 15

    项目 内容 会议时间 2020-04-23 会议主题 OCR紧急会议 会议时长 45min 参会人员 PM + OCR组(赵涛,黎正宇) 项目 内容 会议时间 2020-04-24 会议主题 全体测试 ...

  7. BUAA软件工程个人项目作业

    BUAA软件工程个人项目作业 项目 内容 这个作业属于哪个课程 2020春季计算机学院软件工程(罗杰 任健) 这个作业的要求在哪里 个人项目作业 我在这个课程的目标是 学习软件开发的流程 这个作业在哪 ...

  8. Vue el 使用el-checkbox-group复选框进行单选框操作

    el-checkbox-group这个组件与其他复选框不一样,我当初也是半天不知道怎么操作 页面使用v-model绑定 size就是等比例缩小放大,v-ror循环应该看的懂.重要的是@chage到我们 ...

  9. 热身训练1 Calculator

    题目出处:Calculator 简要题意: 你有一个确定的函数,f(x)=+...*...^...,其中共有n个操作,从左到右依次计算. 共有m次询问,我们每次询问,1.会修改f(x)中的操作:2.输 ...

  10. Linux下有用的命令

    ldd 查看依赖的动态库 加上-r可以查看未定的符号 c++ filt 通过编译换名后的函数名查找某经过编译器换名前的函数名 csh 切换c shell source .chsrc 可以刷新环境变量 ...