我们编译出的.app 文件其实也是Mach O的一种,我们在对Runtime进行分析之前总是绕不开Mach O 和 dyld 这两大块内容,所以在这个系列开始之前对带大家了解下Mach O 和 dyld。

下图是Mach O文件的组成,主要分成三大部分,我们接下来以otool命令行工具来输出这些数据的内容:

Header: 头部区域;
LoadCommands: 加载命令,由多个Segments command组成;
Data:Segments的具体数据;

我们一一看下这些部分的组成数据结构以及功能:

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 */
};
  • magic: 从这个字段上可以看出当前镜像的平台是64位还是32位的:
    由于这里只研究64位的,所以这个值为MH_MAGIC_64
  • cputype: cpu类型 (具体可查看mach/machine.h
  • cpusubtype: cpu子类型 (具体可查看mach/machine.h
  • filetype : 文件类型,是可执行文件还是动态链接文件
  • ncmds : load command 的数量
  • sizeofcmds : load command 占用的空间大小
  • flags : dyld 加载的标志

下面是otool工具打印出的某个应用的Header部分:

$ otool -hV IDLFundation
Mach header
magic cputype cpusubtype caps filetype ncmds sizeofcmds flags
MH_MAGIC_64 X86_64 ALL 0x00 EXECUTE 85 8400 NOUNDEFS DYLDLINK TWOLEVEL WEAK_DEFINES BINDS_TO_WEAK PIE

这里简单提下几种常见的Mach-O类型,也就是上面提到的filetype:

  • MH_EXECUTE
    可单独执行的可执行文件,这种文件一般有main方法作为入口。

  • MH_DYLINKER
    动态链接器,用于加载动态库 在iOS 中就是/usr/lib/dyld,/usr/lib/dyld仅会处理MH_BUNDLE, MH_DYLIB,MH_EXECUTE类型文件。

  • MH_OBJECT
    .m,.c等文件编译出来的目标文件,文件后缀是.o。Static Library类型产出是MH_OBJECT类型文件的archieve。

  • MH_FVMLIB MH_CORE MH_PRELOAD MH_DYLIB
    动态库文件,包括.dylib文件,动态framework;对应Dynamic Library类型产出。

  • MH_BUNDLE
    独立的二进制文件,不支持在项目中添加Link Binary使用。可以在Copy Bundle Resources中作为资源添加。 通过NSBundle load的方式加载;对应Bundle类型产出。典型的例子就是/System/Library/AccessibilityBundles目录的.axbundle后缀的文件。

  • MH_DSYM
    存储二进制文件符号信息的文件,用于Debug分析;

Load Commands

load commands 紧随着mach_header,它的总大小以及命令数都存储在mach_header中。

struct load_command {
uint32_t cmd; /* type of load command */
uint32_t cmdsize; /* total size of command in bytes */
};

load_command结构比较简单就两个字段cmd和cmdsize,cmd存放的是命令的类型,每个类型有一个专门的结构体。

这些加载命令在Mach-O文件加载解析时,用于指导如何加载对应的二进制数据段,下面列出了一些加载命令类型的常量,更多的加载命令类型可以查看loader.h

#define	LC_SEGMENT	     0x1	/* segment of this file to be mapped */
#define LC_SEGMENT_64 0x19 /* 64-bit segment of this file to be mapped */
#define LC_SYMTAB 0x2 /* link-edit stab symbol table info */
#define LC_SYMSEG 0x3 /* link-edit gdb symbol table info (obsolete) */
#define LC_THREAD 0x4 /* thread */
#define LC_UNIXTHREAD 0x5 /* unix thread (includes a stack) */
#define LC_LOADFVMLIB 0x6 /* load a specified fixed VM shared library */
#define LC_IDFVMLIB 0x7 /* fixed VM shared library identification */
#define LC_IDENT 0x8 /* object identification info (obsolete) */
#define LC_FVMFILE 0x9 /* fixed VM file inclusion (internal use) */
#define LC_PREPAGE 0xa /* prepage command (internal use) */
#define LC_DYSYMTAB 0xb /* dynamic link-edit symbol table info */
#define LC_LOAD_DYLIB 0xc /* load a dynamically linked shared library */
#define LC_ID_DYLIB 0xd /* dynamically linked shared lib ident */
#define LC_LOAD_DYLINKER 0xe /* load a dynamic linker */
#define LC_ID_DYLINKER 0xf /* dynamic linker identification */
#define LC_PREBOUND_DYLIB 0x10 /* modules prebound for a dynamically */
/* linked shared library */
#define LC_ROUTINES 0x11 /* image routines */
#define LC_SUB_FRAMEWORK 0x12 /* sub framework */
#define LC_SUB_UMBRELLA 0x13 /* sub umbrella */
#define LC_SUB_CLIENT 0x14 /* sub client */
#define LC_SUB_LIBRARY 0x15 /* sub library */
#define LC_TWOLEVEL_HINTS 0x16 /* two-level namespace lookup hints */
#define LC_PREBIND_CKSUM 0x17 /* prebind checksum */
#define LC_LOAD_WEAK_DYLIB (0x18 | LC_REQ_DYLD)

#define LC_ROUTINES_64 0x1a /* 64-bit image routines */
#define LC_UUID 0x1b /* the uuid */
#define LC_RPATH (0x1c | LC_REQ_DYLD) /* runpath additions */
#define LC_CODE_SIGNATURE 0x1d /* local of code signature */
#define LC_SEGMENT_SPLIT_INFO 0x1e /* local of info to split segments */
#define LC_REEXPORT_DYLIB (0x1f | LC_REQ_DYLD) /* load and re-export dylib */

下面是通过otool命令来输出的load commands:

otool -l IDLFundation
Load command 20
cmd LC_LOAD_DYLIB
cmdsize 80
name @rpath/FLAnimatedImage.framework/FLAnimatedImage (offset 24)
time stamp 2 Thu Jan 1 08:00:02 1970
current version 1.0.0
compatibility version 1.0.0

这里比较重要的是加载segments的命令:

它用于表示当前文件的一部分将会被映射到64位任务的地址空间

struct segment_command_64 { /* for 64-bit architectures */
uint32_t cmd; /* 对于64位系统来说这个值恒为 LC_SEGMENT_64 */
uint32_t cmdsize; /* 用于表示这个命令的大小 */
char segname[16]; /* 该segment的名字 */
uint64_t vmaddr; /* 这个segment的虚拟内存地址空间 */
uint64_t vmsize; /* 这个segment的虚拟内存大小 */
uint64_t fileoff; /* 这个segment位于这个文件的偏移 */
uint64_t filesize; /* 文件的大小 */
vm_prot_t maxprot; /* 最大虚拟内存保护 */
vm_prot_t initprot; /* 初始的内存保护 */
uint32_t nsects; /* segments的session数 */
uint32_t flags; /* flags */
};

通过这个命令会将该段对应的文件从offset处加载 file size大小到虚拟内存 vmaddr处。

Segments & Session

Mach O文件中有一系列的Segment,每个segment又是由一系列的session构成。我们看下Mach O文件中都有哪些Segment:

  • __TEXT :代码段/只读数据段
  • __DATA :数据段
  • __PAGEZERO: 空指针陷阱段,用于捕获对NULL指针的引用
  • __LINKEDIT: 动态链接库使用的原始数据,比如符号,字符串,重定位表条目等等, 该段可读写。
  • __OBJC: 包括会被Runtime使用到的一些数据

可以用如下命令打印出各个段的数据

otool -v -s __DATA __objc_selrefs IDLFundation

下面是****__TEXT以及__DATA****segment下的重要section:

__TEXT 部分

__text	程序代码
__stubs 用于动态链接的存根
__stub_helper 用于动态链接的存根
__const 程序中const关键字修饰的常量变量
__objc_methname objc方法名
__cstring 程序中硬编码的ANSI的字符串
__objc_classname objc类名
__objc_methtype objc方法类型
__gcc_except_tab 异常处理相关
__ustring unicode字符串
__unwind_info 异常处理
__eh_frame 异常处理

__DATA 部分

__nl_symbol_ptr	动态符号链接相关,指针数组
__got 全局偏移表, Global Offset Table
__la_symbol_ptr 动态符号链接相关,也是指针数组,通过dyld_stub_binder辅助链接
__mod_init_func 初始化的全局函数地址,会在main之前被调用
__const const修饰的常量
__cstring 程序中硬编码的ANSI的字符串
__cfstring CF用到的字符串
__objc_classlist objc类列表
__objc_nlclslist objcload方法列表
__objc_catlist objc category列表
__objc_protolist objc protocol列表
__objc_imageinfo 镜像信息
__objc_const objc的常量
__objc_selrefs objc引用的SEL列表
__objc_protorefs objc引用的protocol列表
__objc_classrefs objc引用的class列表
__objc_superrefs objc父类的引用列表
__objc_ivar objcivar信息
__objc_data class信息
__bss 未初始化的静态变量区
__data 初始化的可变变量

关于Mach O结构部分先介绍到这,在博客的末尾简单列下:

-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

大家可以通过任何途径获取到你想研究应用到ipa包,然后把后缀名改为zip,解压缩得到Payload文件夹,里面就是你的APP,打开终端,直接cd到你的xxxx.app目录下。具体做法,输入cd,然后把xxxx.app直接拖到终端里打个回车。然后就可以通过otool命令来进行研究它了。

还有一个otool的扩展版本jtool大家有兴趣可以看下

Contents
  1. 1. Header
  2. 2. Load Commands
  3. 3. Segments & Session