VirtualApp Java层Hook基础

Posted ximsfei

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了VirtualApp Java层Hook基础相关的知识,希望对你有一定的参考价值。

VirtualApp Java层Hook基础-反射注入

Hook技术是VirtualApp(后续简称VA)的核心实现原理之一。

0x00. 什么是Hook

Hook是Windows中提供的一种用以替换DOS下“中断”的系统机制,中文译为“挂钩”或“钩子”。在对特定的系统事件进行hook后,一旦发生已hook事件,对该事件进行hook的程序就会收到系统的通知,这时程序就能在第一时间对该事件做出响应。 – 来自百度百科

android插件化中的Hook机制一般是通过反射注入和动态代理来实现的。

0x01. Hook实现

Android中的Hook本身依赖反射机制,VA的Hook框架基于注解的反射注入技术实现的极为优雅,初看源码可能会不知所云,深入研究就会发现VA虽然模拟了整个AndroidFramework,Hook了大量对象,代码却依然井井有条。

基于VA,研究注解的反射注入技术,相关源码:

mirror/
 - RefClass.java
 - RefConstructor.java
 - RefStaticMethod.java
 - RefStaticObject.java
 - RefStaticInt.java
 - RefMethod.java
 - RefObject.java
 - RefBoolean.java
 - RefDouble.java
 - RefFloat.java
 - RefInt.java
 - RefLong.java
 - MethodParams.java
 - MethodReflectParams.java

mirror/android/app/
 - ActivityThread.java

// Android Framework
frameworks/base/core/java/android/app/
 - ActivityThread.java

ActivityThread的反射注入-普通实现

public class PluginInstrumentation extends Instrumentation 
    private final Instrumentation mBase;

    public PluginInstrumentation(Instrumentation base) 
        mBase = base;
    


public class HookHelper 
    public static void inject() throws Exception 
        Class<?> activityThreadClass = Class.forName("android.app.ActivityThread");
        Method currentActivityThreadMethod = activityThreadClass.getMethod("currentActivityThread");
        currentActivityThreadMethod.setAccessible(true);
        Object currentActivityThread = currentActivityThreadMethod.invoke(null);
        Field instrumentationField = activityThreadClass.getDeclaredField("mInstrumentation");
        instrumentationField.setAccessible(true);
        Instrumentation instrumentation = (Instrumentation) instrumentationField.get(currentActivityThread);
        // HookHelper: 反射注入前 instrumentation = android.app.Instrumentation@3520a6d
        Log.e("HookHelper", "反射注入前 instrumentation = " + instrumentationField.get(currentActivityThread));
        if(!(instrumentation instanceof PluginInstrumentation)) 
            instrumentationField.set(currentActivityThread, new PluginInstrumentation(instrumentation));
        
        // HookHelper: 反射注入后 instrumentation = com.ximsfei.kotlin.hook.PluginInstrumentation@669d6a2
        Log.e("HookHelper", "反射注入后 instrumentation = " + instrumentationField.get(currentActivityThread));
    

ActivityThread的反射注入-VA实现

原理介绍

mirror/MethodParams.java

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MethodParams 
    Class<?>[] value();

该注解MethodParams用于标注某个方法(包括构造方法)包含哪些参数。eg: @MethodParams(IBinder.class, List.class), 参数为Class

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MethodReflectParams 
    String[] value();

该注解MethodReflectParams用于标注某个方法(包括构造方法)包含哪些参数。eg: @MethodReflectParams("android.content.pm.PackageParser$Package", "int"), 参数为字符串对象(全类名),最终通过Class.forName(params);获取对应的Class

public final class RefClass 

    private static HashMap<Class<?>,Constructor<?>> REF_TYPES = new HashMap<Class<?>, Constructor<?>>();
    static 
        try 
            REF_TYPES.put(RefObject.class, RefObject.class.getConstructor(Class.class, Field.class));
            REF_TYPES.put(RefMethod.class, RefMethod.class.getConstructor(Class.class, Field.class));
            REF_TYPES.put(RefInt.class, RefInt.class.getConstructor(Class.class, Field.class));
            REF_TYPES.put(RefLong.class, RefLong.class.getConstructor(Class.class, Field.class));
            REF_TYPES.put(RefFloat.class, RefFloat.class.getConstructor(Class.class, Field.class));
            REF_TYPES.put(RefDouble.class, RefDouble.class.getConstructor(Class.class, Field.class));
            REF_TYPES.put(RefBoolean.class, RefBoolean.class.getConstructor(Class.class, Field.class));
            REF_TYPES.put(RefStaticObject.class, RefStaticObject.class.getConstructor(Class.class, Field.class));
            REF_TYPES.put(RefStaticInt.class, RefStaticInt.class.getConstructor(Class.class, Field.class));
            REF_TYPES.put(RefStaticMethod.class, RefStaticMethod.class.getConstructor(Class.class, Field.class));
            REF_TYPES.put(RefConstructor.class, RefConstructor.class.getConstructor(Class.class, Field.class));
        
        catch (Exception e) 
            e.printStackTrace();
        
    

    public static Class<?> load(Class<?> mappingClass, String className) 
        try 
            return load(mappingClass, Class.forName(className));
         catch (Exception e) 
            return null;
        
    

    public static Class load(Class mappingClass, Class<?> realClass) 
        Field[] fields = mappingClass.getDeclaredFields();
        for (Field field : fields) 
            try 
                if (Modifier.isStatic(field.getModifiers())) 
                    Constructor<?> constructor = REF_TYPES.get(field.getType());
                    if (constructor != null) 
                        field.set(null, constructor.newInstance(realClass, field));
                    
                
            
            catch (Exception e) 
                // Ignore
            
        
        return realClass;
    

