查看原文
其他

7种Android Native Anti Hook的实现思路

爱吃菠菜 看雪学院 2021-03-08


本文为看雪论坛优秀文章

看雪论坛作者ID:爱吃菠菜

 


目录

(1) GOT_HOOK

(2) LDPRELOAD_HOOK

(3) INLINE_HOOK

(4) 异常HOOK

(5) ELF依赖库篡改注入

(6) LINKER_HOOK 

(7) UNICOR仿真器HOOK

(8) 实现代码



01 GOT_HOOK



>>>>

HOOK原理


篡改导入表中存储的,外部符号地址。

>>>>

检测手段


(1)硬编码记录导入符号在外部SO中的偏移值,计算外部SO的模块基址相加后得到地址,与GOT表项值进行对比。
(缺点是外部SO可能更新地址发生变化)

(2)将硬编码改为动态解析,使用dlsmy()函数或自己实现地址解析后得到地址,与GOT表项值对比。
(没有第一种的缺点,灵活一些)

(3)一般在链接后GOT表值不会发生改变,可在初始化完成时,使用前面两个方法逐个校验,然后整体计算此时的GOT表的HASH值,在后期运行时进行HASH校验。
(相比逐个校验,提高了执行效率、代码动作更小、增加了隐蔽性,甚至还可选择MD5 CRC以外的迷你HASH、或跳跃字节校验HASH)

>>>>

实现


有了GOT项下标,符号名,所属库名,再过滤出哪些需要检测的重点GOT项,即可实现检测。

STRUCT GOT{
        gotitem; // GOT项下标(预设)
        libname; // GOT项所属模块(预设)
        elfhash_sym_name; // GOT项符号名(ELFHASH)(预设)
        gotitem; // GOT项内容(运行实时计算)
        sym_addr; // 手动解析获取的符号虚拟地址(运行实时计算)
    }GOT;



02 LDPRELOAD_HOOK



>>>>

HOOK原理


我没有实际测试过这种HOOK,但可以想到,当导入了虚假的导入函数,GOT表项值会和真正的值不同。

和GOT_HOOK表现的结果差不多。

>>>>

检测手段


这样的话,检测LDPRELOAD_HOOK就类似检测GOT_HOOK。

可以使用检测GOT时的第一种硬编码方法,记录导入函数在导入SO中的偏移量然后加基址进行校验。

对于无法获取硬编码偏移量的系统库呢,比如导入了虚假的fopen什么的,
我觉得可以预设一个重点库重点函数名单,比如弄一个libc.so的IO之类的高频函数列表,手动算偏移来解决。


03 INLINE_HOOK



>>>>

HOOK原理


通过篡改函数的指令内容实现,应该是最为通用有效的HOOK手段,可以想到,通常的HOOK框架与工具,最后应该都会通过这种原理来实现。

去检测框架特征肯定不如检测INLINE_HOOK有效,就是搞起来会比较麻烦。

>>>>

检测手段


计算一份函数体的HASH,运行时/调用前/对被调函数做完整性校验。

原理并不复杂,但实际工程有很繁琐的问题,场景对灵活性要求很多。

例如:
A : 怎么确定哪些函数需要做HASH检测?
(人为设定?静态分析?动态沙箱统计?)
C : 什么时机做检测?如何在没有源代码的情况下插入检测代码,做到运行时每次调用前都会执行校验逻辑?
( 方案:基于静态动态分析的结果进行HOOK?或者直接VMP?)
B : 这些函数有的是非导出没有符号或是C++名称粉碎的,怎么获取它们的地址范围?
( 方案:全部函数都计算HASH一把梭?)
D :如何跟壳与进行结合?
( 方案:例如VMP?)
E:  准备的函数HASH数据存储在哪?
( 方案:把检测逻辑单独写成一个库,HASH数据也放库里?放在外部配置文件中?在被检测库开辟的BUFFER中?)
F:  系统库没法提前知道HASH,被INLINE_HOOK了怎么检测?
( 方案:把导入库的libcso符号替换为自己静态编译的libc内容?预设一份高频系统库函数名单在函数体中查跳转特征?)
G:  被检测的库更新了怎么办?

>>>>

实现


基础的信息是函数所属库名称、函数的起始结束地址、函数得HASH值:

<br>
STRUCT FUNC{
    libname; // 所属模块(预设)
    head; // 函数起始偏移(预设)
    end; // 函数结束偏移(预设)
    filehash; // 函数哈希(预设)
    md_base; // 所属模块基址(运行时计算)
    addr; // 函数虚拟地址(运行时计算)
}FUNC;


04 异常HOOK



>>>>

HOOK原理


我没有实际测试过这种HOOK手段,不过看到异常HOOK也会篡改函数指令内容。

所以检测异常HOOK可以使用检测INLINE_HOOK的方式,应该是这样的。


05 依赖库篡改注入



>>>>

HOOK原理


修改ELF中的DYNAMIC结构,添加一个DT_NEEDED,从而增加额外的导入SO。

>>>>

检测手段


篡改内容的这种,都可以做完整性校验。

也是提前计算好SO的关键区段的HASH,运行时再次计算来对比,校验时机的问题,和INLINE_HOOK类似(感觉吧被注入这种事避免不了的,别人HOOK我的代码还能检测检测,别人想往进程里加载SO这个机会可太多了,不改ELF也有N种方法,没什么办法)。

这里还有说一些额外的内容,我们既然篡改DYNAMIC区段可以实现一些功能,类似的,其他区段是不是也有薄弱的地方,是不是也可以篡改它们实现某些功能?

有没有必要做一些校验和保护。


比如:

函数调用这个流程不仅有GOT表,还有PLT表,保护加固校验了GOT表和INLINE,那CRACKER从PLT表下手行不行?


HASH表也在函数调用流程中, CRACKER改一下HASH表是不是也能实现HOOK的功能?

INIT_ARRAY也在流程中,这块是不是也可以实现一些意想不到得功能?


REL表修改了是不是能凭空添加一个导入函数?

>>>>

实现


struct {
    SEC dynsym;
    SEC dynstr;
    SEC hash;
    SEC reldyn;
    SEC relplt;
    SEC plt;
    SEC arm_extab;
    SEC arm_exidx;
    SEC rodata;
}LOAD1;
struct {
    SEC fini_array;
    SEC init_array;
    SEC dynamic;
}LOAD2;<br>



06 LINKER_HOOK



>>>>

HOOK原理


我没有实际测试过这种HOOK手段,不过可以脑补一下,LINKER_HOOK能做什么,应该可以控制GOT表内容,可以添加与HOOK导入函数,还有一些dlopen()相关的事。

>>>>

检测手段


CRACKER要对LINKER动手脚,暂时我也没有想到什么好的办法。

对于GOT表相关的,还是通过保护GOT表的手段去做。dlopen()相关的工程能力强的话可以自己实现dlopen,在关键行为处使用,甚至替换掉哪些调用系统dlopen的部分。


07 UNICOR



>>>>

HOOK原理


效果像是HOOK了CPU一样, HOOK中的大杀器、杀手锏,神挡杀神,X谁谁发抖。


>>>>

检测手段


基本没有办法,不过可以思考,在工程上提高CRACKER使用UNICOR的复杂度、成本。


08 代码



https://github.com/acbocai/vergil

>>>>

形式


目前检测代码,写成了一个单独的SO库形式, 需要用户自己在关键逻辑处手动调用,测试可以检测到UNICOR以外的各种hook手段和工具。

需要说明的是,依赖的参数需要另行编写工具解析构造。(推荐使用IDAT脚本或LIEF库)

>>>>

接口


API只有一个,实现文中大部分功能。

声明为:

#pragma once
#include <stdint.h>
typedef struct ANTI_HOOK{
    uint32_t global_data[48000]; // 存储着检测hook所依赖的数据,这部分的构造搬到了客户端外,编译后来构造。(原因是减少hook检测的动作和痕迹)
}ANTI_HOOK;
 
extern "C" bool anti_hook(
        ANTI_HOOK* global, // anti_hook的参数
        bool is_close, // 调用完是否关闭初始化的资源
        uint32_t* arr_func, // 要检测的函数地址(数组)
        uint32_t count)
; // 数组元素个数


>>>>

逻辑


gothook部分逻辑为:通过解析文件实现mydlsym解析出符号地址与got表对比。

inlinehook部分逻辑为:用户传入想要检测inlinehook的函数地址size,
接着计算其运行时内存的哈希,最后与预设的哈希map进行查找比对。

(作者使用idat写了另一脚本工具,可识别出文件中所有有无符号的函数区间,项目可在作者github找到。
https://bbs.pediy.com/thread-254386.htm

system lib的inlinehook部分逻辑为:预设一份libc.so的重点函数表,
初始化时计算其文件hash,后期调用时再计算内存hash与文件hash比对。

区段检测部分逻辑为:预设了所有可能被动手脚的区段的哈希,在运行时再次计算哈希进行校验,出于效率和隐蔽性考虑,使用了简易的字符串哈希方法,并且隔字节进行计算。

>>>>

补充


该库本身被HOOK、防绕过、调用检测的问题,是另一个问题,该项目不再展开涉及,可以选择与壳融合,或者静态编译libc方式解决等。

检测方法不唯一,应该会有更好的方式,但是作者没发现别人公开类似的工程,只能写一些自己目前想到的检测手段。

若有错误希望能指出,共同学习进步~




- End -







看雪ID:爱吃菠菜

https://bbs.pediy.com/user-760871.htm 


*本文由看雪论坛  爱吃菠菜 原创,转载请注明来自看雪社区







推荐文章++++

PWN入门的一些总结

固件分析--工具、方法技巧浅析(上)

一种还原白盒AES秘钥的方法

CTF中RSA的一些攻击思路

经典整数溢出漏洞示例 XCTF int_overflow








进阶安全圈,不得不读的一本书










“阅读原文”一起来充电吧!

    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存