KSCrash 是一个用于 iOS 平台的崩溃捕捉框架,最近读了其部分源码,在 KSDynamicLinker 文件中有一个函数,代码如下:

/** Get the segment base address of the specified image.
*
* This is required for any symtab command offsets.
*
* @param idx The image index.
* @return The image's base address, or 0 if none was found.
*/
static uintptr_t segmentBaseOfImageIndex(const uint32_t idx)
{
const struct mach_header* header = _dyld_get_image_header(idx); // Look for a segment command and return the file image address.
uintptr_t cmdPtr = firstCmdAfterHeader(header);
if(cmdPtr == 0)
{
return 0;
}
for(uint32_t i = 0;i < header->ncmds; i++)
{
const struct load_command* loadCmd = (struct load_command*)cmdPtr;
if(loadCmd->cmd == LC_SEGMENT)
{
const struct segment_command* segmentCmd = (struct segment_command*)cmdPtr;
if(strcmp(segmentCmd->segname, SEG_LINKEDIT) == 0)
{
return segmentCmd->vmaddr - segmentCmd->fileoff;
}
}
else if(loadCmd->cmd == LC_SEGMENT_64)
{
const struct segment_command_64* segmentCmd = (struct segment_command_64*)cmdPtr;
if(strcmp(segmentCmd->segname, SEG_LINKEDIT) == 0)
{
return (uintptr_t)(segmentCmd->vmaddr - segmentCmd->fileoff);
}
}
cmdPtr += loadCmd->cmdsize;
} return 0;
}

该函数被如此调用:

const uintptr_t segmentBase = segmentBaseOfImageIndex(idx) + imageVMAddrSlide;

0 迷惑现场

一个 image 中会有多个 segment,参数 idx 传递的是 image 的索引,如果返回的是 segment base, 那么是哪个 segment?

有人会说,注释里不是说返回非 0 的话,就表示的是 image base。可是从原理上讲 vmaddr - fileoff 根本得不到 image base(后文有解释)。

而在被调用处,加上由 ASLR 引起的偏移,赋值给了 segmentBase。

fishhook 中,有这么一行代码:

uintptr_t linkedit_base = (uintptr_t)slide + linkedit_segment->vmaddr - linkedit_segment->fileoff;

暂不考虑由 ASLR 造成的 slide,那么又是上边提到的 vmaddr - fileoff,这里的变量命名是 linkedit_base

KSCrash 中的所谓的 segmentBase 和 fishhook 中所谓的 linkedit_base,到底指的是什么?如果指的是 __LINKEDIT 端在内存中的真实地址那应该是 vmaddr + ASLR偏移 才对。

在查找资料的过程中,读了大量的博客、资料,对于这一块的解释,要么没提,要么一带而过,要么是错的。有的认为这个值是__LINKEDIT 段在内存中的基址,有的认为是当前 image 在内存中的基址。

1 揭开面纱

1.1 前置知识

在理解这个值到底是什么之前,我们需要一些前置知识。

  • Mach-O 文件的结构
  • 虚拟内存
  • ASLR

下边我们简单的说一下 Mach-O 文件。

Mach-O

我们知道,进程是可执行文件在内存中加载得到的结果,而 Mach-O 就是一种 macOS 平台的可执行文件格式。

Mach-O 文件分为三个区域 Header、Load commands、Data。其中 Load commands 区的指令指导如何设置并加载二进制数据。下边列出 32 位平台下我们关心的几个:

指令 对应的数据结构 描述
LC_SEGMENT segment_command 定义了这个文件中的一个 segment,在 Mach-O 文件被加载到时,这个 segment 会被映射到对应的地址空间。需要留意,segment_command 中有一个 segname,可通过 segname 来查找指定的 segment。
LC_SYMTAB symtab_command 指定了这个文件的符号表。symtab_command 中包含符号表在文件中的偏移、符号数量、字符串表在文件中的偏移、字符串表的大小。

segment_command 代码如下:

struct segment_command { /* for 32-bit architectures */
uint32_t cmd; /* LC_SEGMENT */
uint32_t cmdsize; /* includes sizeof section structs */
char segname[16]; /* segment name */
uint32_t vmaddr; /* memory address of this segment */
uint32_t vmsize; /* memory size of this segment */
uint32_t fileoff; /* file offset of this segment */
uint32_t filesize; /* amount to map from the file */
vm_prot_t maxprot; /* maximum VM protection */
vm_prot_t initprot; /* initial VM protection */
uint32_t nsects; /* number of sections in segment */
uint32_t flags; /* flags */
};