mirror/RefObject.java

Field类封装了Java反射中java.lang.reflect.Field的field.get()和field.set()方法;其他Field类类似。

public class RefObject<T> 
    private Field field;

    public RefObject(Class<?> cls, Field field) throws NoSuchFieldException 
        this.field = cls.getDeclaredField(field.getName());
        this.field.setAccessible(true);
    

    public T get(Object object) 
        try 
            return (T) this.field.get(object);
         catch (Exception e) 
            return null;
        
    

    public void set(Object obj, T value) 
        try 
            this.field.set(obj, value);
         catch (Exception e) 
            //Ignore
        
    

mirror/RefMethod.java

方法类封装了Java反射中java.lang.reflect.Method的method.invoke()方法;在RefMethod构造方法中,会根据不同的注解MethodParams, MethodReflectParams获取对应的方法参数,从而获取对应的Method。如果没有注解,表明对应方法没有参数。

public class RefMethod<T> 
    private Method method;

    public RefMethod(Class<?> cls, Field field) throws NoSuchMethodException 
        if (field.isAnnotationPresent(MethodParams.class)) 
            Class<?>[] types = field.getAnnotation(MethodParams.class).value();
            for (int i = 0; i < types.length; i++) 
                Class<?> clazz = types[i];
                if (clazz.getClassLoader() == getClass().getClassLoader()) 
                    try 
                        Class.forName(clazz.getName());
                        Class<?> realClass = (Class<?>) clazz.getField("TYPE").get(null);
                        types[i] = realClass;
                     catch (Throwable e) 
                        throw new RuntimeException(e);
                    
                
            
            this.method = cls.getDeclaredMethod(field.getName(), types);
            this.method.setAccessible(true);
         else if (field.isAnnotationPresent(MethodReflectParams.class)) 
            String[] typeNames = field.getAnnotation(MethodReflectParams.class).value();
            Class<?>[] types = new Class<?>[typeNames.length];
            for (int i = 0; i < typeNames.length; i++) 
                Class<?> type = getProtoType(typeNames[i]);
                if (type == null) 
                    try 
                        type = Class.forName(typeNames[i]);
                     catch (ClassNotFoundException e) 
                        e.printStackTrace();
                    
                
                types[i] = type;
            
            this.method = cls.getDeclaredMethod(field.getName(), types);
            this.method.setAccessible(true);
        
        else 
            for (Method method : cls.getDeclaredMethods()) 
                if (method.getName().equals(field.getName())) 
                    this.method = method;
                    this.method.setAccessible(true);
                    break;
                
            
        
        if (this.method == null) 
            throw new NoSuchMethodException(field.getName());
        
    

    public T call(Object receiver, Object... args) 
        try 
            return (T) this.method.invoke(receiver, args);
         catch (InvocationTargetException e) 
            if (e.getCause() != null) 
                e.getCause().printStackTrace();
             else 
                e.printStackTrace();
            
         catch (Throwable e) 
            e.printStackTrace();
        
        return null;
    

    public T callWithException(Object receiver, Object... args) throws Throwable 
        try 
            return (T) this.method.invoke(receiver, args);
         catch (InvocationTargetException e) 
            if (e.getCause() != null) 
                throw e.getCause();
            
            throw e;
        
    

    public Class<?>[] paramList() 
        return method.getParameterTypes();
    

mirror/RefConstructor.java

构造方法类封装了Java反射中java.lang.reflect.Constructor的ctor.newInstance()方法。

