深入理解MachO数据解析规则

深入理解MachO数据解析规则

我们知道Apple设备可执行文件的存储格式是MachO,一个二进制文件。通常在做逆向或者静态分析的时候都会用到这个文件,分析MachO的常用工具是MachOView。今天借助于MachOView,主要分析Code Signature的存储规则。

本篇文章同时也是围绕这几个问题展开的:

1、MachOView是如何确认MachO内容的。

2、二进制数据是如何存储的,如何确认位置。

3、字节码含义如何解析。

前置准备

1、二进制文件其实简单理解就是通过二进制形式进行存储内容的文件,它可以原封不动的读到内存中用于完成各种处理。比如数值3.1415927,文本文件需要9个字节进行存储:3 . 1 4 1 5 9 2 7 这 9 个 ASCII 值,而如果是二进制的话4个字节就够了:DB 0F 49 40。

2、二进制文件读到内存中通常是连续存储的,它不需要额外的处理,原本怎样,在内存里就是怎样的。

3、每个进程都会被分配一个虚拟地址空间,进程寻址的范围就是在这个虚拟地址空间进行的,虚拟地址到物理地址之间有一个映射表进行管理。

4、可以简单理解:虚拟地址 = 随机基址(ASLR)+ 逻辑地址(段内偏移)。

后面的内容也会出现很多偏移量(offset)的概念,它的含义很简单就是相对某一位置偏移多少字节。关键是需要确认它是相对哪个位置进行的偏移,在不同的数据段,这个相对的锚点是不一样的。但通常来说偏移量都是相对于当前的数据段来说的。

5、FAT格式的MachO可以理解为多个架构的顺序组合,所以分析某个架构时,还需要加上对应架构的偏移量。

6、uint32_t占4个字节,uint8_t占1个字节,char占一个字节。

Mach-O格式

格式分析

可以简单看下Mach-O的数据结构:

Mach-O文件大致分为三部分:

Header

表示当前的Mach-O文件整体信息,包含CPU架构、子版本、文件类型、加载命令数等内容。数字内容好表示,那CPU架构这样的类别是如何表示的呢?二进制数据说到底也是数字,这些类别信息也只能通过数字表示,但需要一个具有特殊含义的数字,这个数字通常叫magic(魔数)。比如0xCAFEBABE表示FAT,0xFEEDFACF表示ARM64。

Header的定义地址:https://opensource.apple.com/source/xnu/xnu-792/EXTERNAL_HEADERS/mach-o/loader.h.auto.html

Load Commands

记录各个数据段的信息和位置,只是类别和标记的介绍,包含一些信息的偏移地址、文件大小等内容。

Data

记录具体的内容信息。不同类别的信息对应不同的数据含义。注意上图右侧由Load Commands到Data的箭头,Data的位置是由Load Commands指定的。

他们三者的关系如果用一本书表示的话就是:Header是封面,Load Commands是目录,Data是书的内容。

寻找Code Signature

本节的重点是找到Code Signature(代码签名)这部分内容,它没被MachOView解析,还是原始的数据形态,是一个比较好的分析案例。

分析文件是系统的ls,它的路径在/bin/ls,把它放到MachOView里。ls是一个FAT文件,它包含两个架构,Fat Header里记录了各个架构的类别、偏移量、大小等信息。

我们只关注X86_64架构下的内容,展开这个架构下的Load Commands,找到代表代码签名的LC_CODE_SIGNATURE信息:

右侧是真实的数据内容,MachOView已经帮我们对应好了字段描述:

Data Offset:代表数据偏移 53808,换成16进制就是0xD230

Data Size:代表文件大小 5728,换成16进制就是0x1660

这俩16进制值其实就是Data对应的内容,Value是MachOView帮我们做的处理。

这里的偏移跟上面Fat Header的偏移含义已经不一样了,Fat Header说的是总文件偏移,这里的偏移则是针对X86文件的偏移。所以实际的偏移应该是:0xD230 + 0x4000 = 0x11230。

找到Data部分的Code Signature内容:

这里pFile就是相对当前文件的偏移量(也可以理解为逻辑偏移量),它的起始位置正是上面计算得的:0x11230。由大小0x1660,我们还可以计算得出Code Signature最后一个字节所在位置是:0x11230 + 0x1660 - 0x1 = 0x1288F。

解析Code Signature

CS_SuperBlob

我们已经找到了代码签名位置,现在开始解析它吧。解析的第一步就是需要找到数据定义,有了定义才能分析出数据含义。Code Signature相关内容的定义在这里:https://opensource.apple.com/source/xnu/xnu-3789.51.2/bsd/sys/codesign.h.auto.html

整个签名的头部是一个CS_SuperBlob结构体,它的定义如下:

1
2
3
4
5
6
7
typedef struct __SC_SuperBlob {
uint32_t magic; /* magic number */
uint32_t length; /* total length of SuperBlob */
uint32_t count; /* number of index entries following */
CS_BlobIndex index[]; /* (count) entries */
/* followed by Blobs in no particular order as indicated by offsets in index */
} CS_SuperBlob;

这个结构体第一个参数是magic,它的定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/*
* Magic numbers used by Code Signing
*/
enum {
CSMAGIC_REQUIREMENT = 0xfade0c00, /* single Requirement blob */
CSMAGIC_REQUIREMENTS = 0xfade0c01, /* Requirements vector (internal requirements) */
CSMAGIC_CODEDIRECTORY = 0xfade0c02, /* CodeDirectory blob */
CSMAGIC_EMBEDDED_SIGNATURE = 0xfade0cc0, /* embedded form of signature data */
CSMAGIC_EMBEDDED_SIGNATURE_OLD = 0xfade0b02, /* XXX */
CSMAGIC_EMBEDDED_ENTITLEMENTS = 0xfade7171, /* embedded entitlements */
CSMAGIC_DETACHED_SIGNATURE = 0xfade0cc1, /* multi-arch collection of embedded signatures */
CSMAGIC_BLOBWRAPPER = 0xfade0b01, /* CMS Signature, among other things */
//...
}

第二个参数是length,表示整个SuperBlob的长度。

第三个参数是count,表示index实体条目的数量。

第四个参数是为CS_BlobIndex的一个结构体。

大端小端

1、这个是64位架构的二进制数据,其实有两种64位架构,他们分别表示为大端64位和小端64位,上面MachOView分析的X86 Header中的魔数是0xFEEDFACF,代表的就是当前二进制文件是小端64位格式。

2、比如0x1234这个数据,在小端情况下,12会存放在低字节处,34会放于高字节处,大端则相反。

数据解析

我们把Code Signature的第一个行数据拿出来分析:

这里注意Data部分,有两个标签:Data LO和Data HI,是用于表示当前的字节序列,前面是低字节,后面是高字节。这样按照小端的规则,我们就可以按自然顺序取数据了,所以可以得出以下内容:

magic

为0xFADE0CC0,对应CSMAGIC_EMBEDDED_SIGNATURE,代表嵌入的代码签名数据。

length

是0x1486,我们可以计算得出最后一个字节位置:0x11230 + 0x1486 - 0x1 = 0x126B5

红色标记的字节就是Code Signature结束的地方,在这之后的内容全部由0x00填充,就非实体内容了。

count

是3,表示接下来有3个实体内容,这个实体对应的是结构体:CS_BlobIndex。

CS_BlobIndex

我们来看下CS_BlobIndex这个结构体:

1
2
3
4
5
6
7
8
/*
* Structure of an embedded-signature SuperBlob
*/

typedef struct __BlobIndex {
uint32_t type; /* type of entry */
uint32_t offset; /* offset of entry */
} CS_BlobIndex;

它有两个成员变量,type表示实体类型,offset表示实体偏移量。

一般表示类型的肯定有特殊数字对应的含义,这里的type也是一样的,这个type在上面的magic在一个enum里定义。

1
2
3
4
5
6
7
8
9
10
11
12
CSSLOT_CODEDIRECTORY = 0,				/* slot index for CodeDirectory */
CSSLOT_INFOSLOT = 1,
CSSLOT_REQUIREMENTS = 2,
CSSLOT_RESOURCEDIR = 3,
CSSLOT_APPLICATION = 4,
CSSLOT_ENTITLEMENTS = 5,

CSSLOT_ALTERNATE_CODEDIRECTORIES = 0x1000, /* first alternate CodeDirectory, if any */
CSSLOT_ALTERNATE_CODEDIRECTORY_MAX = 5, /* max number of alternate CD slots */
CSSLOT_ALTERNATE_CODEDIRECTORY_LIMIT = CSSLOT_ALTERNATE_CODEDIRECTORIES + CSSLOT_ALTERNATE_CODEDIRECTORY_MAX, /* one past the last */

CSSLOT_SIGNATURESLOT = 0x10000, /* CMS Signature */

数据解析

我们再回到数据部分,根据上面结构体进行分析:

能够解析出三条CS_BlobIndex数据:

type type含义 offset
0x00 CSSLOT_CODEDIRECTORY 0x24
0x02 CSSLOT_REQUIREMENTS 0x261
0x10000 CSSLOT_SIGNATURESLOT 0x29D

这里又出现了一个offset,这个offset存在于Code Signature的最外部,所以它表示的就是相对Code Signature的偏移量。

这个表相当于又提供了一个目录,它告诉我们,之后的内容有三部分(三个结构体)组成,各个部分的页码是什么。

CS_CodeDirectory

我们先分析CSSLOT_CODEDIRECTORY,它对应的是CS_CodeDirectory结构体:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/*
* C form of a CodeDirectory.
*/
typedef struct __CodeDirectory {
uint32_t magic; /* magic number (CSMAGIC_CODEDIRECTORY) */
uint32_t length; /* total length of CodeDirectory blob */
uint32_t version; /* compatibility version */
uint32_t flags; /* setup and mode flags */
uint32_t hashOffset; /* offset of hash slot element at index zero */
uint32_t identOffset; /* offset of identifier string */
uint32_t nSpecialSlots; /* number of special hash slots */
uint32_t nCodeSlots; /* number of ordinary (code) hash slots */
uint32_t codeLimit; /* limit to main image signature range */
uint8_t hashSize; /* size of each hash in bytes */
uint8_t hashType; /* type of hash (cdHashType* constants) */
uint8_t platform; /* platform identifier; zero if not platform binary */
uint8_t pageSize; /* log2(page size in bytes); 0 => infinite */
uint32_t spare2; /* unused (must be zero) */
/* Version 0x20100 */
uint32_t scatterOffset; /* offset of optional scatter vector */
/* Version 0x20200 */
uint32_t teamOffset; /* offset of optional team identifier */
/* followed by dynamic content as located by offset fields above */
} CS_CodeDirectory;

数据解析

我们先把这段数据拿出来,然后根据结构体进行分析:

这里仅挑一些重要的内容进行分析。

magic是0xFADE0C02,作为标记存在,代表CodeDirectory

length是0x23D,表示数据段长度

identoffset是0x30,表示identifier字符串的偏移量,这里的identifier对应的就是我们的bundleId

需要提醒的是当前的CodeDirectory是数据SuperBlob的内部结构体,所以这里的offset就变成了结构体内部偏移了,这里的起始位置也即是0xFADE0C02所在的位置是0x11254,所以可以算出indentoffset的文件偏移量是:

identoffset地址为:0x11254 + 0x30 = 0x11284

这里你可能会疑惑,只有偏移量怎么确认从哪结束呢,这里并没有提供数据大小。其实字符串是不需要知道大小也可以确认它到哪结束的,字符里面有结束位\0啊,在ASCII码里结束位就是0x00。

