android hook详解

在谈hook之前,我们先谈下代理。代理对学编程的人来说,应该非常熟悉,毕竟代理模式还是很常用的。考虑到真有人不知道代理模式,我们先从静态代理开始讲解。

先写个接口:

public interface Animal {
    void eat();
    int feet();
}

实现该接口:

public class Cat implements Animal{
    @Override
    public void eat() {
        System.out.print("cat eat");
    }

    @Override
    public int feet() {
        return 4;
    }
}

现在写过静态代理ProxyAnimal:

public class ProxyAnimal implements Animal {
    private Animal realAnim;
    
    public ProxyAnimal(Animal realAnim){
        this.realAnim = realAnim;
    }
    @Override
    public void eat() {
        realAnim.eat();
    }

    @Override
    public int feet() {
        return realAnim.feet();
    }
}

这样很简单,就是实现了一个静态代理模式,如果第三方想访问cat,你给它一个ProxyAnimal就可以了,这样也避免暴露Cat对象,具体作用可以百度代理模式。

静态代理模式尽管简单,但是它有点繁琐,假如我还有一个Shopping也需要个代理,那你还需要依葫芦画瓢,写一个ShopingProxy,这样公式化代码太多,为避免这种局面,出现了动态代理。无论为多少个接口实现代理,都只需要写一个代理类即可。如:

public class ProxyHandler implements InvocationHandler {
    private Object realObj;

    public ProxyHandler(Object realObj){
        this.realObj = realObj;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) {
        System.out.println("excute before >>>" + method.getName());
        Object obj;
        try {
            obj = method.invoke(realObj, args);
        } catch (Exception e) {
            obj = null;
        }
        System.out.println("excute after >>>" + method.getName());
        return obj;
    }
}

接着就可以在client测试如下:

public static void main(String [] args){
        Animal cat = new Cat();
        Animal proxy = (Animal)Proxy.newProxyInstance(Animal.class.getClassLoader(),cat.getClass().getInterfaces(),
                new ProxyHandler(cat));
        proxy.eat();
        Shopping shopping = new ShoppingImpl();
        Shopping shopProxy = (Shopping)Proxy.newProxyInstance(Shopping.class.getClassLoader(),
                shopping.getClass().getInterfaces(),new ProxyHandler(shopping));
        shopProxy.doShopping();

    }

这样就避免去写AnimalProxy和ShoppingProxy,用一个ProxyHandler就搞定。

到这基础已经讲解完毕,接着开始进入正题。

谈到hook,其实说白了就是找一个hook点,hook点的选取十分重要,好坏决定难易程度。通常我们优先考虑静态属性或方法, & public属性或方法,实在没办法才去考虑保护型或私有型属性&方法(毕竟这种类型需要考虑版本兼容)。

原理知道后,还是实战来得舒畅,下面以startActivity启动一个activity为例,在Activity中启动另一个Activity,首先我们需要了解activity的启动流程,一步步跟踪,发现最终在Activity.startActivityForResult中以如下方式启动:

 Instrumentation.ActivityResult ar =
                mInstrumentation.execStartActivity(
                    this, mMainThread.getApplicationThread(), mToken, this,
                    intent, requestCode, options);
            if (ar != null) {
                mMainThread.sendActivityResult(
                    mToken, mEmbeddedID, requestCode, ar.getResultCode(),
                    ar.getResultData());
            }

如果你对Activity启动熟悉的话会发现,此处的mInstrumentation就是ActivityThread通过ativity.attach传过来的,而ActivityThread一个app只有一个,而mInstrumentation就是在ActivityThread创建后马上创建的,此时,是不是感觉这个mInstrumentation符合hook点,ok,先hook一把