public class RefConstructor<T> 
    private Constructor<?> ctor;

    public RefConstructor(Class<?> cls, Field field) throws NoSuchMethodException 
        if (field.isAnnotationPresent(MethodParams.class)) 
            Class<?>[] types = field.getAnnotation(MethodParams.class).value();
            ctor = cls.getDeclaredConstructor(types);
         else if (field.isAnnotationPresent(MethodReflectParams.class)) 
            String[] values = field.getAnnotation(MethodReflectParams.class).value();
            Class[] parameterTypes = new Class[values.length];
            int N = 0;
            while (N < values.length) 
                try 
                    parameterTypes[N] = Class.forName(values[N]);
                    N++;
                 catch (Exception e) 
                    e.printStackTrace();
                
            
            ctor = cls.getDeclaredConstructor(parameterTypes);
         else 
            ctor = cls.getDeclaredConstructor();
        
        if (ctor != null && !ctor.isAccessible()) 
            ctor.setAccessible(true);
        
    

    public T newInstance() 
        try 
            return (T) ctor.newInstance();
         catch (Exception e) 
            return null;
        
    

    public T newInstance(Object... params) 
        try 
            return (T) ctor.newInstance(params);
         catch (Exception e) 
            return null;
        
    

VA实例

对应ActivityThread的反射注入-普通实现,在VA中的反射注入实现:

mirror/android/app/ActivityThread.java

mirror.android.app.ActivityThread类加载时,会调用RefClass.load(ActivityThread.class, "android.app.ActivityThread");方法,遍历mirror.android.app.ActivityThread的成员变量,并从android.app.ActivityThread中获取对应的Field封装成上述类赋给mirror.android.app.ActivityThread

public class ActivityThread 
    public static Class<?> TYPE = RefClass.load(ActivityThread.class, "android.app.ActivityThread");
    public static RefStaticMethod currentActivityThread;
    public static RefMethod<String> getProcessName;
    public static RefMethod<Handler> getHandler;
    public static RefMethod<Object> installProvider;
    public static RefObject<Object> mBoundApplication;
    public static RefObject<Handler> mH;
    public static RefObject<Application> mInitialApplication;
    public static RefObject<Instrumentation> mInstrumentation;
    public static RefObject<Map<String, WeakReference<?>>> mPackages;
    public static RefObject<Map> mProviderMap;
    @MethodParams(IBinder.class, List.class)
    public static RefMethod<Void> performNewIntents;
    public static RefStaticObject<IInterface> sPackageManager;
    @MethodParams(IBinder.class, String.class, int.class, int.class, Intent.class)
    public static RefMethod<Void> sendActivityResult;
    public static RefMethod<Binder> getApplicationThread;

注: 在mirror.android.app.ActivityThread中的成员变量必须与framework中android.app.ActivityThread中对应的成员变量名相同,因为在RefClass.load()方法中是通过遍历mirror.android.app.ActivityThread中的Fields来获取对应的android.app.ActivityThread中的Fields的

com/lody/virtual/client/hook/delegate/AppInstrumentation.java

在业务层使用的时候,只需要调用ActivityThread.mInstrumentation.get(VirtualCore.mainThread());方法就可以获取当前android.app.ActivityThread中mInstrumentation的值,调用ActivityThread.mInstrumentation.set(VirtualCore.mainThread(), this);就可以通过反射注入业务层自己实现的AppInstrumentation了。

public final class AppInstrumentation extends InstrumentationDelegate implements IInjector 
    ...

    private AppInstrumentation(Instrumentation base) 
        super(base);
    


    @Override
    public void inject() throws Throwable 
        base = ActivityThread.mInstrumentation.get(VirtualCore.mainThread());
        ActivityThread.mInstrumentation.set(VirtualCore.mainThread(), this);
    

    ...

0x02. 总结

对比ActivityThread的反射注入-普通实现ActivityThread的反射注入-VA实现,初一看,可能觉得VA实现多了很多代码,还有些绕;但是普通方法实现,每对一个属性进行反射注入都需要写下方流程中的所有代码,或者部分代码;通过VA的实现方法,只需要在对应的类中(例如: mirror.android.app.ActivityThread)写上需要反射注入的属性,业务层代码也更简洁可读性更高。

反射注入的一般流程

  1. 获取需要注入的类的Class对象

Class<?> activityThreadClass = Class.forName("android.app.ActivityThread");

  1. 获取Class对象中需要注入的Field

Field instrumentationField = activityThreadClass.getDeclaredField("mInstrumentation");

  1. 通过Field的set方法注入业务层已实现的对象

instrumentationField.set(currentActivityThread, new PluginInstrumentation(instrumentation));

反射调用方法的一般流程

  1. 获取需要注入的类的Class对象

Class<?> activityThreadClass = Class.forName("android.app.ActivityThread");

  1. 获取Class对象中需要调用的Method

Method currentActivityThreadMethod = activityThreadClass.getMethod("currentActivityThread");

  1. 通过Method的invoke方法调用

Object currentActivityThread = currentActivityThreadMethod.invoke(null);

Github地址: https://github.com/ximsfei/RefInject

以上是关于VirtualApp Java层Hook基础的主要内容,如果未能解决你的问题,请参考以下文章

android常见hook框架都有哪些

android java层实现hook替换method

初识Frida--Android逆向之Java层hook

TS基础应用 & Hook中的TS

Java关闭钩子的应用 - Shutdown Hook

Android基础项目 Xposed HOOK MAC地址与详细的hook入门