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

来源:互联网 发布:qq空间 for mac客户端 编辑:程序博客网 时间:2024/05/21 12:08

上一篇文章里面已经把动态代理的作用以及实现方法分析了一下,很明显我们可以用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方法:

@Overridepublic 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成功了。