❗️iOS高级强化

iOS高级强化--002:ABI Mach-O

2021-02-20  本文已影响0人  帅驼驼

Mach-OMach Object)是macOSiOSiPadOS存储程序和库的⽂件格式。对应系统通过应⽤⼆进制接⼝(application binary interface,缩写为ABI)来运⾏该格式的⽂件。

Mach-O格式⽤来替代BSD系统的a.out格式。Mach-O⽂件格式保存了在编译过程和链接过程中产⽣的机器代码和数据,从⽽为静态链接和动态链接的代码提供了单⼀⽂件格式。

苹果很多文件都采用Mach-O格式,最常见的就是可执行文件和动态库。当然,还有.o的目标文件、.a.framework的静态库以及动态连接器dyld等等。

项目运行时,IPA包里的可执行文件会被加载

可执⾏⽂件调⽤过程:

  • 调⽤fork函数,创建⼀个process(进程)
  • 调⽤execve或其衍⽣函数,在该进程上加载,执⾏我们的Mach-O⽂件

当调⽤execve(程序加载器)时,内核实际上在执⾏以下操作:

  • 将⽂件加载到内存
  • 开始分析Mach-O中的mach_header,以确认它是有效的Mach-O⽂件

Mach-O文件结构

Header包含该二进制文件的一般信息

  • 字节顺序、架构类型、加载指令的数量等
  • 使得可以快速确认一些信息,比如当前文件用于32位还是64位,对应的处理器是什么、文件类型是什么

Load commands一张包含很多内容的表

  • 内容包括区域的位置、符号表、动态符号表等

Data通常是对象文件中最大的部分

  • 包含Segement的具体数据

Header包含Mach HeaderSegmentSections三种类型

  • Header内的Sections描述了对应的⼆进制信息
  • Mach Header属于Header的⼀部分,它包含了整个⽂件的信息和Segment信息
  • Segmentssegment commands):指定操作系统应该将Segments加载到内存中的什么位置,以及为该Segments分配的字节数。还指定⽂件中的哪些字节属于该Segments,以及⽂件包含多少sections。之前段始终是4096字节4KB的倍数,其中4096字节是最小大小。现在段是16KB的倍数,在macOS_x86_64上是16KB,在iOS上是32KBSegments名称的约定是使⽤全⼤写字⺟,后跟双下划线(例如__TEXT
  • Section:所有sections都在每个segment之后⼀个接⼀个地描述。sections⾥⾯定义其名称,在内存中的地址、⼤⼩、⽂件中section数据的偏移量和segment名称。Section的名称约定是使⽤全⼩写字⺟,再加上双下划线(例如__text

Header的最开始是Magic Number,表示这是一个Mach-O文件

使用objdump --macho -private-header 【Mach-O路径】命令,查看Mach Header信息

也可以使用otool -h 【Mach-O路径】命令,这种方式打印的更像是原始数据,开发者难以阅读

Mach-OLoad CommandsData是分开的

  • Load Commands:二进制⽂件加载进内存要执⾏的⼀些指令
    这⾥的指令主要在负责APP对应进程的创建和基本设置(分配虚拟内
    存,创建主线程,处理代码签名/加密的⼯作),然后对动态链接库(.dylib
    系统库和自定义的动态库)进⾏库加载和符号解析的⼯作
  • Data:由多个⼆进制组成,逐一排列。包含__TEXT代码__DATA代码符号表,存储实际的代码和数据
    __TEXT段:只读区域,包含可执⾏代码和常量数据
    __DATA段:读/写,包含初始化和未初始化数据和⼀些动态链接专属数据
  • 例如:Load Command __TEXT中,记录了代码起始位置和大小等配置信息,dyld根据这些配置信息读取__TEXT代码段的实际代码
  • Mach-O中都是二进制数据,根据结构体内存对齐规则,数据会按照指定方式在二进制中进行排列,dyld按照相同方式逐一读取Load Command
  • Mach-O中保存了执行过程中的所有信息:
    Load Command __LINKEDIT:动态连接器需要的信息
    Load Command LC_LOAD_DYLINKER:链接器的位置
    Load Command LC_UUIDMach-O的唯一标识符
    Load Command LC_BUILD_VERSIONMach-O的编译信息
    Load Command LC_MAIN:入口函数,默认为main,也可以修改
    Load Command LC_LOAD_DYLIB:加载动态库的信息

使用objdump命令,查看Mach-O文件

objdump --macho --private-headers 【Mach-O路径】

查看Load Commands中入口函数的配置(LC_MAIN

objdump --macho --private-headers 【Mach-O路径】 | grep 'LC_MAIN' -A 3

__TEXT代码段的格式:

main函数为例,在x86架构下通过clang编译出来的并不是汇编代码,而是机器指令,这些机器指令对应每一条汇编代码都是固定不变的

使用objdump --macho -d 【Mach-O路径】命令,查看__TEXT代码段信息

  • __TEXT段__text section中,main函数以100003fa0地址开始,至100003fb5地址结束。中间列是机器码,最后列的是汇编代码
  • 汇编代码转换机器码在底层有类似字典的映射关系;汇编是给开发者提供的,而机器只需要读取最原始的机器码即可

通过代码读取Mach-O文件

machoinfo是一个读取Mach-O文件的项目,通过main函数接收参数

编译后生成Mach-O文件

打开终端,进入Mach-O所在目录,使用./machoinfo作为命令执行

./machoinfo 【将要读取的Mach-O文件路径】

machoinfo项目进行调试

打开Edit Scheme...配置默认参数

main函数设置断点并运行项目,可以接收到配置的默认参数

通过lldb可以查看结果

总结
上一篇下一篇

猜你喜欢

热点阅读