Android Hook告诉你 如何启动未注册的Activity

一 未在配置文件中注册的Activity可以启动吗?


从0学的时候就知道Activity必须在配置文件中注册,否则无法启动且报错。但是Hook告诉你的是,未在配置文件中注册Activity是可以启动的,惊不惊喜?意不意外?

通过本文你可以学到:

1.通过对startActivity方法进行Hook,实现为startActivity方法添加日志。

1.1 通过对Instrumentation进行Hook

1.2 通过对AMN进行Hook

2.如何启动一个未在配置文件中注册的Activity实现插件化

本片文章基础建立在 Java反射机制App启动流程解析,建议不太了解的小伙伴可以先移步至这两篇文章。

二 对startActivity方法进行Hook


通过对查阅startActivity的源码可以看出startActivity最终都会走到startActivityFoResult方法中

public void startActivityForResult(Intent intent, int requestCode, Bundle options) {

if(this.mParent == null) {

ActivityResult ar = this.mInstrumentation.execStartActivity(this, this.mMainThread.getApplicationThread(), this.mToken, this, intent, requestCode, options);

if(ar != null) {

this.mMainThread.sendActivityResult(this.mToken, this.mEmbeddedID, requestCode, ar.getResultCode(), ar.getResultData());

}

if(requestCode >= 0) {

this.mStartedActivity = true;

}

} else if(options != null) {

this.mParent.startActivityFromChild(this, intent, requestCode, options);

} else {

this.mParent.startActivityFromChild(this, intent, requestCode);

}

}

通过mInstrumentation.execStartActivity调用(ps:详细的源码解析已在上篇文章中讲解),再看mInstrumentation.execStartActivity方法源码如下:

public Instrumentation.ActivityResult execStartActivity(Context who, IBinder contextThread, IBinder token, Activity target, Intent intent, int requestCode, Bundle options) {

IApplicationThread whoThread = (IApplicationThread)contextThread;

if(this.mActivityMonitors != null) {

Object e = this.mSync;

synchronized(this.mSync) {

int N = this.mActivityMonitors.size();

for(int i = 0; i < N; ++i) {

Instrumentation.ActivityMonitor am = (Instrumentation.ActivityMonitor)this.mActivityMonitors.get(i);

if(am.match(who, (Activity)null, intent)) {

++am.mHits;

if(am.isBlocking()) {

return requestCode >= 0?am.getResult():null;

}

break;

}

}

}

}

try {

intent.setAllowFds(false);

intent.migrateExtraStreamToClipData();

int var16 = ActivityManagerNative.getDefault().startActivity(whoThread, intent, intent.resolveTypeIfNeeded(who.getContentResolver()), token, target != null?target.mEmbeddedID:null, requestCode, 0, (String)null, (ParcelFileDescriptor)null, options);

checkStartActivityResult(var16, intent);

} catch (RemoteException var14) {

;

}

return null;

}

