首页 > 运维 > linux运维 > 正文

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

蓮花仙者
发布: 2025-07-16 08:00:21
原创
909人浏览过

前言

Android Hook 插件化技术已经不再新奇,你是否想过支付宝中的小软件,如淘票票、火车票等,是否都是支付宝自己编写的?这显然是不可能的,否则需要十年的开发时间,软件体积可能达到几十G。实际上,游戏中的皮肤包也是根据用户需求下载的。

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

学习Android时,我们知道Activity必须在配置文件中注册,否则无法启动并会报错。然而,Hook技术告诉我们,未在配置文件中注册的Activity也可以启动,这是否让你感到惊讶?

通过本文你可以学到:

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

    1.1 通过对Instrumentation进行Hook

    1.2 通过对AMN进行Hook

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

本文的基础建立在Java反射机制和App启动流程解析之上,建议不了解的朋友先阅读相关文章。

二、对startActivity方法进行Hook

通过查阅startActivity的源码,可以发现startActivity最终都会调用startActivityForResult方法。

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调用(详细的源码解析已在上篇文章中讲解),再看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 = 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);
登录后复制

运行日志如下:

Android Hook告诉你 如何启动未注册的Activity这个时候我们就成功的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<T> {    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:代理模式分为静态代理和动态代理,如果对代理模式不了解可以百度一波,也可以关注我,等待我的代理模式相关文章)

Supermeme
Supermeme

Supermeme是一个AI驱动的Meme生成器,可以快速生成有趣的Meme梗图

Supermeme 114
查看详情 Supermeme
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,运行日志如下:

Android Hook告诉你 如何启动未注册的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 break="" if="" index="i;" instanceof="" intent=""></args.length>
登录后复制

你可能会问你怎么知道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);
登录后复制
args[index] = newIntent;
登录后复制

这样目标Activity就被替换成了Main2Activity,不过这个冒充者还要将原本的目标携带过去,等待真正打开的时候再替换回来,否则就真的启动这个冒充者了

newIntent.putExtra(AmsHookHelperUtils.TUREINTENT,intent);
登录后复制

这个时候我们调用这个方法什么都不做,这个时候启动Main3Activity

startActivity(new Intent(this,Main3Activity.class));
登录后复制

显示的其实是Main2Activity,如图所示:

Android Hook告诉你 如何启动未注册的Activity这样说明我们的冒充者已经成功替换了真实目标,所以我们接下来要在启动的时候,将冒充者再重新替换为目标者,ActivityThread通过mH发消息给AMS

synchronized(this) {    Message msg = Message.obtain();    msg.what = what;    msg.obj = obj;    msg.arg1 = arg1;    msg.arg2 = arg2;    this.mH.sendMessage(msg);}
登录后复制

AMS收到消息后进行处理

public void handleMessage(Message msg) {    ActivityThread.ActivityClientRecord data;    switch(msg.what) {    case 100:        Trace.traceBegin(64L, "activityStart");        data = (ActivityThread.ActivityClientRecord)msg.obj;        data.packageInfo = ActivityThread.this.getPackageInfoNoCheck(data.activityInfo.applicationInfo, data.compatInfo);        ActivityThread.this.handleLaunchActivity(data, (Intent)null);        Trace.traceEnd(64L);
登录后复制

mH是Handler类型的消息处理类,所以sendMessage方法会调用callback,Handler消息处理机制可看我之前一篇博客

深入理解Android消息机制,所以我们可以对callback字段进行Hook。

新建hookActivityThread方法,首先我们获取当前的ActivityThread对象

Object currentActivityThread = Reflex.getStaticFieldObject("android.app.ActivityThread", "sCurrentActivityThread");
登录后复制

然后获取对象的mH对象

Handler mH = (Handler) Reflex.getFieldObject(currentActivityThread, "mH");
登录后复制

将mH替换为我们的自己自定义的MyCallback。

Reflex.setFieldObject(Handler.class, mH, "mCallback", new MyCallback(mH));
登录后复制

自定义MyCallback首先 Handler.Callback接口,重新处理handleMessage方法

@Overridepublic boolean handleMessage(Message msg) {    switch (msg.what) {              case 100:            handleLaunchActivity(msg);            break;        default:            break;    }    mBase.handleMessage(msg);    return true;}
登录后复制

我们获取传递过来的目标对象

Object obj = msg.obj;Intent intent = (Intent) Reflex.getFieldObject(obj, "intent");
登录后复制

然后从目标对象中取出携带过来的真实对象,并将intent修改为真实目标对象的信息,这样就可以启动真实的目标Activity

Intent targetIntent = intent.getParcelableExtra(AmsHookHelperUtils.TUREINTENT);intent.setComponent(targetIntent.getComponent());
登录后复制

MyCallback如下

/** * Created by Huanglinqing on 2019/4/30. */public class MyCallback implements Handler.Callback {    Handler mBase;    public MyCallback(Handler base) {        mBase = base;    }    @Override    public boolean handleMessage(Message msg) {        switch (msg.what) {                     case 100:                handleLaunchActivity(msg);                break;            default:                break;        }        mBase.handleMessage(msg);        return true;    }    private void handleLaunchActivity(Message msg) {            Object obj = msg.obj;        Intent intent = (Intent) Reflex.getFieldObject(obj, "intent");        Intent targetIntent = intent.getParcelableExtra(AmsHookHelperUtils.TUREINTENT);        intent.setComponent(targetIntent.getComponent());    }}
登录后复制

这个时候再启动未注册的Main3Activity,就可以成功启动了

startActivity(new Intent(this,Main3Activity.class));
登录后复制

Android Hook告诉你 如何启动未注册的Activity这样我们就成功的启动了未注册Activity

以上就是Android Hook告诉你 如何启动未注册的Activity的详细内容,更多请关注php中文网其它相关文章!

最佳 Windows 性能的顶级免费优化软件
最佳 Windows 性能的顶级免费优化软件

每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。

下载
来源:php中文网
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
最新问题
开源免费商场系统广告
热门教程
更多>
最新下载
更多>
网站特效
网站源码
网站素材
前端模板
关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号