安卓基础知识笔记
安卓四大组件
Activity(活动)、Service(服务)、BroadcastReceiver(广播接收者)、ContentProvider(内容提供者)
JNI 静态注册和动态注册
Java 支持调用 C/C++ 代码,JNI(Java Native Interface)的作用是粘合 Java 代码和 C/C++ 代码
静态注册 : 在 AndroidManifest.xml
中注册 遵循一定的命名规则,一般是
Java_packagename_classname_methodname(JNIEnv *env,jclass/jobject,...)
动态注册 : 通过代码在运行时注册 通过
registerReceiver() 方法实现,需要在适当时候调用
unregisterReceiver() 注销
使用结构体 JNINativeMethod 来记录 java 方法和 jni 函数的对应关系
1
2
3
4
5typedef struct {
const char* name; //Java方法名
const char* signature; //方法的参数和返回值,使用字符串记录,格式形如`()V, (I)I`,括号内表示函数参数,括号右侧表示函数返回值
void* fnPtr; // 指向JNI函数的函数指针
} JNINativeMethod;
Frida 注入原理
Frida 注入流程通常是: - 利用 ptrace 附加到目标进程 -
强制修改寄存器(如 PC 指针),让目标进程去执行一段临时的
shellcode - 这段 shellcode 会调用
dlopen, 将 Frida 的动态库(frida-agent.so)
加载到目标进程的内存空间 - Agent 加载成功后,它就直接在目标进程内部进行
inline hook - 撤销 ptrace
Frida hook 原理
Frida 的 Interceptor 实现 hook 主要通过四级跳板完成 -
先通过 inline hook 的一级跳板到达 on_enter_trampoline
的位置,此处为二级跳板 - 在 on_enter_trampoline 处通过
BR X16 跳到第三级跳板 enter_trunk,在这里把 CPU
寄存器全部保存到栈上或者专门的 CpuContext 结构体中 - 调用
Invocation 进入四级跳板,执行 on_enter 代码 -
完成 on_enter 处注册代码后根据函数是否被替换分为两种情况:
- 如果函数被替换,则直接跳转到替换函数 - 如果函数没有被替换,则跳转到
on_leave_trampoline 处,这里将执行原本的函数代码 -
执行完成后,根据是否设置 on_leave 又分为两种情况: -
如果没有设置在,则直接返回 - 如果设置了,则跳回到二级跳板执行
leave_trunk,和 enter_trunk
类似,结束后返回
Inline hook 原理: - 找到目标函数在内存中的起始地址 -
读取目标函数的开头几一段指令,因为即将要覆盖这部分指令,所以把它们备份到一块新的内存区域(
trampoline 跳板区) ps: 如果备份的指令中包含相对跳转(比如 CALL
+0x100),直接复制到新位置相对偏移量就错了,需要进行指令修复将其转化为绝对跳转或者重新计算偏移量
- 将目标函数开头的那段指令覆盖为跳转指令,这条跳转指令指向 trampoline
跳板区
所以 Interceptor 其实是对 inline hook 的一种封装
Frida 检测及绕过
- 端口检测 : Frida 默认使用
27042和27043端口,可以通过扫描这些端口来检测 Frida 是否在运行- 绕过:通过端口转发修改 Frida 使用的端口
- 检测
/data/local/tmp目录下的frida-server文件- 绕过:修改文件名
- 双进程保护: 一些应用会启动一个守护进程来监控主进程,如果检测到
Frida attach,内核中
TracePid != 0,则杀死主进程,应用闪退- 绕过:用
spawn模式启动
- 绕过:用
- 检测
proc/self/maps文件中是否存在frida-agent-64.so、frida-agent-32.so等文件- 绕过:
- 定义一个函数用来阻止字符串的搜索匹配(
strstr、strcmp),避免检测到敏感内容如REJECTFrida等 - 重定向
maps文件的内容,隐藏特定的库和路径信息 - 用
eBPFhook 系统调用并修改参数:在内核真正去查找文件之前,挂载在sys_enter_openat上的eBPF程序被触发,检测到系统调用的第一个参数(路径字符串)是proc/self/maps, 使用bpf_probe_write_user将该内存地址的内容改为事先准备好的抹去了 frida 信息的maps文件1
2char placeholder[] = "/data/data/.../maps";
bpf_probe_write_user((void*)addr, placeholder, sizeof(placeholder));
- 定义一个函数用来阻止字符串的搜索匹配(
- 绕过:
- 检测
proc/stack/线程 id/status文件中是否有gmain/gdbus/gum-js-loop/pool-frida等- Frida 使用
Glib库,gamain是主循环事件GMainLoop的线程名; GDBus是Glib提供的用于D-Bus通信的库,gdbus表示D-Bus相关线程;gum-js-loop表示Gum(Gum 是 Frida 运行时引擎)执行注入的 js 代码的线程pool-frida表示 Frida 中的线程池
- 绕过:类似上文绕过 maps 文件检测的方式,hook
libc.so中的strstr和strcmp函数,一旦检测到敏感字符串就返回未找到
- Frida 使用
- 检测
inline hook,比较内存和本地字节,如果不一致则可认为被 inline hook 了- 绕过:找到校验逻辑并 hook 掉
- SVC(Supervisor Call)
指令:软件中断指令,允许用户态程序发起一个系统调用,CPU
从用户态切换到内核态执行特权操作。
- 绕过:进行内核追踪,找到 SVC 调用出并根据栈回溯往前找到检测逻辑