> 近期突然发现64位APP分析需求激增。。然而手边好用的 inlineHook 只有 Frida 一款,所以打算稍微研究下 Frida 的思路,以作借鉴,然后写一款满足简单自用需求的 AArch64 inlineHook 工具,话不多说,我们开始
> 根据之前开发 AArch32 inlineHook 框架的经验,总结 inlineHook 框架开发的几个关键点大抵如下:
> 对我来说一个简单的工具只要满足前3点就足够了,第4点待后续优化的时候再行完善,所以我们接下来看看 Frida 是如何完成以上这几点的
> 首先我们简单编写一个 com.example.x64 应用作为目标 APP,且在 libx64.so 中放置一个 native 函数: Java_com_example_x64_JNI_aal
,马上使用 Frida Hook 他的说
存在以下两种情况:
1> Frida Hook 函数开头指令(即直接 Hook 导出函数)
2> Hook 函数中间指定位置的指令
> Frida 代码如下:
> Hook 完毕,执行结果如下:
> 不知道为什么打出来了两次 hook1 on leave
,之后我使劲检查代码,确定并没有写错。。好吧,猜测原因或许是Hook点2在最终返回值的设置上出现了什么问题吧。。
我们暂时忽略上面的问题,接下来分析这两个地址上的指令发生了甚么变化
> 挂上我们的调试器,首先对 Hook1 进行分析:
> Hook1 对应 Java_com_example_x64_JNI_aal
函数的入口位置( 0x7fac430b70 ),可以看到前16字节已经被替换掉,新指令为利用 x16 寄存器制作的一个跳板(trampoline),其目标为 0x7face7c600:
> Hook2 情况与 Hook1 类似,也是生成了16字节的跳板指令(依然使用 x16 寄存器)来替换掉 0xBBA0 位置的16字节原始指令,在此不做展示
P.S. > 在后续多次测试中发现,偶尔也会出现使用单条 Branch 指令(4字节)来替换掉被 Hook 地址的单条指令(4字节)的情况发生,如下图所示
> 因为 Branch 指令存在跳转范围(+-128MB),所以 Frida 使用这种形式的 trampoline 需要对被 Hook 地址前后 128MB 范围进行检测,寻找空闲地址,不过这对本文实现一个简单的 inlineHook 模型并无太大影响,故不做深入讨论
P.P.S. > 其实 Frida 还有一种跳转范围扩大至 +-4GB 大小的 trampoline 生成规则,在此也不做讨论了,因为在原理上大同小异,单纯属于细节优化问题
> 另外还有不得不提的一点,当 trampoline 使用 x16 寄存器作为跳板寄存器时,Hook 结束后 x16 寄存器无疑会被污染,然而事实上 Frida 同时使用了 x16 与 x17 寄存器,那么关于这两个寄存器有什么说法呢?官方对这两个寄存器作用的描述如下:
> 描述中提到 x16、x17 寄存器作为内部过程调用中的临时寄存器,结合下图便能更好的理解官方的定义
> 关于 trampoline 的研究就到此为止了,接下来我们看他生成的 shellCode
> 接下来我们开始分析 shellcode 部分,以 Hook1 为例
> Java_com_example_x64_JNI_aal
函数入口: 0x7fac430b70
> 入口处 trampoline 汇编代码如下:
> 进入 0x7face7c600 位置,分析如下图:
> 首先 mmap 了一段匿名内存( 7face7c000-7face83000 rwxp
),在 0x7face7c600 位置放置了以下几条汇编指令构成第二段跳板
> ldr x17, =0x7facec12e0
> ldr x16, =0x7face7c000
> br x16
> 其中 x17 寄存器装载了一个地址( 0x7facec12e0 ),这个地址内部保存着 0x7fac430b70 ,正是 Java_com_example_x64_JNI_aal
函数入口地址
> 而 x16 寄存器装载了此番生成的 shellCode 的地址( 0x7face7c000 ),将该段内存 dump 下来,拖入 ida 进行分析:
> 绿色、蓝色部分合并完成了栈平衡、寄存器保护与恢复工作;
> 我们在外部用 JS 编写的 Hook 功能代码( onEnter 部分 ),由 BLR X4
( 0x7F7D8D8360 ) 跳转至 frida-agent-64.so
(见下图)来完成;
> 在 JS 中可以打印,甚至修改函数入参的原因是因为入参(前8个在 X0-X7 寄存器上,后面的在栈上)已全部由绿色块指令压入栈中保存,所以在 BLR X4
进行函数调用时,合理设置 X0-X3 寄存器,使其正确的指向栈上某位置尤为关键
> 我们接下来在 shellcode 最后一条 BR X16
指令上插入断点,分析函数的运行情况
> 当断点触发时 BR X16
欲跳转至内存 0x7face7c630,其对应的汇编代码如上图所见,其中包含 Java_com_example_x64_JNI_aal
函数开头被替换的4条原始指令
> 之后再次使用 x16 寄存器跳转至 0x7fac430b80,即函数 Java_com_example_x64_JNI_aal
开头偏移 0x10 的位置,以完成原函数的执行动作
> 此时 hook1 on enter
打印完毕,但 hook1 on leave
还未打印,所以注意到 x30 寄存器中保存的返回地址是 0x7face7c60c,即前文中暂未分析的第三段跳板指令,汇编代码如下:
> ldr x17, =0x7facec12e0
> ldr x16, =0x7face7c100
> br x16
> x17 寄存器行为与之前一致,x16 寄存器装载了第二段 shellCode 的地址( 0x7face7c100 ),刚才已经一起 dump 下来了,直接在 ida 分析
> 绿色、蓝色部分代码作用不变,由 BLR X3
( 0x7F7D8D86C8 ) 跳转至 frida-agent-64.so
来完成外部 JS 写的 Hook 功能代码中 onLeave 的部分
> 最后由 BR X16
返回 Java_com_example_x64_JNI_aal
函数被调用时真正的 LR
> 至此 shellcode 部分也大体分析完毕了,此时我们应该能够写出一款简单的 AArch64 inlineHook 工具模型了
> 结合前文的分析,我们的 inlineHook 应该具备以下这几点功能:
> OK,有了以上几点需求,我们现在可以开始开发了 ( 源码下载见文章末尾 )
> 我们首先来设计 shellcode 部分
> 在本简易版工具中,我们的跳板指令选择使用 x16 寄存器的 16 字节 trampoline ,代码如下:
> 接下需要做参数和返回地址入栈工作,以及全寄存器状态保护,代码如下:
> 接下来调用 Hook 功能函数的 onEnter
部分,并恢复寄存器及栈状态,最后取出返回地址并返回原函数执行
> 对于 onLeave 部分的 shellcode 与之大体类似,就不贴图展示了;
> 接下来开始编写函数完成 inlineHook 的插入
> inlineHook1 函数主要作用是分配 shellcode 的内存及设置其中的关键数据,并使用 trampoline 替换原指令完成 Hook,函数内注释较为详细,就不做过多解释了
> 最后我们来编写 onEnter
及 onLeave
函数
> 在 onEnter
函数中需要保存原始函数的返回地址 LR 寄存器值至 TLS 中,并在最后设置临时返回地址为 onLeave
函数对应的 shellcode,最后再 onLeave
中再取回真实的 LR 并返回实际的函数调用链中,完成整个 inlineHook 流程
> 另外 Hook 指定位置汇编指令的代码并未贴出,因为原理是一致的,仅仅在 onEnter
函数中不设置临时返回地址即可
> 仅开启 Hook1 时的效果如下图所示
> #总结:借鉴 Frida 的 inlineHook 原理设计了一款简单的 inlineHook 框架,满足了部分常用需求;关于框架的 trampoline 优化,PC-relative address 相关指令解析执行等工作,待后续继续开发优化...
> 代码已上传:
Gitee链接: https://gitee.com/zzy_cs/inline-hook
Git链接: https://github.com/zzyccs/inlineHook
/
/
function hookTest1() {
var helloAddr
=
Module.findExportByName(
"libx64.so"
,
"Java_com_example_x64_JNI_aal"
);
console.log(helloAddr);
if
(helloAddr !
=
null){
Interceptor.attach(helloAddr,{
onEnter: function(args){
console.log(
"hook1 on enter"
);
},
onLeave: function(retval){
console.log(
"hook1 on leave"
);
}
});
}
}
/
/
function hookTest2() {
var libutilityAddr
=
Module.findBaseAddress(
"libx64.so"
);
var getOriginalStringAddr
=
libutilityAddr.add(
0x000000000000BBA0
);
console.log(getOriginalStringAddr);
if
(getOriginalStringAddr !
=
null){
Interceptor.attach(getOriginalStringAddr,{
onEnter: function(args){
console.log(
"hook2 on enter"
);
},
onLeave: function(retval){
console.log(
"hook2 on leave"
);
}
});
}
}
/
/
function hookTest1() {
var helloAddr
=
Module.findExportByName(
"libx64.so"
,
"Java_com_example_x64_JNI_aal"
);
console.log(helloAddr);
if
(helloAddr !
=
null){
Interceptor.attach(helloAddr,{
onEnter: function(args){
console.log(
"hook1 on enter"
);
},
onLeave: function(retval){
console.log(
"hook1 on leave"
);
}
});
}
}
/
/
function hookTest2() {
var libutilityAddr
=
Module.findBaseAddress(
"libx64.so"
);
var getOriginalStringAddr
=
libutilityAddr.add(
0x000000000000BBA0
);
console.log(getOriginalStringAddr);
if
(getOriginalStringAddr !
=
null){
Interceptor.attach(getOriginalStringAddr,{
onEnter: function(args){
console.log(
"hook2 on enter"
);
},
onLeave: function(retval){
console.log(
"hook2 on leave"
);
}
});
}
}
_trampoline_:
LDR X16, x64code0
BR X16
x64code0:
_jmp_addr_:
.dword
0x1111111111111111
_trampoline_:
LDR X16, x64code0
BR X16
x64code0:
_jmp_addr_:
.dword
0x1111111111111111
/
/
extern
"C"
JNIEXPORT jstring JNICALL
Java_com_cs_inline_MainActivity_stringFromJNI(
JNIEnv
*
env,
jobject
/
*
thisobj
*
/
,
jstring jstr) {
std::string hello
=
"Hello from C++: "
;
hello.append(env
-
>GetStringUTFChars(jstr, nullptr));
return
env
-
>NewStringUTF(hello.c_str());
}
/
/
extern
"C"
JNIEXPORT void JNICALL
Java_com_cs_inline_MainActivity_inlineHook1(JNIEnv
*
env,
jobject
/
*
thisobj
*
/
)
{
/
/
u_long func_addr
=
(u_long)Java_com_cs_inline_MainActivity_stringFromJNI;
extern u_long _shellcode_start_, _the_func_addr_, _end_func_addr_, _ori_ins_set1_, _retback_addr_, _shellcode_end_, _trampoline_, _jmp_addr_, _shellcode_part2_;
/
/
u_long total_len
=
(u_long)&_shellcode_end_
-
(u_long)&_shellcode_start_;
LOGD(ANDROID_LOG_DEBUG,
"[+] ShellCode len: %d, target func: %p"
, total_len, func_addr);
/
/
u_long page_size
=
getpagesize();
u_long shellcode_mem_start
=
(u_long)mmap(
0
, page_size, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_ANONYMOUS | MAP_PRIVATE,
0
,
0
);
memset((void
*
)shellcode_mem_start,
0
, page_size);
memcpy((void
*
)shellcode_mem_start, (void
*
)&_shellcode_start_, total_len);
LOGD(ANDROID_LOG_DEBUG,
"[+] shellcode_mem_start: %p"
, shellcode_mem_start);
/
/
*
(u_long
*
)&_jmp_addr_
=
shellcode_mem_start;
u_long mem_the_func_addr_
=
(u_long)&_the_func_addr_
-
(u_long)&_shellcode_start_
+
shellcode_mem_start;
u_long mem_end_func_addr_
=
(u_long)&_end_func_addr_
-
(u_long)&_shellcode_start_
+
shellcode_mem_start;
u_long mem_ori_ins_set1_
=
(u_long)&_ori_ins_set1_
-
(u_long)&_shellcode_start_
+
shellcode_mem_start;
u_long mem_retback_addr_
=
(u_long)&_retback_addr_
-
(u_long)&_shellcode_start_
+
shellcode_mem_start;
if
(!off_shellcode_part2_)
off_shellcode_part2_
=
(u_long)&_shellcode_part2_
-
(u_long)&_shellcode_start_;
/
/
*
(u_long
*
)mem_the_func_addr_
=
(u_long)on_enter_1;
*
(u_long
*
)mem_end_func_addr_
=
(u_long)on_leave_1;
/
/
*
(u_long
*
)mem_retback_addr_
=
(u_long)func_addr
+
0x10
;
/
/
*
(u_long
*
)mem_ori_ins_set1_
=
*
(u_long
*
)func_addr;
*
(u_long
*
)(mem_ori_ins_set1_
+
8
)
=
*
(u_long
*
)(func_addr
+
8
);
/
/
u_long entry_page_start
=
(u_long)(func_addr) & (~(page_size
-
1
));
mprotect((u_long
*
)entry_page_start, page_size, PROT_READ | PROT_WRITE | PROT_EXEC);
*
(u_long
*
)func_addr
=
*
(u_long
*
)&_trampoline_;
*
(u_long
*
)(func_addr
+
8
)
=
*
(u_long
*
)(((u_long)&_trampoline_)
+
8
);
}
/
/
extern
"C"
JNIEXPORT jstring JNICALL
Java_com_cs_inline_MainActivity_stringFromJNI(
JNIEnv
*
env,
jobject
/
*
thisobj
*
/
,
jstring jstr) {
std::string hello
=
"Hello from C++: "
;
hello.append(env
-
>GetStringUTFChars(jstr, nullptr));
return
env
-
>NewStringUTF(hello.c_str());
}
/
/
extern
"C"
JNIEXPORT void JNICALL
Java_com_cs_inline_MainActivity_inlineHook1(JNIEnv
*
env,
jobject
/
*
thisobj
*
/
)
{
/
/
u_long func_addr
=
(u_long)Java_com_cs_inline_MainActivity_stringFromJNI;
extern u_long _shellcode_start_, _the_func_addr_, _end_func_addr_, _ori_ins_set1_, _retback_addr_, _shellcode_end_, _trampoline_, _jmp_addr_, _shellcode_part2_;
/
/
u_long total_len
=
(u_long)&_shellcode_end_
-
(u_long)&_shellcode_start_;
LOGD(ANDROID_LOG_DEBUG,
"[+] ShellCode len: %d, target func: %p"
, total_len, func_addr);
/
/
u_long page_size
=
getpagesize();
u_long shellcode_mem_start
=
(u_long)mmap(
0
, page_size, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_ANONYMOUS | MAP_PRIVATE,
0
,
0
);
memset((void
*
)shellcode_mem_start,
0
, page_size);
memcpy((void
*
)shellcode_mem_start, (void
*
)&_shellcode_start_, total_len);
LOGD(ANDROID_LOG_DEBUG,
"[+] shellcode_mem_start: %p"
, shellcode_mem_start);
/
/
[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!