对于每一个 segment 而言,设置进程虚拟内存的过程就是将相应的内容加载到内存中,也就是从 Mach-O 文件的 fileoff 初加载 filesize 字节到虚拟内存地址的 vmaddr 处,占用 vmsize 字节。需要留意,对某些 segment 来说,vmsize 可能会大于 filesize,如__Data、__LINKEDIT。

在后边的讨论中,我们需要关心的是 segname__LINKEDIT 的段。__LINKEDIT 段由 dyld 使用,包含符号表、字符串表以及其他数据。

symtab_command 代码如下:

struct symtab_command {
uint32_t cmd; /* LC_SYMTAB */
uint32_t cmdsize; /* sizeof(struct symtab_command) */
uint32_t symoff; /* symbol table offset */
uint32_t nsyms; /* number of symbol table entries */
uint32_t stroff; /* string table offset */
uint32_t strsize; /* string table size in bytes */
};

symtab_command 中,symoff 为符号表在 Mach-O 文件中的偏移、stroff 为字符串表在 Mach-O 文件中的偏移。

1.2 揭秘

我们可以使用 MachOView 来打开一个 Mach-O 文件,观察 LC_SEGMENT(__LINKEDIT)、LC_SYMTAB。限于篇幅,这里就不截图观察了。但是你应当留意到符号表、字符串表在 Mach-O 文件的位置,位于 __LINKEDIT 段中,这也验证了上边对 __LINKEDIT 段的介绍。

我们从符号表在虚拟内存中的地址来倒推上边那个所谓的 segmentBaselinkedit_base,看一张图(不是很准确,但可以帮助我们搞明白这个问题)。

我们先忽略 ASLR,图中的深灰色背景表示是虚拟内存,__TEXT 段、__DATA 段我们不关心,图中没有体现。

sym_vmaddr 是指的是符号表在虚拟内存中地址,而在虚拟内存中符号表在 __LINKEDIT 段中偏移,即 sym_vmaddr - vmaddr,与其在 MachO 文件中的偏移,即 symoff - fileoff 相等。

也就是sym_vmaddr - vmaddr = symoff - fileoff

vmaddr 移到右边,即 sym_vmaddr = symoff - fileoff + vmaddr

发现什么了吗?

接着上边推:

减去符号表偏移symoff:sym_vmaddr - symoff = vmaddr - fileoff(式1),

式 1 等号右边的部分加上 ASLR 偏移 slide:vmaddr - fileoff + slide,也就是所谓的 segmentBaselinkedit_base

至此,真相大白。

参考

  • 深入解析Mac OS X & iOS操作系统

  • 深入理解计算机系统

  • Mach-O File Format

  • The Mac Hacker's Handbook

Mach-O在内存中符号表地址、字符串表地址的计算的更多相关文章

  1. 数据在内存中的存储方式( Big Endian和Little Endian的区别 )(x86系列则采用little endian方式存储数据)

    https://www.cnblogs.com/renyuan/archive/2013/05/26/3099766.html 1.故事的起源 “endian”这个词出自<格列佛游记>.小 ...

  2. PE格式第四讲,数据目录表之导入表,以及IAT表

    PE格式第四讲,数据目录表之导入表,以及IAT表 一丶IAT(地址表) 首先我们思考一个问题,程序加载的时候会调用API,比如我们以前写的标准PE 那么他到底是怎么去调用的? 他会Call 下边的Jm ...

  3. 第四讲,数据目录表之导入表,以及IAT表

    一丶IAT(地址表) 首先我们思考一个问题,程序加载的时候会调用API,比如我们以前写的标准PE 那么他到底是怎么去调用的? 它会Call 下边的Jmp位置 而Jmp位置则是对一个全局变量取内容. 看 ...

  4. 小表驱动大表, 兼论exists和in

    给出两个表,A和B,A和B表的数据量, 当A小于B时,用exists select * from A where exists (select * from B where A.id=B.id) ex ...

  5. 线性表之顺序表(C语言实现)

    线性表是从数据元素的逻辑结构上定义的. 这种数据元素的逻辑结构的特征如下: 1.除开第一个和最后一个元素之外.所有元素都有一个前驱元素和后继元素. 2.第一个元素无前驱元素,但有后继元素. 3.最后一 ...

  6. ARM裸板开发:04_MMU 链接地址与运行地址不一致时,(SDRAM)初始化程序地址无关码问题的分析

    ARM裸板开发过程,程序的链接地址设置为为0x30000000,而前期的启动代码以及相关硬件的初始化代码需要在内部iRAM(steppingstone,起始地址0x0)的4K中运行.链接地址与运行地址 ...

  7. 浮点数在计算机内存中的表示(IEEE 754规定1位是符号位,8位是指数,剩下的23位为有效数字)

    本文转载自:阮一峰的博客,http://www.ruanyifeng.com/blog/2010/06/ieee_floating-point_representation.html 张玉彬的博客 h ...

  8. In-Memory:在内存中创建临时表和表变量

    在Disk-Base数据库中,由于临时表和表变量的数据存储在tempdb中,如果系统频繁地创建和更新临时表和表变量,大量的IO操作集中在tempdb中,tempdb很可能成为系统性能的瓶颈.在SQL ...

  9. DataTable to Excel(使用NPOI、EPPlus将数据表中的数据读取到excel格式内存中)

    /// <summary> /// DataTable to Excel(将数据表中的数据读取到excel格式内存中) /// </summary> /// <param ...

