吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 3086|回复: 2
收起左侧

[Android 原创] 简易Android ARM&ARM64 GOT Hook (二)

  [复制链接]
XhyEax 发表于 2021-8-31 21:30
本帖最后由 XhyEax 于 2021-9-1 09:38 编辑

概述

前文简易Android ARM&ARM64 GOT Hook (一),基于链接视图,解析section查找GOT表偏移值,无法应对section被处理过的情况(如加固)。

本文基于ELF的执行视图(Execution View),讨论Android ARM&ARM64架构的GOT/PLT Hook(仍以Hook公共库libc.sogetpid函数为例)。

思路

  1. 通过maps文件获取模块基址,解析内存中的ELF。
  2. 查找并解析.dynamic段,得到.rel.plt.rel.dyn.dynsym.dynstr.hash表。
  3. 通过.hash -> .dynsym -> .dynstr查找导入符号,再遍历.rel.plt.rel.dyn,得到函数偏移。
  4. 基址加上偏移得到内存地址,修改函数地址即可。

注入方式及编译环境同前文,不再赘述。

具体实现

核心代码

// 基于执行视图解析ELF
uintptr_t hackBySegment(const char *moudle_path, const char *target_lib, const char *target_func,
                        uintptr_t replace) {
    LOGI("hackDynamic start.\n");
    // 获取目标函数地址
    void *handle = dlopen(target_lib, RTLD_LAZY);
    auto ori = (uintptr_t) dlsym(handle, target_func);
    LOGI("hackDynamic getpid addr: %lX\n", ori);
    // 获取符号地址 (解析Segment)
    uintptr_t replaceAddr = getSymAddrDynamic(moudle_path, target_func);
    // 替换地址
    replaceFunction(replaceAddr, replace, ori);
    return ori;
}

完整代码见AndroidGotHook

获取.dynamic段

解析maps文件获取到模块地址后,基于执行视图解析ELF。

elf header中得到program header table的起始偏移(e_phoff)、program header大小(e_phentsize)和总program header个数(e_phnum

遍历program header table,查找p_typePT_DYNAMICprogram header,得到.dynamic段在内存中的偏移值(p_vaddr)和大小(p_memsz

将模块基址与.dynamic偏移值相加,得到.dynamic段的起始地址。

解析.dynamic段

遍历.dynamic段,根据d_tag解析不同的表。
.rel.pltDT_JMPREL(大小储存在DT_PLTRELSZ

.rel.dynDT_REL(大小储存在DT_RELSZ

.dynsymDT_SYMTAB
.dynstrDT_STRTAB

.hashDT_HASH

注:解析.dynamic的ELF模板来自Android7.0以上命名空间详解(dlopen限制)附上010editor模块

查找符号

通过.hash -> .dynsym -> .dynstr查找符号。

ELFW(Sym) *target = nullptr;
uint32_t hash = elf_sysv_hash((const uint8_t *) symName);
for (uint32_t i = buckets[hash % buckets_cnt];
        0 != i; i = chains[i]) {
    ELFW(Sym) *sym = dynsym + i;
    unsigned char type = ELF_ST_TYPE(sym->st_info);
    if (STT_FUNC != type && STT_GNU_IFUNC != type && STT_NOTYPE != type)
        continue; // find function only, allow no-type
    if (0 == strcmp(dynstr + sym->st_name, symName)) {
        target = sym;
        break;
    }
}

具体操作为:

  1. 计算符号名称的哈希值,然后从.hash获取符号在.dynsym中的索引。

    接受符号名称的散列函数会返回一个值,用于计算 bucket 索引。因此,如果散列函数为某个名称返回值 x,则 bucket [x% nbucket] 将会计算出索引 y。此索引为符号表和链表的索引。如果符号表项不是需要的名称,则 chain[y] 将使用相同的散列值计算出符号表的下一项。

  2. 通过索引从.dynsym获取符号,根据符号类型和符号名(通过st_name.dynstr获取字符串),判断与目标函数是否匹配。

PS:也可直接遍历.dynsym并比对字符串,时间复杂度更高

.hash -> .dynsym -> .dynstr,时间复杂度:O(x) + O(1) + O(1)
.dynsym -> .dynstr,时间复杂度:O(n) + O(1)

计算内存地址

遍历.rel.plt.rel.dyn,比对符号和重定位类型,获取函数偏移值(r_offset)。与模块基址相加得到内存地址。

    for (int i = 0; i < rel_plt_cnt; i++) {
        ELFW(Rel) &rel = rel_plt[i];
        if (&(dynsym[ELF_R_SYM(rel.r_info)]) == target &&
            ELF_R_TYPE(rel.r_info) == ELF_R_JUMP_SLOT) {
//            LOGI("target r_offset: %lX", rel.r_offset);
            return moduleBase + rel.r_offset;
        }
    }
    for (int i = 0; i < rel_dyn_cnt; i++) {
        ELFW(Rel) &rel = rel_dyn[i];
        if (&(dynsym[ELF_R_SYM(rel.r_info)]) == target &&
            (ELF_R_TYPE(rel.r_info) == ELF_R_ABS
             || ELF_R_TYPE(rel.r_info) == ELF_R_GLOB_DAT)) {
//            LOGI("target r_offset: %lX", rel.r_offset);
            return moduleBase + rel.r_offset;
        }
    }

之后替换该内存地址对应的函数即可,同前文

适配ARM64

#if defined(__LP64__)
#define ELFW(what) Elf64_ ## what
#define ELF_R_TYPE(what) ELF64_R_TYPE(what)
#define ELF_R_SYM(what) ELF64_R_SYM(what)
#else
#define ELFW(what) Elf32_ ## what
#define ELF_R_TYPE(what) ELF32_R_TYPE(what)
#define ELF_R_SYM(what) ELF32_R_SYM(what)
#endif

#if defined(__arm__)
#define ELF_R_JUMP_SLOT R_ARM_JUMP_SLOT     //.rel.plt
#define ELF_R_GLOB_DAT  R_ARM_GLOB_DAT      //.rel.dyn
#define ELF_R_ABS       R_ARM_ABS32         //.rel.dyn
#elif defined(__aarch64__)
#define ELF_R_JUMP_SLOT R_AARCH64_JUMP_SLOT
#define ELF_R_GLOB_DAT  R_AARCH64_GLOB_DAT
#define ELF_R_ABS       R_AARCH64_ABS64
#endif

测试

前文,替换动态库即可测试。

日志

ARM

ARM64

作为动态链接库

详见项目vitcim_app模块。

通过配置CMakeLists.txtlibinject.so作为动态库链接,在JNI_OnLoad调用hackBySegment替换getpid函数。

日志如下:

总结

主要学习了.dynamic段的解析和导入符号的查找方式。虽然实现了基于执行视图解析ELF,但仍存在许多不足。不过这次是真的短期内不会再改动了。(不要重复造轮子

参考

基于Android的ELF PLT/GOT符号和重定向过程ELF Hook实现
ELF文件格式与got表hook简单实现
重定位节 - 链接程序和库指南
符号表节 - 链接程序和库指南
散列表节 - 链接程序和库指南
动态节 - 链接程序和库指南
ELF文件结构详解
PLT HOOK
bhook
xhook

免费评分

参与人数 6威望 +2 吾爱币 +105 热心值 +5 收起 理由
j2000yk + 1 + 1 谢谢@Thanks!
qtfreet00 + 2 + 100 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
A3uRa + 1 我很赞同!
itachi137 + 1 + 1 热心回复!
笙若 + 1 + 1 谢谢@Thanks!
lfm333 + 1 + 1 谢谢@Thanks!

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

andixingchen 发表于 2021-8-31 21:58
这个厉害了大神啊!
lfm333 发表于 2021-9-1 07:47
liu.fei 发表于 2021-9-1 11:16
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2024-11-24 16:37

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表