Mach-O
Mach-O是Mach object的缩写,是Mac\iOS上用于存储程序、库的标准格式
一个Mach-O可执行文件包含一个由一组加载命令组成的头文件。对于使用共享库或框架的程序,其中一个命令指定用于加载程序的链接器的位置。如果你使用Xcode,它总是/usr/lib/dyld,这是标准的OS X动态链接器。
#define MH_OBJECT 0x1 /* relocatable object file */
#define MH_EXECUTE 0x2 /* demand paged executable file */
#define MH_FVMLIB 0x3 /* fixed VM shared library file */
#define MH_CORE 0x4 /* core file */
#define MH_PRELOAD 0x5 /* preloaded executable file */
#define MH_DYLIB 0x6 /* dynamically bound shared library */
#define MH_DYLINKER 0x7 /* dynamic link editor */
#define MH_BUNDLE 0x8 /* dynamically bound bundle file */
#define MH_DYLIB_STUB 0x9 /* shared library stub for static */
/* linking only, no section contents */
#define MH_DSYM 0xa /* companion file with only debug */
/* sections */
属于Mach-O格式的文件类型有
可以在xnu源码中,查看到Mach-O格式的详细定义(https://opensource.apple.com/tarballs/xnu/)
EXTERNAL_HEADERS/mach-o/fat.h
EXTERNAL_HEADERS/mach-o/loader.h
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的崩溃信息)**
2.Mach-O的基本结构
官方描述
https://developer.apple.com/library/content/documentation/DeveloperTools/Conceptual/MachOTopics/0-Introduction/introduction.html
**一个Mach-O文件包含3个主要区域**
Header
文件类型、目标架构类型等
**Load commands**
描述文件在虚拟内存中的逻辑结构、布局
**Raw segment data**
在Load commands中定义的Segment的原始数据
3.窥探Mach-O的结构
命令行工具
file:查看Mach-O的文件类型
file 文件路径
otool:查看Mach-O特定部分和段的内容
lipo:常用于多架构Mach-O文件的处理
查看架构信息:lipo -info 文件路径
导出某种特定架构:lipo 文件路径 -thin 架构类型 -output 输出文件路径
合并多种架构:lipo 文件路径1 文件路径2 -output 输出文件路径
GUI工具
MachOView(https://github.com/gdbinit/MachOView)
4.Universal Binary(通用二进制文件
通用二进制文件
同时适用于多种架构的二进制文件
包含了多种不同架构的独立的二进制文件
因为需要储存多种架构的代码,通用二进制文件通常比单一平台二进制的程序要大
由于两种架构有共同的一些资源,所以并不会达到单一版本的两倍之多
由于执行过程中,只调用一部分代码,运行起来也不需要额外的内存
因为文件比原来的要大,也被称为“胖二进制文件”(Fat Binary)
dyld和Mach-O
dyld用于加载以下类型的Mach-O文件
MH_EXECUTE
MH_DYLIB
MH_BUNDLE
APP的可执行文件、动态库都是由dyld负责加载的
APP从开发到安装到手机的过程
MJRefreshExample.app中的MJRefreshExample文件是iOS中的可执行文件,文件格式是Mach-O
逆向APP的思路
界面分析
Cycript、Reveal
代码分析
对Mach-O文件的静态分析
MachOView、class-dump、Hopper Disassembler、ida等
动态调试
对运行中的APP进行代码调试
debugserver、LLDB
代码编写
注入代码到APP中
必要时还可能需要重新签名、打包ipa
class-dump
顾名思义,它的作用就是把Mach-O文件的class信息给dump出来(把类信息给导出来),生成对应的.h头文件
官方地址:http://stevenygard.com/projects/class-dump/
下载完工具包后将class-dump文件复制到Mac的/usr/local/bin目录,这样在终端就能识别class-dump命令了
常用格式
class-dump -H Mach-O文件路径 -o 头文件存放目录
-H表示要生成头文件
-o用于制定头文件的存放目录
代码的编译过程
动态库共享缓存(dyld shared cache)
从iOS3.1开始,为了提高性能,绝大部分的系统动态库文件都打包存放到了一个缓存文件中(dyld shared cache)
缓存文件路径:/System/Library/Caches/com.apple.dyld/dyld_shared_cache_armX
dyld_shared_cache_armX的X代表ARM处理器指令集架构
v6
iPhone、iPhone3G
iPod Touch、iPod Touch2
v7
iPhone3GS、iPhone4、iPhone4S
iPad、iPad2、iPad3(The New iPad)
iPad mini
iPod Touch3G、iPod Touch4、iPod Touch5
v7s
iPhone5、iPhone5C
iPad4
arm64
iPhone5S、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
所有指令集原则上都是向下兼容的
动态库共享缓存一个非常明显的好处是节省内存
动态库的加载
在Mac\iOS中,是使用了/usr/lib/dyld程序来加载动态库
dyld
dynamic link editor,动态链接编辑器
dynamic loader,动态加载器
dyld源码
https://opensource.apple.com/tarballs/dyld/
从动态库共享缓存抽取动态库
可以使用dyld源码中的launch-cache/dsc_extractor.cpp
将#if 0前面的代码删除(包括#if 0),把最后面的#endif也删掉
编译dsc_extractor.cpp
clang++ -o dsc_extractor dsc_extractor.cpp
使用dsc_extractor
./dsc_extractor 动态库共享缓存文件的路径 用于存放抽取结果的文件夹
执行Mach-O文件
为了执行它们的目标,程序必须执行流程并链接到动态共享库。要使用其他库或模块,
应用程序必须定义对这些模块中的符号的引用;这些引用在运行时解析。在运行时,
应用程序使用的所有模块的符号名称都位于共享的名称空间中,类似于目录。
为了在将来增强应用程序及其使用的库,应用程序和库开发人员必须确保为其函数和数据选择的
名称与其他模块中使用的名称不冲突。
OS X v10.1的两级命名空间特性
OS X v10.1引入了两级符号命名空间特性。两级名称空间的第一级是包含符号的库的名称,第二级是符号的名称。启用了两级名称空间特性后,当静态链接器记录对导入符号的引用时,它将记录对包含符号和符号名称的库的名称的引用。与平面名称空间相比,使用两级名称空间特性链接程序有两个好处:
增强了搜索符号时的性能。使用两级名称空间,动态链接器知道从哪里开始查找符号的实现。使用平面名称空间,动态链接器必须搜索所有加载的库,以查找包含符号的库。
增强向前兼容性。在平面命名空间中,两个或多个库不能包含具有相同名称的不同实现的符号,因为动态链接器无法知道哪个库包含首选实现。这在一开始并不是一个问题,因为静态链接器会在您第一次构建应用程序时捕获任何此类问题。但是,如果某个依赖共享库的供应商后来发布了一个新版本的库,其中包含与您的程序或另一个依赖共享库中的代码同名的符号,则程序将无法运行。
您的应用程序必须直接链接到包含符号的共享库(或者,如果该库是伞形框架的一部分,则必须链接到包含符号的伞形框架)。
在启用两级名称空间特性的程序中获取符号时,必须指定对包含符号的共享库的引用。
默认情况下,OS X v10.1及以后版本中的静态链接器对所有Mach-O文件使用两级名称空间。
这里作为补充引入几个问题
1.可执行文件与动态库同为mach-o 的最终产物,它们的关系是什么
可执行文件与动态库同为mach-o文件,当可执行文件里面用到动态库,就会在其内部保留对动态库的引用。
2.动态库与可执行文件作为最终产物,它为什么比可执行文件大
动态库里面所有的.o都会被保留,因为它无法确定哪些符号会被外部引用,因此一般的动态库比可执行文件要大
3.静态库也支持two-level namespace吗?
只有mach-o的文件才支持two-level namespace功能。
4.two-level namespace 解决了符号重复的问题,但是类名也还是那个类名,会不会有问题
KVO 的实现简单来说就是创建了一个NSKVONotifying_A的新类,继承被观察的类A,它替换原有A类的isa 指针,重写了所提供的keyPath 相关的setter方法,实现观察A类属性变化。平时用,一般不会有什么问题,但是当遇上动态库,情况就不一样了。