首页
社区
课程
招聘
[原创]Android elf hook的方式
发表于: 2020-1-2 12:58 11594

[原创]Android elf hook的方式

2020-1-2 12:58
11594

自己写了之后觉得,got和plt应该分开更好一些。got hook有爱奇艺的xhook,个人或者github上一些开源的got hook。爱奇艺说是plt hook,个人觉得很疑惑,看实现也是替换got表中的地址值,和plt没太大关系吧。

plt hook我知道的应该是Facebook:https://github.com/facebookincubator/profilo/tree/master/deps/plthooks
待测试,应该是替换/hook plt的代码,所以应该可以获取到的函数地址还是真实函数的地址,所以可以绕过hook检测。但同时缺陷也比较明显,如果先获取函数地址,在直接调用这个函数地址/指针,则不经过plt,那么就无法hook。

图片描述
图片描述

直接取got表中的值,不经过plt。

所以各有优缺点,plt hook有inline hook的原理,比got hook到的几率更小。

plt可以理解为就是一个跳板
图片描述

got表存储绝对地址
图片描述

plt的延迟绑定在Android上面并没有什么体现,所以简单理解为跳板即可。

对于dlsym获取函数再调用的无效,可以hook dlsym过滤。如果没有显示引用函数a,但是有dlsym调用,那么plt/got中没有函数a,只能过滤dlsym。

通过获取函数地址,和函数应该所在的so在maps中的地址做对比,不在so的代码中,绝大多数就是被hook了。

可以hook读maps文件,过滤为合理的地址,但是实际应该自己分析有没有影响到一些正常功能的逻辑,比如你把libc.so的地址改变了,会不会有其他的正常代码去读取这个内存地址的内容或者解析elf等。

还有一个方式,是发现很多检测方式存在不合理的地方,通过maps确定libc.so的地址,只是线性的获取,从第一个到最后一个。那么其实可以再次加载libc.so,这样maps中有两个so了,而只要hook函数的地址在第二个libc.so和第一个libc.so之间即可。所以说写代码一定要逻辑严谨。

这个叫法可能会有些歧义。
got表其实可以看做既有导入也有导出,导入的就是其他so的一些函数,比如libc的open,导出就是供其他so调用的一些函数,有的在got表中也存在。这里说的导出表hook不是指这种,因为这和got表hook是一样的,不过确实存在一些情况,本so的导出函数被本so的其他函数调用但是不是直接采用偏移而是走plt、got,所以这种的got表hook可以实现hook本so的导出函数。
图片描述

在linker加载so的时候会把要导入的函数绝对地址写入got表,dlsym也会返回一个导出函数的绝对地址。分析这两个过程发现都是通过解析导出函数的so,通过基址+偏移值确定函数绝对地址的。所以把这个偏移值修改,指向hook函数即可完成hook。

导出表hook后,通过日志查看,open函数还是真实地址,dlsym返回的dlopen是hook函数,执行也触发hook了。高版本open和open64是同一个函数。
图片描述

再次加载so,可以不是同一个so,主要是触发init函数。got表中的open函数为上次的hook函数。因为再次导出表hook了,所以dlsym返回的open函数为新的hook函数。
图片描述

通过这两次测试,得出结论:
1、因为libc.so的open函数已经被导出表hook了,所以之后加载的so如果导入open函数,那么不用再进行got表hook了,linker会把hook函数地址写入got表。但是对已经加载过的so,因为got已经写入完成,无法hook。
2、dlsym获取函数也会返回hook函数地址,对所有so生效,已加载和未加载的。

可以发现导出表hook和got表hook是相反的且互补的。
使用got表hook只能对已加载的so的got表进行修改,未加载的无法完成,当然可以定时扫描新加载了哪些so或者hook dlopen(但是依赖的so的加载拦截不到),但都不是很完美。且对一些在init、init_array、JNI_OnLoad函数内的hook很难生效,因为时机不可控。对dlsym方式,got表hook无效,不过可以hook dlsym做一个变通。

使用导出表hook弥补了这些不足,不同于got表hook对需要导入函数的so进行修改,而是对导出函数的so进行修改。所以一次hook可以对之后加载的so都生效,但对已经加载的so,解析好got表的就无效了。对dlsym同样生效。

所以结合优缺点应该got表和导出表hook同时使用,能起到不错的效果。但是好像没什么人实现导出表hook(可能是我孤陋寡闻了),写了个简单的测试导出表hook,抽时间完善开源。

对于特征检测和隐藏和got表其实是一样的。

注:谢谢@Amun提醒,确实存在这种情况,如果是前向偏移是实现不了的,所以hook前需要判断hook函数和被hook函数/so的地址是向前还是向后偏移。

这个在Android上好像没什么人用,我记得好像只见过梆梆的壳里对art的处理使用过。没啥难度,资料也多就不细说了。因为对于Android应用程序来说都是通过zygot/zygote64 fork出来的,这个LD_PRELOAD hook基本没什么用武之地,毕竟新的纯linux进程的情况在Android上太少用了,只在一些特殊情况使用,所以放在最后吧,有时间再说。

有了got表hook和导出表hook为什么还要有inline hook,那是因为前面的hook都是对于导出函数而言的,多是用于系统api的hook,对于非导出函数就无能为力了。还有一个场景,在执行到某个函数的中间的某条指令想打印下/修改寄存器。以上的hook方式检测起来太容易。

已实现一个inline hook框架,关于inline hook参见如下链接。
https://bbs.pediy.com/thread-257020.htm

虽然inline hook能实现以上hook能做到也能实现不能做到的,但是其难度是较高的且并不像以上的hook方式那么保险,可能要建立在先逆向的基础上才能保证hook成功,比如函数指令长度至少要能放下一个跳板,不然覆盖了后面的指令造成无法预料的错误。

因为是在原函数地址覆盖指令,所以没有改变函数地址,不存在以上的函数的特征。
检测常用的就是采集函数的指令,比如常见的hook框架的跳板或者正常函数的指令,甚至可以做个数据库保存每个厂家、版本系统的so的某个函数指令。而解析指令这种事情最好不要放在客户端,不然逻辑、想做什么都暴露了。

不太常用的还有,比如覆盖指令的时候需要修改一页内存至少为可写,当然一般都是读写、执行,所以在maps中这一页内存会被单独列出来,等于告诉别人这页内存我修改过了,那么这页内的函数多数也是被修改过了。

还有检测hook框架特征等等吧,比以上的hook来说检测起来会麻烦很多。当然inline hook的这些特征检测方式,以上几种hook也是可以用来检测的。

通过dlopen的一个问题也可以引出一种检测方式,检测lr寄存器。如果调用该函数a的上一个函数b被hook了,如果需要返回值,那么lr寄存器肯定不是函数b范围内的地址,那么通过检测这个地址就可以判断是否被hook。

我是觉得这个是最接近完美的方法,可以解决inline hook的指令太短的问题。且只需备份一条指令,修复一条指令所需考虑的比inline hook修复多条指令要容易的多。

借助异常机制,捕获返回寄存器的数组,通过操作pc寄存器即可控制接下来的流程。
好像没见到这种hook框架(对我而言),打算近期完善开源。

特征的话和inline hook基本是一样的。


[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

最后于 2020-1-2 16:12 被卓桐编辑 ,原因:
收藏
免费 2
支持
分享
最新回复 (6)
雪    币: 916
活跃值: (3434)
能力值: ( LV8,RANK:120 )
在线值:
发帖
回帖
粉丝
2
利用signal的hook之前写过一个,100行代码左右就可以实现基本功能。
后来github也有人开源了一个: https://github.com/dodola/TrapHook, 其实也是老技术了
2020-1-2 14:30
0
雪    币: 6737
活跃值: (796)
能力值: ( LV13,RANK:393 )
在线值:
发帖
回帖
粉丝
3
葫芦娃 利用signal的hook之前写过一个,100行代码左右就可以实现基本功能。 后来github也有人开源了一个: https://github.com/dodola/TrapHook, 其实也是老技 ...
嗯,这些hook方式都是老技术,不过很多都是零散的,很少有真正不需要自己修复、定制就可用的,或者太注重代码艺术、框架本身造成参与开发成本较高。还有就是那种hook方式最适合什么场景、优缺点等很少有人提及,写这个也主要是答疑,都不是新技术
2020-1-2 15:06
0
雪    币: 1110
活跃值: (3274)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
4
因为导出表的符号偏移是无符号类型,不能指哪打哪
2020-1-2 15:35
0
雪    币: 6737
活跃值: (796)
能力值: ( LV13,RANK:393 )
在线值:
发帖
回帖
粉丝
5
Amun 因为导出表的符号偏移是无符号类型,不能指哪打哪
多谢提醒,加上备注
2020-1-2 16:09
0
雪    币: 35
活跃值: (88)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
7
关于got表的hook有个问题想请教大佬。
文章中,在got hook的检测里提到:“通过获取函数地址,和函数应该所在的so在maps中的地址做对比”
但我实际测试的时候发现一个问题:直接通过函数名取到的地址,跟调用时的地址是不同的。比如下面这行代码:
LOGD("HOOKTEST open %x %d", open, open("asd", O_RDONLY));

打印出的第一个open的地址仍然是hook前的地址,但调用的却是修改之后的open的地址。用IDA查看这个so发现,两个open分别是两个不同的got表项。所以导致了这个结果。

所以“获取函数地址”是否必须通过读取got表项完成?
顺带问一下,除了解析elf header之外,还有什么别的方法获取到got表项?
2020-4-30 15:52
0
游客
登录 | 注册 方可回帖
返回
//