0

Android黑科技:如何启动未注册的Activity

 2 years ago
source link: https://segmentfault.com/a/1190000041355453
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

如果有人问你,未在配置文件中注册的Activity可以启动吗。可能你一开始会回答不行,但是细细思考,你会发现,使用Android Hook等技术启动未注册的Activity也是可以的,这也是Android Hook 插件化技术原理的基础。

使用Android Hook 技术启动未注册的Activity,需要了解Java的反射机制Android App启动流程非常熟悉。

下面,我们从两点来讲解Android Hook 技术启动未注册的Activity:

  • 通过对Instrumentation进行Hook
  • 通过对AMN进行Hook

二、 对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()方法。

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;
}

而execStartActivity()方法最终又会走到checkStartActivityResult()方法。所以,如果我们想要对startActivity方法进行Hook么,那么就需要在checkStartActivityResult()方法之前进行Hook。

对mInstrumentation进行Hook

接下来,我们使用一个简单的例子:打印日志来说明如果使用mInstrumentation进行Hook

首先,打开Activity.class类,在里面我们可以Activity.class类中定义了私有变量Instrumentation。

private Instrumentation mInstrumentation;

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

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("MyInstrumentation","Instrumentation Hook11111");
        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("MyInstrumentation","Instrumentation Hook22222");
        return (ActivityResult) ReflexUtil.invokeInstanceMethod(instrumentation,"execStartActivity",classes,objects);

    }

}

熟悉Java反射的同学都知道,我们可以使用Class.forName(name)来获取类名,也可以使用getDeclaredMethod来获取类的参数。为了方便使用,我们对这些常用的反射进行了封装。

public class ReflexUtil {
    /**
     * 获取无参构造函数
     * @param className
     * @return
     */
    public static Object createObject(String className) {
        Class[] pareTyples = new Class[]{};
        Object[] pareVaules = new Object[]{};

        try {
            Class r = Class.forName(className);
            return createObject(r, pareTyples, pareVaules);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }

        return null;
    }

    /**
     * 获取无参构造方法
     * @param clazz
     * @return
     */
    public static Object createObject(Class clazz) {
        Class[] pareTyple = new Class[]{};
        Object[] pareVaules = new Object[]{};

        return createObject(clazz, pareTyple, pareVaules);
    }

    /**
     * 获取一个参数的构造函数  已知className
     *
     * @param className
     * @param pareTyple
     * @param pareVaule
     * @return
     */
    public static Object createObject(String className, Class pareTyple, Object pareVaule) {

        Class[] pareTyples = new Class[]{pareTyple};
        Object[] pareVaules = new Object[]{pareVaule};

        try {
            Class r = Class.forName(className);
            return createObject(r, pareTyples, pareVaules);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }

        return null;
    }


    /**
     * 获取单个参数的构造方法 已知类
     *
     * @param clazz
     * @param pareTyple
     * @param pareVaule
     * @return
     */
    public static Object createObject(Class clazz, Class pareTyple, Object pareVaule) {
        Class[] pareTyples = new Class[]{pareTyple};
        Object[] pareVaules = new Object[]{pareVaule};

        return createObject(clazz, pareTyples, pareVaules);
    }

    /**
     * 获取多个参数的构造方法 已知className
     * @param className
     * @param pareTyples
     * @param pareVaules
     * @return
     */
    public static Object createObject(String className, Class[] pareTyples, Object[] pareVaules) {
        try {
            Class r = Class.forName(className);
            return createObject(r, pareTyples, pareVaules);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }

        return null;
    }


    /**
     * 获取构造方法
     *
     * @param clazz
     * @param pareTyples
     * @param pareVaules
     * @return
     */
    public static Object createObject(Class clazz, Class[] pareTyples, Object[] pareVaules) {
        try {
            Constructor ctor = clazz.getDeclaredConstructor(pareTyples);
            ctor.setAccessible(true);
            return ctor.newInstance(pareVaules);
        } catch (Exception e) {
            e.printStackTrace();
        }

        return null;
    }


    /**
     * 获取多个参数的方法
     * @param obj
     * @param methodName
     * @param pareTyples
     * @param pareVaules
     * @return
     */
    public static Object invokeInstanceMethod(Object obj, String methodName, Class[] pareTyples, Object[] pareVaules) {
        if (obj == null) {
            return null;
        }

        try {
            //调用一个private方法 //在指定类中获取指定的方法
            Method method = obj.getClass().getDeclaredMethod(methodName, pareTyples);
            method.setAccessible(true);
            return method.invoke(obj, pareVaules);

        } catch (Exception e) {
            e.printStackTrace();
        }

        return null;
    }

    /**
     * 获取一个参数的方法
     * @param obj
     * @param methodName
     * @param pareTyple
     * @param pareVaule
     * @return
     */
    public static Object invokeInstanceMethod(Object obj, String methodName, Class pareTyple, Object pareVaule) {
        Class[] pareTyples = {pareTyple};
        Object[] pareVaules = {pareVaule};

        return invokeInstanceMethod(obj, methodName, pareTyples, pareVaules);
    }

    /**
     * 获取无参方法
     * @param obj
     * @param methodName
     * @return
     */
    public static Object invokeInstanceMethod(Object obj, String methodName) {
        Class[] pareTyples = new Class[]{};
        Object[] pareVaules = new Object[]{};

        return invokeInstanceMethod(obj, methodName, pareTyples, pareVaules);
    }


    /**
     * 无参静态方法
     * @param className
     * @param method_name
     * @return
     */
    public static Object invokeStaticMethod(String className, String method_name) {
        Class[] pareTyples = new Class[]{};
        Object[] pareVaules = new Object[]{};

        return invokeStaticMethod(className, method_name, pareTyples, pareVaules);
    }

    /**
     * 获取一个参数的静态方法
     * @param className
     * @param method_name
     * @param pareTyple
     * @param pareVaule
     * @return
     */
    public static Object invokeStaticMethod(String className, String method_name, Class pareTyple, Object pareVaule) {
        Class[] pareTyples = new Class[]{pareTyple};
        Object[] pareVaules = new Object[]{pareVaule};

        return invokeStaticMethod(className, method_name, pareTyples, pareVaules);
    }

    /**
     * 获取多个参数的静态方法
     * @param className
     * @param method_name
     * @param pareTyples
     * @param pareVaules
     * @return
     */
    public static Object invokeStaticMethod(String className, String method_name, Class[] pareTyples, Object[] pareVaules) {
        try {
            Class obj_class = Class.forName(className);
            return invokeStaticMethod(obj_class, method_name, pareTyples, pareVaules);
        } catch (Exception e) {
            e.printStackTrace();
        }

        return null;
    }

    /**
     * 无参静态方法
     * @param method_name
     * @return
     */
    public static Object invokeStaticMethod(Class clazz, String method_name) {
        Class[] pareTyples = new Class[]{};
        Object[] pareVaules = new Object[]{};

        return invokeStaticMethod(clazz, method_name, pareTyples, pareVaules);
    }

    /**
     * 一个参数静态方法
     * @param clazz
     * @param method_name
     * @param classType
     * @param pareVaule
     * @return
     */
    public static Object invokeStaticMethod(Class clazz, String method_name, Class classType, Object pareVaule) {
        Class[] classTypes = new Class[]{classType};
        Object[] pareVaules = new Object[]{pareVaule};

        return invokeStaticMethod(clazz, method_name, classTypes, pareVaules);
    }

    /**
     * 多个参数的静态方法
     * @param clazz
     * @param method_name
     * @param pareTyples
     * @param pareVaules
     * @return
     */
    public static Object invokeStaticMethod(Class clazz, String method_name, Class[] pareTyples, Object[] pareVaules) {
        try {
            Method method = clazz.getDeclaredMethod(method_name, pareTyples);
            method.setAccessible(true);
            return method.invoke(null, pareVaules);
        } catch (Exception e) {
            e.printStackTrace();
        }

        return null;
    }


    public static Object getFieldObject(String className, Object obj, String filedName) {
        try {
            Class obj_class = Class.forName(className);
            return getFieldObject(obj_class, obj, filedName);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        return null;
    }

    public static Object getFieldObject(Class clazz, Object obj, String filedName) {
        try {
            Field field = clazz.getDeclaredField(filedName);
            field.setAccessible(true);
            return field.get(obj);
        } catch (Exception e) {
            e.printStackTrace();
        }

        return null;
    }


    public static void setFieldObject(Class clazz, Object obj, String filedName, Object filedVaule) {
        try {
            Field field = clazz.getDeclaredField(filedName);
            field.setAccessible(true);
            field.set(obj, filedVaule);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void setFieldObject(String className, Object obj, String filedName, Object filedVaule) {
        try {
            Class obj_class = Class.forName(className);
            setFieldObject(obj_class, obj, filedName, filedVaule);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }


    public static Object getStaticFieldObject(String className, String filedName) {
        return getFieldObject(className, null, filedName);
    }

    public static Object getStaticFieldObject(Class clazz, String filedName) {
        return getFieldObject(clazz, null, filedName);
    }

    public static void setStaticFieldObject(String classname, String filedName, Object filedVaule) {
        setFieldObject(classname, null, filedName, filedVaule);
    }

    public static void setStaticFieldObject(Class clazz, String filedName, Object filedVaule) {
        setFieldObject(clazz, null, filedName, filedVaule);
    }

可以看到,在MyInstrumentation类中,我们直接反射execStartActivity方法来和默认的方法保持一致。

(ActivityResult) Reflex.invokeInstanceMethod(instrumentation,"execStartActivity",classes,objects)

然后,再使用我们自定义的MyInstrumentation替换原来的Instrumentation即可。完整代码如下:

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

2.2 对AMN进行Hook

如果大家去看execStartActivity()方法的源码,就可以看得到,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()方法。

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类中处理反射代码。

public class AMNInvocationHandler implements InvocationHandler {
    private String actionName = "startActivity";

    private Object target;

    public AMNInvocationHandler(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if (method.getName().equals(actionName)) {
            Log.d("AMNInvocationHandler", "I Am AMN Hook");
            return method.invoke(target, args);
        }
        return method.invoke(target, args);
    }

}

所有的代理类都要实现InvocationHandler接口,在invoke方法中method.invoke(target,args);表示的就是执行被代理对象所对应的方法。

然后,我们将IActivityManager接口中gDefault字段替换为我们的代理类,如下。

ReflexUtil.setFieldObject("android.util.Singleton",gDefault,"mInstance",proxy);

我们定义一个AmsHookHelperUtil类,然后添加一个hook方法,里面使用代理的方式进行Hook。

public class AmsHookHelperUtil {
    public static void hookAmn() throws ClassNotFoundException {
        Object gDefault = ReflexUtil.getStaticFieldObject("android.app.ActivityManagerNative","gDefault");
        Object mInstance = ReflexUtil.getFieldObject("android.util.Singleton",gDefault,"mInstance");

        Class<?> classInterface = Class.forName("android.app.IActivityManager");
        Object proxy = Proxy.newProxyInstance(classInterface.getClassLoader(),
                new Class<?>[]{classInterface},new AMNInvocationHandler(mInstance));
        ReflexUtil.setFieldObject("android.util.Singleton",gDefault,"mInstance",proxy);
    }
}

三、如何启动一个未注册的Activity

如何启动一个未注册的Activity,首先我们了解Activity的启动流程,如果还不了解Activity启动流程的,可以参考:Android Activity启动流程分析

假设,现在MainActivity,Main2Activity,Main3Activity,其中Main3Activity未注册,我们在MainActivity中启动Main3Activity,当启动Main3Activity的时候,AMS会在配置文件中检查,是否有Main3Activity的配置信息如果不存在则报错,存在则启动Main3Activity,这是我们已经知道的常规流程。

所以,如果要启动未注册的Activity,那么我们可以将要启动的Activity在发送给AMS之前,替换未已经注册Activity Main2Activity,这样AMS就可以检验通过,当AMS要启动目标Activity的时候再将Main2Activity替换为真正要启动的Activity即可,也是很多热修复空间的的Hook的原理。

我们按照上面逻辑先对startActivity方法进行Hook,这里采用对AMN Hook的方式。和上述代码一样,不一样的地方在于mInstance的代理类不同。首先, 新建一个AMNInvocationHanlder对象同样继承自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类的参数。因为Java反射的invoke方法中最终会执行下面的代码:

return method.invoke(target,args);

而Android的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);

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

接下来,我们要做的就是,如何将冒充者再重新替换为目标者。我们可以使用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。新建hookActivityThread方法,首先我们获取当前的ActivityThread对象,然后获取对象的mH对象,将mH替换为我们的自己自定义的MyCallback。

Object currentActivityThread = Reflex.getStaticFieldObject("android.app.ActivityThread", "sCurrentActivityThread");

Handler mH = (Handler) Reflex.getFieldObject(currentActivityThread, "mH");
ReflexUtil.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;
}

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

Object obj = msg.obj;
Intent intent = (Intent) ReflexUtil.getFieldObject(obj, "intent");

Intent targetIntent = intent.getParcelableExtra(AmsHookHelperUtils.TUREINTENT);
intent.setComponent(targetIntent.getComponent());

以下是MyCallbackt的完整代码:

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) ReflexUtil.getFieldObject(obj, "intent");
        Intent targetIntent = intent.getParcelableExtra(AmsHookHelperUtils.TUREINTENT);
        intent.setComponent(targetIntent.getComponent());
    }

}

然后,再启动未注册的Main3Activity就可以成功启动了,是不是很简单。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK