如果觉得格式不美观,请拉到结尾下载pdf~
为什么市面上那么多成品的InlineHook,比如Dobby,SandHook,ShadowHook,还要再写一个?
因为这些hook大多数是项目级别的,不带教程讲解,上手难度略高,以及现在对InlineHook检测比较严重,有必要了解原理,从头写一个,知道哪里能检测,哪里需要注意,需要改的点在哪
我写这篇文章主要是记录开发过程,方便后边回顾,以及给一些感兴趣的朋友作为一个入手的点,快速的上手InlineHook的原理。
先放项目地址:d7aK9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1k6B7K9i4q4A6N6e0t1H3x3U0u0Q4x3V1k6d9k6g2A6W2M7X3!0t1L8$3!0C8
目前有两个分支,主分支只支持单个hook,newhook支持多个hook,都会开展讲解。
提交记录完全,可以看到排除各种坑的提交
本项目还不完善,比如完全没写测试用例,只是简单的写了两个函数测试,但是不代表我以后不写
为什么这么着急写文章,因为也搞了一星期多了,想详细记录一下
感谢389K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1k6*7K9s2g2G2N6r3!0F1k6#2)9J5c8V1q4F1k6s2u0G2K9h3c8Q4y4h3k6u0L8X3I4A6L8X3g2t1L8$3!0C8
提供研究的动力,尽管项目很老了,但是非常清晰,我给修复支持了最新的NDK(之前只能在NDK20)并移植到了Cmake编译(只有Arm64)
修复后地址:67dK9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1k6B7K9i4q4A6N6e0t1H3x3U0u0Q4x3V1k6u0L8X3I4A6L8X3g2t1L8$3!0C8i4K6u0V1k6X3W2^5
欢迎大家直接学习
感谢c33K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1k6T1P5i4c8W2k6r3q4F1j5$3g2Q4x3V1k6S2L8X3c8J5L8$3W2V1i4K6u0V1K9h3&6D9K9h3&6W2i4K6u0V1K9r3!0G2K9H3`.`.
提供了指令修复的思路
什么是InlineHook,简单来说就是给函数(指令)的几条汇编指令备份,然后跳到我们自己的函数逻辑上。
注意:不要联想整个项目,现在只是流程讲解,项目实现会在后面接着讲,结合着流程。
举个例子:
比如open函数,我们查看地址
前四条汇编指令是这样的,当我们hook后:
就变成了
注意:在修改之前一定要备份这些指令,因为这些指令在后面要被执行
第一条是把地址的内容存到x17,第二条是跳转到x17
第三四条不是汇编指令,是目标要跳转到的地址,我们这个跳板用了16字节,当然你也可以继续跳转字节更少的跳板,本文用的是比较简单的跳板,让我们看看frida用的跳板是什么样的
看来我们跟frida的跳板差不多~
跳转到我们自定义的函数之后呢?
要保存哪些寄存器呢?最简单的肯定包含X0-X30(包含了lr lr就是x30) 可以选择性保存sp(当然如果栈平衡就无所谓了) NZCV状态寄存器 以及Q系列寄存器(本项目还没有保存)
让我们看看frida是怎么做的
为什么要保存呢? 因为在函数调用前如果调用了pre_hookcallback 或者在函数调用后调用post_hookcallback,里面的逻辑会污染原来的寄存器,导致函数崩溃。
我们简单举个例子,现在有一个函数
这个函数主要用到了x0-x3
如果我们在pre_hookcallback调用了任意函数,会刷新x0寄存器,如果返回的是一个地址,那么x0的值在刷新后,会传入我们原来的函数中,造成寄存器污染。
frida是怎么做的?frida是直接将所有寄存器压入了栈中,然后在按顺序弹出,保证栈平衡
弹出过程(压入过程看上面)
这种设计需要保证栈平衡,在进入我们的跳板函数和出跳板函数 sp的值要一致,当然在不污染寄存器的情况下,你可以保存sp,在结束之前恢复(不推荐,很麻烦)
当然也可以和我一样,存在一个结构体中,我们的hook框架是怎么做的?
然后获取结构体的地址,将数据全部压入结构体中(Q系列寄存器还没有加入):
这样不用考虑栈平衡的问题,因为我们没用到栈(当然会有新的分支,使用栈来储存,因为他真的很方便)
pre_hookcallback顾名思义就是在调用hook之前的回调函数,在这时,原函数还没有开始执行,但是参数已经被压入到了寄存器中
如何调用?
我们非常简单的就实现了调用,在这里我们可以读取寄存器,修改寄存器
因为前面做了太多的操作,比如
我们需要把结构体里的寄存器复原,然后在调用原函数
做完了该做的事情以后,终于轮到被hook的函数执行了
第一种实现:
把之前备份的指令还原回被hook函数,然后跳转执行
因为函数是在我们这里被“主动调用的”
所以在执行完成后还是会返回我们的汇编指令
这种方法不提倡,因为如果被hook函数是多线程执行的(大部分都是这样)
会发生崩溃,具体大家可以思考一下
第二种实现:
将备份后的指令执行,然后跳回原来的函数(+16字节的位置)
第二种实现涉及到指令修复的问题:
为什么会出现指令修复,因为有一些特殊指令是和当前pc相关的,我们改变了指令的位置
对应的pc值也会发生改变:
我们采用shadowhook里面的代码进行修复(我给重写并加上了注释)
拿修复adrp的部分做例子:
其实就是把adrp(8字节)等价替换成了 (16字节的指令)
在调用完原函数以后,相应的寄存器的值发生了变化,我们再次保存,然后调用函数调用后的回调函数
注释写的很清楚,和上面高速相似,相信大家已经看懂了
就结束了吗?还没有结束,如果你在post_callback修改了寄存器的值,还需要恢复
当然这里可以只恢复x0(返回值)
到此我们已经完整完成了一个hook的流程
项目地址放在上面了。本次讲解的是newhook分支
项目的总hook管理结构体是:
这里重点注意三个函数:
registerHook
注册函数
removeHook
移除注册函数
getHook
获取hook状态(比如传入open地址,看是不是已经被hook了) 链式hook准备做,但是感觉没啥用
hook信息储存结构体,在汇编里获取的也是这个
每一个hook,对应一个hookinfo
重点讲一下createHook一些点:
backup_orig_instructions(hookInfo)
主要是备份原来的函数,将指令拷贝到结构体里
每次都创建一个页大小的内存,来存储跳板函数就是.s里面的
.s里面我更喜欢叫他模板函数,因为他不是最终执行的,每创建一个函数都会创建一块内存,然后memcpy到mmap出来的这一块内存里,在填入当前hook的hookinfo
这一部分对备份的指令进行修复,然后填入mmap的那块空间里
之后添加跳回原来函数的跳转指令:
mmap分配出的空间目前是
填充过的.s模板汇编地址|修复过的原函数指令|跳回原函数执行的地址
修改目标函数头部,跳转到模板函数+16字节处(16字节存储地址)
hookInfo->backup_func指针指向修复过的指令地址,并且执行完修复的指令回跳回原函数位置
由于
使用的是blr x30寄存器被我们修改在这里了,所以回跳回模板函数
取消hook就很简单了,把目标函数头部还原即可
目前框架还有很多bug,而且只支持arm64,我会不断发展,开出多个分支,并给出实战(配合我的注入框架,以及开发出server,像frida一样通过JIT生成代码进行hook)
有任何疑问欢迎留言,我会解答,第二篇就打算完善后继续写,或者结合我的注入框架写一篇实战。
目前TODO:
支持Q系列寄存器
使用栈作为参数,精简汇编
解决X16 X17寄存器污染问题
最后特别感谢先辈的伟大项目,能让我学习到特别多的技巧,特别感谢卓童老师开展这样一个教学性的项目,让我受益匪浅。
void
default_register_callback(HookInfo *info) {
RegisterContext *ctx = &info->ctx;
LOGI(
"Register dump:"
);
for
(
int
i = 0; i < 31; i++) {
LOGI(
"X%d: 0x%llx"
, i, ctx->x[i]);
}
}
void
default_register_callback(HookInfo *info) {
RegisterContext *ctx = &info->ctx;
LOGI(
"Register dump:"
);
for
(
int
i = 0; i < 31; i++) {
LOGI(
"X%d: 0x%llx"
, i, ctx->x[i]);
}
}
static
size_t
fix_adrp(uint32_t *out_ptr, uint32_t ins,
void
*old_addr,
void
*new_addr) {
uint64_t pc = (uint64_t) old_addr;
uint32_t rd = SH_UTIL_GET_BITS_32(ins, 4, 0);
uint64_t immlo = SH_UTIL_GET_BITS_32(ins, 30, 29);
uint64_t immhi = SH_UTIL_GET_BITS_32(ins, 23, 5);
uint64_t offset = SH_UTIL_SIGN_EXTEND_64((immhi << 14u) | (immlo << 12u), 33u);
uint64_t addr = (pc & 0xFFFFFFFFFFFFF000) + offset;
out_ptr[0] = 0x58000040u | rd;
out_ptr[1] = 0x14000003;
out_ptr[2] = addr & 0xFFFFFFFF;
out_ptr[3] = addr >> 32u;
return
16;
}
static
size_t
fix_adrp(uint32_t *out_ptr, uint32_t ins,
void
*old_addr,
void
*new_addr) {
uint64_t pc = (uint64_t) old_addr;
uint32_t rd = SH_UTIL_GET_BITS_32(ins, 4, 0);
uint64_t immlo = SH_UTIL_GET_BITS_32(ins, 30, 29);
uint64_t immhi = SH_UTIL_GET_BITS_32(ins, 23, 5);
uint64_t offset = SH_UTIL_SIGN_EXTEND_64((immhi << 14u) | (immlo << 12u), 33u);
uint64_t addr = (pc & 0xFFFFFFFFFFFFF000) + offset;
out_ptr[0] = 0x58000040u | rd;
out_ptr[1] = 0x14000003;
out_ptr[2] = addr & 0xFFFFFFFF;
out_ptr[3] = addr >> 32u;
return
16;
}
out_ptr[0] = 0x58000040u | rd;
out_ptr[1] = 0x14000003;
out_ptr[2] = addr & 0xFFFFFFFF;
out_ptr[3] = addr >> 32u;
out_ptr[0] = 0x58000040u | rd;
out_ptr[1] = 0x14000003;
out_ptr[2] = addr & 0xFFFFFFFF;
out_ptr[3] = addr >> 32u;
class
HookManager {
private
:
static
std::map<
void
*, HookInfo *> hook_map;
static
std::mutex hook_mutex;
public
:
static
void
registerHook(HookInfo *info) {
if
(!info)
return
;
setCurrentHook(info);
std::lock_guard<std::mutex> lock(hook_mutex);
hook_map[info->target_func] = info;
}
static
void
setCurrentHook(HookInfo *info) {
current_executing_hook = info;
}
static
HookInfo *getCurrentHook() {
return
current_executing_hook;
}
static
HookInfo *getHook(
void
*target_func) {
std::lock_guard<std::mutex> lock(hook_mutex);
auto
it = hook_map.find(target_func);
return
(it != hook_map.end()) ? it->second : nullptr;
}
static
void
removeHook(
void
*target_func) {
std::lock_guard<std::mutex> lock(hook_mutex);
hook_map.erase(target_func);
}
};
class
HookManager {
private
:
static
std::map<
void
*, HookInfo *> hook_map;
static
std::mutex hook_mutex;
public
:
static
void
registerHook(HookInfo *info) {
if
(!info)
return
;
setCurrentHook(info);
std::lock_guard<std::mutex> lock(hook_mutex);
hook_map[info->target_func] = info;
}
static
void
setCurrentHook(HookInfo *info) {
current_executing_hook = info;
}
static
HookInfo *getCurrentHook() {
return
current_executing_hook;
}
static
HookInfo *getHook(
void
*target_func) {
std::lock_guard<std::mutex> lock(hook_mutex);
auto
it = hook_map.find(target_func);
return
(it != hook_map.end()) ? it->second : nullptr;
}
static
void
removeHook(
void
*target_func) {
std::lock_guard<std::mutex> lock(hook_mutex);
hook_map.erase(target_func);
}
};
struct
HookInfo {
void
(*pre_callback)(HookInfo *pHookInfo);
void
(*post_callback)(HookInfo *ctx);
void
*backup_func;
void
*target_func;
void
*hook_func;
void
*user_data;
RegisterContext ctx;
uint8_t original_code[1024];
size_t
original_code_size;
};
struct
HookInfo {
void
(*pre_callback)(HookInfo *pHookInfo);
void
(*post_callback)(HookInfo *ctx);
void
*backup_func;
void
*target_func;
void
*hook_func;
void
*user_data;
RegisterContext ctx;
uint8_t original_code[1024];
size_t
original_code_size;
};
void
* openaddr =dlsym(RTLD_DEFAULT,
"open"
);
HookInfo *hookInfo = createHook((
void
*) openaddr,
my_register_callback,
post_hook_callback,
(
void
*) hello.c_str());
void
* openaddr =dlsym(RTLD_DEFAULT,
"open"
);
HookInfo *hookInfo = createHook((
void
*) openaddr,
my_register_callback,
post_hook_callback,
(
void
*) hello.c_str());
HookInfo *createHook(
void
*target_func,
void
(*pre_callback)(HookInfo *) = nullptr,
void
(*post_callback)(HookInfo *) = nullptr,
void
*user_data = nullptr) {
LOGI(
"Creating hook - target: %p"
, target_func);
if
(!target_func )
return
nullptr;
HookInfo *existing = HookManager::getHook(target_func);
if
(existing) {
LOGE(
"Function already hooked!"
);
return
nullptr;
}
auto
*hookInfo =
new
HookInfo();
if
(!hookInfo)
return
nullptr;
memset
(hookInfo, 0,
sizeof
(HookInfo));
hookInfo->target_func = target_func;
hookInfo->pre_callback = pre_callback ? pre_callback : default_register_callback;
hookInfo->post_callback = post_callback;
hookInfo->user_data = user_data;
if
(!backup_orig_instructions(hookInfo)) {
delete
hookInfo;
return
nullptr;
}
size_t
trampoline_size = 1024;
void
*trampoline = mmap(nullptr, trampoline_size,
PROT_READ | PROT_WRITE | PROT_EXEC,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if
(trampoline == MAP_FAILED) {
delete
hookInfo;
return
nullptr;
}
LOGI(
"Trampoline allocated at %p"
, trampoline);
hookInfo->backup_func = trampoline;
LOGI(
"two jump start addr = %p"
,two_jump_start);
LOGI(
"two jump end addr = %p"
,two_jump_end);
size_t
two_jump_size =two_jump_end-two_jump_start;
memcpy
(hookInfo->backup_func, two_jump_start, two_jump_size);
LOGI(
"hook info addr = %p"
,hookInfo);
uint64_t info_addr = (uint64_t)hookInfo;
uint64_t hook_addr = (uint64_t)hookInfo->hook_func;
memcpy
(hookInfo->backup_func, &info_addr,
sizeof
(info_addr));
memcpy
((uint8_t*)hookInfo->backup_func + 8, &hook_addr,
sizeof
(hook_addr));
uint32_t *orig = (uint32_t *) hookInfo->original_code;
for
(
size_t
i = 0; i < hookInfo->original_code_size / 4; i++) {
LOGI(
"Original instruction[%zu]: 0x%08x"
, i, orig[i]);
}
size_t
fixed_size = ARM64Fixer::fix_instructions(
(uint32_t *) hookInfo->original_code,
hookInfo->original_code_size,
hookInfo->target_func,
(uint32_t *)((
uintptr_t
)hookInfo->backup_func + two_jump_size)
);
void
*return_addr = (uint8_t *) target_func + hookInfo->original_code_size;
if
(!create_jump((uint8_t *) hookInfo->backup_func + fixed_size+two_jump_size,
return_addr,
false
)) {
munmap(trampoline, trampoline_size);
delete
hookInfo;
return
nullptr;
}
if
(!create_jump(target_func, (uint8_t*)hookInfo->backup_func+16,
false
)) {
munmap(trampoline, trampoline_size);
delete
hookInfo;
return
nullptr;
}
hookInfo->backup_func=(uint8_t*)hookInfo->backup_func+two_jump_size;
HookManager::registerHook(hookInfo);
LOGI(
"hookinfo addr %p"
,hookInfo);
LOGI(
"ctx addr %p"
,&hookInfo->ctx.x[0]);
return
hookInfo;
}
[招生]系统0day安全-IOT设备漏洞挖掘(第6期)!
最后于 2024-12-5 10:31
被棕熊编辑
,原因: 增加pdf版本