最终会交给 int var16 = ActivityManagerNative.getDefault().startActivity(whoThread, intent,…处理,所以如果我们想对startActivity方法进行Hook,可以从这两个地方入手(其实不止这两个地方,我们只讲解着两个地方,下面使用的反射封装类也在上篇文章中给出)。

  • 2.1 对mInstrumentation进行Hook

在Activity.class类中定义了私有变量

private Instrumentation mInstrumentation;

我们首先要做的就是修改这个私有变量的值,在执行方法前打印一行日志,首先我们通过反射来获取这一私有变量。

Instrumentation instrumentation = (Instrumentation) Reflex.getFieldObject(Activity.class,MainActivity.this,“mInstrumentation”);

我们要做的是将这个Instrumentation替换成我们自己的Instrumentation,所以下面我们新建MyInstrumentation继承自Instrumentation,并且MyInstrumentation的execStartActivity方法不变。

public class MyInstrumentation extends Instrumentation {

private Instrumentation instrumentation;

public MyInstrumentation(Instrumentation instrumentation) {

this.instrumentation = instrumentation;

}

public ActivityResult execStartActivity(Context who, IBinder contextThread, IBinder token, Activity target, Intent intent, int requestCode, Bundle options) {

Log.d(“-----”,“啦啦啦我是hook进来的!”);

Class[] classes = {Context.class,IBinder.class,IBinder.class,Activity.class,Intent.class,int.class,Bundle.class};

Object[] objects = {who,contextThread,token,target,intent,requestCode,options};

Log.d(“-----”,“啦啦啦我是hook进来的!!”);

return (ActivityResult) Reflex.invokeInstanceMethod(instrumentation,“execStartActivity”,classes,objects);

}

我们直接通过反射调用这个方法 参数就是 Class[] classes = {Context.class,IBinder.class,IBinder.class,Activity.class,Intent.class,int.class,Bundle.class}与方法名中一致

(ActivityResult) Reflex.invokeInstanceMethod(instrumentation,“execStartActivity”,classes,objects)

如果我们这里不调用它本身的execStartActivity方法的话,那么startActivity就无效了。

然后我们将自定义的替换为原来的Instrumentation

Reflex.setFieldObject(Activity.class,this,“mInstrumentation”,instrumentation1);

完整代码就是

Instrumentation instrumentation = (Instrumentation) Reflex.getFieldObject(Activity.class,MainActivity.this,"mInstrumentation"); MyInstrumentation instrumentation1 = new MyInstrumentation(instrumentation); Reflex.setFieldObject(Activity.class,this,"mInstrumentation",instrumentation1);

运行日志如下:

这个时候我们就成功的Hook了startActivity方法

2.2 对AMN进行Hook

execStartActivity方法最终会走到ActivityManagerNative.getDefault().startActivity方法

try {

intent.setAllowFds(false);

intent.migrateExtraStreamToClipData();

int var16 = ActivityManagerNative.getDefault().startActivity(whoThread, intent, intent.resolveTypeIfNeeded(who.getContentResolver()), token, target != null?target.mEmbeddedID:null, requestCode, 0, (String)null, (ParcelFileDescriptor)null, options);

checkStartActivityResult(var16, intent);

} catch (RemoteException var14) {

;

}

你可能会说上面说过了啊,替换ActivityManagerNative.getDefault(),重写startActivity方法,我们来看ActivityManagerNative.getDefault()

public static IActivityManager getDefault() {

return (IActivityManager)gDefault.get();

}

public final T get() {

synchronized(this) {

if(this.mInstance == null) {

this.mInstance = this.create();

}

return this.mInstance;

}

}

可以看出IActivityManager是一个接口,gDefault.get()返回的是一个泛型,上述方案我们无法入手,所以我们这里要用动态代理方案

我们定义一个AmsHookHelperUtils类,在AmsHookHelperUtils类中处理反射代码

gDefault是个final静态类型的字段,首先我们获取gDefault字段

Object gDefault = Reflex.getStaticFieldObject(“android.app.ActivityManagerNative”,“gDefault”);

gDefault是 Singleton类型的对象,Singleton是一个单例模式

public abstract class Singleton {

private T mInstance;

public Singleton() {

}

protected abstract T create();

public final T get() {

synchronized(this) {

if(this.mInstance == null) {

this.mInstance = this.create();

}

return this.mInstance;

}

}

}

接下里我们来取出mInstance字段

Object mInstance = Reflex.getFieldObject(“android.util.Singleton”,gDefault,“mInstance”);

然后创建一个代理对象

Class<?> classInterface = Class.forName(“android.app.IActivityManager”);

Object proxy = Proxy.newProxyInstance(classInterface.getClassLoader(),

new Class<?>[]{classInterface},new AMNInvocationHanlder(mInstance));

我们的代理对象就是new AMNInvocationHanlder(mInstance),(ps:代理模式分为静态代理和动态代理,如果对代理模式不了解可以百度一波,也可以关注我,等待我的代理模式相关文章)

public class AMNInvocationHanlder implements InvocationHandler {

private String actionName = “startActivity”;

private Object target;

public AMNInvocationHanlder(Object target) {

this.target = target;

}

@Override

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

if (method.getName().equals(actionName)){

Log.d(“—”,“啦啦啦我是hook AMN进来的”);

return method.invoke(target,args);

}

return method.invoke(target,args);

}

}

所有的代理类都要实现InvocationHandler接口,在invoke方法中method.invoke(target,args);表示的就是 执行被代理对象所对应的方法。因为AMN Singleton做的事情比较多,所以这里只对startActivity方法hook

if (method.getName().equals(actionName)){

Log.d(“—”,“啦啦啦我是hook AMN进来的”);

return method.invoke(target,args);

}

然后我们将gDefault字段替换为我们的代理类

Reflex.setFieldObject(“android.util.Singleton”,gDefault,“mInstance”,proxy);

AmsHookHelperUtils方法整体如下:

public class AmsHookHelperUtils {

public static void hookAmn() throws ClassNotFoundException {

Object gDefault = Reflex.getStaticFieldObject(“android.app.ActivityManagerNative”,“gDefault”);

Object mInstance = Reflex.getFieldObject(“android.util.Singleton”,gDefault,“mInstance”);

Class<?> classInterface = Class.forName(“android.app.IActivityManager”);

Object proxy = Proxy.newProxyInstance(classInterface.getClassLoader(),

new Class<?>[]{classInterface},new AMNInvocationHanlder(mInstance));

Reflex.setFieldObject(“android.util.Singleton”,gDefault,“mInstance”,proxy);

}

}

我们调用AmsHookHelperUtils.hookAmn();然后启动一个新的Activity,运行日志如下:

这样我们就成功Hook了AMN的getDefault方法。

2.3 如何启动一个未注册的Activity

如何启动一个未注册的Activity,首先我们了解Activity的启动流程,App的启动流程已经在上篇文章中讲解了,APP启动流程解析,还不了解的小伙伴,可先移步至上篇文章。假设现在MainActivity,Main2Activity,Main3Activity,其中Main3Activity未注册,我们在MainActivity中启动Main3Activity,当启动Main3Activity的时候,AMS会在配置文件中检查,是否有Main3Activity的配置信息如果不存在则报错,存在则启动Main3Activity。

所以我们可以做的是,将要启动的Activity发送给AMS之前,将要启动的Activity替换未已经注册Activity Main2Activity,这样AMS就可以检验通过,当AMS要启动目标Activity的时候再将Main2Activity替换为真正要启动的Activity。

首先我们按照上面逻辑先对startActivity方法进行Hook,这里采用对AMN Hook的方式。和上述代码一样,不一样的地方在于mInstance的代理类不同。

新建一个AMNInvocationHanlder1对象同样继承自InvocationHandler,只拦截startActivity方法。

if (method.getName().equals(actionName)){}

在这里我们要做的就是将要启动的Main3Activity替换为Main2Activity,这样能绕过AMS的检验,首先我们从目标方法中取出目标Activity。

Intent intent;

int index = 0;

for (int i = 0;i<args.length;i++){

if (args[i] instanceof Intent){

index = i;

break;

}

}

你可能会问你怎么知道args中一定有intent类的参数,因为invoke方法中最终会执行

return method.invoke(target,args);

表示会执行原本的方法,而我们来看原本的startActivity方法如下:

int var16 = ActivityManagerNative.getDefault().startActivity(whoThread, intent, intent.resolveTypeIfNeeded(who.getContentResolver()), token, target != null?target.mEmbeddedID:null, requestCode, 0, (String)null, (ParcelFileDescriptor)null, options);

所以我们说args中肯定有个intent类型的参数,获取真实目标Activity之后,我们获取目标的包名

intent = (Intent) args[index];

String packageName = intent.getComponent().getPackageName();

新建一个Intent 将intent设置为 冒充者Main2Activity的相关信息

Intent newIntent = new Intent();

ComponentName componentName = new ComponentName(packageName,Main2Activity.class.getName());

newIntent.setComponent(componentName);

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

img

img

img

img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)

