论坛首页 安全工具分享区 阅读主题

[原创]Android从ELF-Loader到自定义Linker的实现及原理

388 浏览 14 回复
#1 楼主 2026-06-01 21:09:12
本文为完善旧坑所作, 学习ELF文件结构后, 实现了解析器和加载器, 当初便想实现自定义Linker, 无奈碰到抽象bug暂时弃坑, 断断续续尝试几次终于解决bug成功跑通 之后让AI重构屎山代码, 更加清晰易懂, 遂填坑Push AI一把嗦固然非常爽, 但我个人的体验往往是脑中空空, 学得快忘得快, 印象不深, 长此以往容易让人浮躁, 知识消化吸收不良最后, 希望给学习相关知识点的师傅一些帮助, 同师傅们静下心学习底层原理阅读本文的正确姿势:本文分为以下部分:ELF文件结构ELF文件相关理论基础快速入门Linker概述系统Linker的工作流程简介 + 自定义Linker的概述ELF Loader一个简单的ELF可执行程序加载器, 用于辅助理解自定义Linker核心流程Custom Linker将ELF Loader迁移到Android平台, 手搓自定义LinkerCustom Linker Test 自定义Linker效果测试, 匿名内存模块扫描脚本, 抹去ELF关键信息增加逆向难度Linker源码阅读分析从Android 4.4和Android 10源码深入Android系统linker内部流程参考及推荐资料学习自定义Linker期间参考的资料, 以及部分推荐资料附件:ELF-LoaderELF Loader源码SelfDefineLoader自定义Linker源码项目scan_hidden_modules.js匿名内存模块frida扫描脚本DumpedSO测试自定义Linker时dump出的SOSoFixer 小风编译的修复版SoFixer环境:声明:在学习自定义linker前, 先过一遍ELF文件结构相关知识点Linux 的一个 .so 或可执行文件本质上是 ELF(Executable and Linkable Format) 文件ELF文件结构解析和加载器实现可以参考本人之前的一篇文章: ELF文件结构浅析-解析器和加载器实现可以将ELF文件分为5大核心块:ELF Header 描述ELF文件核心结构的基本信息, 例如文件类型, 架构, 入口点地址等并且指定了Program Headers和Section Headers的起始地址以及对应表项数目Program Headers每个表项描述段(Segment)的基本信息, 包括段的起始地址, 大小, 权限等供Linker判断segment是否需要加载, 如何加载到内存中Sections / Segments节 Section 是文件视图, 每个节都有其功能, 划分各个节有助于静态分析工具和用户了解ELF文件的细节段 Segment 是内存视图, Linker加载ELF文件到内存中只需要关注段如何加载, 不需要了解各个节的细节一个段可以包含多个节, 如果节的权限相同且地址连续便可以视为同一个段方便Dynamic段Section Headers 中, 为 .dynamic 节, Program Headers中, 为 PT_DYNAMIC 段 称之为 dynamic段 更加合适, 因为Linker在加载和链接时非常依赖它的信息其指向了符号表(导入/导出符号), 重定位表, Hash表(导出表), 依赖库, PLT, GOT, .init, .init_array等结构Section Headers每个表项描述节(Section)的基本信息, 包括节名, 起始地址, 大小等通常位于ELF文件末尾, Linker加载ELF文件时通常不会加载它, 因为运行时并不需要该结构, 但静态分析工具非常需要ELF文件按照顺序的布局大致如下 (以'.'开头的均为节区, 它们的顺序并非固定, 和编译器生成规则有关):Section和Segment两者的关系:一个Segment可以包含多个Section, 一个Section只属于一个Segment加载器只关心Segment(Program Header), 不需要Section Headerstrip掉Section Header的SO仍然可以正常加载运行, 但会让IDA等逆向工具难以分析Section和Segment在不同视角下的映射图:IDA显示的segments, 可以发现相同权限的section通常是连续的即多个相同权限的section可以视为同一个segmentELF Header位于文件最开头, 64位下固定64字节, 是整个文件的"身份证", 包含:ELF文件类型, 目标CPU架构, 入口点地址ELF Header大小, Program / Section Headers的起始地址, 表项大小, 表项数等关键信息Linker会首先读取该结构, 校验Magic Number (\x7fELF) 和 CPU架构, 然后从 e_phoff 定位段表每个Section Header描述一个节的名称、类型、在文件中的位置和大小:一些常见的节区列表汇总如下, 文章后续会讲解这些节区:以上并非所有节区, 一般还会有支持异常处理, 调试器辅助信息等功能的节区010editor查看Section Headers效果如下Program Header描述了文件中哪些部分需要映射到内存, 以及映射的属性:常见的段类型:010editor查看Program Headers效果如下, 带有很多辅助信息:Linker加载ELF文件时, 会遍历所有PT_LOAD段, 计算映像大小并分配内存, 将段填充至指定的虚拟地址, 之后设置段权限完成加载ELF文件中有很多字符串,例如段名,变量名等, 由于字符串长度往往不固定,所以使用固定结构描述比较困难常见做法是将字符串集中起来存放到一张字符串表,然后通过索引查表来引用字符串字符串表的内部结构极其简单:一块连续的字节数组设计规则:每个字符串都以空字符 \0 (NULL) 结尾表的第 0 个字节永远是 \0一个字符串可以包含另一个字符串例如,如果有 "printf\0",恰好有个符号叫 rintf,那么偏移量向后移动 1 位,就可以复用这段内存ELF文件中有3种字符串表, 其中 .dynstr 最重要:.shstrtab 节头字符串表存储“节区”自身的名字, 例如 ".text", ".data", ".bss" 等字符串非运行时必须, 主要供链接器和静态分析工具(如 readelf)解析文件结构时使用.strtab 静态字符串表存储“静态符号”的名字, 包含了代码中所有的函数名, 全局变量名,用于调试的局部变量名和源文件名称非运行时必须, 用于静态链接和调试。为了减小文件体积,发布前常使用 strip 命令将其剥离.dynstr 动态字符串表存储“动态链接”所需的名字: 1. 动态符号名(导入/导出函数和变量) 2. 依赖的外部共享库名称如 "libc.so.6"运行时必须, 它是linker在运行时寻找外部函数、加载依赖库的符号名称来源,不可剥离。010editor查看.dynstr效果如下:符号表记录了ELF导出和导入的所有符号(函数/全局变量等):通过符号表和对应的字符串表可以得到符号名,符号大小,符号地址等信息.symtab 静态符号表存储文件中的所有符号,

...(已截断)

---
来源: 看雪论坛
原文链接: https://bbs.kanxue.com/thread-290643.htm
#2 2026-06-01 21:09:12
严肃学习
#3 2026-06-01 21:09:12
x1a0f3n9

严肃学习
风佬带带
#4 2026-06-01 21:09:12
可真有你的, 正好复习下
#5 2026-06-01 21:09:12
同感,AI是提高了效率,但也觉得自己浮躁很多,感谢示范~
#6 2026-06-01 21:09:12
火钳刘明
#7 2026-06-01 21:09:12
rbq
#8 2026-06-01 21:09:12
严肃学习
#9 2026-06-01 21:09:12
感谢分享
#10 2026-06-01 21:09:12
逐字分析
#11 2026-06-01 21:09:12
学习
#12 2026-06-01 21:09:12
666,学习下
#13 2026-06-01 21:09:12
666认真学习,大佬辛苦了
#14 2026-06-01 21:09:12
666感谢大佬分享
#15 2026-06-01 21:09:12
技术又更新了么,又白学了么

请登录后参与讨论

立即登录 注册账号