深入理解MachO數(shù)據(jù)解析規(guī)則

我們知道Apple設(shè)備可執(zhí)行文件的存儲(chǔ)格式是MachO,一個(gè)二進(jìn)制文件。通常在做逆向或者靜態(tài)分析的時(shí)候都會(huì)用到這個(gè)文件,分析MachO的常用工具是MachOView[1]。今天借助于MachOView,主要分析Code Signature的存儲(chǔ)規(guī)則。
本篇文章同時(shí)也是圍繞這幾個(gè)問(wèn)題展開(kāi)的:
1、MachOView是如何確認(rèn)MachO內(nèi)容的。
2、二進(jìn)制數(shù)據(jù)是如何存儲(chǔ)的,如何確認(rèn)位置。
3、字節(jié)碼含義如何解析。
前置準(zhǔn)備
1、二進(jìn)制文件其實(shí)簡(jiǎn)單理解就是通過(guò)二進(jìn)制形式進(jìn)行存儲(chǔ)內(nèi)容的文件,它可以原封不動(dòng)的讀到內(nèi)存中用于完成各種處理。比如數(shù)值3.1415927,文本文件需要9個(gè)字節(jié)進(jìn)行存儲(chǔ):3 . 1 4 1 5 9 2 7 這 9 個(gè) ASCII 值,而如果是二進(jìn)制的話4個(gè)字節(jié)就夠了:DB 0F 49 40。
2、二進(jìn)制文件讀到內(nèi)存中通常是連續(xù)存儲(chǔ)的,它不需要額外的處理,原本怎樣,在內(nèi)存里就是怎樣的。
3、每個(gè)進(jìn)程都會(huì)被分配一個(gè)虛擬地址空間,進(jìn)程尋址的范圍就是在這個(gè)虛擬地址空間進(jìn)行的,虛擬地址到物理地址之間有一個(gè)映射表進(jìn)行管理。
4、可以簡(jiǎn)單理解:虛擬地址 = 隨機(jī)基址(ASLR)+ 邏輯地址(段內(nèi)偏移)。
后面的內(nèi)容也會(huì)出現(xiàn)很多偏移量(offset)的概念,它的含義很簡(jiǎn)單就是相對(duì)某一位置偏移多少字節(jié)。關(guān)鍵是需要確認(rèn)它是相對(duì)哪個(gè)位置進(jìn)行的偏移,在不同的數(shù)據(jù)段,這個(gè)相對(duì)的錨點(diǎn)是不一樣的。但通常來(lái)說(shuō)偏移量都是相對(duì)于當(dāng)前的數(shù)據(jù)段來(lái)說(shuō)的。
5、FAT格式的MachO可以理解為多個(gè)架構(gòu)的順序組合,所以分析某個(gè)架構(gòu)時(shí),還需要加上對(duì)應(yīng)架構(gòu)的偏移量。
6、uint32_t占4個(gè)字節(jié),uint8_t占1個(gè)字節(jié),char占一個(gè)字節(jié)。
Mach-O格式
格式分析
可以簡(jiǎn)單看下Mach-O的數(shù)據(jù)結(jié)構(gòu):