最后

那我们该怎么做才能做到年薪60万+呢,对于程序员来说,只有不断学习,不断提升自己的实力。我之前有篇文章提到过,感兴趣的可以看看,到底要学习哪些知识才能达到年薪60万+。

通过职友集数据可以查看,以北京 Android 相关岗位为例,其中 【20k-30k】 薪酬的 Android 工程师,占到了整体从业者的 30.8%!

北京 Android 工程师「工资收入水平 」

今天重点内容是怎么去学,怎么提高自己的技术。

1.合理安排时间

2.找对好的系统的学习资料

3.有老师带,可以随时解决问题

4.有明确的学习路线

当然图中有什么需要补充的或者是需要改善的,可以在评论区写下来,一起交流学习。

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!**

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)

[外链图片转存中…(img-XcAcrG45-1713715891521)]

最后

那我们该怎么做才能做到年薪60万+呢,对于程序员来说,只有不断学习,不断提升自己的实力。我之前有篇文章提到过,感兴趣的可以看看,到底要学习哪些知识才能达到年薪60万+。

通过职友集数据可以查看,以北京 Android 相关岗位为例,其中 【20k-30k】 薪酬的 Android 工程师,占到了整体从业者的 30.8%!

北京 Android 工程师「工资收入水平 」

[外链图片转存中…(img-7MKPI2jT-1713715891522)]

今天重点内容是怎么去学,怎么提高自己的技术。

1.合理安排时间

2.找对好的系统的学习资料

3.有老师带,可以随时解决问题

4.有明确的学习路线

当然图中有什么需要补充的或者是需要改善的,可以在评论区写下来,一起交流学习。

[外链图片转存中…(img-78zITeoR-1713715891523)]

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

  • 11
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值