so加载详细流程解析
字数 2709 2025-09-01 11:26:17

SO文件加载流程深度解析

1. SO文件基础概念

1.1 SO文件定义

  • 全称:Shared Object(共享对象)
  • 性质:动态共享库,存放可被多个程序共享并在运行时链接的代码和数据
  • 优势
    • 运行时加载和卸载
    • 节省内存和磁盘空间
    • 库更新时无需重新编译依赖程序

1.2 ELF格式基础

  • 定义:Executable and Linkable Format,用于存储可执行文件、目标代码、共享库及核心转储文件
  • 采用系统:所有现代Unix-like系统(Linux、Solaris、BSD等)
  • 三种主要类型
    1. 可重定位文件(.o):包含编译器生成的代码和数据,用于链接生成可执行文件或共享库
    2. 可执行文件:可直接执行的程序
    3. 共享目标文件(.so):即SO文件,包含代码和数据

1.3 SO文件与ELF的关系

  • SO文件采用ELF格式组织和存储内容
  • 统一使用ELF格式保证了跨平台兼容性和工具链统一性

2. SO文件生成过程

2.1 编译流程

  1. 使用C编译器将C文件编译成目标文件(.o)
  2. 使用gcc或ld命令将目标文件链接成SO文件(.so)
  3. 将生成的SO文件与程序进行动态链接

3. Android SO加载详细流程

3.1 Java层加载入口

