关于android:Android-Activity-Deeplink启动来源获取源码分析

69次阅读

共计 6725 个字符,预计需要花费 17 分钟才能阅读完成。

一、前言

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

正文完
 0