2.26 Android Hook
PLT Hook - Android Native Hook
IO Canary使用的方案, 当动态链接的ELF程序里调用共享库的函数时, 第一次调用先到PLT表查找, PLT表中再跳到GOT表获得该函数的地址, 最终通过_dl_runtime_resolve
获得函数的实际地址, PLT Hook通过直接修改GOT表, 使得调用共享库的函数时跳转到用户自定义的Hook功能代码
- ELF文件格式(可执行与可链接格式)
- ELF文件头 (定义了ELF魔数、硬件平台、入口地址、程序头入口和长度)
- 段表 (描述各个段的信息, 包含段名、段的长度、在文件的偏移)
- 代码段 (.text)
- 数据段(.data)和只读数据段(.rodata), .data段保存已初始化的全局静态变量和局部静态变量
- 符号表(.symtab) 将不同的目标文件合并, 在链接的过程, 函数和变量统称为符号; 每个目标文件都有相应的符号表, 记录目标文件所有使用的符号, 符号都有对应的符号值, 变量和函数的符号值就是它们的地址
- 重定位表(.rel.text), 链接器处理目标文件, 对目标文件的代码段和数据中的绝对地址引用的位置进行重定位
- 链接的过程
- 静态链接
- 空间与地址分配, 扫描所有输入目标文件, 并获得各个段的长度、属性和位置, 并将输入目标文件的符号表所有的符号定义和符号引用收集起来放到全局符号表中
- 符号解析与重定位, 使用第一步收集到的信息, 读取输入文件中段的数据, 重定位信息, 进行符号解析与重定位, 调整代码地址等
- 动态链接 (.so和普通共享对象一样被映射到进程的地址空间, 系统运行前首先把控制权交给动态链接器, 由它完成所有的动态链接工作), 动态链接模块的装载地址是从0x00000000开始, 该地址无效, 共享对象的最终装载地址在编译时不确定, 在装载时, 装载器根据当前地址空间的情况动态配置虚拟地址空间给共享对象
- 地址无关代码(PIC)
- 全局偏移表(GOT), 用于模块间数据访问, 在数据段建立指向外部模块变量的指针数组, 当代码需要引用该全局变量时, 可通过GOT相对用的项间接引用; 当指令访问变量b时, 程序先找到GOT, 根据GOT变量对应的项找到变量的目标地址
- 延迟绑定(PLT), 动态链接对于全局和静态的数据访问都要进行复杂GOT定位, 然后间接寻址, 模块间的调用则先定位GOT, 再间接跳转; 延时绑定的实现, 函数第一次用到才进行绑定(符号查找、重定位), 没用到则不绑定
- .dynamic段, 保存动态链接器所需要的基本信息, 依赖共享对象, 动态链接符号表的位置等
- 动态符号表(.dynsym), 只保存动态链接相关符号, 而
.symtab
则保存所有符号, 包括.dynsym
中的符号
- 静态链接
// Android PLT Hook
int hook_call(char *soname, char *symbol, unsigned newVal) {
soinfo *si = NULL:
// 重定向表
Elf32_Rel *rel = NULL;
// 符号表
Elf32_Sym *s = NULL;
// 获取so的信息
si = (soinfo*)dlopen(soname, 0);
// 通过so信息对象, 根据变量查找符号表
s = soinfo_elf_lookup(si, elfhash(symbol), symbol);
...
unsigned sym = ELF32_R_SYM(rel->r_info)
...
if (sym_offset == sym) {
switch(type) {
case R_ARM_JUMP_SLOT:
...
// 替换成新的函数引用
*((unsigned*)reloc) = newval;
return 1;
}
}
static Elf32_Sym *soinfo_elf_lookup(soinfo *si, unsigned hash, const)
{
Elf32_Sym *s;
Elf32_Sym *symtab = si->symtab;
const char *strtab = si->strtab;
...
if(strcmp(strtab + s->st_name, name)) continue;
return s;
}
...
}
inline Hook - Android Native Hook
在代码段中插入跳转指令, 从而程序执行流程引向用户需要的功能代码中, 以达到Hook效果
- 在想要Hook的目标代码处备份下面几条指令, 然后插入跳转指令, 把程序流程跳转到stub段
- 在stub代码段上先把所有寄存器的状态保存好, 并调用用户自定义的Hook功能函数, 然后把所有寄存器的状态恢复并跳转到备份代码处
- 在备份代码处把当初备份的指令都执行, 然后跳转到当时备份代码位置下的下面接着执行