可以解析得出ls的bundleId是com.apple.ls

这里再补充一点:MachO里字符串的编码不是通过ASCII,而是使用UTF-8进行编码的,只不过UTF-8兼容了ASCII,所以我们当做ASCII也能解析出正确的内容。

CS_GenericBlob

我们现在来看下证书的解析,查上面记录的偏移表,CSSLOT_SIGNATURESLOT对应的结构体是CS_Generic_Blob:

1
2
3
4
5
typedef struct __SC_GenericBlob {
uint32_t magic; /* magic number */
uint32_t length; /* total length of blob */
char data[];
} CS_GenericBlob;

上个表格我们记录了它的offset是0x29D位置,所以它的起始位置就是:0x11230 + 0x29D = 0x114CD,找到这个位置,带入结构体进行解析:

magic是0xFADE0B01,对应了CSSLOT_SIGNATURESLOT值。

数据长度是0x11E9(4585字节),这表示的CS_GenericBlob的大小,而在这之后的内容都是data,表示的就是证书部分。

我们可以计算出证书data结束的最后一个字节位置:0x114CD + 0x11E9 - 0x8 - 0x1 = 0x126AD。

说明:根据《iOS应用逆向与安全》一书说明,借助于010 Editor等二进制工具,我们把data部分的数据复制出来(需要借助于Hooper这类工具),保存为cer格式,就能获取到一个证书文件。但对ls的测试并不能成功,推测这里的data可能还有其余内容,需要拆分。

Jtool

只要有了对应数据结构,签名部分的所有信息我们都是可以解析出来的。但每次都逐字节分析,显然很费事,能不能写个程序,用于上述内容解析呢?当然是可以的,已经有这样的工具了,就是Jtool。jtool比otool功能更强大,解析的数据也更详细。可以通过homebrew进行安装:

1
$ brew install jtool

如果通过jtool查看上面x86_64架构的签名信息,可以这样:

1
$ jtool -arch x86_64 --sig /bin/ls

输出结果为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
Blob at offset: 53808 (5728 bytes) is an embedded signature
Code Directory (573 bytes)
Version: 20100
Flags: none
Platform Binary
CodeLimit: 0xd230
Identifier: com.apple.ls (0x30)
CDHash: 46cc1da7c874a5853984a286ffecb48daf2f65f023d10258a31118acfc8a3697 (computed)
# of Hashes: 14 code + 2 special
Hashes @125 size: 32 Type: SHA-256
Requirement Set (60 bytes) with 1 requirement:
0: Designated Requirement (@20, 28 bytes): SIZE: 28
Ident: (com.apple.ls) AND Apple Anchor
Blob Wrapper (4585 bytes) (0x10000 is CMS (RFC3852) signature)
CA: Apple Certification Authority CN: Apple Root CA
CA: Apple Certification Authority CN: Apple Code Signing Certification Authority
CA: Apple Certification Authority CN: Apple Root CA
CA: Apple Certification Authority CN: Apple Root CA
CA: Apple Certification Authority CN: Apple Code Signing Certification Authority
CA: Apple Software CN: Software Signing
Time: 201222002625Zi

第一行里的offset 53808 对应16进制是0xD230,就是LC_CODE_SIGNATURE里记录的偏移量。

根据输出信息也能得出code signature由三部分内容组成:Code Diretory、Requeirement Set、Blob Wrapper。证书部分解析出了6个证书,说明这里应该还有别的结构体可以拆分。

回顾

如果你看到这里,可以回顾下开始讲到的三个问题,用于检验你的理解程度。

1、MachOView是如何确认MachO内容的。

2、二进制数据是如何存储的,如何确认位置。

3、字节码含义如何解析。

作者

zhangferry

发布于

2021-04-05

更新于

2021-04-11

许可协议

评论