Hook技术之Hook Activity

栏目: IOS · Android · 发布时间: 6年前

内容简介:Hook技术的核心实际上是动态分析技术,动态分析是指在程序运行时对程序进行调试的技术。众所周知,Android系统的代码和回调是按照一定的顺序执行的,这里举一个简单的例子,如图所示。对象A调用类对象B,对象B处理后将数据回调给对象A。接下来看看采用Hook的调用流程,如下图:上图中的Hook可以是一个方法或者一个对象,它就想一个钩子一样,始终连着AB,在AB之间互传信息的时候,hook会在中间做一些处理,比如修改方法的参数和返回值等,就这样hook起到了欺上瞒下的作用,我们把hook的这种行为称之为劫持。同

Hook技术的核心实际上是动态分析技术,动态分析是指在程序运行时对程序进行调试的技术。众所周知,Android系统的代码和回调是按照一定的顺序执行的,这里举一个简单的例子,如图所示。

Hook技术之Hook Activity

对象A调用类对象B,对象B处理后将数据回调给对象A。接下来看看采用Hook的调用流程,如下图:

Hook技术之Hook Activity

上图中的Hook可以是一个方法或者一个对象,它就想一个钩子一样,始终连着AB,在AB之间互传信息的时候,hook会在中间做一些处理,比如修改方法的参数和返回值等,就这样hook起到了欺上瞒下的作用,我们把hook的这种行为称之为劫持。同理,大家知道,系统进程和应该进程之间是相互独立的,应用进程要想直接去修改系统进程,这个是很难实现的,有了hook技术,就可以在进程之间进行行为更改了。如图所示:

Hook技术之Hook Activity

可见,hook将自己融入到它所劫持的对象B所在的进程中,成为系统进程的一部分,这样我们就可以通过hook来更改对象B的行为了,对象B就称为hook点。

二、Hook Instrumentation

上面讲了Hook可以劫持对象,被劫持的对象叫hook点,用代理对象来替代这个Hook点,这样我们就可以在代理上实现自己想做的操作。这里我们用Hook startActivity来举例。Activity的插件化中需要解决的一个问题就是启动一个没有在AndroidManifest中注册的Activity,如果按照正常的启动流程是会报crash的。这里先简要介绍一下Activity的启动,具体的启动方式讲解还需移步专门的文献。

2.1 Activity的Hook点

启动Activity时应用进程会发消息给AMS,请求AMS创建Activity,AMS在SystemServer系统进程中,其与应用进程是隔离的,AMS管理所有APP的启动,所以我们无法在系统进程下做hook操作,应该在应用进程中。为了绕过AMS的验证,我们需要添加一个在Manifest中注册过的Activity,这个Activity称为 占坑 ,这样可以达到欺上瞒下的效果,当AMS验证通过后再用插件Activity替换占坑去实现相应的功能。 核心功能两点:

  • 替换插件Activity为占坑Activity
  • 绕过AMS验证后需要还原插件Activity

启动Activity的时候会调用Activity的startActivity()如下:

@Override
    public void startActivity(Intent intent) {
        this.startActivity(intent, null);
    }
复制代码

接着又调用了startActivity()

@Override
    public void startActivity(Intent intent, @Nullable Bundle options) {
        if (options != null) {
            startActivityForResult(intent, -1, options);
        } else {
            // Note we want to go through this call for compatibility with
            // applications that may have overridden the method.
            startActivityForResult(intent, -1);
        }
    }
复制代码

查看startActivityForResult方法

public void startActivityForResult(@RequiresPermission Intent intent, int requestCode,
            @Nullable Bundle options) {
        if (mParent == null) {
            options = transferSpringboardActivityOptions(options);
            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());
            }
            if (requestCode >= 0) {
                // If this start is requesting a result, we can avoid making
                // the activity visible until the result is received.  Setting
                // this code during onCreate(Bundle savedInstanceState) or onResume() will keep the
                // activity hidden during this time, to avoid flickering.
                // This can only be done when a result is requested because
                // that guarantees we will get information back when the
                // activity is finished, no matter what happens to it.
                mStartedActivity = true;
            }

            cancelInputsAndStartExitTransition(options);
            // TODO Consider clearing/flushing other event sources and events for child windows.
        } else {
            if (options != null) {
                mParent.startActivityFromChild(this, intent, requestCode, options);
            } else {
                // Note we want to go through this method for compatibility with
                // existing applications that may have overridden it.
                mParent.startActivityFromChild(this, intent, requestCode);
            }
        }
    }
复制代码

上述方法中调用mInstrumentation的execStartActivity方法来启动Activity,这个mInstrumentation是Activity的成员变量,我们就选择Instrumentation为Hook点,用代理的Instrumentation去替换原始的Instrumentation来完成Hook,如下是代理类:

public class InstrumentationProxy extends Instrumentation {

    private Instrumentation mInstrumentation;
    private PackageManager mPackageManager;

    public InstrumentationProxy(Instrumentation instrumentation, PackageManager packageManager) {
        this.mInstrumentation = instrumentation;
        this.mPackageManager = packageManager;
    }

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

        List<ResolveInfo> resolveInfo = mPackageManager.queryIntentActivities(intent, PackageManager.MATCH_ALL);
        //判断启动的插件Activity是否在AndroidManifest.xml中注册过
        if (null == resolveInfo || resolveInfo.size() == 0) {
            //保存目标插件
            intent.putExtra(HookHelper.REQUEST_TARGET_INTENT_NAME, intent.getComponent().getClassName());
            //设置为占坑Activity
            intent.setClassName(who, "replugin.StubActivity");
        }

        try {
            Method execStartActivity = Instrumentation.class.getDeclaredMethod("execStartActivity",
                    Context.class, IBinder.class, IBinder.class, Activity.class,
                    Intent.class, int.class, Bundle.class);
            return (ActivityResult) execStartActivity.invoke(mInstrumentation, who, contextThread, token, target, intent, requestCode, options);
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
        return null;
    }

    public Activity newActivity(ClassLoader cl, String className, Intent intent) throws InstantiationException,
            IllegalAccessException, ClassNotFoundException {
        String intentName = intent.getStringExtra(HookHelper.REQUEST_TARGET_INTENT_NAME);
        if (!TextUtils.isEmpty(intentName)) {
            return super.newActivity(cl, intentName, intent);
        }
        return super.newActivity(cl, className, intent);
    }

}
复制代码

InstrumentationProxy类继承类Instrumentation,实现了类execStartActivity方法,接着通过反射去用原始Instrumentation的execStartActivity方法,这就是替换为占坑Activity的过程。Activity的创建是在ActivityThread中,里面有个performLaunchActivity方法;

private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
    ...
    try {
        java.lang.ClassLoader cl = appContext.getClassLoader();
        activity = mInstrumentation.newActivity(
                    cl, component.getClassName(), r.intent);
        StrictMode.incrementExpectedActivityCount(activity.getClass());
        r.intent.setExtrasClassLoader(cl);
        r.intent.prepareToEnterProcess();
        if (r.state != null) {
            r.state.setClassLoader(cl);
        }
    }
    ...
    activity.attach(appContext, this, getInstrumentation(), r.token,
                        r.ident, app, r.intent, r.activityInfo, title, r.parent,
                        r.embeddedID, r.lastNonConfigurationInstances, config,
                        r.referrer, r.voiceInteractor, window, r.configCallback);
    ...
}
复制代码

这里的newActivity就是创建Activity的过程,我们同样的在代理类中去实现这个方法,这就是还原插件Activity 的过程。

接下来我们看个例子: 占位坑Activity:

public class StubActivity extends BaseActivity {
    @Override
    public int bindLayout() {
        return R.layout.activity_stub;
    }

    @Override
    public void initViews() {
    }

    @Override
    public void onClick(View v) {

    }
}
复制代码

这个Activity一定是需要在AndroidManifest中去注册。 再写一个插件Activity

public class TargetActivity extends BaseActivity {
    @Override
    public int bindLayout() {
        return R.layout.activity_target;
    }

    @Override
    public void initViews() {

    }

    @Override
    public void onClick(View v) {

    }
}
复制代码

都是很简单的Activity,TargetActivity并没有注册,现在我们需要启动这个Activity。代理类上面代码已经贴出来了。接下来就是替换代理类,达到Hook的目的,我们在Application中做这个事情:

public class MyApplication extends Application {

    @Override
    protected void attachBaseContext(Context base) {
        super.attachBaseContext(base);
        hookActivityThreadInstrumentation();
        
    }

    private void hookActivityThreadInstrumentation() {
        try {
            Class<?> activityThreadClass=Class.forName("android.app.ActivityThread");
            Field activityThreadField=activityThreadClass.getDeclaredField("sCurrentActivityThread");
            activityThreadField.setAccessible(true);
            //获取ActivityThread对象sCurrentActivityThread
            Object activityThread=activityThreadField.get(null);

            Field instrumentationField=activityThreadClass.getDeclaredField("mInstrumentation");
            instrumentationField.setAccessible(true);
            //从sCurrentActivityThread中获取成员变量mInstrumentation
            Instrumentation instrumentation= (Instrumentation) instrumentationField.get(activityThread);
            //创建代理对象InstrumentationProxy
            InstrumentationProxy proxy=new InstrumentationProxy(instrumentation,getPackageManager());
            //将sCurrentActivityThread中成员变量mInstrumentation替换成代理类InstrumentationProxy
            instrumentationField.set(activityThread,proxy);
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}
复制代码

这样就把原始的Instrumentation替换为代理的了,具体的操作我们在InstrumentationProxy中去做实现。接下来我们就是从主界面跳转插件Activity了:

public class PluginActivity extends BaseActivity {
    @Override
    public int bindLayout() {
        return R.layout.activity_stub;
    }

    @Override
    public void initViews() {
        Log.d("", "initViews: ");
        findViewById(R.id.btn_start_replugin).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                startActivity(new Intent(PluginActivity.this, TargetActivity.class
                ));
            }
        });
    }

    @Override
    public void onClick(View v) {

    }

    public static void startActivity(Context context) {
        Intent i = new Intent(context, PluginActivity.class);
        context.startActivity(i);
    }

}
复制代码

以上所述就是小编给大家介绍的《Hook技术之Hook Activity》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Design systems

Design systems

Not all design systems are equally effective. Some can generate coherent user experiences, others produce confusing patchwork designs. Some inspire teams to contribute to them, others are neglected. S......一起来看看 《Design systems》 这本书的介绍吧!

JS 压缩/解压工具
JS 压缩/解压工具

在线压缩/解压 JS 代码

在线进制转换器
在线进制转换器

各进制数互转换器

SHA 加密
SHA 加密

SHA 加密工具