iOS逆向实战--016:MachO

MachO文件概述

Mach-O其实是Mach Object文件格式的缩写,是macOS以及iOS上可执行文件的格式,类似于Windows上的PE格式(Portable Executable),Linux上的ELF格式(Executable and Linking Format

Mach-O文件格式

Mach-O是一种用于可执行文件、目标代码、动态库的文件格式。作为a.out格式的替代,Mach-O提供了更强的扩展性

属于MachO格式的常见文件

  • 目标文件.o
  • 库文件:.a.dylibFramework
  • 可执行文件
  • dyld
  • .dSYM

file命令

使用file命令,用来探测给定文件的类型

语法

file (选项) (参数)

选项

-b:列出辨识结果时,不显示文件名称;
-c:详细显示指令执行过程,便于排错或分析程序执行的情形;
-f<名称文件>:指定名称文件,其内容有一个或多个文件名称时,让file依序辨识这些文件,格式为每列一个文件名称;
-L:直接显示符号连接所指向的文件类别;
-m<魔法数字文件>:指定魔法数字文件;
-v:显示版本信息;
-z:尝试去解读压缩文件的内容。

参数

文件:要确定类型的文件列表,多个文件之间使用空格分开,可以使用Shell通配符匹配多个文件

案例1:

生成目标文件,查看文件格式

使用clang命令,将test.m生成目标文件

clang -c test.m -o test.o

使用file命令,查看目标文件的文件类型

file test.o
-------------------------
test.o: Mach-O 64-bit object x86_64

案例2:

生成可执行文件,查看文件格式

使用clang命令,将test.o生成可执行文件

clang test.o

使用file命令,查看可执行文件的文件类型

file a.out
-------------------------
a.out: Mach-O 64-bit executable x86_64

案例3:

使用clang命令,可以将目标文件生成可执行文件,也可以直接将.m源文件生成可执行文件

将目标文件生成test1可执行文件

clang -o test1 test.o

.m源码文件生成test2可执行文件

clang -o test2 test.m

不同方式生成的可执行文件,使用HASH验证它们的一致性

md5 a.out
md5 test1
md5 test2
-------------------------
MD5 (a.out) = e3e459acc7a9aecee970ed12a0c1368d
MD5 (test1) = e3e459acc7a9aecee970ed12a0c1368d
MD5 (test2) = e3e459acc7a9aecee970ed12a0c1368d

在源码没有变化的时候,使用clang编译出的二进制文件是一致的

案例4:

将多个.m源码文件,生成一个可执行文件

创建test1.m文件,写入以下代码:

#import 

int main(){
   return 1;
}

创建test2.m文件,写入以下代码:

#import 

int test2(){
   return 2;
}

将多个源码文件,生成一个可执行文件

clang -o test1 test1.m test2.m

案例5:

将多个目标文件,链接成一个可执行文件

使用clang命令,将test1.mtest2.m生成目标文件

clang -c test1.m test2.m

将多个目标文件,链接成一个可执行文件

clang -o test2 test2.o test1.o

不同方式生成的可执行文件,使用HASH验证它们的一致性

md5 test1
md5 test2
-------------------------
MD5 (test1) = 1c9ad179590712c5a2a8683af9173d6e
MD5 (test2) = 40f53096fe90fab7a531cee885e0b8f1

源码并没有发生变化,但两个可执行文件生成的HASH值不同。问题的本质,两次生成可执行文件时,编译源文件的顺序是不一样的

使用objdump命令,分别查看test1test2可执行文件的代码段

objdump --macho -d test1
-------------------------
test1:
(__TEXT,__text) section
_main:
100003f80: 55  pushq   %rbp
100003f81: 48 89 e5    movq    %rsp, %rbp
...
_test2:
100003fa0: 55  pushq   %rbp
100003fa1: 48 89 e5    movq    %rsp, %rbp
...
objdump --macho -d test2
-------------------------
test2:
(__TEXT,__text) section
_test2:
100003f90: 55  pushq   %rbp
100003f91: 48 89 e5    movq    %rsp, %rbp
...
_main:
100003fa0: 55  pushq   %rbp
100003fa1: 48 89 e5    movq    %rsp, %rbp
...

两个可执行文件的代码截然不同

可执行文件是一个或多个目标文件的集合,会受到文件编译顺序的影响。原理和Xcode中的Compile Sources一样

使用相同编译顺序,将多个目标文件,链接成一个可执行文件

clang -o test3 test1.o test2.o

使用HASH验证它们的一致性

md5 test1
md5 test3
-------------------------
MD5 (test1) = 1c9ad179590712c5a2a8683af9173d6e
MD5 (test3) = 1c9ad179590712c5a2a8683af9173d6e

多个目标文件,当源码和编译顺序相同,使用clang编译出的二进制文件是一致的

案例6:

查看.a的文件格式

file libAFNetworking.a
-------------------------
libAFNetworking.a: current ar archive

案例7:

查看.dylib的文件格式

file libAFNetworking.dylib
-------------------------
libAFNetworking.dylib: Mach-O 64-bit dynamically linked shared library x86_64

案例8:

查看dyld的文件格式

使用find命令,找到dyld文件路径

find /usr -name "dyld"
-------------------------
find: /usr/sbin/authserver: Permission denied
/usr/lib/dyld
/usr/share/file/magic/dyld

查看文件格式

file /usr/lib/dyld
-------------------------
/usr/lib/dyld: Mach-O universal binary with 2 architectures: [i386:Mach-O dynamic linker i386] [x86_64:Mach-O 64-bit dynamic linker x86_64]
/usr/lib/dyld (for architecture i386): Mach-O dynamic linker i386
/usr/lib/dyld (for architecture x86_64):   Mach-O 64-bit dynamic linker x86_64
  • 动态链接器

案例9:

查看.dSYM的文件格式

右键TestInject.app.dSYM文件,显示包内容

进入Contents/Resources/DWARF目录,找到TestInject文件

查看文件格式

file TestInject
-------------------------
TestInject: Mach-O 64-bit dSYM companion file x86_64
  • 二进制符号文件,常用于分析App的崩溃信息
可执行文件

项目中,在Build SettingsMach-O Type配置项,默认为Executable(可执行文件)

iOS11及以上系统中,项目生成的可执行文件为arm64单一架构

file WeChat
-------------------------
WeChat: Mach-O 64-bit executable arm64

使用iOS10.3系统编译项目

file WeChat
-------------------------
WeChat: Mach-O universal binary with 2 architectures: [arm_v7:Mach-O executable arm_v7] [arm64:Mach-O 64-bit executable arm64]
WeChat (for architecture armv7):   Mach-O executable arm_v7
WeChat (for architecture arm64):   Mach-O 64-bit executable arm64
  • 格式变为通用二进制文件,包含armv7arm64两种架构

项目中,Build Settings中包含架构的设置

  • Architectures:设置支持的架构
  • Build Active Architecture Only:只编译当前设备支持的架构,Debug模式默认YES
  • $(ARCHS_STANDARD)Xcode内置的环境变量,不同Xcode版本值不一样,当前版本下表示armv7 + arm64

案例1:

增加一个兼容架构

iOS系统下,还有一个armv7s架构,支持iPhone5iPhone5c

打开项目,在Build Settings中的Architectures配置项,选择other,增加armv7s

编译项目

file WeChat
-------------------------
WeChat: Mach-O universal binary with 3 architectures: [arm_v7:Mach-O executable arm_v7] [arm_v7s:Mach-O executable arm_v7s] [arm64:Mach-O 64-bit executable arm64]
WeChat (for architecture armv7):   Mach-O executable arm_v7
WeChat (for architecture armv7s):  Mach-O executable arm_v7s
WeChat (for architecture arm64):   Mach-O 64-bit executable arm64
  • 同时包含三种架构,armv7armv7sarm64
通用二进制文件

通用二进制文件(Universal Binary

  • 苹果公司提出的一种程序代码,能同时适用多种架构的二进制文件
  • 同一个程序包中同时为多种架构提供最理想的性能
  • 因为需要储存多种代码,通用二进制应用程序通常比单一平台二进制的程序要大
  • 由于两种架构有共通的非执行资源(代码以外的),所以并不会达到单一版本的两倍之多
  • 由于执行中只调用一部分代码,运行起来也不需要额外的内存
  • 也被称为“胖二进制文件”(Fat Binary

lipo命令

使用lipo -info,可以查看MachO文件包含的架构

lipo -info MachO文件

使用lipo -thin,拆分某种架构

lipo MachO文件 -thin 架构 -output 输出文件路径

使用lipo -create,合并多种架构

lipo -create MachO1 MachO2 -output 输出文件路径

案例1:

从通用二进制文件中拆分架构

查看可执行文件

lipo -info WeChat
-------------------------
Architectures in the fat file: WeChat are: armv7 armv7s arm64
  • WeChat中包含armv7armv7sarm64三种架构

使用lipo命令,拆分出arm64架构

lipo WeChat -thin arm64 -output WeChat_arm64

使用lipo命令,拆分出armv7架构

lipo WeChat -thin armv7 -output WeChat_armv7

案例2:

合并架构

使用lipo命令,将arm64armv7架构合并

lipo -create WeChat_arm64 WeChat_armv7 -output WeChat_64_v7

查看WeChat_64_v7可执行文件

lipo -info WeChat_64_v7
-------------------------
Architectures in the fat file: WeChat_64_v7 are: armv7 arm64
  • 包含armv7arm64两种架构
MachO文件结构

因为MachO文件本身是一种文件格式,所以我们一定需要了解其文件内部结构

Mach-O文件结构

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

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

Load Commands:一张包含很多内容的表

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

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

  • 包含Segement的具体数据

通用二进制文件,最上面是Fat Header,包含自身信息和架构信息。多架构之间,对应多套Mach-O的文件结构

otool命令

用来查看可执行文件的MachO信息

Usage: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/otool [-arch arch_type] [-fahlLDtdorSTMRIHGvVcXmqQjCP] [-mcpu=arg] [--version]  ...
  -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   start dissassemble from routine name
  -s   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:

查看可执行文件的Header信息

otool -f WeChat

案例2:

查看App所使用的动态库

otool -L WeChat

案例3:

查看ipa是否已砸壳

otool -l WeChat | grep cryptid -A 1 -B 5
  • cryptid0,表示已砸壳
MachOView

使用MachOView打开WeChat可执行文件

  • 包含一个Fat Header和三个架构的文件结构
  • cputypeCPU类型,比如ARM
  • cpusubtypeCPU的具体类型,arm64armv7
  • offset:偏移值
  • size:当前架构大小
  • align:对齐方式。16384,即:16字节

armv7架构的偏移值为16384,大小为79104armv7s架构的偏移值等于v7架构的偏移值 + 大小,即95488,由于16字节对齐,即98304arm64架构同理向后排列

Header

Header的数据结构

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位
  • cputypeCPU类型,比如ARM
  • cpusubtypeCPU的具体类型,arm64armv7
  • filetype:文件类型,例如:可执行文件
  • ncmdsLoadCommands的条数
  • sizeofcmdsLoadCommands的大小
  • flags:标志位,标识二进制文件支持的功能。主要是和系统加载、链接有关
  • reserved:占位符
Load Commands

Load Commands的数据结构

__PAGEZERO:空指针陷阱段,映射到虚拟内存空间的第一页,用于捕捉对NULL指针的引用

  • VM Address:虚拟内存地址
  • VM Size:虚拟内存大小
  • File Offset:数据在文件中偏移量
  • File Size:数据在文件中的大小

__PAGEZERO:自身不占大小,仅占用4G虚拟内存大小。用于区分不同架构的系统,在arm64架构下为0X100000000,在非arm64架构下为0X4000

可以使用size命令,查看MachO的内存分布

size -l -m -x WeChat

__TEXT:代码段,包含执行代码及其他只读数据。该段为只读数据段

  • File Offset0,表示文件从代码段开始

__DATA:数据段,包括全局变量等。该段可读、可写

__LINKEDIT:包含需要被动态链接器使用的信息

  • 包括符号表、字符串表、重定位项表、签名等。该段和__PAGEZERO一样,末尾没有额外的Section信息。File Offset + File Size即为MachO文件末尾,等于文件的大小

LC_DYLD_INFO_ONLY:动态链接相关信息

  • Rebase:用于重定向。Rebase Info Offset表示函数开始位置。Rebase Info Size表示从开始位置往后多少字节的数据需要重定向。当启动App加载MachO时,从Offset函数开始位置+ ASLR,后面的数据同理,一直加到Size大小的位置结束
  • Binding:绑定数据,外部符号需要将地址进行绑定。从开始位置,往后多少字节的数据需要绑定
  • Weak Binding:弱绑定数据
  • Lazy Binding:懒绑定数据,由dyld记录下来,使用的时候进行绑定
  • Export:对外开放的函数,提供给外部调用

LC_SYMTAB:符号表地址

LC_DYSYMTAB:动态符号表地址

LC_LOAD_DYLINKER:使用何种动态加载器,iOS系统上为dyld

LC_UUIDMachO文件的唯一标识

  • crash文件中也会有,用来检查crash文件与dYSM文件是否匹配

LC_VERSION_MIN_MACOSX:支持最低的系统版本

LC_SOURCE_VERSION:源代码版本

LC_MAIN:设置程序主线程的入口地址和栈大小

LC_ENCRYPTION_INFO_64:加密信息

LC_LOAD_DYLIB (XXX):依赖库的路径,包含三方库

LC_RPATH@rpath路径

LC_FUNCTION_STARTS:函数起始地址表

LC_DATA_IN_CODE: 定义在代码段内的非指令的表

LC_CODE_SIGNATURE: 代码签名

Data

Load Commands以下,都属于数据部分,包含__TEXT__DATA__LINKEDIT

__TEXT

Section 含义
__text 代码实现,文件从代码段开始
__stubs 符号桩,本质上就是一段会跳转到Lazy Binding表中,对应项指针指向的地址的代码
__stubs_helper 辅助函数,上述Lazy Binding表中没有找到符号地址都指向这里
__objc_methname OC方法名称
__objc_classname OC类名
__objc_methtype OC方法类型
__cstring cstring字符串常量
__unwid_info 用于存储异常情况信息

__DATA

Section 含义
__got Lazy Binding的符号表
__la_symbol_prt Lazy Binding符号表,每个表项中的符号一开始指向__stubs_helper
__cfstring CoreFoundation String
__objc_classlist OC类列表
__objc_protollist OC协议列表
__objc_imageinfo OC镜像信息
__objc_const OC类信息、方法列表、属性列表、变量列表
__objc_selfrefs OC类实例自引用(self
__objc_classfrefs OC类类自引用
__objc_superrefs OC类超类引用(super
__objc_ivar OC属性
__objc_data OCISA
_data 存放数据
总结

MachO概述:

  • MachO属于一种文件格式
  • 包含目标文件、可执行文件、静态库、动态库、dyld.dSYM
  • file命令,用来探测给定文件的类型

通用二进制文件

  • 集合多种架构,也被称为“胖二进制文件”
  • lipo命令,通过-thin拆分架构,通过-create合并架构

MachO文件结构

  • Header:用于快速确定该文件的CPU类型、文件类型
  • Load Commands:指示加载器如何设置并加载二进制数据
  • Data:存放代码、数据、字符串常量、类、方法等数据

你可能感兴趣的:(iOS逆向实战--016:MachO)