1. 项目概述
TInjector_Symbi-master是一个基于Frida框架的开源项目,主要用于在Android系统上进行动态注入和hook操作。该项目通过分析arm64架构下的属性结构体偏移,实现了对zygote进程的高效注入,从而能够在目标应用中加载自定义的.so库。
核心功能包括:
- 定位zygote进程中的关键函数地址
- 构建并注入stub结构体
- 通过内存操作实现函数hook
- 在目标进程中加载自定义so库
2. 技术原理分析
2.1 arm64架构下的结构体布局
项目中定义了一个关键结构体_TStub,用于存储各种函数指针和参数:
c复制struct _TStub {
char mark[20]; // 标识符
int (*original_set_argv0)(void*, void*, void*); // 原始函数指针
uintptr_t slot_addr; // 槽位地址
int uid; // 目标UID
char so_path[128]; // so库路径
// 各种系统函数指针
int (*socket_fn)(int, int, int);
int (*connect_fn)(int, const void*, unsigned int);
// ...其他函数指针
};
结构体成员的对齐和偏移计算非常重要,特别是在arm64架构下。通过offsetof宏可以获取各成员的精确偏移:
c复制LOGI("original_set_argv0: offset = 0x%02zx", offsetof(struct _TStub, original_set_argv0));
2.2 内存操作与进程注入
项目通过以下步骤实现注入:
- 定位zygote进程:
c复制pid_t target_pid = find_pid_by_name("zygote64");
- 暂停目标进程:
c复制kill(target_pid, SIGSTOP);
- 获取进程内存映射:
c复制auto maps = get_process_maps(target_pid);
- 查找关键函数地址:
c复制uintptr_t set_argv0_address = libandroid_runtime_base + symbol_offset;
- 准备并注入stub:
c复制ssize_t written_code = pwrite(mem_fd, stub_binary, stub_binary_size, shellcode_base);
3. 核心实现细节
3.1 stub汇编分析
stub的核心汇编代码实现了以下功能:
- 保存寄存器状态:
asm复制stp x29, x30, [sp, #-0x40]! ; 保存帧指针和返回地址
- 获取JNI参数:
asm复制ldr x8, [x0] ; 获取JNIEnv函数表
mov x19, x2 ; 保存jstring参数
- 调用原始函数:
asm复制ldr x8, [x23] ; 获取original_set_argv0指针
blr x8 ; 调用原始函数
- UID验证:
asm复制ldr w8, [x23, #0x10] ; 获取目标UID
cmp w0, w8 ; 比较当前UID和目标UID
b.ne <fail_branch> ; 不匹配则跳转
3.2 关键函数解析
3.2.1 获取符号偏移
c复制uintptr_t get_symbol_offset_from_elf(const std::string &elf_path, const char *symbol_name) {
// 打开ELF文件
int fd = open(elf_path.c_str(), O_RDONLY);
// 映射文件到内存
void *map_base = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
// 解析ELF头
auto *ehdr = (Elf64_Ehdr *) map_base;
auto *shdr = (Elf64_Shdr *) ((uintptr_t) map_base + ehdr->e_shoff);
// 遍历节区查找符号
for (int i = 0; i < ehdr->e_shnum; i++) {
if (shdr[i].sh_type == SHT_DYNSYM) {
// 处理符号表...
}
}
// 计算加载偏移
uintptr_t load_bias = 0;
auto *phdr = (Elf64_Phdr *) ((uintptr_t) map_base + ehdr->e_phoff);
for (int i = 0; i < ehdr->e_phnum; i++) {
if (phdr[i].p_type == PT_LOAD) {
load_bias = phdr[i].p_vaddr;
break;
}
}
return symbol_offset - load_bias;
}
3.2.2 远程内存搜索
c复制uintptr_t find_needle_in_remote_memory(int mem_fd, const MemoryMap &map, uintptr_t needle) {
// 读取远程内存区域
std::vector<uint8_t> buffer(region_size);
pread(mem_fd, buffer.data(), region_size, map.start);
// 搜索目标值
auto *found = static_cast<uint8_t *>(memmem(start_ptr, region_size, &needle, sizeof(needle)));
if (found) {
return map.start + (found - start_ptr);
}
return 0;
}
4. 实战经验与注意事项
4.1 常见问题排查
-
权限问题:
- 确保有足够的权限访问/proc文件系统
- 目标so文件需要设置正确的owner和权限
-
内存对齐:
- arm64架构对内存访问有严格的对齐要求
- 结构体成员偏移计算必须准确
-
进程状态管理:
- 注入前必须暂停目标进程
- 操作完成后需要恢复进程执行
4.2 性能优化建议
-
减少内存操作:
- 批量读取/写入内存,减少系统调用次数
- 使用mmap代替频繁的read/write
-
缓存关键地址:
- 缓存常用函数地址,避免重复解析ELF
-
错误处理:
- 对所有系统调用进行错误检查
- 提供详细的日志输出
5. 项目扩展与改进方向
-
多架构支持:
- 目前主要针对arm64,可以扩展支持armv7等其他架构
-
注入方式优化:
- 探索更隐蔽的注入方式,避免暂停目标进程
-
功能扩展:
- 支持更多类型的hook操作
- 提供更友好的配置接口
在实际使用中,我发现这个项目最需要注意的就是内存操作的精确性。特别是在计算结构体偏移和函数地址时,一个字节的偏差都可能导致注入失败。建议在开发类似功能时,一定要添加详细的日志输出,方便定位问题。