随机推荐

  1. 「每日五分钟,玩转JVM」:线程独占区

    前言 如果我们对计算机组成有所了解,那么我们一定会知道在计算机中有一块儿特殊的区域,称之为寄存器,寄存器包括了指令寄存器和程序计数器,这两样位于CPU中,作为程序运行的大脑来控制程序的运行和流转. 而 ...

  2. 启xin宝app的token算法破解——frida篇(四)

    前两篇文章分析该APP的抓包.的逆向: 启xin宝app的token算法破解--抓包分析篇(一) 启xin宝app的token算法破解--逆向篇(二) 启xin宝app的token算法破解--toke ...

  3. (五十)c#Winform自定义控件-滑块

    前提 入行已经7,8年了,一直想做一套漂亮点的自定义控件,于是就有了本系列文章. GitHub:https://github.com/kwwwvagaa/NetWinformControl 码云:ht ...

  4. Liunx软件安装之Redis

    Redis是一个开源(BSD许可),内存数据结构存储,用作数据库,缓存和消息代理.它支持数据结构,如字符串,散列,列表,集合,带有范围查询的排序集,位图,超级日志和带有半径查询的地理空间索引.Redi ...

  5. Ajax数据解析格式

    Ajax数据解析格式 一.前言 在服务器端 AJAX 是一门与语言无关的技术.在业务逻辑层使用何种服务器端语言都可以. 从服务器端接收数据的时候,那些数据必须以浏览器能够理解的格式来发送.服务器端的编 ...

  6. Attention机制的精要总结,附:中英文机器翻译的实现!

    1. 什么是Attention机制 在"编码器-解码器(seq2seq)"⼀节⾥,解码器在各个时间步依赖相同的背景变量来获取输⼊序列信息.当编码器为循环神经⽹络时,背景变量来⾃它最 ...

  7. (数据科学学习手札68)pandas中的categorical类型及应用

    一.简介 categorical是pandas中对应分类变量的一种数据类型,与R中的因子型变量比较相似,例如性别.血型等等用于表征类别的变量都可以用其来表示,本文就将针对categorical的相关内 ...

  8. ECfinal-D-Ice Cream Tower-二分+贪心

    传送门:https://vjudge.net/problem/Gym-101194D 题意:在一堆数中,找到对多的组合,使得每个组合的个数为K,且满足在排序后,后一个是前一个的两倍: 思路:二分,贪心 ...

  9. JOBDU 1027 欧拉回路

    题目1027:欧拉回路 时间限制:1 秒 内存限制:32 兆 特殊判题:否 提交:3620 解决:1847 题目描述:     欧拉回路是指不令笔离开纸面,可画过图中每条边仅一次,且可以回到起点的一条 ...

  10. yzoj P2344 斯卡布罗集市 题解

    共t条街对于每一条街上有n个店铺(n可能不相同),每次只能选两端第一个营业的店铺采购,采购第i个店铺会获得幸福度ai,采购完后,这个店铺和它相邻的店铺便会关门,问最大幸福度? 考场想了一下dp,一开始 ...