什么是Mach-O文件?
Mach-O是Mach object的缩写,是Mac\iOS上用来存储程序、库的标准格式
Mach-O文件类型
- 可以点击下载xnu源码,在源码中的<font color=red>EXTERNAL_HEADERS/mach-o/loader.h
</font>文件中,我们可以看到Mach-O格式的所有文件类型
xun是苹果MacOS\iOS等操作系统的内核
- 常见的Mach-O文件类型
Mach-O类型 | 示例文件 |
---|---|
MH_OBJECT | 目标文件(.o) 静态库文件(.a)注: |
MH_EXECUTE | 可执行文件,存放App的所有源码信息,在.app/xx |
MH_DYLIB | 动态库文件.dylib 或者 .framework/xx |
MH_DYLINKER | 动态链接编辑器,也就是之前所说的/usr/lib/dyld工具 |
MH_DSYM | 此文件中存储这二进制文件符号信息,在开发中,我们经常使用此文件来分析App的崩溃信息 |
Mach-O的基本结构
可以点击官网查看Mach-O的介绍。
Mach-O组成
Mach-O由3个部分组成
- Header,包含文件类型、目标架构类型等等
- Load commands,是描述文件在虚拟内存中的逻辑结构和布局,相当于一份目录索引
- Raw segment data,在Load commands中所定义的Segment,在这里都能找到原始数据。
Raw segment data存放了所有的原始数据,而Load commands相当于Raw segment data的索引目录
窥探Mach-O的结构
常用工具
- 命令行工具,通过file命令查看Mach-O文件的基本信息
file 文件路径
- otool,查看Mach-O特定部分和段的内容
#查看Mach-O文件的header信息
otool -h 文件路径
#查看Mach-O文件的load commands信息
otool -l 文件路径
更多使用方法,终端输入otool -help查看
- lipo,用来处理多架构Mach-O文件,常用命令如下
#查看架构信息
lipo -info 文件路径
#导出某种类型的架构
lipo 文件路径 -thin 架构类型 -output 输出文件路径
#合并多种架构类型
lipo 文件路径1 文件路径2 -output 输出文件路径
- GUI工具,MachOView的使用
- 点击查看MachOView官网
- 直接点击下载MachOView.dmg,提取码: 62jk
Universal Binary(通用二进制文件)
通用二进制文件就是同时适用于多种架构的二进制文件,它包含了多种不同架构的独立的二进制文件,它有以下特点
- 因为需要存储多种架构的代码,所以通用二进制文件要比单架构二进制文件要大
- 因为两种种架构之间可以共用一些资源,所以两种架构的通用二进制文件大小不会达到单一架构版本的两倍。
- 运行过程中只会调用其中的部分代码,所以运行起来不会占用额外的内存
- 通用二进制文件通常也被称为“胖二进制文件(Fat binary)”
dyld和Mach-O
dyld是iOS中用来加载可执行文件、动态库的工具,其实它本身也是一个Mach-O文件。
什么是dyld?
- dyld 动态加载器(又叫做动态链接编辑器)
- dyld的源码可以点击此处下载
dyld的作用。
dyld可以用来加载以下三种类型的Mach-O文件
- MH_EXECUTE
- MH_DYLIB
- MH_BUNDLE
通过查看dyld的源码可以看到加载文件时的类型校验
从编码到App安装到手机
想要了解Mach-O文件,首先要了解从编写代码,开发App到App打包并安装到手机上的整个过程。
- 首先我们编写完成代码之后,会通过LLVM编译器预处理我们的代码,比如将宏放在指定的位置
- 预处理结束之后,LLVM会对代码进行词法分析和语法分析,生成AST。AST是抽象语法树,主要用来进行快速遍历,实现静态代码检查的功能。
- AST会生成IR,IR是一种更加接近机器码的语言,通过IR可以生成不同平台的机器码。对于iOS平台,IR生成的可执行文件就是Mach-O.
- 然后通过链接器将符号和地址绑定在一起,并且将项目中的多个Mach-O文件合并成一个Mach-O文件。
- 最后通过签名等操作生成.app文件,然后对.app文件进行压缩就生成了我们可以安装的ipa包。
- 当然,ipa包的安装途径有两种:
- 通过开发者账号上传到App Store,然后在App Store上下载安装。
- 通过PP助手、iFunBox、Xcode等工具来安装
逆向App,我们需要做哪些工作?
初步了解了什么是Mach-O文件,以及App从开发到安装的过程,我们就可以来学习如何逆向一款App
- 界面分析
通过之前的学习,我们已经可以使用Cycript和Reveal对App的界面进行分析 - 代码分析
iOS开发中,所有的代码最后都会经过编译生成Mach-o文件,所以我们需要对Mach-O文件进行静态分析
静态分析的工具有MachOView、class-dump、Hopper Disassembler、ida等等,后面会一一学习
- 动态调试
除了静态分析,我们还需要运行目标App,对App进行动态调试
动态调试的工具有debugserver、LLDB等等
- 代码编写、注入
进行完界面分析、代码分析和动态调试之后,我们可以在特定位置注入我们自己写的代码,必要时可以重新签名并且打包ipa
调试工具
calss-dump
class-dump的作用就是把Mach-O文件的class信息给导出来,生成对应的.h头文件
- 可以点击官网下载class-dump工具包
- 下载完成之后将其中的class-dump可执行文件复制到Mac上的<font color=red>/usr/local/bin</font>目录中,这样在终端就能识别class-dump命令了
在Mac中,终端执行的所有指令都会去<font color=red>/usr/bin</font>目录和<font color=red>/usr/local/bin</font>目录下寻找
- class-dump的常用命令如下
# -H表示需要生成头文件 -o用于指定头文件的存放目录
class-dump -H Mach-O文件路径 -o 头文件存放目录
Hopper Disassmbler
Hopper Disassmbler可以将Mach-O文件的机器语言反编译成汇编代码、OC伪代码或者是Swift伪代码。最常使用的快捷键有
#找出哪里引用了这个方法
Shift + Option + X
下载地址:https://pan.baidu.com/s/1yP_VcBlQ2G-rWsRue3uSBg
静态库和动态库
在iOS开发中,有很多功能都是现成可用的,不关你的App在用,其它的App也在用,比如UIKit框架、GUI框架、I/O、网络等等。这些库都是通过链接器链接到Mach-O文件中的。
静态库
静态库是编译时链接的库,需要连接进入Mach-O文件中,如果需要更新就必须重新编译一次,无法做到动态加载和更新
动态库
动态库是运行时链接的库。
Mach-O是文件编译之后的产物,所以动态库并没有参与Mach-O文件的编译和链接。所以Mach-O文件中没有包含动态库的符号定义,也就是说这些符号会直接显示未定义,但是他们的名字和对应库的路径会被记录下来。在运行时通过dlopen和dlsym导入动态库时,会根据记录的路径找到对应的库,再通过记录的名字符号找到绑定的地址。
动态库共享缓存(dyld shared cache)
从iOS3.1开始,为了提高性能,绝大部分的系统动态库文件都打包存放到了一个缓存文件中(dyld shared cache),缓存路径是<font color=red>/System/Library/Caches/com.apple.dyld/dyld_shared_cache_armX</font>
dyld_shared_cache_armX里面的X代表ARM处理器指令集的架构
ARM指令集
ARM指令集(CPU指令的集合)有以下几种
ARM指令集 | 支持的设备 |
---|---|
armv6 | iPhone、iPhone3G iPod Touch、iPod Touch2 |
armv7 | iPhone3GS、iPhone4、iPhone4S iPad、iPad2、iPad3(The New iPad)、iPad mini iPod、Touch3G、iPod Touch4、iPod Touch5 |
armv7s | iPhone5、iPhone5C、iPad4 |
arm64 | iPhone5S、iPhone6、iPhone6 Plus、iPhone6S、iPhone6S Plus iPhoneSE、iPhone7、iPhone7Plus、iPhone8、iPhone8 Plus、iPhoneX iPad5、iPad Air、iPad Air2、iPad Pro、iPad Pro2 iPad mini with Retina display、iPad mini3、iPad mini4 iPod Touch6 |
以上所有的指令集都是向下兼容的
为什么要使用动态库共享缓存呢?最大的好处就是节省内存。
从动态库共享缓存抽取动态库
由于动态库共享缓存太大,如果想获取其中某个动态库,例如UIKit,就需要从动态库共享缓存中抽取对应的动态库
-
使用dyld源码中提供的方式来进行抽取,工具在源码中的<font color=red>launch-cache/dsc_extractor.cpp</font>文件中
- 首先需要去掉源码中的<font color=red>#if 0</font>判断
- 然后使用如下命令编译<font color=red>dsc_extractor.cpp</font>文件
clang++ -o dsc_extractor dsc_extractor.cpp
此处是将dsc_extractor.cpp编译生成可执行文件dsc_extractor
- 进入执行文件dsc_extractor所在目录。通过以下的命令来抽取动态库
./dsc_extractor 动态库共享缓存文件的路径 用于存放抽取结果的目录
>建议抽取armv7s架构的动态库,arm64抽取时会报以上错误,原因是dsc_extractor.bundle不能在Xcode10之后使用
- 抽取完成之后,使用Hopper Disassmbler打开想要逆向的动态库,就可以看到动态库中的源码信息。
![image](https://upload-images.jianshu.io/upload_images/20878107-73f56e5bab50be16?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)