(1) GOT_HOOK
(2) LDPRELOAD_HOOK
(3) INLINE_HOOK
(4) 异常HOOK
(5) ELF依赖库篡改注入
(6)
LINKER_HOOK
(7) UNICOR仿真器HOOK
(8) 实现代码
···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;
···
HOOK原理
··· :
我没有实际测试过这种HOOK,
但可以想到,当导入了虚假的导入函数,GOT表项值会和真正的值不同
和GOT_HOOK表现的结果差不多。
··· 检测手段 ··· :
这样的话,检测LDPRELOAD_HOOK就类似检测GOT_HOOK
可以使用检测GOT时的第一种硬编码方法,
记录导入函数在导入SO中的偏移量然后加基址进行校验。
对于无法获取硬编码偏移量的系统库呢,
比如导入了虚假的fopen什么的,
我觉得可以预设一个重点库重点函数名单,
比如弄一个libc.so的IO之类的高频函数列表,
手动算偏移来解决。
··· 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值
STRUCT FUNC{ libname; // 所属模块(预设) head; // 函数起始偏移(预设) end; // 函数结束偏移(预设) filehash; // 函数哈希(预设) md_base; // 所属模块基址(运行时计算) addr; // 函数虚拟地址(运行时计算) }FUNC;
··· HOOK原理 ··· :
我没有实际测试过这种HOOK手段,
不过看到异常HOOK也会篡改函数指令内容,
所以检测异常HOOK可以使用检测INLINE_HOOK的方式,
应该是这样的。
··· 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;
··· HOOK原理 ··· :
我没有实际测试过这种HOOK手段,
不过可以脑补一下,
LINKER_HOOK能做什么,
应该可以控制GOT表内容,
可以添加与HOOK导入函数,
还有一些dlopen()相关的事。
··· 检测手段 ··· :
CRACKER要对LINKER动手脚,
暂时我也没有想到什么好的办法,
对于GOT表相关的,
还是通过保护GOT表的手段去做。
dlopen()相关的
工程能力强的话可以自己实现dlopen,
在关键行为处使用,甚至替换掉哪些调用系统dlopen的部分。
··· HOOK原理 ···:
效果像是HOOK了CPU一样,
HOOK中的大杀器、杀手锏,
神挡杀神,X谁谁发抖。
··· 检测手段 ··· :
基本没有办法,
不过可以思考,
在工程上提高CRACKER使用UNICOR的复杂度。
(1)目前检测代码这里写成了一个单独的SO库的形式,
(2)检测需要用户自己手动调用,HASH数据也需要参数传递。
(3)检测库本身被HOOK、被绕过、加保护的问题,
可以选择ANTIHOOK与壳融合,
或者静态编译libc导入函数,
代码中没有涉及,
这部分不在这一块实现了,
这个代码只作为验证。
(4)API只有一个,实现文中大部分功能。
形式为:
#pragma once #include <stdint.h> typedef struct ANTI_HOOK{ uint32_t global_data[48000]; // 作为BUFFER存储文件HASH等预设数据 }ANTI_HOOK; extern "C" bool anti_hook( ANTI_HOOK* global, // anti_hook的参数 bool is_close, // 调用完是否关闭初始化的资源 uint32_t* arr_func, // 要检测的函数地址(数组) uint32_t count); // 数组元素个数
----------------------------------------------------
方法并不唯一,应该会有更好的方式,但是没发现别人公开,只能写一些我能想到的。
若有错误希望能指出,共同学习进步~
----------------------------------------------------
[培训]《安卓高级研修班》彻底搞定函数抽取型壳!现在报名得源码和安卓8.1脱壳机!10月20日深圳专场不见不散!
最后于 15小时前 被爱吃菠菜编辑 ,原因: