使用黑科技启动未注册的Activity

640?wx_fmt=jpeg


/   今日科技快讯   /


近日,苹果公司首席执行官蒂姆·库克否认苹果是一家垄断企业。美国政府正准备对苹果公司展开一场可能前所未有的调查,调查这家iPhone制造商和其他科技巨头是否在滥用他们巨大的市场力量。库克接受采访时表示,苹果控制了部分市场份额,但不算太大。他还说,不赞成美国部分政治人物关于分拆苹果公司的建议。


/   作者简介   /


大家周四好,明天就是端午节假期了,提前祝大家端午节快乐。好好享受小长假吧!我们节后见。


本篇文章来自小鲜肉黄林晴的投稿,分享了如何利用Hook技术调用未注册Activity的相关内容,相信会对大家有所帮助!同时也感谢作者贡献的精彩文章。


黄林晴的博客地址:

https://blog.csdn.net/huangliniqng


/   前言   /


Android Hook 插件化其实已经不是什么新鲜的技术了,不知你有没有想过,支付宝中那么多小软件:淘票票 ,火车票等软件,难道是支付宝这个软件自己编写的吗?那不得写个十年,软件达到几十G,但是并没有,玩游戏时那么多的皮肤包肯定时用户使用哪个就下载哪个皮肤包。


/   未注册的Activity可以启动吗?   /


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


通过本文你可以学到:


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


  • 通过对Instrumentation进行Hook

  • 通过对AMN进行Hook


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


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


Java反射机制:

https://blog.csdn.net/huangliniqng/article/details/88554510


App启动流程解析:

https://blog.csdn.net/huangliniqng/article/details/89364064


/   对startActivity方法进行Hook   /


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



通过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,可以从这两个地方入手(其实不止这两个地方,我们只讲解着两个地方,下面使用的反射封装类也在上篇文章中给出)。


对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);


运行日志如下:


640?wx_fmt=png


对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<IActivityManager>类型的对象,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:代理模式分为静态代理和动态代理,如果对代理模式不了解可以百度一波)。


 
 

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,运行日志如下:


640?wx_fmt=png


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


如何启动一个未注册的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);


 
 

args[index] = newIntent;


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


 
 

newIntent.putExtra(AmsHookHelperUtils.TUREINTENT,intent);


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


 
 

startActivity(new Intent(this,Main3Activity.class));


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


640?wx_fmt=gif


这样说明我们的冒充者已经成功替换了真实目标,所以我们接下来要在启动的时候,将冒充者再重新替换为目标者,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,所以我们可以对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方法:


 
 

@Override
public 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());


MyCallbackt如下:


 
 

**
 * 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));


640?wx_fmt=gif


这样我们就成功的启动了未注册Activity。


推荐阅读:

原来这些设计模式的理念都是相同的

Canvas可以画出任何你想要的效果

一篇文章带你看遍Google I/O 2019大会


欢迎关注我的公众号

学习技术或投稿


640.png?


640?wx_fmt=jpeg

长按上图,识别图中二维码即可关注


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值