Mach-O文件大致分為三部分:
Header
表示當(dāng)前的Mach-O文件整體信息,包含CPU架構(gòu)、子版本、文件類型、加載命令數(shù)等內(nèi)容。數(shù)字內(nèi)容好表示,那CPU架構(gòu)這樣的類別是如何表示的呢?二進(jìn)制數(shù)據(jù)說(shuō)到底也是數(shù)字,這些類別信息也只能通過(guò)數(shù)字表示,但需要一個(gè)具有特殊含義的數(shù)字,這個(gè)數(shù)字通常叫magic(魔數(shù))。比如0xCAFEBABE表示FAT,0xFEEDFACF表示ARM64。
Header的定義地址:https://opensource.apple.com/source/xnu/xnu-792/EXTERNAL_HEADERS/mach-o/loader.h.auto.html
Load Commands
記錄各個(gè)數(shù)據(jù)段的信息和位置,只是類別和標(biāo)記的介紹,包含一些信息的偏移地址、文件大小等內(nèi)容。
Data
記錄具體的內(nèi)容信息。不同類別的信息對(duì)應(yīng)不同的數(shù)據(jù)含義。注意上圖右側(cè)由Load Commands到Data的箭頭,Data的位置是由Load Commands指定的。
他們?nèi)叩年P(guān)系如果用一本書表示的話就是:Header是封面,Load Commands是目錄,Data是書的內(nèi)容。
尋找Code Signature
本節(jié)的重點(diǎn)是找到Code Signature(代碼簽名)這部分內(nèi)容,它沒(méi)被MachOView解析,還是原始的數(shù)據(jù)形態(tài),是一個(gè)比較好的分析案例。
分析文件是系統(tǒng)的ls,它的路徑在/bin/ls,把它放到MachOView里。ls是一個(gè)FAT文件,它包含兩個(gè)架構(gòu),F(xiàn)at Header里記錄了各個(gè)架構(gòu)的類別、偏移量、大小等信息。

我們只關(guān)注X86_64架構(gòu)下的內(nèi)容,展開(kāi)這個(gè)架構(gòu)下的Load Commands,找到代表代碼簽名的LC_CODE_SIGNATURE信息:

右側(cè)是真實(shí)的數(shù)據(jù)內(nèi)容,MachOView已經(jīng)幫我們對(duì)應(yīng)好了字段描述:
Data Offset:代表數(shù)據(jù)偏移 53808,換成16進(jìn)制就是0xD230
Data Size:代表文件大小 5728,換成16進(jìn)制就是0x1660
這倆16進(jìn)制值其實(shí)就是Data對(duì)應(yīng)的內(nèi)容,Value是MachOView幫我們做的處理。
這里的偏移跟上面Fat Header的偏移含義已經(jīng)不一樣了,F(xiàn)at Header說(shuō)的是總文件偏移,這里的偏移則是針對(duì)X86文件的偏移。所以實(shí)際的偏移應(yīng)該是:0xD230 + 0x4000 = 0x11230。
找到Data部分的Code Signature內(nèi)容:

這里pFile就是相對(duì)當(dāng)前文件的偏移量(也可以理解為邏輯偏移量),它的起始位置正是上面計(jì)算得的:0x11230。由大小0x1660,我們還可以計(jì)算得出Code Signature最后一個(gè)字節(jié)所在位置是:0x11230 + 0x1660 - 0x1 = 0x1288F。
解析Code Signature
CS_SuperBlob
我們已經(jīng)找到了代碼簽名位置,現(xiàn)在開(kāi)始解析它吧。解析的第一步就是需要找到數(shù)據(jù)定義,有了定義才能分析出數(shù)據(jù)含義。Code Signature相關(guān)內(nèi)容的定義在這里:https://opensource.apple.com/source/xnu/xnu-3789.51.2/bsd/sys/codesign.h.auto.html
整個(gè)簽名的頭部是一個(gè)CS_SuperBlob結(jié)構(gòu)體,它的定義如下:
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;
這個(gè)結(jié)構(gòu)體第一個(gè)參數(shù)是magic,它的定義如下:
/*
* 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 */
//...
}
第二個(gè)參數(shù)是length,表示整個(gè)SuperBlob的長(zhǎng)度。
第三個(gè)參數(shù)是count,表示index實(shí)體條目的數(shù)量。
第四個(gè)參數(shù)是為CS_BlobIndex的一個(gè)結(jié)構(gòu)體。
大端小端
1、這個(gè)是64位架構(gòu)的二進(jìn)制數(shù)據(jù),其實(shí)有兩種64位架構(gòu),他們分別表示為大端64位和小端64位,上面MachOView分析的X86 Header中的魔數(shù)是0xFEEDFACF,代表的就是當(dāng)前二進(jìn)制文件是小端64位格式。
2、比如0x1234這個(gè)數(shù)據(jù),在小端情況下,12會(huì)存放在低字節(jié)處,34會(huì)放于高字節(jié)處,大端則相反。
數(shù)據(jù)解析
我們把Code Signature的第一個(gè)行數(shù)據(jù)拿出來(lái)分析:

這里注意Data部分,有兩個(gè)標(biāo)簽:Data LO和Data HI,是用于表示當(dāng)前的字節(jié)序列,前面是低字節(jié),后面是高字節(jié)。這樣按照小端的規(guī)則,我們就可以按自然順序取數(shù)據(jù)了,所以可以得出以下內(nèi)容:
magic
為0xFADE0CC0,對(duì)應(yīng)CSMAGIC_EMBEDDED_SIGNATURE,代表嵌入的代碼簽名數(shù)據(jù)。
length
是0x1486,我們可以計(jì)算得出最后一個(gè)字節(jié)位置:0x11230 + 0x1486 - 0x1 = 0x126B5

紅色標(biāo)記的字節(jié)就是Code Signature結(jié)束的地方,在這之后的內(nèi)容全部由0x00填充,就非實(shí)體內(nèi)容了。
count
是3,表示接下來(lái)有3個(gè)實(shí)體內(nèi)容,這個(gè)實(shí)體對(duì)應(yīng)的是結(jié)構(gòu)體:CS_BlobIndex。
CS_BlobIndex
我們來(lái)看下CS_BlobIndex這個(gè)結(jié)構(gòu)體:
/*
* Structure of an embedded-signature SuperBlob
*/
typedef struct __BlobIndex {
uint32_t type; /* type of entry */
uint32_t offset; /* offset of entry */
} CS_BlobIndex;
它有兩個(gè)成員變量,type表示實(shí)體類型,offset表示實(shí)體偏移量。
一般表示類型的肯定有特殊數(shù)字對(duì)應(yīng)的含義,這里的type也是一樣的,這個(gè)type在上面的magic在一個(gè)enum里定義。
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 */
數(shù)據(jù)解析
我們?cè)倩氐綌?shù)據(jù)部分,根據(jù)上面結(jié)構(gòu)體進(jìn)行分析:

能夠解析出三條CS_BlobIndex數(shù)據(jù):
| type | type含義 | offset |
|---|---|---|
| 0x00 | CSSLOT_CODEDIRECTORY | 0x24 |
| 0x02 | CSSLOT_REQUIREMENTS | 0x261 |
| 0x10000 | CSSLOT_SIGNATURESLOT | 0x29D |
這里又出現(xiàn)了一個(gè)offset,這個(gè)offset存在于Code Signature的最外部,所以它表示的就是相對(duì)Code Signature的偏移量。
這個(gè)表相當(dāng)于又提供了一個(gè)目錄,它告訴我們,之后的內(nèi)容有三部分(三個(gè)結(jié)構(gòu)體)組成,各個(gè)部分的頁(yè)碼是什么。
CS_CodeDirectory
我們先分析CSSLOT_CODEDIRECTORY,它對(duì)應(yīng)的是CS_CodeDirectory結(jié)構(gòu)體:
/*
* 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;
數(shù)據(jù)解析
我們先把這段數(shù)據(jù)拿出來(lái),然后根據(jù)結(jié)構(gòu)體進(jìn)行分析:

這里僅挑一些重要的內(nèi)容進(jìn)行分析。
magic是0xFADE0C02,作為標(biāo)記存在,代表CodeDirectory
length是0x23D,表示數(shù)據(jù)段長(zhǎng)度
identoffset是0x30,表示identifier字符串的偏移量,這里的identifier對(duì)應(yīng)的就是我們的bundleId
需要提醒的是當(dāng)前的CodeDirectory是數(shù)據(jù)SuperBlob的內(nèi)部結(jié)構(gòu)體,所以這里的offset就變成了結(jié)構(gòu)體內(nèi)部偏移了,這里的起始位置也即是0xFADE0C02所在的位置是0x11254,所以可以算出indentoffset的文件偏移量是:
identoffset地址為:0x11254 + 0x30 = 0x11284
這里你可能會(huì)疑惑,只有偏移量怎么確認(rèn)從哪結(jié)束呢,這里并沒(méi)有提供數(shù)據(jù)大小。其實(shí)字符串是不需要知道大小也可以確認(rèn)它到哪結(jié)束的,字符里面有結(jié)束位\0啊,在ASCII碼里結(jié)束位就是0x00。

可以解析得出ls的bundleId是com.apple.ls。
這里再補(bǔ)充一點(diǎn):MachO里字符串的編碼不是通過(guò)ASCII,而是使用UTF-8進(jìn)行編碼的,只不過(guò)UTF-8兼容了ASCII,所以我們當(dāng)做ASCII也能解析出正確的內(nèi)容。
CS_GenericBlob
我們現(xiàn)在來(lái)看下證書的解析,查上面記錄的偏移表,CSSLOT_SIGNATURESLOT對(duì)應(yīng)的結(jié)構(gòu)體是CS_Generic_Blob:
typedef struct __SC_GenericBlob {
uint32_t magic; /* magic number */
uint32_t length; /* total length of blob */
char data[];
} CS_GenericBlob;
上個(gè)表格我們記錄了它的offset是0x29D位置,所以它的起始位置就是:0x11230 + 0x29D = 0x114CD,找到這個(gè)位置,帶入結(jié)構(gòu)體進(jìn)行解析:

magic是0xFADE0B01,對(duì)應(yīng)了CSSLOT_SIGNATURESLOT值。
數(shù)據(jù)長(zhǎng)度是0x11E9(4585字節(jié)),這表示的CS_GenericBlob的大小,而在這之后的內(nèi)容都是data,表示的就是證書部分。
我們可以計(jì)算出證書data結(jié)束的最后一個(gè)字節(jié)位置:0x114CD + 0x11E9 - 0x8 - 0x1 = 0x126AD。
說(shuō)明:根據(jù)《iOS應(yīng)用逆向與安全》一書說(shuō)明,借助于010 Editor等二進(jìn)制工具,我們把data部分的數(shù)據(jù)復(fù)制出來(lái)(需要借助于Hooper這類工具),保存為cer格式,就能獲取到一個(gè)證書文件。但對(duì)ls的測(cè)試并不能成功,推測(cè)這里的data可能還有其余內(nèi)容,需要拆分。
Jtool
只要有了對(duì)應(yīng)數(shù)據(jù)結(jié)構(gòu),簽名部分的所有信息我們都是可以解析出來(lái)的。但每次都逐字節(jié)分析,顯然很費(fèi)事,能不能寫個(gè)程序,用于上述內(nèi)容解析呢?當(dāng)然是可以的,已經(jīng)有這樣的工具了,就是Jtool[2]。jtool比otool功能更強(qiáng)大,解析的數(shù)據(jù)也更詳細(xì)。可以通過(guò)homebrew進(jìn)行安裝:
$ brew install jtool
如果通過(guò)jtool查看上面x86_64架構(gòu)的簽名信息,可以這樣:
$ jtool -arch x86_64 --sig /bin/ls
輸出結(jié)果為:
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 對(duì)應(yīng)16進(jìn)制是0xD230,就是LC_CODE_SIGNATURE里記錄的偏移量。
根據(jù)輸出信息也能得出code signature由三部分內(nèi)容組成:Code Diretory、Requeirement Set、Blob Wrapper。證書部分解析出了6個(gè)證書,說(shuō)明這里應(yīng)該還有別的結(jié)構(gòu)體可以拆分。
回顧
如果你看到這里,可以回顧下開(kāi)始講到的三個(gè)問(wèn)題,用于檢驗(yàn)?zāi)愕睦斫獬潭取?/p>
1、MachOView是如何確認(rèn)MachO內(nèi)容的。
2、二進(jìn)制數(shù)據(jù)是如何存儲(chǔ)的,如何確認(rèn)位置。
3、字節(jié)碼含義如何解析。
參考資料
MachOView: https://github.com/fangshufeng/MachOView
[2]jtool: http://newosxbook.com/tools/jtool.html
