什么是Mach-O?可执行文件是怎么加载的?
一、APP分析
1.1. APP从开发到安装流程
Objective-C或Swift代码、图片/音视频等文件资源、info.plist配置、XIB、Storyboard等构成了APP的源代码。
通过编译、链接、签名操作后,代码会构建成一个
***.app
文件。而这个app文件中包含开发所用的到APP资源以及代码,代码经过编译后会压缩在同名文件中,这个文件格式就是Mach-O,是一个可执行文件。通过对
***.app
压缩(文件放入Payload
文件夹中,然后再压缩)得到最终的安装包(压缩后需要把后缀名修改为ipa
)。一般情况下都是直接把
***.ipa
上传到AppStore进行审核,审核通过后用户就可以到AppStore中进行正常下载。APP也通过PP助手、iFunBox、Xcode等工具进行安装。
1.2. 逆向APP的思路
界面分析
- 行为:查看界面相关元素之间的依赖关系
- 工具:Cycript(代码层级结构)、Reveal(视图层级可视化结构)
代码分析
- 行为:对Mach-O文件的静态分析
- 工具:MachOView(MachO文件分析工具)、class-dump(MachO头文件导出命令)、Hopper Disassembler(MachO文件反编译成代码)、ida
动态调试
- 行为:对运行中的APP进行代码调试
- 工具:debugserver、LLDB
代码编写
- 行为:注入代码到APP中
- 必要时还可能需要重新签名、打包ipa
1.3. class-dump
顾名思义,它的作用就是把Mach-O文件的class信息给dump出来(把类信息给导出来),生成对应的.h
头文件。
官方地址:http://stevenygard.com/projects/class-dump/
下载完工具包后将class-dump文件复制到Mac的/usr/local/bin
目录,这样在终端就能识别class-dump命令了。
注意:
usr/bin
系统已经不给用户写入权限了,后加入的指令都是放到/usr/local/bin
目录中的。
常用格式:
1 | class-dump -H Mach-O文件路径 -o 头文件存放目录 |
-H
:表示要生成头文件。
-o
:用于制定头文件的存放目录。
1.4. 代码的编译过程
OC/Swift语言通过编译生成汇编代码,汇编再编译后就是生成机器指令(Mach-O文件)。
在同一种架构平台下,每一条汇编指令都有与之对应的唯一的机器指令。所以机器指令(即Mach-O文件)可以反编译出对应的汇编代码。
但是汇编代码是不能反编译成OC/Swift代码的,因为不同的源代码编译成的汇编代码可能是一样的。
1.5. Hopper Disassmbler
Hopper Disassmbler能够将Mach-O文件的机器语言代码反编译成汇编代码、OC伪代码或者Swift伪代码。
二、动态库
2.1. 动态库共享缓存
从iOS3.1开始,为了提高性能,绝大部分的系统动态库文件(比如UIKit、MapKit、CoreGraphics等)都打包存放到了一个缓存文件中(dyld shared cache)。
缓存文件路径(iPhone):/System/Library/Caches/com.apple.dyld/dyld_shared_cache_armX
扩展:以前是动态库的Mach-O文件在
/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk/System/Library/Frameworks/UIKit.framework
里面可以看到,现在已经没有了。
dyld_shared_cache_armX
的X
代表ARM处理器指令集架构(所有指令集原则上都是向下兼容的):
v6
1
2iPhone、iPhone3G
iPod Touch、iPod Touch2v7
1
2
3
4iPhone3GS、iPhone4、iPhone4S
iPad、iPad2、iPad3(The New iPad)
iPad mini
iPod Touch3G、iPod Touch4、iPod Touch5v7s
1
2iPhone5、iPhone5C
iPad4arm64
1
2
3
4
5iPhone5S、iPhone6、iPhone6 Plus、iPhone6S、iPhone6S Plus
iPhoneSE、iPhone7、iPhone7 Plus、iPhone8、iPhone8 Plus、iPhoneX
iPad5、iPad Air、iPad Air2、iPad Pro、iPad Pro2
iPad mini with Retina display、iPad mini3、iPad mini4
iPod Touch6
动态库共享缓存一个非常明显的好处是节省内存。现在的ida、Hopper反编译工具都可以识别动态库共享缓存。
2.2. 动态库的加载
在Mac/iOS中,动态库是使用了/usr/lib/dyld
程序来加载动态库。
dyld,有多种叫法,最常见的是:
- dynamic link editor,动态链接编辑器
- dynamic loader,动态加载器
dyld源码:https://opensource.apple.com/tarballs/dyld/
2.3. 从动态库共享缓存抽取动态库
动态库共享缓存文件中存放的是所有动态库,每次分析都需要加载很久(大概700MB左右),我们可以尝试把动态库分离出来,然后按需进行分析。
可以使用dyld源码中的launch-cache/dsc_extractor.cpp
。
将
#if 0
前面的代码删除(包括#if 0
),把最后面的#endif也
删掉。编译
dsc_extractor.cpp
1
clang++ -o dsc_extractor dsc_extractor.cpp
使用
dsc_extractor
1
./dsc_extractor 动态库共享缓存文件的路径 用于存放抽取结果的文件夹
上面命令执行完成后,用于存放抽取结果的文件夹里就是动态库共享缓存中的所有动态库。
注意:动态库共享缓存文件是在iPhone手机里(参考上面2.1),操作的时候需要把文件拷贝到电脑中。
三、Mach-O
Mach-O是Mach object的缩写,是Mac/iOS上用于存储程序、库的标准格式。
属于Mach-O格式的文件类型有:
可以在xnu源码中(https://opensource.apple.com/tarballs/xnu/
),查看到Mach-O格式的详细定义(如下路径):
EXTERNAL_HEADERS/mach-o/fat.h
EXTERNAL_HEADERS/mach-o/loader.h
3.1. 常见的Mach-O文件类型
MH_OBJECT
- 目标文件(.o)
- 静态库文件(.a),静态库其实就是N个.o合并在一起
MH_EXECUTE:可执行文件
- .app/xx
MH_DYLIB:动态库文件
- .dylib
- .framework/xx
MH_DYLINKER:动态链接编辑器
- /usr/lib/dyld
MH_DSYM:存储着二进制文件符号信息的文件
- .dSYM/Contents/Resources/DWARF/xx(常用于分析APP的崩溃信息)
在Xcode中查看target的Mach-O类型:
3.2. Mach-O的基本结构
一个Mach-O文件包含3个主要区域:
Header
- 文件类型、目标架构类型等
Load commands
- 描述文件在虚拟内存中的逻辑结构、布局
Raw segment data
- 在Load commands中定义的Segment的原始数据
3.3. 窥探Mach-O的结构
3.3.1. 命令行工具
file:查看Mach-O的文件类型
file 文件路径
otool:查看Mach-O特定部分和段的内容
lipo:常用于多架构Mach-O文件的处理
- 查看架构信息:
lipo -info 文件路径
- 导出某种特定架构:
lipo 文件路径 -thin 架构类型 -output 输出文件路径
- 合并多种架构:
lipo 文件路径1 文件路径2 -output 输出文件路径
- 查看架构信息:
3.3.2. GUI工具
MachOView:https://github.com/gdbinit/MachOView
3.4. Universal Binary
通用二进制文件,同时适用于多种架构的二进制文件,包含了多种不同架构的独立的二进制文件。
在Xcode中(Target -> Build Settings -> Architectures)就可以修改应用或静态库支持哪几种架构(x86_64、armv6、armv7、armv7s、arm64)。
Architectures:通用的一些架构值。默认值是$(ARCHS_STANDARD)
,这个默认值是Xcode内置的环境变量,不同版本的Xcode可能其值也不同。可以添加和删除架构类型。
Valid Architectures:当前应用支持的架构。可以添加和删除架构类型。
最终支持哪几种架构是根据Architectures和Valid Architectures的交集确定的。
通用架构的特点:
因为需要储存多种架构的代码,通用二进制文件通常比单一平台二进制的程序要大。
由于两种架构有共同的一些资源,所以并不会达到单一版本的两倍之多。
由于执行过程中,只调用一部分代码,运行起来也不需要额外的内存。
因为文件比原来的要大,也被称为“胖二进制文件”(Fat Binary)。
3.5. dyld和Mach-O
dyld用于加载以下类型的Mach-O文件:
1 | MH_EXECUTE |
APP的可执行文件、动态库都是由dyld负责加载的。
四、扩展
Drawin、xnu、Mach、BSD之间的关系如下所示: