肥宅钓鱼网
当前位置: 首页 钓鱼百科

hook深度剖析(Hook技术简介)

时间:2023-06-08 作者: 小编 阅读量: 3 栏目名: 钓鱼百科

在Android操作系统中系统维护着自己的一套事件分发机制。应用程序,包括应用触发事件和后台逻辑处理,也是根据事件流程一步步地向下执行。APIHook技术是一种用于改变API执行结果的技术,能够将系统的API函数执行重定向。在Android系统中使用了沙箱机制,普通用户程序的进程空间都是独立的,程序的运行互不干扰。当然,根据Hook对象与Hook后处理的事件方式不同,Hook还分为不同的种类,比如消息Hook、APIHook等。

1. 什么是 Hook

Hook 英文翻译过来就是「钩子」的意思,那我们在什么时候使用这个「钩子」呢?在 Android 操作系统中系统维护着自己的一套事件分发机制。应用程序,包括应用触发事件和后台逻辑处理,也是根据事件流程一步步地向下执行。而「钩子」的意思,就是在事件传送到终点前截获并监控事件的传输,像个钩子钩上事件一样,并且能够在钩上事件时,处理一些自己特定的事件。

Hook 的这个本领,使它能够将自身的代码「融入」被勾住(Hook)的程序的进程中,成为目标进程的一个部分。API Hook 技术是一种用于改变 API 执行结果的技术,能够将系统的 API 函数执行重定向。在 Android 系统中使用了沙箱机制,普通用户程序的进程空间都是独立的,程序的运行互不干扰。这就使我们希望通过一个程序改变其他程序的某些行为的想法不能直接实现,但是 Hook 的出现给我们开拓了解决此类问题的道路。当然,根据 Hook 对象与 Hook 后处理的事件方式不同,Hook 还分为不同的种类,比如消息 Hook、API Hook 等。

  1. 使用 Java 反射实现 API Hook
  2. 通过对 Android 平台的虚拟机注入与 Java 反射的方式,来改变 Android 虚拟机调用函数的方式(ClassLoader),从而达到 Java 函数重定向的目的,在这里我们将此类操作称为 Java API Hook。

下面通过 Hook View 的 OnClickListener 来说明 Hook 的使用方法。

首先进入 View 的 setOnClickListener 方法,我们看到 OnClickListener 对象被保存在了一个叫做 ListenerInfo 内部类里,其中 mListenerInfo 是 View 的成员变量。ListeneInfo 里面保存了 View 的各种监听事件,比如 OnClickListener、OnLongClickListener、OnKeyListener 等等。

public void setOnClickListener(@Nullable OnClickListener l) {if (!isClickable()) {setClickable(true);}getListenerInfo().mOnClickListener = l;}ListenerInfo getListenerInfo() {if (mListenerInfo != null) {return mListenerInfo;}mListenerInfo = new ListenerInfo();return mListenerInfo;}

我们的目标是 Hook OnClickListener,所以就要在给 View 设置监听事件后,替换 OnClickListener 对象,注入自定义的操作。

private void hookOnClickListener(View view) {try {// 得到 View 的 ListenerInfo 对象Method getListenerInfo = View.class.getDeclaredMethod("getListenerInfo");getListenerInfo.setAccessible(true);Object listenerInfo = getListenerInfo.invoke(view);// 得到 原始的 OnClickListener 对象Class<?> listenerInfoClz = Class.forName("android.view.View$ListenerInfo");Field mOnClickListener = listenerInfoClz.getDeclaredField("mOnClickListener");mOnClickListener.setAccessible(true);View.OnClickListener originOnClickListener = (View.OnClickListener) mOnClickListener.get(listenerInfo);// 用自定义的 OnClickListener 替换原始的 OnClickListenerView.OnClickListener hookedOnClickListener = new HookedOnClickListener(originOnClickListener);mOnClickListener.set(listenerInfo, hookedOnClickListener);} catch (Exception e) {log.warn("hook clickListener failed!", e);}}class HookedOnClickListener implements View.OnClickListener {private View.OnClickListener origin;HookedOnClickListener(View.OnClickListener origin) {this.origin = origin;}@Overridepublic void onClick(View v) {Toast.makeText(Mainactivity.this, "hook click", Toast.LENGTH_SHORT).show();log.info("Before click, do what you want to to.");if (origin != null) {origin.onClick(v);}log.info("After click, do what you want to to.");}}

到这里,我们成功 Hook 了 OnClickListener,在点击之前和点击之后可以执行某些操作,达到了我们的目的。下面是调用的部分,再给 Button 设置 OnClickListener 后,执行 Hook 操作。点击按钮后,日志的打印结果是:Before click → onClick → After click。

Button btnSend = (Button) findViewById(R.id.btn_send);btnSend.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {log.info("onClick");}});hookOnClickListener(btnSend);

我们再来看一个很常见的例子 startActivity

下面我们Hook掉 startActivity 这个方法,使得每次调用这个方法之前输出一条日志;(当然,这个输入日志有点点弱,只是为了展示原理,如果你想可以替换参数,拦截这个 startActivity 过程,使得调用它导致启动某个别的Activity,指鹿为马!)

我们知道对于Context.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);}

这里,实际上使用了 ActivityThread 类的 mInstrumentation 成员的 execStartActivity 方法;注意到, ActivityThread 实际上是主线程,而主线程一个进程只有一个,因此这里是一个良好的Hook点。

接下来就是想要Hook掉我们的主线程对象,也就是把这个主线程对象里面的 mInstrumentation 被替换成我们修改过的代理对象;要替换主线程对象里面的字段,首先我们得拿到主线程对象的引用,如何获取呢? ActivityThread 类里面有一个静态方法 currentActivityThread 可以帮助我们拿到这个对象类;但是 ActivityThread 是一个隐藏类,我们需要用反射去获取,代码如下:

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

拿到这个 currentActivityThread 之后,我们需要修改它的 mInstrumentation 这个字段为我们的代理对象,我们先实现这个代理对象,由于JDK动态代理只支持接口,而这个 Instrumentation 是一个类,没办法,我们只有手动写静态代理类,覆盖掉原始的方法即可。( cglib 可以做到基于类的动态代理,这里先不介绍)

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");}}}

Ok,有了代理对象,我们要做的就是偷梁换柱!代码比较简单,采用反射直接修改:

public static void attactContext() throws Exception{// 先获取到当前的ActivityThread对象Class<?> activityThreadClass = Class.forName("android.app.ActivityThread");Field currentActivityThreadField = activityThreadClass.getDeclaredField("sCurrentActivityThread");currentActivityThreadField.setAccessible(true);Object currentActivityThread = currentActivityThreadField.get(null);// 拿到原始的 mInstrumentation字段Field mInstrumentationField = activityThreadClass.getField("mInstrumentation");mInstrumentationField.setAccessible(true);Instrumentation mInstrumentation = (Instrumentation) mInstrumentationField.get(currentActivityThread);// 创建代理对象Instrumentation evilInstrumentation = new EvilInstrumentation(mInstrumentation);// 偷梁换柱mInstrumentationField.set(currentActivityThread, evilInstrumentation);}

好了,我们启动一个Activity测试一下,结果如下:

总结一下:

Hook 过程:

寻找 Hook 点,原则是静态变量或者单例对象,尽量 Hook public 的对象和方法。

选择合适的代理方式,如果是接口可以用动态代理。

偷梁换柱——用代理对象替换原始对象。

Android 的 API 版本比较多,方法和类可能不一样,所以要做好 API 的兼容工作。

举个例子

Android10后添加了 ActivityTaskManager

int result = ActivityTaskManager.getService().startActivity(whoThread,who.getBasePackageName(), who.getAttributionTag(), intent,intent.resolveTypeIfNeeded(who.getContentResolver()), token,target != null ? target.mEmbeddedID : null, requestCode, 0, null, options);

int result = ActivityManagerNative.getDefault().startActivity(whoThread, who.getBasePackageName(), intent,intent.resolveTypeIfNeeded(who.getContentResolver()),token, target != null ? target.mEmbeddedID : null,requestCode, 0, null, null, options);

2. Xposed

通过替换 /system/bin/app_process 程序控制 Zygote 进程,使得 app_process 在启动过程中会加载 XposedBridge.jar 这个 Jar 包,从而完成对 Zygote 进程及其创建的 Dalvik 虚拟机的劫持。

Xposed 在开机的时候完成对所有的 Hook Function 的劫持,在原 Function 执行的前后加上自定义代码。

现在安装Xposed比较方便,因为Xposed作者开发了一个Xposed Installer App,下载后按照提示傻瓜式安装(前提是root手机)。其实它的安装过程是这个样子的:首先探测手机型号,然后按照手机版本下载不同的刷机包,最后把Xposed刷机包刷入手机重启就好。刷机包下载 里面有所有版本的刷机包。

刷机包解压打开里面的问件构成是这个样子的:

META-INF/里面有文件配置脚本 flash-script.sh 配置各个文件安装位置。system/bin/替换zygote进程等文件system/framework/XposedBridge.jar jar包位置system/lib system/lib64 一些so文件所在位置xposed.prop xposed版本说明文件

所以安装Xposed的过程就上把上面这些文件放到手机里相同文件路径下。

通过查看文件安装脚本发现:

system/bin/下面的文件替换了app_process等文件,app_process就是zygote进程文件。所以Xposed通过替换zygote进程实现了控制手机上所有app进程。因为所有app进程都是由Zygote fork出来的。

Xposed的基本原理是修改了ART/Davilk虚拟机,将需要hook的函数注册为Native层函数。当执行到这一函数是虚拟机会优先执行Native层函数,然后再去执行Java层函数,这样完成函数的hook。如下图:

通过读Xposed源码发现其启动过程:

  1. 手机启动时init进程会启动zygote这个进程。由于zygote进程文件app_process已被替换,所以启动的时Xposed版的zygote进程。
  2. Xposed_zygote进程启动后会初始化一些so文件(system/lib system/lib64),然后进入XposedBridge.jar中的XposedBridge.main中初始化jar包完成对一些关键Android系统函数的hook。
  3. Hook则是利用修改过的虚拟机将函数注册为native函数。
  4. 然后再返回zygote中完成原本zygote需要做的工作。
  5. 这只是在宏观层面稍微介绍了下Xposed,要想详细了解需要读它的源码了。
    推荐阅读
  • overlord第四季多久更新(OVERLORD第四季具体放送时间将公布)

    《OVERLORD》第四季具体放送时间将公布!——NHK广播第1节目『animestellar』与《OVERLORD》特集的配信决定!多角度深入挖掘日本动画的成人谈话节目『animestellar』,《OVERLORD》导演伊藤尚往和系列构成菅原雪绘作为嘉宾出演,将会在这公布《OVERLORD》第四季的最新消息!2022年7月份快要来临了,时隔四年,《OVERLORD》第四季终于要回归了,这次所要公开的情报必然是“具体的放送时间”!

  • 电磁炉温度h6什么意思(电磁炉ec什么意思)

    需要针对检锅电路或者其他电路进行查测,即便没有资料进行处理也需要看看是电压方面供电有问题还是电流方面检查有问题或者是什么原因保护导致。电磁炉显示ec是什么意思EC不是代码,就是显示板坏了。

  • 红糖能补气养血(红糖有补血养颜的养生效果)

    红糖一直被认为是补血佳品。红糖排毒滋润的这种特殊作用,主要得益于它的天然成分。1红糖滋润茶绿茶2克,红糖30克,沸水冲泡后,加盖5分钟即可饮用。将红糖用热水溶化,加入鲜牛奶或奶粉,冲调后涂于面部,30分钟后用清水洗净。作法:将山楂肉、桂枝装入瓦煲内,加清水2碗,用文火煎至l碗时,加入红糖,调匀,煮沸即可。适用于女子寒性痛经症及面色无华。

  • 亚巨龟属于陆龟吗(亚巨龟属于什么龟)

    亚巨龟并不是属于陆龟,它是属于偏水性的半水龟,不过在陆地上同样可以长时间生活。它是属于杂食性的乌龟,最主要喜欢吃的就是植物,如果是人工饲养,可能有些对肉类会产生很大的兴趣。它不能承受寒冷的温度,否则会引发它的呼吸系统疾病,如果非常干冷对它而言是致命的,因此在天气凉的时候,最好把它放在室内去养护,同时它的体积比较大,需要放在大型的范围内去饲养。

  • 冬季短款羽绒棉衣实用穿搭(想告别臃肿时代)

    寒潮来袭,抵御寒潮势不可挡。无负担却温暖满分,即便在上班的路上都不会感到寒冷。冬装保暖真毛领带帽羽绒棉服女中长款修身大码女装棉衣外套袄这款羽绒棉服面料不错,质量也不错,穿的舒服,也不紧,还很显瘦。这款羽绒棉服衣服版型是宽松型的,腰部为内收腰,面料是那种棉感的,但是又不粘毛。衣服穿上真的抗寒,面料比实体店的要好,毛领也大。面料手感很好哦,很漂亮哦,没有色差,衣服品质真的很好,而且上档次!

  • iphone6打开sarari网页慢的问题(iphone safari打开网页特别慢)

    iphone6打开sarari网页慢的原因是网络卡顿或手机内存不足,导致网页打开缓慢。建议清理手机垃圾,删除不必要的应用,关闭网络后重启手机即可。Safari是一款由苹果开发的网页浏览器,是各类苹果设备的默认浏览器。Safari使用WebKit浏览器引擎。Safari以惊人速度渲染网页,让最爱网站一目了然,聪明的它还会检查拼写和语法。

  • 江苏86岁老人与沈安娜相逢(江苏86岁老人与沈安娜相逢)

    期间,她多次提到了姐姐沈伊娜和舒曰信的名字。所以在强压之下,姚子健所在的学校被强制解散。最重要的是,国民党的官员并不是为了百姓着想。毕竟姚子健的工作领域十分重要,在当时掌握有效的地图,对敌人的打击可想而知。因为当时情报的工作发生了变化,姚子健对上线的信息一无所知。之后,沈安娜赶紧将这个消息上报给了上级。经过一系列的核实之后,最终确认了姚子健中央特科工作人员的身份。

  • 来大姨妈能吃猪肝吗(月经期间能不能吃猪肝呢)

    虽然女性的月经期有很多的饮食禁忌,但是猪肝是月经期非常好的一种食物。猪肝中的铁元素含量非常丰富,而且很容易被人体吸收和利用,猪肝中的蛋白质含量也很高。在月经期时吃猪肝,可以起到补充铁质及优质蛋白质的功效,对于预防和缓解女性月经期因慢性失血,引起的缺铁性贫血症状具有可靠的效果。当然,猪肝中同时也含有比较高的胆固醇和嘌呤,高胆固醇血症以及高尿酸的患者,应当控制摄入量,以免引起其他身体不适的症状。

  • 大青枣的营养价值表(软枣的营养价值)

    软枣又称黑枣、君迁子、野柿子、乌枣,每100克中含有:蛋白质1.7克、脂肪0.3克、碳水化合物54.7克、膳食纤维2.6克、胡萝卜素40微克、维生素E1.88毫克、钙108毫克、磷63毫克、钾478毫克、镁32毫克、铁1.2毫克、锌0.44毫克、硒0.53微克。软枣常用来嫁接柿子,果实手指头大小,成熟后是黑色的,很甜。不成熟吃不成,因它含有鞣酸,吃起来很涩。

  • 守望先锋联赛阶段复赛介绍 守望先锋联赛百度百科

    OWL的复赛又有什么特别的地方呢?守望先锋联赛阶段复赛介绍守望先锋联赛在第1、2、3阶段结束后将展开阶段复赛,届时将共有八支队伍进行比拼,包括阶段赛的两个分区冠军,以及其后前六名的队伍。OWL阶段复赛的奖金池共计50万美元,其中冠军20万美元,亚军10万美元,第三和第四名各5万美元,第五至第八名各2.5万美元。以上为大家带来的就是守望先锋联赛阶段复赛介绍,希望能帮助大家更好的理解OWL的赛程相关。