一、前言

目前有很多的业务模块提供了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. TCP协议基本概念

    TCP协议最主要的特点 TCP是面向连接的运输层协议.这就是说,应用程序在使用TCP协议之前,必须要建立TCP连接,且在传输完毕后,还要断开连接. 每一条TCP连接只能有两个端点,每一条TCP连接只能 ...

  2. dbus客户端使用指南

    DBus是Linux使用的进程间通信机制,允许各个进程互相访问,而不需要为每个其他组件实现自定义代码.即使对于系统管理员来说,这也是一个相当深奥的主题,但它确实有助于解释linux的另一部分是如何工作 ...

  3. 创业公司用 Serverless,到底香不香?

    作者 | Mike Butusov 来源 | Serverless 公众号 在过去的 5 年里,使用云厂商处理应用后台的流行程度大幅飙升.其一,初创企业主采用 Serverless 方式,以节省基础设 ...

  4. 洛谷4299首都(LCT维护动态重心+子树信息)

    这个题目很有意思 QWQ 根据题目描述,我们可以知道,首都就是所谓的树的重心,那么我们假设每颗树的重心都是\(root\)的话,对于每次询问,我们只需要\(findroot(x)\)就可以. 那么如何 ...

  5. SudokuSolver 1.0:用C++实现的数独解题程序 【二】

    本篇是 SudokuSolver 1.0:用C++实现的数独解题程序 [一] 的续篇. CQuizDealer::loadQuiz 接口实现 1 CQuizDealer* CQuizDealer::s ...

  6. Mybatis 一级缓存 (20)

    Mybatis中的一级缓存和二级缓存(本博文只是针对一级缓存说明) 概述 ORM框架一般都会有缓存机制,做为其中一员的Mybatis也存在缓存.功能是用以提升查询的效率和服务给数据库带来压力.同样的M ...

  7. Install WSL

    Install WSL Prerequisites You must be running Windows 10 version 2004 and higher (Build 19041 and hi ...

  8. MyBatis的框架设计

    1.MyBatis的框架设计 2.整体设计 2.1 总体流程 (1)加载配置并初始化       触发条件:加载配置文件 配置来源于两个地方,一处是配置文件,一处是Java代码的注解,将SQL的配置信 ...

  9. SharkCTF2021 Babyhttp && get_or_lose

    两道web. Babyhttp: 直接dirsearch,发现同时存在git和bak泄露:经验证,git的没用. 访问index.php.bak, 下载源码: 抓包,改包,发包即可. get_or_l ...

  10. JDK中的SPI机制

    前言 最近学习类加载的过程中,了解到JDK提供给我们的一个可扩展的接口:java.util.ServiceLoader, 之前自己不了解这个机制,甚是惭愧... 什么是SPI SPI全称为(Servi ...