System.loadLibrary()

  • 调用路径System.loadLibrary()Runtime.loadLibrary0()
  • Runtime.loadLibrary0()主要逻辑
    1. 库名检查

      • 确保库名中不包含目录分隔符("/"或"")
      • 检查方法:libname.indexOf(File.separatorChar) != -1
    2. ClassLoader处理

      • 非NULL情况:根据ClassLoader查找规则查找实际文件路径
      • NULL情况
        • 使用System.mapLibraryName(libraryName)将库名映射为实际文件名(如"libfirstndk.so")
        • 调用getLibPaths()获取系统预设库搜索路径(如/vendor/lib、/system/lib等)
        • 遍历路径,构造候选路径并检查可读性(IoUtils.canOpenReadOnly(candidate)
        • 调用nativeLoad(candidate, loader)尝试加载

3.2 Native层加载流程

Runtime_nativeLoad

  • Java层nativeLoad对应的Native实现
  • 核心流程由动态链接器(Linker)完成

动态链接器核心流程

  1. dlopen系列函数调用

    • 显式调用dlopen或隐式依赖(静态链接时自动加载)
  2. 动态链接器介入

    • 解析库路径
    • 加载库文件到内存
    • 处理符号重定位
    • 执行初始化代码
  3. 库文件查找与加载

    • 搜索路径
      • /system/lib[64]
      • /vendor/lib[64]
      • /data/app/.../lib
      • 通过LD_LIBRARY_PATH环境变量扩展
    • 加载步骤
      a. 检查库是否已加载(避免重复)
      b. 读取ELF文件头验证有效性
      c. 创建内存映射(mmap)将库载入进程地址空间
  4. ELF文件解析与内存映射

    • 关键ELF结构
      • Program Header Table:描述段(Segment)信息
      • Section Header Table:描述节(Section)信息
    • 内存映射策略
      • 代码段(.text):PROT_READ | PROT_EXEC
      • 数据段(.data、.bss):PROT_READ | PROT_WRITE
  5. 符号重定位(Relocation)

    • 目的:解决跨库函数调用和数据引用的地址问题
    • 关键步骤
      a. 解析动态符号表(.dynsym)和重定位表(.rel.dyn/.rel.plt)
      b. 遍历所有需要重定位的项(ElfW(Rela)),修正地址
      c. PLT/GOT机制
      • PLT(Procedure Linkage Table):跳转桩代码
      • GOT(Global Offset Table):存储函数实际地址
  6. 初始化函数执行顺序

    1. .init段:编译器生成的初始化函数(通常由_init函数实现)
    2. .init_array段:函数指针数组,按顺序执行每个初始化函数
    3. JNI_OnLoad(如果存在):用于动态注册JNI方法

3.3 关键函数分析

do_dlopen

  • 调用两个重要函数:
    1. find_library:完成SO的装载链接
    2. soinfoCallConstructors:调用SO的初始化函数

find_library_internal

  1. 通过find_loaded_library_by_name判断目标SO是否已加载
  2. 未加载则调用load_library继续加载流程

load_library

  • 核心加载函数
    1. 通过ElfReader对象的load方法将SO文件装载到内存
    2. 调用soinfo_alloc为SO分配新的soinfo结构
    3. 调用soinfo_link_image完成SO的装载

soinfo结构体

  • 作用:描述并管理已加载共享库(.so)的各种信息
  • 核心功能
    • 记录库的基地址
    • 管理符号表
    • 处理依赖关系
    • 维护重定位信息

CallConstructors

  • 初始化函数指定方式
    • 链接选项-init
    • 函数属性__attribute__((constructor))
  • 调用时机:SO装载链接后被调用,之后才将soinfo指针返回给dl_open调用者
  • 安全应用
    • SO层面保护的两个介入点:
      1. jni_onload
      2. 初始化函数(常用于反调试、脱壳等)

init和init_array函数

  • 特性:SO文件在被加载/卸载时自动执行的函数
  • 执行顺序init函数优先于init_array函数
  • 安全应用:可通过hook这些早期执行的函数绕过关键检测点

4. 关键技术与应用

4.1 动态加载hook点

  • 常见问题:传统hook时机可能错过init初始化
  • 解决方案:hook android_dlopen_ext函数(SO加载的关键函数)

4.2 安全相关技术

  1. 反调试:在initJNI_OnLoad中实现
  2. 脱壳:利用初始化函数进行解密操作
  3. 防护:检测关键函数是否被hook

5. 参考资源

  1. ELF文件结构详解
  2. CTF Wiki - ELF基础信息
  3. Android 9源码
SO文件加载流程深度解析 1. SO文件基础概念 1.1 SO文件定义 全称 :Shared Object(共享对象) 性质 :动态共享库,存放可被多个程序共享并在运行时链接的代码和数据 优势 : 运行时加载和卸载 节省内存和磁盘空间 库更新时无需重新编译依赖程序 1.2 ELF格式基础 定义 :Executable and Linkable Format,用于存储可执行文件、目标代码、共享库及核心转储文件 采用系统 :所有现代Unix-like系统(Linux、Solaris、BSD等) 三种主要类型 : 可重定位文件(.o) :包含编译器生成的代码和数据,用于链接生成可执行文件或共享库 可执行文件 :可直接执行的程序 共享目标文件(.so) :即SO文件,包含代码和数据 1.3 SO文件与ELF的关系 SO文件采用ELF格式组织和存储内容 统一使用ELF格式保证了跨平台兼容性和工具链统一性 2. SO文件生成过程 2.1 编译流程 使用C编译器将C文件编译成目标文件(.o) 使用gcc或ld命令将目标文件链接成SO文件(.so) 将生成的SO文件与程序进行动态链接 3. Android SO加载详细流程 3.1 Java层加载入口 System.loadLibrary() 调用路径 : System.loadLibrary() → Runtime.loadLibrary0() Runtime.loadLibrary0()主要逻辑 : 库名检查 : 确保库名中不包含目录分隔符("/"或"") 检查方法: libname.indexOf(File.separatorChar) != -1 ClassLoader处理 : 非NULL情况 :根据ClassLoader查找规则查找实际文件路径 NULL情况 : 使用 System.mapLibraryName(libraryName) 将库名映射为实际文件名(如"libfirstndk.so") 调用 getLibPaths() 获取系统预设库搜索路径(如/vendor/lib、/system/lib等) 遍历路径,构造候选路径并检查可读性( IoUtils.canOpenReadOnly(candidate) ) 调用 nativeLoad(candidate, loader) 尝试加载 3.2 Native层加载流程 Runtime_ nativeLoad Java层 nativeLoad 对应的Native实现 核心流程由 动态链接器(Linker) 完成 动态链接器核心流程 dlopen系列函数调用 显式调用 dlopen 或隐式依赖(静态链接时自动加载) 动态链接器介入 解析库路径 加载库文件到内存 处理符号重定位 执行初始化代码 库文件查找与加载 搜索路径 : /system/lib[64] /vendor/lib[64] /data/app/.../lib 通过 LD_LIBRARY_PATH 环境变量扩展 加载步骤 : a. 检查库是否已加载(避免重复) b. 读取ELF文件头验证有效性 c. 创建内存映射( mmap )将库载入进程地址空间 ELF文件解析与内存映射 关键ELF结构 : Program Header Table :描述段(Segment)信息 Section Header Table :描述节(Section)信息 内存映射策略 : 代码段(.text): PROT_READ | PROT_EXEC 数据段(.data、.bss): PROT_READ | PROT_WRITE 符号重定位(Relocation) 目的 :解决跨库函数调用和数据引用的地址问题 关键步骤 : a. 解析动态符号表(.dynsym)和重定位表(.rel.dyn/.rel.plt) b. 遍历所有需要重定位的项( ElfW(Rela) ),修正地址 c. PLT/GOT机制 : PLT(Procedure Linkage Table):跳转桩代码 GOT(Global Offset Table):存储函数实际地址 初始化函数执行顺序 .init 段:编译器生成的初始化函数(通常由 _init 函数实现) .init_array 段:函数指针数组,按顺序执行每个初始化函数 JNI_OnLoad (如果存在):用于动态注册JNI方法 3.3 关键函数分析 do_ dlopen 调用两个重要函数: find_library :完成SO的装载链接 soinfo 的 CallConstructors :调用SO的初始化函数 find_ library_ internal 通过 find_loaded_library_by_name 判断目标SO是否已加载 未加载则调用 load_library 继续加载流程 load_ library 核心加载函数 : 通过 ElfReader 对象的 load 方法将SO文件装载到内存 调用 soinfo_alloc 为SO分配新的 soinfo 结构 调用 soinfo_link_image 完成SO的装载 soinfo结构体 作用 :描述并管理已加载共享库(.so)的各种信息 核心功能 : 记录库的基地址 管理符号表 处理依赖关系 维护重定位信息 CallConstructors 初始化函数指定方式 : 链接选项 -init 函数属性 __attribute__((constructor)) 调用时机 :SO装载链接后被调用,之后才将 soinfo 指针返回给 dl_open 调用者 安全应用 : SO层面保护的两个介入点: jni_onload 初始化函数(常用于反调试、脱壳等) init和init_ array函数 特性 :SO文件在被加载/卸载时自动执行的函数 执行顺序 : init 函数优先于 init_array 函数 安全应用 :可通过hook这些早期执行的函数绕过关键检测点 4. 关键技术与应用 4.1 动态加载hook点 常见问题 :传统hook时机可能错过 init 初始化 解决方案 :hook android_dlopen_ext 函数(SO加载的关键函数) 4.2 安全相关技术 反调试 :在 init 或 JNI_OnLoad 中实现 脱壳 :利用初始化函数进行解密操作 防护 :检测关键函数是否被hook 5. 参考资源 ELF文件结构详解 CTF Wiki - ELF基础信息 Android 9源码