12 - Mach-O

Mach-O 简介

Mach-O其实是Mach Object文件格式的缩写,是MacOSiOS上一种用于可执行文件、目标代码、动态库的文件格式。类似于Windows上的PE格式(Protable Executable),Linux上的ELF格式(Executable and Linking Format)。Mach-O作为a.out格式的替代,提供了更强的扩展性。

Mach-O 文件类型

16206262137187.jpg

属于Mach-O格式的常见文件:

  • 目标文件 *.o
  • 库文件
    • *.a
    • *.dylib
    • *.framework
  • 可执行文件
  • dyld
  • *.dsym

Mach-O 文件结构

16204596529344.jpg

由上图可以看出,Mach-O主要分三大块:

  • Header:保存Mach-O的一些基本信息,包括平台、文件类型、指令数、指令总大小、dyld标记Flags等。
  • Load commands:加载Mach-O文件时会使用这部分数据来确定内存分布,对系统内核加载器和动态连接器起指导作用。
  • Data:每个Segment的具体数据保存在这里,包含具体的代码、数据等。

Header

Header的数据结构如下:

/*
 * The 32-bit mach header appears at the very beginning of the object file for
 * 32-bit architectures.
 */
struct mach_header {
    uint32_t    magic;      /* mach magic number identifier */
    cpu_type_t  cputype;    /* cpu specifier */
    cpu_subtype_t   cpusubtype; /* machine specifier */
    uint32_t    filetype;   /* type of file */
    uint32_t    ncmds;      /* number of load commands */
    uint32_t    sizeofcmds; /* the size of all the load commands */
    uint32_t    flags;      /* flags */
};

/* Constant for the magic field of the mach_header (32-bit architectures) */
#define MH_MAGIC    0xfeedface  /* the mach magic number */
#define MH_CIGAM    0xcefaedfe  /* NXSwapInt(MH_MAGIC) */

/*
 * The 64-bit mach header appears at the very beginning of object files for
 * 64-bit architectures.
 */
struct mach_header_64 {
    uint32_t    magic;      /* mach magic number identifier */
    cpu_type_t  cputype;    /* cpu specifier */
    cpu_subtype_t   cpusubtype; /* machine specifier */
    uint32_t    filetype;   /* type of file */
    uint32_t    ncmds;      /* number of load commands */
    uint32_t    sizeofcmds; /* the size of all the load commands */
    uint32_t    flags;      /* flags */
    uint32_t    reserved;   /* reserved */
};

/* Constant for the magic field of the mach_header_64 (64-bit architectures) */
#define MH_MAGIC_64 0xfeedfacf /* the 64-bit mach magic number */
#define MH_CIGAM_64 0xcffaedfe /* NXSwapInt(MH_MAGIC_64) */

各变量释义:

名称 定义
magic Mach-O魔数。如:
FAT:0xcafebabe
ARMv7:0xfeedface
ARM64:0xfeedfacf
cputype、cpusubtype CPU架构及子版本
filetype 文件类型,共有11种宏定义类型,如:
MH_EXECUTABLE(可执行二进制文件)
MH_OBJECT(目标文件)
MH_DYLIB(动态库)
ncmds 加载命令的数量
sizeofcmds 所有加载命令的大小
flags dyld加载需要的一些标记,有28种宏定义,具体看源码,其中MH_PIE表示启用ASLR地址空间布局随机化
reserved 64位保留字段

如何查看Mach-O文件的header信息

  • 使用objdump指令
Mac ~/testDemo.app objdump --macho -private-header testDemo
Mach header
      magic cputype cpusubtype  caps    filetype ncmds sizeofcmds      flags
MH_MAGIC_64   ARM64        ALL  0x00     EXECUTE    23       2888   NOUNDEFS DYLDLINK TWOLEVEL PIE
  • 使用otools指令
Mac ~/testDemo.app otool -h testDemo
testDemo:
Mach header
      magic  cputype cpusubtype  caps    filetype ncmds sizeofcmds      flags
 0xfeedfacf 16777228          0  0x00           2    23       2888 0x00200085
  • 使用MachOView查看
16206293716647.jpg

Load Commands

数据结构如下:

/*
 * The load commands directly follow the mach_header.  The total size of all
 * of the commands is given by the sizeofcmds field in the mach_header.  All
 * load commands must have as their first two fields cmd and cmdsize.  The cmd
 * field is filled in with a constant for that command type.  Each command type
 * has a structure specifically for it.  The cmdsize field is the size in bytes
 * of the particular load command structure plus anything that follows it that
 * is a part of the load command (i.e. section structures, strings, etc.).  To
 * advance to the next load command the cmdsize can be added to the offset or
 * pointer of the current load command.  The cmdsize for 32-bit architectures
 * MUST be a multiple of 4 bytes and for 64-bit architectures MUST be a multiple
 * of 8 bytes (these are forever the maximum alignment of any load commands).
 * The padded bytes must be zero.  All tables in the object file must also
 * follow these rules so the file can be memory mapped.  Otherwise the pointers
 * to these tables will not work well or at all on some machines.  With all
 * padding zeroed like objects will compare byte for byte.
 */
struct load_command {
    uint32_t cmd;       /* type of load command */
    uint32_t cmdsize;   /* total size of command in bytes */
};

Load Commands的数目和总大小已经在Header中记录,见ncmdssizeofcmds。所有加载指令都是以cmd、cmdsize开头,cmd字段表示该指令的类型,cmdsize字段以字节为单位,主要记录偏移量,以便于Load Commands指针查找下一条加载指令。32位架构的cmdsize是以4字节的倍数,64位结构的cmdsize是以8字节的倍数,不够用0填充。

Load Commands中常见的加载指令:

  • LC_SEGMENT_64:定义一段(Segment),加载后被映射到进程的内存空间中,包括里面的节(Section)
  • LC_DYLD_INFO_ONLY:记录有关链接的信息,包括在__LINKEDIT中动态链接的相关信息的具体偏移与大小(重定位,绑定,弱绑定,懒加载绑定,导出信息等),ONLY表示该指令是程序运行所必需的。
  • LC_SYMTAB:定义符号表和字符串表,链接文件时被dyld使用,也用于调试器映射符号到源文件。符号表定义的本地符号仅用于调试,而已定义和未定义的external符号被链接器使用
  • LC_DYSYMTAB:将符号表中给出符号的额外信息提供给dyld
  • LC_LOAD_DYLINKER:dyld的默认路径
  • LC_UUID:Mach-O唯一ID
  • LC_VERSION_MIN_IPHONES:系统要求的最低版本
  • LC_SOURCE_VERSION:构建二进制文件的源代码版本号
  • LC_MAIN:应用程序入口,dyld的_main函数获取该地址,然后跳转
  • LC_ENCRYPTION_INFO_64:文件加密标志,加密内容偏移和大小
  • LC_LOAD_DYLIB:依赖的动态库,含动态库名,版本号等信息
  • LC_RPATH:@rpath搜索路径
  • LC_DATA_IN_CODE:定义在代码段内的非指令的表
  • LC_CODE_SIGNATURE:代码签名信息

如何查看Mach-O文件的Load Command信息

  • 使用objdump指令
objdump --macho --private-headers 【Mach-O路径】
16204683422789.jpg
  • 使用otool指令
 Mac ~/testDemo.app otool -l testDemo
16206339480322.jpg
  • 使用MachOView查看
16206336854994.jpg

Data

由多个二进制组成,逐一排列,包含__TEXT代码、__DATA代码、符号表,存储实际的代码和数据

数据结构如下:

struct section_64 { /* for 64-bit architectures */
    char        sectname[16];   /* 节名 */
    char        segname[16];    /* 所属段名 */
    uint64_t    addr;       /* 映射到虚拟地址的偏移 */
    uint64_t    size;       /* 节的大小 */
    uint32_t    offset;     /* 节在当前架构文件中的偏移 */
    uint32_t    align;      /* 节的字节对齐大小n,2^n */
    uint32_t    reloff;     /* 重定位入口的文件偏移 */
    uint32_t    nreloc;     /* 重定位入口个数 */
    uint32_t    flags;      /* 节的类型和属性*/
    uint32_t    reserved1;  /* reserved (for offset or index) */
    uint32_t    reserved2;  /* reserved (for count or sizeof) */
    uint32_t    reserved3;  /* 保留位,以上两同理 */
};

Section节已经是最小的分类了,Data的大部分数据内容集中在__TEXT、__DATA这两段中。

__TEXT节 含义
__text 程序可执行代码区域
__stubs 间接符号存根,用于跳转到懒加载指针表
__stubs_helper 懒加载符号加载辅助函数
__cstring 只读的C字符串,包含OC的部分字符串和属性名
__DATA节 含义
__nl_symbol_ptr 非懒加载指针表,dyld加载时立即绑定值
__la_symbol_ptr 懒加载指针表,第1次调用才绑定值
__got 非懒加载全局指针表
__mod_init_func constructor函数
__cfstring OC字符串

Mach-O 学习使用过程中用到的指令

file命令

  • 功能:用于辨识文件类型

  • 语法:

file [-bcLvz][-f <名称文件>][-m <魔法数字文件>...][文件或目录...]
  • 参数:

    • -b:列出辨识结果时,不显示文件名称
    • -c:详细显示指令执行过程,便于排错或分析程序执行的情形
    • -f<名称文件>:指定名称文件,其内容有一个或多个文件名称时,让file依序辨识这些文件,格式为每列一个文件名称
    • -F:使用指定分隔符号替换输出文件名后的默认的":"分隔符
    • -L:直接显示符号连接所指向的文件的类别
    • -m<魔法数字文件>:指定魔法数字文件
    • -v:显示版本信息
    • -z:尝试去解读压缩文件的内容
    • -i:输出mime类型的字符串
    • \[文件或目录...\]:要确定类型的文件列表,多个文件之间使用空格分开,可以使用shell通配符匹配多个文件
  • 示例:

//不加参数
[root@localhost ~]# file install.log
install.log: UTF-8 Unicode text

//-b:列出辨识结果时,不显示文件名称
[root@localhost ~]# file -b install.log
UTF-8 Unicode text

//-i:输出mime类型的字符串
[root@localhost ~]# file -i install.log
install.log: text/plain; charset=utf-8

//-F:使用指定分隔符号替换输出文件名后的默认的":"分隔符
[root@localhost ~]# file -F "--" install.log
install.log-- ASCII text
[root@localhost ~]# file -F "++" install.log
install.log++ ASCII text

otool命令

  • 功能:用来查看可执行文件的Mach-O信息
  • 语法:
otool [-arch arch_type] [-fahlLDtdorSTMRIHGvVcXmqQjCP] [-mcpu=arg] [--version] <object file> ...
  • 参数:
-f print the fat headers
-a print the archive header
-h print the mach header
-l print the load commands
-L print shared libraries used
-D print shared library id name
-t print the text section (disassemble with -v)
-x print all text sections (disassemble with -v)
-p <routine name>  start dissassemble from routine name
-s <segname> <sectname> print contents of section
-d print the data section
-o print the Objective-C segment
-r print the relocation entries
-S print the table of contents of a library (obsolete)
-T print the table of contents of a dynamic shared library (obsolete)
-M print the module table of a dynamic shared library (obsolete)
-R print the reference table of a dynamic shared library (obsolete)
-I print the indirect symbol table
-H print the two-level hints table (obsolete)
-G print the data in code table
-v print verbosely (symbolically) when possible
-V print disassembled operands symbolically
-c print argument strings of a core file
-X print no leading addresses or headers
-m don't use archive(member) syntax
-B force Thumb disassembly (ARM objects only)
-q use llvm's disassembler (the default)
-Q use otool(1)'s disassembler
-mcpu=arg use `arg' as the cpu for disassembly
-j print opcode bytes
-P print the info plist section as strings
-C print linker optimization hints
--version print the version of /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/otool
  • 示例:

1、查看App所使用的动态库

Mac ~/testDemo.app otool -L testDemo
testDemo:
    /System/Library/Frameworks/Foundation.framework/Foundation (compatibility version 300.0.0, current version 1774.101.0)
    /usr/lib/libobjc.A.dylib (compatibility version 1.0.0, current version 228.0.0)
    /usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1292.60.1)
    /System/Library/Frameworks/CoreFoundation.framework/CoreFoundation (compatibility version 150.0.0, current version 1774.101.0)
    /System/Library/Frameworks/UIKit.framework/UIKit (compatibility version 1.0.0, current version 4006.0.0)

2、查看ipa是否已经砸壳

Mac ~/testDemo.app otool -l testDemo| grep crypt
     cryptoff 16384
    cryptsize 16384
      cryptid 0

其中:cryptid为0表示已经砸壳。cryptid为1表示未砸壳。

3、 查看Mach-O头部信息

Mac ~/testDemo.app otool -h testDemo
testDemo:
Mach header
      magic  cputype cpusubtype  caps    filetype ncmds sizeofcmds      flags
 0xfeedfacf 16777228          0  0x00           2    23       2888 0x00200085

通用二进制文件

通用二进制(Universal binary)是苹果公司提出的一种程序代码,使程序能以本地程序的形式运行在使用PowerPC或者英特尔微处理器(x86)的麦金塔电脑上,在同一个程序包中同时为两种架构提供最理想的性能。硬件方面,苹果电脑公司已经将其产品线上的所有麦金塔电脑在2006年内转为英特尔处理器,相对应的软件方面,苹果最早是在2005年世界开发者大会(WWDC)上就发布了通用二进制的内容来适应这种转换。 当程序在操作系统中运行后,将自动检测通用二进制代码,根据使引用的架构自动选择合适的代码来执行,实现无损的本地程序运行速度

  • 查看当前Mach-O的包含的架构信息
lipo -info 【MachO文件】

//示例
lipo -info WeChat
Architectures in the fat file: WeChat are: armv7 armv7s arm64
  • 当Mach-O包含多种架构时,可对其进行架构拆分
lipo [MachO文件] -thin 架构 -output 输出文件路径

//示例
lipo WeChat -thin arm64 -output WeChat_arm64
lipo -info WeChat_arm64
Architectures in the fat file: WeChat_arm64 are: arm64

lipo WeChat -thin armv7 -output WeChat_armv7
lipo -info WeChat_armv7
Architectures in the fat file: WeChat_armv7 are: armv7
  • 可对多种架构的Mach-O进行合并
lipo -create MachO1 MachO2 -output 输出文件路径

//示例
lipo -create WeChat_arm64 WeChat_armv7 -output WeChat_64_v7

lipo -info WeChat_64_v7
Architectures in the fat file: WeChat_64_v7 are: armv7 arm64

总结

  • Mach-O是一种文件格式,包含目标文件、可执行文件、静态库、动态库等,可用file命令来查看文件类型。
  • Mach-O文件结构包含三部分:
    • Header:用来快速确定该文件的CPU类型、文件类型
    • Load Commands:指示加载器如何设置并加载二进制数据
    • Data:存放代码、数据、字符串常量、类、方法等数据
  • file命令:用于辨识文件类型
  • otool命令:用来查看可执行文件的Mach-O信息
  • 通用二进制是集合多种架构,也称为”胖二进制“,可通过lipo命令进行架构的查看、拆分、合并等操作
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,717评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,501评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,311评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,417评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,500评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,538评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,557评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,310评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,759评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,065评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,233评论 1 343
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,909评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,548评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,172评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,420评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,103评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,098评论 2 352

推荐阅读更多精彩内容