前言
frida 封装的真的厉害. 这里的分析主要关注 interceptor
拦截器部分.
frida 使用的是 inlinehook
(废话), 模块化做的非常好, 把小模块都进行了拆分和封装. 这里主要是为了学习 frida 进行 inlinehook
的套路.
这里额外提一点, frida-tracer 并不是 hook 了 objc_msgSend
而是进行正则匹配查找符号, 进行批量的 inlinehook
, 所以才会在 __handlers__
出现那么多模块.
具体分析
前置知识
|
|
拦截器初始化部分
这里主要是初始化 内存分配模块
和 调度器模块
.
内存分配模块, 预先分配了很多内存页, 针对 darwin
架构的系统采用的内存页分配函数是 mach_vm_allocate
具体可以参考 frida-gum-master/gum/backend-darwin/gummemory-darwin.c
, 并没有使用 mmap
, 至于为什么, 可以参考附录, 简单提一句, darwin
架构上的实现本质是利用 mach_vm_allocate
.
调度器模块, 可以理解为所有被 hook 的函数都必须经过的函数, 类似于 objc_msgSend
, 在这里通过栈来函数(pre_call
, replace_call
, post_call
)调用顺序.
|
|
添加 hook-listenr 构造跳板
这里涉及到如何构造跳板以及指令修复.
跳板函数的构造, 跳板函数主要作用就是进行跳转, 并准备 跳转目标
需要的参数. 举个例子, 被 hook 的函数经过入口跳板(enter_trampoline
), 跳转到调度函数(enter_chunk
), 需要被 hook 的函数相关信息等, 这个就需要在构造跳板是完成
指令修复, 由于涉及到覆盖原函数指令, 这里需要进行备份原指令, 由于备份地址发生改变, 需要修改跟 pc 相关的指令.
|
|
函数调用导向
在 frida-gum
中是指 listener
, 通用说应该是 pre_call
和 post_call
.
如何利用栈内的保存的函数返回地址, 进行函数调用顺序导向?
处理函数在 frida-gum-master/gum/backend-arm64/guminterceptor-arm64.c:gum_emit_enter_thunk
, 可以具体对照参考.
|
|
被拦截函数指令执行流程
|
|
被拦截函数指令执行流程(流程图表示)
这里演示了如何利用栈返回地址控制函数指令流程, 感觉和 ROP
有点像.
|
|
code patch(内存属性修改)
|
|
对原函数进行 code patch, 需要修改内存属性 r-x
为 rw-
, 在修改完后重新修改为 r-x
, 很容易想到的就是 mach_vm_protect
, 这里存在一个坑, 其实只是需要几十字节的内存属性的变更. 但是 mach_vm_protect
会做修正, 起始地址必须是页对齐, 以及长度必须是页大小的倍数. 这就导致一个问题, 在修改函数入口指令时, 会导致整个页的内存属性为 rw-
, 任何执行到此地址范围的之指令都会异常. 但是也可以判断内存也是否支持 rwx
, 大致的判断判方法就是, 先分配一页内存, 之后尝试设置内存属性 (PROT_READ | PROT_WRITE | PROT_EXEC)
看是否确实设置为 rwx
这里介绍在 substrate
和 frida-gum
中使用到的方法, 两个方法稍有不同, 但本质都是利用 mmap
.
在 substrate
中先分配一页内存, 复制目标函数那一页的内容到该页, 在该页做内存属性修改和 code patch, 之后需要修改该页内存属性为 r-x
, 最后通过 mach_vm_remap
函数重新映射到 目标内存页.
在 frida-gum
中前面的步骤类似, 最后一步有所不同, frida-gum
将需要替换的指令持久化成一个临时文件, 之后通过 mmap
+ 文件描述符, 重新映射到目标内存页.
总结
对几个方面做一个总结
模块化方面
frida 把一部分都分省了单个小模块, 以保证自由度. 例如: on_enter_trampoline
和 enter_thunk
每一部分只负责一小部分功能.
指令修复部分
指令修复是保存原始指令很重要的一步. 这里以 arm64
的指令修复为例. 主要实现在函数 gum_arm64_relocator_write_one
.
大致步骤是需要使用 capstone
判断指令 id
, 是否为 PC
相关的指令, 这里可以参考 ARM Architecture Reference Manual ARMv8, for ARMv8-A architecture profile> C6.1.2 Use of the PC
, 这其实也就是 frida-gum
如何判断哪些指令进行修复.
hook 思路方面
仔细研究会发现其实 frida 的 Interceptor(拦截器)
和 objc_msgSend
有异曲同工之妙, 这也是之前想搞的一个思路, 所有被拦截(hook) 的函数都会经过 interceptor_backend
(enter_thunk
和 leave_thunk
) 进行之后的分支跳转, 比如 on_enter
或 on_leave
.
所有被 hook 的函数都被封装为 _GumFunctionContext
, 之后根据 target
函数地址进行 hashmap 快速的查找.
如果再说一点就是利用栈返回地址控制函数指令流程, 有点像 ROP
的思路.
附录
|
|