介绍
Mach-o文件是MacOS和iOS上的可执行文件。其格式如下图所示:
用Mach-o分析工具(MachOView )查看,其界面如下:
header
结合apple源码来看。
- 如果是多架构fat文件,文件的头部定义为:
1 |
|
fat_header结构体的nfat_arch字段,标识了fat文件含有几个架构,而每个架构又由fat_arch结构体定义了文件偏移和架构类型,找到文件偏移之后,架构的结构和单架构是一样的,所以解析多架构文件和解析单架构文件,除了头部结构有区别,其他的都一样。
- 如果是单架构Mach-o文件,其头部定义为:
1 | /* |
各个字段的含义,注释都标注的很清楚,就不再赘述。
load_command
在header之下,又有多个load_commad段。header的定义中,给出了Mach-o文件的load_commad数,以及所有段的大小(ncmds/sizeofcmds)。load_command其结构体定义为:
1 | struct load_command { |
如果你以为load_command的定义就两个字段那么简单,那你可就错了。每个load_command的cmd字段,标识了该load_command的类型。
以下列出了cmd的所有类型以(代码来自apple source):
1 | /* |
所有段的定义以及含义,注释的都比较清楚。这里,我们主要关注LC_SEGMENT_64(LC_SEGMENT)段。
定义为该类型的段有:
1 |
|
section
其中,SEG_TEXT以及SEG_DATA段中,又含有多个Section:
1 | struct section { /* for 32-bit architectures */ |
下面介绍几个常见的section:
1 | __TEXT,__text: 代码区 |
代码分析Mach-o文件(class-dump的实现原理探析)
从上面的分析过程,可以看出,多架构文件只是含有多个架构文件的fat文件。所以这里,直接用一个单架构的Mach-o文件解析。
MachOView + 010 Editor分析
用MachOView大致浏览一下文件
找到__DATA,__objc_classlist
节
这里保存了所有类的指针,我们取第一个去查看:
1 | uintptr_t cls_ptr = 0x0000000100514468; |
获取指针所对应的文件偏移
首先用010 Editor查看当前__DATA,__objc_classlist
的起始地址和文件偏移
1 | uintptr_t address = 0x100429400; |
一般地,虚拟地址—》文件偏移的公式为:文件偏移=虚拟地址-当前section的起始地址+当前section的文件偏移。
1 | uint32 cls_offset = 0x0000000100514468-0x100429400+4363264 = 0x514468; |
经过计算,cls_ptr指针指向的就是文件的这个位置。
在class-dump的源码中,对cls_ptr指针指向的位置进行了如下定义:
1 | struct cd_objc2_class { |
意味着0x514468处,存储的是cd_objc2_class
结构体的数据。
而这个结构体中的data,即为存储class数据的指针。
查看cd_objc2_class数据
用MachOView查看文件偏移为0x514468的地方,可以看到,这个地址是__DATA,__objc_data
的首地址,其中,0x00514488处,即为data指针的值。
1 | uintptr_t data = 0x010042baa0; |
获取data指针的文件偏移
和上面的一样,用010 Editor查看基地址和文件偏移
1 | uintptr_t address = 0x100514468; |
根据之前说的公式,可以算出:
1 | uint32 data_offset = 0x010042baa0-0x100514468+5325928 = 0x0x42BAA0; |
在cd_objc2_class
的定义中已经告诉了我们,data指针指向的是class_ro_t
,所以0x0x42BAA0存储的应该是class_ro_t
结构体的数据。
class_ro_t
的定义:
1 | struct cd_objc2_class_ro_t { |
查看cd_objc2_class_ro_t
数据
根据结构体的定义:
1 | name = 0x01003d1cdc; |
用同样的方法,找到基地址和文件偏移
1 | uintptr_t address = 0x10042B1F8; |
计算name和baseMethods的文件偏移
1 | name_offset = 0x01003d1cdc-0x10042B1F8+4370936 = 0x3D1CDC; |
查看name
name = “WXShotSendPersonCardView”;
查看baseMethods
这个位置存储的就是类q的方法。
其定义为:
1 | struct method_t { |
method_list_t
的前八个字节存储的是method_t
的大小和method_t
的个数。
method_t
的存储方式为:
我们从0x42B670+0x8处,往下取三个指针:
1 | sel = 0x0100365c2f; |
用上述公式计算出文件偏移:
1 | sel_offset=0x0100365c2f-0x10042B1F8+4370936 = 0x365C2F; |
- 查看sel
1 | sel = initWithCustomFrame: |
- 查看type
1 | type = "@48@0:8{CGRect={CGPoint=dd}{CGSize=dd}}16"; |
- 查看imp
imp指向的是代码段,所以这里直接在ida上查看
可以看出,该偏移正好对上[WXShotSendPersonCardView initWithCustomFrame:]
的实现部分。