首页
社区
课程
招聘
[原创]Frida hook 系统点位原理分析 --Linker
发表于: 2025-6-6 14:04 187

[原创]Frida hook 系统点位原理分析 --Linker

2025-6-6 14:04
187
  1. 什么是linker/linker64

    1. 加载并链接可执行文件(ELF)及其依赖的共享库(.so)
    2. 解析符号(Symbol Resolution)
    3. 重定位 (Relocation)
    4. 初始化程序环境(TLS, 构造函数)
    5. 在Android 中,linker/linker64是32/64位架构下的动态链接器(后面统一用linker 代替动态链接器),其作用是:

    1. 位置

      1. 默认

      2. 也可以通过 find 查找

      3. APEX (Android Pony Express)

      4. Android 版本linker64 路径说明
        Android 5-8/system/bin/linker64传统路径
        Android 9+/system/bin/linker64_arm64<br>/system/bin/linker64_x86_64按架构细分路径
        Android 10+/apex/com.android.runtime/bin/linker64引入 APEX 模块后,路径可能变化
      5. find /system -name linker64
      6. find /apex -name linker64
      7. 文件格式:APEX 本质是一个ZIP 压缩包, 包含以下

      8. 挂载路径:系统启动时,APEX模块会挂载到 /apex/<module_name>路径下,例如:

      9. Android 10 开始,linker64 会优先从APEX 模块加载依赖库。
        文件/目录说明
        apex_manifest.json模块元数据(名称、版本、依赖等)
        AndroidManifest.xml模块声明(兼容性要求)
        payload.bin实际内容(文件系统镜像)
        payload_properties.txt镜像属性(如哈希值、大小)
        apex_pubkey签名公钥(用于验证模块)
        apex_signature模块签名(防篡改)
        /apex/com.android.runtime/bin/linker64  # linker64 动态链接器
        /apex/com.android.art/bin/dalvikvm       # ART 虚拟机
        模块名称作用
        com.android.artAndroid 运行时(ART)
        com.android.runtimeBionic C 库、linker64 动态链接器
        com.android.llvmClang 编译器工具链
        com.android.conscryptJava 加密库(Conscrypt)
        com.android.media多媒体框架(如 OMX、MediaCodec)
        com.android.os.statsd系统统计服务(StatsD)
      1. Android 10 引入的一种系统模块容器格式, 用于将底层系统组件以模块化的方式打包和管理。 核心目标是解耦系统组件与系统镜像(system.img), 以实现更灵活更新,隔离和安全控制
      2. APEX 应用场景

      3. APEX 结构和工作原理

      1. 源码位置 : 位于AOSP 项目的 system/core/linker
      2. Android 位置 (有些厂商会做自定义修改)

      1. 核心流程分析

      1. linker 调用顺序图示 

        1. ElfReader::Load()
          │
          ├── soinfo::soinfo() 创建 soinfo
          │
          ├── soinfo::parse_dynamic() 解析动态段
          │
          ├── soinfo::load_library() 加载依赖库
          │   └── soinfo::find_library()
          │       └── soinfo::load_library_from_path()
          │           └── 再次调用 ElfReader::Load()
          │
          ├── soinfo::resolve_symbol() 解析符号
          │
          ├── soinfo::relocate() 重定位
          │
          ├── soinfo::allocate_tls() TLS 初始化
          │
          └── soinfo::call_constructors() 调用构造函数
          │   └── .init()  初始化函数
          │       └── .init_array()  初始化函数组
      2. 加载ELF 文件

        1. 读取ELF 文件头
        2. 解析程序头,确定加载段(PT_LOAD)

        3. 分配内存并映射可执行段 (.text, .rodata等)
        4. 返回加载后的及地址 (base)
        5. 字段名含义
          p_type段类型(值为 PT_LOAD 表示可加载段)。
          p_offset文件中的偏移量(即该段在文件中的起始位置)。
          p_vaddr虚拟地址(VMA):段在进程地址空间中的目标加载地址。
          p_paddr物理地址(LMA):通常用于嵌入式系统,普通系统中可忽略。
          p_filesz文件中该段的大小(字节)。
          p_memsz内存中该段的大小(可能比 p_filesz 大,未初始化部分填充零)。
          p_flags内存权限标志:PF_R(读)、PF_W(写)、PF_X(执行)。
          p_align对齐方式(通常按页对齐,如 0x1000)。
        6. 代码段(.text): 可执行命令
        7. 数据段(.data):已初始化的全局变量
        8. BSS段(.bss):未初始化的全局变量(运行时分配)
        9. 堆栈空间:某些编译器会通过PT_LOAD 预留堆栈内存
        10. PT_LOAD 段定义了进程执行时需要从文件加载到内存的连续区域。 这些段通常包含:

        11. 操作系统会根据PT_LOAD 的描述将文件中的特定部分映射到进程的虚拟地址空间,并设置内存权限(可读,可写,可执行)
        12. 在ELF 文件中, PT_LOAD 段描述由ELF_Phdr 结构体定义,不同架构略有差异

        1. ElfReader::Load()

        2. AOSP 路径:system/core/linker/ElfReader.cpp
        1. 创建soinfo 结构

          1. struct soinfo {
                const char* name;        // 模块名
                void* base;              // 加载基地址
                size_t size;             // 模块大小
                Elf_Phdr* phdr;          // 程序头
                size_t phnum;            // 程序头数量
                Elf_Dyn* dynamic;        // 动态段指针
                soinfo* next;            // 链表指针
                void (*entry)(void);     // 入口点
                void call_constructors(); // 构造函数调用
            };
          2. soinfo 是linker 内部用于表示加载的ELF 模块的数据结构
          3. AOSP 路径:system/core/linker/soinfo.cpp 实现soinfo 结构及加载,解析逻辑
          4. 关键字段如下

        2. 解析动态段

          1. 遍历 .dynamic 段,提取关键信息

          2. .dynamic 段是 动态链接器工作的基础,当程序启动时,内核会加载linker, 并由linker执行 .dynamin 段中的信息,递归加载所有依赖的.so , 并完成符号绑定和重定位
          3. DT_SYMTAB:符号表地址
          4. DT_STRTAB: 字符串表地址
          5. DT_JMPREL:PLT 重定位表
          6. DT_RELA/DT_REL: 动态重定位表
          7. DT_INIT/DT_INIT_ARRAY: 构造函数地址
          8. DT_FINI/DT_FINI_ARRAY:析构函数地址
          9. DT_NEEDED:依赖的共享库名称
          10. soinfo::parse_dynamic()

          11. AOSP 路径:system/core/linker/soinfo.cpp 实现soinfo 结构及加载,解析逻辑
          1. 加载依赖库

            1. 遍历DT_NEEDED表中的依赖库名称
            2. 对每个依赖库调用 soinfo::find_library() 或 soinfo::load_library_from_path()
            3. 递归加载依赖库形成依赖链
            4. soinfo::load_library(const std::vector<std::string>& needed)

            5. AOSP 路径:system/core/linker/soinfo.cpp 实现soinfo 结构及加载,解析逻辑
          2. 符号解析

            1. 遍历.dynsym 符号表,查找未定义符号(UND)

            2. 使用dlsym或全局符号表查找符号定义
            3. 更新符号地址用于后续重定位
            4. typedef struct {
                  uint32_t st_name;   // 符号名称在 .dynstr 中的偏移
                  unsigned char st_info; // 符号类型和绑定信息
                  unsigned char st_other; // 符号可见性
                  uint16_t st_shndx;  // 所在段索引(如 SHN_UNDEF 表示未定义)
                  uint64_t st_value;  // 符号地址(虚拟地址)
                  uint64_t st_size;   // 符号大小
              } Elf64_Sym;
            5. 在.rela.dyn 或 .rela.plt 段中, 重定位条目会引用 .dynsym 表中的符号索引,以确定要修正的地址
            6. .dynsym 表中记录了模块导出的全局符号(如函数,全局变量), 供其他模块调用。
            7. 例如:libnative.so 导出的 Java_com_example_MyClass_myMethod 函数会出现在 .dynsym 中。
            1. 在动态链接过程中,linker 会遍历 .dynsym 表,查找未定义符号的定义位置
            2. Eg: 在.so 文件中引用了printf 函数, 动态链接器会在libc.so 的 .dynsym中查找printf的地址
            3.       .dynsym 是ELF 文件的一个关键段,用于存储动态链接所需的符号信息。

            4. 符号解析

            5. 动态链接

            6. 重定位

            7. .dynsym 结构

            1. soinfo::resolve_symbol()

            2. AOSP 路径:system/core/linker/symbol.cpp 实现符号解析逻辑
            1. 重定位

              1. 遍历.rela.dyn 和 .rela.plt 重定位段
              2. 根据重定位类型(如 R_AARCH64_RELATIVE, R_AARCH64_JUMP_SLOT)修改代码段中的地址引用

              3. 修正全局偏移表(GOT)和过程链接表(PLT)和 动态重定位段(DYN)

              4. Android 中的动态链接流程

              5. 特性R_AARCH64_RELATIVER_AARCH64_JUMP_SLOT
                用途修正基于基地址的绝对地址修正 PLT 表项(外部函数地址)
                是否需要符号表
                应用场景全局变量、静态变量、函数指针外部函数调用(如 PLT)
                性能影响低(无需符号解析)较高(需符号解析)
                与 ASLR 的关系直接支持 ASLR间接支持 ASLR(通过 PLT 修正)
              6. ASLR (Address Space Layout Randomization) 是Android 系统的内存保护机制,通过随机化进程的地址空间布局(如堆,栈,共享库,可执行文件基址等)来增加攻击者预测内存地址的难度,从而防御缓冲区溢出等攻击

              7. 可执行文件基址若启用 PIE(Position Independent Executable),程序入口地址随机化。
                动态链接库(.so)加载地址随机化(通过 /system/bin/linker 实现)。
                堆(Heap)堆起始地址随机化(由内核的 mmap 分配策略决定)。
                栈(Stack)栈基址随机化(由内核的 execve 调用决定)。
                mmap 区域动态分配的内存(如 mmap)地址随机化。
              8. 缓冲区溢出,是由程序未正确检查输入数据边界,导致写入缓冲区的数据超出其分配内存范围,从而覆盖相邻内存区域的安全漏洞。
              9. 随机化范围

              1. 用于.PLT表项的重定位, 即动态链接器在运行时解析外部函数地址后,将其填充到.plt 表中
              2. PLT 表项的地址 = 符号(外部函数)的实际地址(运行时解析)+家数(通常为0)
              3. 用于相对地址重定位。即在程序加载到内存时,根据模块的基地址动态调试某些地址值
              4. 需要修正的目标地址 (.got 表项地址)=模块加载的基址(运行时linker确定)+符号的偏移或常量
              1. R_AARCH64_RELATIVE

              2. R_AARCH64_JUMO_SLOT

              3. 二者对比

              4. 结构: 每个外部函数在.PLT对应一个跳转桩(Stub)初始跳转桩会触发动态链接器解析函数地址,后续直接跳转到实际地址
              5. __GOT_printf.GOTprintf 的条目,初始指向 linker 的解析函数。
              6. ; PLT 示例代码(ARM64)
                printf@PLT:
                    adrp    x16, __GOT_printf@PAGE
                    ldr     x16, [x16, __GOT_printf@PAGEOFF]
                    br      x16
              1. 结构:每个外部函数或变量在.GOT中对应一个条目, 初始时,.GOT 的地址可能会指向动态链接器的解析逻辑,后续通过 linker 填充实际地址。
              2. 示例: 对printf 的调用:程序首次调用时,.GOT 中的printf 地址指向linker 的解析函数,解析后地址更新为libc.so 中的printf 的真实地址
              3. GOT (Global Offset Table) 全局偏移表,用于存储外部符号(如库函数,全局变量)的实际地址

              4. PLT (Procedure Linkage Table)过程链接表,用于间接调用外部函数,延迟绑定时,通过PLT 调用动态解析GOT中的地址

              5. DYN (Dynamic Relocation) 动态重定位段。记录了需要动态链接器处理的全局变量,外部函数地址等符号的重定位信息。主要用于修正数据引用(全局变量,静态变量)或非PLT 函数调用地址。
              1. 若设置环境变量LD_BIND_NOW=1。所有外部符号在程序启动时解析,而非延迟绑定。
              2. LD_BIND_NOW 是linker 的一个环境变量,强制linker启动时立即解析所有外部符号,可以减少.GOT被修改的窗口期,但是会增加启动时间,部分支持,受Android版本影响
              3. 原理:仅在首次调用某个外部符号时解析其地址
              4. 当程序首次调用外部函数(如 printf)时:

              1. 执行.PLT 中的跳转桩,跳转到.GOT 条目
              2. .GOT 条目初始指向linker的解析函数
              3. linker 解析符号的实际地址。更新.GOT 条目
              4. 后续调用直接跳转到.GOT 中已解析的地址
              5. Linker 加载主程序和依赖的.so 库
              6. 解析.dynamin 段,确定依赖关系和符号表
              1. 程序启动

              2. 延迟绑定(Lazy Binding):

              3. 立即绑定:

              4. soinfo:relocate()

              5. AOSP 路径:system/core/linker/relocation.cpp 实现重定位逻辑
              1. TLS 初始化

                1. 为模块分配TLS(Thread-Local Storage) 内存
                2. 初始化TLS模板,用于线程创建时复制
                3. soinfo::allocate_tls()

                4. TLS(Thread Local Storage 线程局部存储) 一种线程私有数据存储基质,允许每个线程拥有独立的变量副本,避免多线程环境下的数据竞争问题。
                5. AOSP 路径:system/core/linker/tls.cpp 实现TLS初始化逻辑
              2. 调用构造函数

                1. Android10 call_constructors 偏移量:0x523FC (不同设备偏移有差异)
                2. 调用.init段中的构造函数

                3. 调用.init_array段中的构造函数数组

                4. 顺序:依赖库的构造函数先执行,当前模块的后执行
                5. .init 段时一个单入口函数指针,通常指向一个初始化函数,用于执行全局初始化逻辑
                6. .init_array 段存储的是函数指针列表。每个指针指向一个初始化函数。 Jni_Onload 就是其中的一个函数
                1. soinfo:call_constructors()

                2. 核心流程
                3. void soinfo::call_constructors() {
                      // 1. 查找 DT_INIT 段(旧式构造函数)
                      for (Elf_Dyn* d = dynamic; d->d_tag != DT_NULL; ++d) {
                          if (d->d_tag == DT_INIT) {
                              void (*init_func)(void) = reinterpret_cast<void (*)(void)>(base + d->d_un.d_val);
                              init_func();  // 调用构造函数
                          }
                      }
                  
                      // 2. 查找 DT_INIT_ARRAY 段(新式构造函数数组)
                      for (Elf_Dyn* d = dynamic; d->d_tag != DT_NULL; ++d) {
                          if (d->d_tag == DT_INIT_ARRAY) {
                              void (**init_array)(void) = reinterpret_cast<void (**)(void)>(base + d->d_un.d_ptr);
                              size_t count = d->d_un.d_val / sizeof(void*);
                              for (size_t i = 0; i < count; ++i) {
                                  init_array[i]();  // 依次调用每个构造函数
                              }
                          }
                      }
                  }
                4. 获取Android 设备中的 偏移地址, 将linker 拖入ida 可得

                5. AOSP bionic/linker/linker_soinfo.cpp 调用构造函数
                1. AOSP system/core/linker/linker_main.cpp 主流程控制
                1. liker 使用场景

                  1. Android 应用中通过 JNI 调用的Native代码 也是由linker加载的
                  2. 所有使用C/C++编写的远程程序在启动时都会由linker加载
                  3. 这些依赖的.so 文件 也会通过linker 进行动态链接
                  4. 在Android 启动过程中,zygote进程是通过/system/bin/app_process 启动的
                  5. app_process 是一个ELF 可执行文件,动态链接器位linker
                  6. 系统启动阶段

                  7. 原生程序 (Native Apps)

                  8. JNI 调用



                  [培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

                  收藏
                  免费 0
                  支持
                  分享
                  最新回复 (0)
                  游客
                  登录 | 注册 方可回帖
                  返回