public static void hookCurrentThread(){
        try {
            Class<?> activityThreadCls = Class.forName("android.app.ActivityThread");
            //1.获取ActivityThread对象
            //hook点,有public的方法或属性,优先
            Method currentActThreadMethod = activityThreadCls.getDeclaredMethod("currentActivityThread");
            Object curThreadObj = currentActThreadMethod.invoke(null);
            //获取mInstrumentation
            Field instrumentationField = curThreadObj.getClass().getDeclaredField("mInstrumentation");
            instrumentationField.setAccessible(true);
            instrumentationField.set(curThreadObj,new InstrumentProxy((Instrumentation) instrumentationField.get(curThreadObj)));
        } catch (Exception e) {
            e.printStackTrace();
        }

其中InstrumentProxy如下:

public class InstrumentProxy extends Instrumentation{
    private Instrumentation realObj;
    public InstrumentProxy(Instrumentation obj){
        this.realObj = obj;
    }
    public ActivityResult execStartActivity(
            Context who, IBinder contextThread, IBinder token, Activity target,
            Intent intent, int requestCode, Bundle options) {
       
        // 开始调用原始的方法, 调不调用随你,但是不调用的话, 所有的startActivity都失效了.
        // 由于这个方法是隐藏的,因此需要使用反射调用;首先找到这个方法
        try {
            Method execStartActivity = Instrumentation.class.getDeclaredMethod(
                    "execStartActivity",
                    Context.class, IBinder.class, IBinder.class, Activity.class,
                    Intent.class, int.class, Bundle.class);
            execStartActivity.setAccessible(true);
            return (ActivityResult) execStartActivity.invoke(realObj, who,
                    contextThread, token, target, intent, requestCode, options);
        } catch (Exception e) {
            // 某该死的rom修改了  需要手动适配
            throw new RuntimeException("do not support!!! pls adapt it");
        }
    }
}

此处,先获取ActivityThread中的mInstrumentation属性,然后再采用静态代理将其替换掉,这样就hook住系统的方法,我们也就可以在InstrumentProxy中任意插桩。

倘若你没有发现mInstrumentation符合hook点,你可以继续跟踪Instrumentation.execStartActivity方法,里面有个非常明显的hook点:

try {
1494            intent.migrateExtraStreamToClipData();
1495            intent.prepareToLeaveProcess();
1496            int result = ActivityManagerNative.getDefault()
1497                .startActivity(whoThread, who.getBasePackageName(), intent,
1498                        intent.resolveTypeIfNeeded(who.getContentResolver()),
1499                        token, target != null ? target.mEmbeddedID : null,
1500                        requestCode, 0, null, options);
1501            checkStartActivityResult(result, intent);
1502        } catch (RemoteException e) {
1503        }

对就是ActivityManagerNative.getDefault(),我们可以进入ActivityManagerNative类,发现getDefault方法实现如下:

static public IActivityManager getDefault() {
81          return gDefault.get();
82      }

其中gDefault是个static属性,完全符合hook要求,具体hook如下:

public static void hookAMNative(){
        try {
            Class<?> actManagerNativeCls = Class.forName("android.app.ActivityManagerNative");

            //获取gDefault
            Field gDefaultField = actManagerNativeCls.getDeclaredField("gDefault");
            gDefaultField.setAccessible(true);
            Object gDefaultObj = gDefaultField.get(null);
//            Method getField = gDefaultObj.getClass().getDeclaredMethod("get");
//            Object activityImpl = getField.invoke(null);

            Class<?> singleton = Class.forName("android.util.Singleton");
            Field mInstanceField = singleton.getDeclaredField("mInstance");
            mInstanceField.setAccessible(true);

            Object activityImpl = mInstanceField.get(gDefaultObj);

//            Method activityManagerMethod= actManagerNativeCls.getMethod("getDefault");
//            Object actManagerImpl = activityManagerMethod.invoke(null);

            Object actProxy = Proxy.newProxyInstance(activityImpl.getClass().getClassLoader(),
                    activityImpl.getClass().getInterfaces(),new ProxyHandler(activityImpl,null));
            mInstanceField.set(gDefaultObj,actProxy);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

其中ProxyHandler如下:

public class ProxyHandler implements InvocationHandler{
    private Object realObj;
    public ProxyHandler(Object obj,Object d){
        this.realObj = obj;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        String methodName = method.getName();
        if("startActivity".equals(methodName)){
            android.util.Log.e("hooktest",">>>>proxyhandler>>>startActivityBefore");
            Object retur = method.invoke(realObj,args);
            android.util.Log.e("hooktest",">>>>proxyhandler>>>startActivityAfter");
            return retur;
        }

        return method.invoke(realObj,args);
    }
}

采用动态代理的方法对IActivityManager进行了接管,同样完成了startActivity的hook。

其实不选单例、静态属性或共有属性,整个private的也是可以的,还以启动startActivity为例,考虑到Activity是继承ContextWrapper,而ContextWrapper中有个属性mBase,如果我们能对mBase hook也是可以的,这样就需要对ContextImpl来个代理就可以了,代码可以如下:

public static void hookContextWrapper(ContextWrapper wrapper) {
        try {
            Field mBaseFiled;
            Class<?> wrapperClass = ContextWrapper.class;

            mBaseFiled = wrapperClass.getDeclaredField("mBase");
            mBaseFiled.setAccessible(true);
            mBaseFiled.set(wrapper,new HookWrapper((Context) mBaseFiled.get(wrapper)));

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

这样尽管可以,但是启动activity就需要注意了,只要弄走到mBase.startActivity(intent)接口才生效,如果没走它,就hook失效啰,所以hook点选择很关键,尽管都hook到了东西,但是是不是hook住了全部,还需要验证下。

hook重在选hook点!!!


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章