IOS逆向学习-Mach-o
1. 动态库
1.1 动态库的缓存
-
从IOS3.1开始,为了提高性能,绝大部分的系统动态库文件都打包存放到一个缓存文件夹中(
dyld shared cache
) -
缓存文件的路径:
/System/Library/Caches/com.apple.dyld/dyld_share_cache_armX(X: 代表ARM处理器指令集架构)
-
所有的框架的可执行文件(
Mach-o
)文件都不在/System/Library/Frameworks/xxxx.framework
中,改路径下存放都的都是各个各个framework
的一些资源配置文件,可执行文件都合并到一个caches中,这样做的好处是:节省内存
1.2 动态库的加载
- 在Mac\IOS中,是使用了
/usr/lib/dyld
程序来加载动态库 dyld
:dynamic link editor
: 动态链接编辑器dynamic loader
: 动态加载器
- dyld源码
- 苹果开源代码地址
- dyld源码查看过程截图
1.3 拆分dyld_share_cache_armX(动态库)
文件
- 网上有些工具可以拆分,但是我们可以使用苹果官方拆分方法
- 下载dyld的源码 ,然后找到
dsc_extractor.cpp
文件,该文件所在路径(源码823.73版本)dyld/shared_cache
文件加下dsc_extractor.cpp
类 - 进入当前文件所在目录,然后在终端使用命令编译该文件(本次编译会报错):
- 如果编译报错, 我们把该文件的一些代码删除只保留如下部分,然后再编译:
// test program
#include <stdio.h>
#include <stddef.h>
#include <dlfcn.h>
typedef int (*extractor_proc)(const char* shared_cache_file_path, const char* extraction_root_path,
void (^progress)(unsigned current, unsigned total));
int main(int argc, const char* argv[])
{
if ( argc != 3 ) {
fprintf(stderr, "usage: dsc_extractor <path-to-cache-file> <path-to-device-dir>\n");
return 1;
}
//void* handle = dlopen("/Volumes/my/src/dyld/build/Debug/dsc_extractor.bundle", RTLD_LAZY);
void* handle = dlopen("/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/usr/lib/dsc_extractor.bundle", RTLD_LAZY);
if ( handle == NULL ) {
fprintf(stderr, "dsc_extractor.bundle could not be loaded\n");
return 1;
}
extractor_proc proc = (extractor_proc)dlsym(handle, "dyld_shared_cache_extract_dylibs_progress");
if ( proc == NULL ) {
fprintf(stderr, "dsc_extractor.bundle did not have dyld_shared_cache_extract_dylibs_progress symbol\n");
return 1;
}
int result = (*proc)(argv[1], argv[2], ^(unsigned c, unsigned total) { printf("%d/%d\n", c, total); } );
fprintf(stderr, "dyld_shared_cache_extract_dylibs_progress() => %d\n", result);
return 0;
}
-
变成该文件之后,我们需要在终端执行该文件, 那么你在执行这个文件的时候需要指明路径,然后开始拆分库:
./dsc.extractor(可执行键) dyld_shared_cachexx(缓存文件路径) 拆分文件路径
:/dsc_extractor dyld_shared_cache_arm64 arm64
,最后拆分: -
拆解完成之后,你可以使用
hopper disassemble
来分析你想分析的框架
2. Mach-O
2.1 查看Mach-O的文件类型
-
Mach-O是Mach object的缩写,是Mac\iO上用于储存程序、库的标准格式
-
Mach-O格式的文件类型有:
-
可以在xnu源码中,查看到Mach-O格式的详细定义源码地址
-
xnu
: Mac系统的一些内核源码,先下载xnu的源码(xnu-7195.81.3版本
),然后把整个源码拖拽到sublineText工具中,根据下面路径可以查看Mach-o
类包含的文件类型- EXTEENAL_HEADERS/mach-o/loader.h
- EXTEENAL_HEADERS/mach-o/loader.h
2.2 常见的Mach-O文件类型
我们在在终端,输入命令file xxx
查看某个文件的类型
.o
文件被称为目标文件:.c 文件 ---(编译)---> .o文件(目标文件)-----(链接)----> 可执行文件
(文件的编译过程)
-
MH_OBJECT
: 目标文件(.o)、静态库文件(.a),静态库其实就是N个.o
合并在一起- 示例:我们可以生成一个.文件,然后编译生成.o文件,然后查看文件类型:
- 由上面过程我们可以看出
.o
文件其实是Mach-O文件类型,可以通过同样的方式查看.a
文件的类型
- 示例:我们可以生成一个.文件,然后编译生成.o文件,然后查看文件类型:
-
MH_EXECUTE
:可执行文件 -
MH_DYLIB
:动态库文件 ;.dyld
、.framework/xx
-
MH_DYLINKER
:动态链接编辑器;/usr/lib/dyld
-
MH_DSYM
:存储这二进制文件符号信息的文件,.dSYM.Content/Resources/DWAFR/xx
常用于分析APP的崩溃信息 -
find . -name "*.a"
: 在当前路径下查找后缀名为.a的文件 (. 代表当前路径)
-
在Xcode中查看target的Mach-O类型:
2.3 Universal Binary(通用二进制文件)
-
通用二进制文件 :
- 同时适用于多种架构的二进制文件
- 包含了多种不同架构的独立二进制文件
-
因为需要储存多种代码的结构,通用二进制文件通常比单一平台的二进制文件的程序要大
-
由于两种架构的有共同的一些资源,所以并不会达到单一版本的两倍之多
-
由于执行过程中,只调用一部分代码,运行起来也不需要额外的内存
-
因为文件比原来的大,也被称为
胖二进制文件(Fat Binary)
-
$(ARCHS_STANDARD)
: Xcode内置的环境变量,不同版本的Xcode的值不一样,通过的一些架构值- Xcode9中的
$(ARCHS_STANDARD)
值可能是arm64、armv7 - Xcode4中的
$(ARCHS_STANDARD)
值可能是armv7
- Xcode9中的
-
Xcode
打包出的二进制
(Mach-O类型)文件包含的架构是这两个变量的交集,也就是说两个变量同时支持的架构,打包出来的文件才会支持这个架构 -
在开发中我们一般不会去修改两个参数,但是比如在开发静态库的时候, 你可以通过设置这两个参数,来设置静态库支持什么架构
-
如何拆分和合并多个架构的二进制文件:
2.4窥探Mach-O的结构
2.4.1 查看Mach-o文件常用命令
- 命令行工具:file:查看Mach-O的文件类型
file 文件路径
- otools:
查看Mach-O特定部分和段的内容
,苹果自带的工具 - lipo :常用与查看多架构Mach-O文件的处理
- 查看架构信息:
lipo -info 文件路径
- 导出某种特定架构:
lipo 文件路径 -thin 架构类型(如:arm64) -output 输出文件路径
- 合并多种架构:
lipo -create 文件路径1 文件路劲2 -output 输出文件路径
- 查看架构信息:
- GUI工具:MachOView
2.4.2 Mach-O结构
- 内存是分段管理的,常见代码段、数据段
- 一个Mach-O文件主要包括3个主要区域
Header
: 文件类型、目标架构类型,等Load commands
:描述文件在虚拟内存中的逻辑结构、布局(有点类似指针的作用,告诉原始数据存放的一些位置信息)Raw segement data
: 在Load commands
中定义的Segement的原始数据
我么只有了解了Mach-O文件的结构,你才可能往里面注入自己的代码,达到修改app的效果
-
我们可以通过苹果自带的命令 来查看
Mach-O
的文件的结构信息:
-
除了苹果自带的
otools
命令,我们还可以通过MachOView
工具来查看,可以按照上面下载地址下载源码然后运行项目,如果运行项目报SDK
错误,我们要修改工程中的SDK版本,选择最新版本,在运行即可:
2.4.3 Mach-o和dyld
dyld
:也是Mach-O类型的文件,属于MH_DYLINKER
类型(动态连接器),dyld
可以加载三种类型的Mach-O类型的文件:MH_EXECUTE
: 可执行文件MH_DYLIB
:动态库MH_BUNDLE
: bundle类型的文件
- APP的可执行文件、动态库都是由
dyld
负责加载的,下面我们从源码中查看dyld
支持加载的Mach-O类型的文件: