动态代理实现方法以及对象HooK

上一篇文章里面已经把动态代理的作用以及实现方法分析了一下,很明显我们可以用HooK做很多事情,比如例子里面的代理做了拿了回扣和偷换行货这种肮脏龌龊的事情。

在真正应用的时候我们可以做更多的事情,比如用户登录的时候动态代理他的验证方法,是不是就可以获取用户的账号密码呢?还有比如Activity的启动,我们使用动态代理的手段将contextImp对象进行动态代理,对startActivity()函数进行修改,比如修改Intent的flag或者Intent内部数据等等,导致跳转的页面发生变化,或者改变传递的信息等等。

而实现HOOK的步骤一般分为三步:
1. 寻找Hook点,原则是静态变量或者单例对象,尽量Hook pulic的对象和方法,非public不保证每个版本都一样,需要适配。
2. 选择合适的代理方式,如果是接口可以用动态代理;如果是类可以手动写代理也可以使用cglib。
3. 偷梁换柱——用代理对象替换原始对象

下面以改变startActivity() 逻辑为例来展示HOOK的威力。

首先我们得找到被Hook的对象,我称之为Hook点;什么样的对象比较好Hook呢?自然是容易找到的对象。什么样的对象容易找到?静态变量和单例;在一个进程之内,静态变量和单例变量是相对不容易发生变化的,因此非常容易定位,而普通的对象则要么无法标志,要么容易改变。我们根据这个原则找到所谓的Hook点。

然后我们分析一下startActivity的调用链,找出合适的Hook点。
我们知道对于Context.startActivityActivity.startActivity的调用链与之不同,由于Context的实现实际上是ContextImpl;我们看ConetxtImpl类的startActivity方法:

@Override
public void startActivity(Intent intent, Bundle options) {
    warnIfCallingFromSystemProcess();
    if ((intent.getFlags()&Intent.FLAG_ACTIVITY_NEW_TASK) == 0) {
        throw new AndroidRuntimeException(
                "Calling startActivity() from outside of an Activity "
                + " context requires the FLAG_ACTIVITY_NEW_TASK flag."
                + " Is this really what you want?");
    }
    mMainThread.getInstrumentation().execStartActivity(
        getOuterContext(), mMainThread.getApplicationThread(), null,
        (Activity)null, intent, -1, options);
}

可以看到contextImp的startActivity的逻辑是:

(1) 先判断intent的flag是不是FLAG_ACTIVITY_NEW_TASK类型的,如果是走步骤2;如果不是,说明startActivity 不是在activity中调用的,在activity中调用的话,走的流程如下:

 Step 1. Activity.startActivity通过指定名称“activity.subactivity”来告诉应用程序框架层,它要隐式地启动SubActivity。所不同的是传入的参数intent没有Intent.FLAG_ACTIVITY_NEW_TASK标志,表示这个SubActivity和启动它的MainActivity运行在同一个Task中。

 Step 2. Activity.startActivityForResult

 Step 3. Instrumentation.execStartActivity

 Step 4. ActivityManagerProxy.startActivity

 详见罗老师源码分析:[Android应用程序内部启动Activity过程(startActivity)的源代码分析](http://blog.csdn.net/Luoshengyang/article/details/6703247)

(2) 调用了ActivityThread类的mInstrumentation成员的execStartActivity方法。

注意到,ActivityThread 实际上是主线程,而主线程一个进程只有一个mInstrumentation,只在应用刚刚打开第一个activity的时候创建(单例模式),之后不会发生变化,而且看到不管是Activity中startActivity还是在其他地方,调用的都是mInstrumentation.execStartActivity(),因此mInstrumentation对象是一个良好的Hook点。
分析完我们的HOOK的点后,接下来就要替换了我们的mInstrumentation对象了,代码如下:

第一步首先通过反射把当前进程的ActivityThread对象拿到手:

// 先获取到当前的ActivityThread对象
Class<?> activityThreadClass = Class.forName("android.app.ActivityThread");
Method currentActivityThreadMethod = activityThreadClass.getDeclaredMethod("currentActivityThread");
currentActivityThreadMethod.setAccessible(true);
Object currentActivityThread = currentActivityThreadMethod.invoke(null);

第二步,虽然动态代理可以非常方便的进行代理对象,但是我们的mInstrumentation对象不是接口,因此没有办法采用动态代理方式创建代理类,那就没办法只能通过继承来静态代理我们的mInstrumentation,然后覆写我们的mInstrumentation的execStartActivity方法,代码如下:

public class EvilInstrumentation extends Instrumentation {

    private static final String TAG = "EvilInstrumentation";

    // ActivityThread中原始的对象, 保存起来
    Instrumentation mBase;

    public EvilInstrumentation(Instrumentation base) {
        mBase = base;
    }

    public ActivityResult execStartActivity(
            Context who, IBinder contextThread, IBinder token, Activity target,
            Intent intent, int requestCode, Bundle options) {

        // Hook之前, XXX到此一游!
        Log.d(TAG, "\n执行了startActivity, 参数如下: \n" + "who = [" + who + "], " +
                "\ncontextThread = [" + contextThread + "], \ntoken = [" + token + "], " +
                "\ntarget = [" + target + "], \nintent = [" + intent +
                "], \nrequestCode = [" + requestCode + "], \noptions = [" + 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(mBase, who, 
                    contextThread, token, target, intent, requestCode, options);
        } catch (Exception e) {
            // 某该死的rom修改了  需要手动适配
            throw new RuntimeException("do not support!!! pls adapt it");
        }
    }
}

上面代码就是静态代理的代码,execStartActivity中先是打印一些信息,然后通过反射拿到Instrumentation的execStartActivity方法,进行调用,之所以要反射是因为这个方法不可见,必须要反射才能调用,这是静态代理所不能实现的功能。
创建了Instrumentation的代理对象,又找到了HOOK点,最后就只需要把需要替换的对象换掉就可以了。

第三步,使用反射进行Instrumentation对象的替换:代码如下:

public static void attachContext() throws Exception{
    // 1先获取到当前的ActivityThread对象
    Class<?> activityThreadClass = Class.forName("android.app.ActivityThread");
    Method currentActivityThreadMethod = activityThreadClass.getDeclaredMethod("currentActivityThread");
    currentActivityThreadMethod.setAccessible(true);
    Object currentActivityThread = currentActivityThreadMethod.invoke(null);

    // 2拿到原始的 mInstrumentation字段
    Field mInstrumentationField = activityThreadClass.getDeclaredField("mInstrumentation");
    mInstrumentationField.setAccessible(true);
    Instrumentation mInstrumentation = (Instrumentation) mInstrumentationField.get(currentActivityThread);

    //3 创建代理对象,使用反射偷梁换柱
    Instrumentation evilInstrumentation = new EvilInstrumentation(mInstrumentation);
    mInstrumentationField.set(currentActivityThread, evilInstrumentation);
}

最后都完成了,那么就要测试一下了,看看能不能打印出我们代理函数里面的数据了,打印结果如下:

07-11 22:19:20 9207-9207/com.dynamic_proxy_hook.app D/EvilInstrumentation:执行了startActivity,参数如下:
who = [android.app.Application@76726c01],
contextThread = [android.app.ActivityThread$ApplicationThread@4353489dd1],
token = [null],
target = [null],
intent = [Intent { act=android.intent.action.test dat=sadjksadk flg-0x10000000}],
requestCode = [-1],
options = [null]

可以看到打印出来了,结果就是HOOK成功了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值