轉(zhuǎn)自 http://xelz.info/blog/2019/01/11/ios-code-signature-4/,版權(quán)歸原作者所有
導(dǎo)航
- 一口氣讀完,大約需要40-60分鐘
- 分步閱讀
- 細(xì)說iOS代碼簽名(一):簽名的作用及原理
- 細(xì)說iOS代碼簽名(二):開發(fā)者證書、Entitlements、Provisioning Profile
- 細(xì)說iOS代碼簽名(三):簽名的過程及代碼簽名的數(shù)據(jù)結(jié)構(gòu)
- 細(xì)說iOS代碼簽名(四):簽名校驗(yàn)、越獄、重簽名
0x06 簽名的校驗(yàn)
簽名的校驗(yàn)并非一次性完成,在安裝、啟動(dòng)、和運(yùn)行時(shí)有著不同的校驗(yàn)規(guī)則。
安裝
App安裝時(shí)的校驗(yàn)由位于iOS設(shè)備上的/usr/lib/libmis.dylib (dyld_shared_cache)提供。

App的安裝是由/usr/libexec/installd完成的,installd會(huì)通過libmis.dylib校驗(yàn)ProvisioningProfile、Entitlements及簽名的合法性,并遞歸地校驗(yàn)簽名時(shí)每一個(gè)步驟生成的哈希值:CDHash, Code Directory, _CodeSignature/CodeResources。
$ otool -L installd | grep mis
/usr/lib/libmis.dylib (compatibility version 1.0.0, current version 1.0.0)
$ nm installd | grep ValidateSignature
U _MISValidateSignatureAndCopyInfo
U _kMISValidationOptionValidateSignatureOnly
啟動(dòng)
進(jìn)程啟動(dòng)時(shí),loader會(huì)先將可執(zhí)行文件加載到虛擬內(nèi)存,在加載的過程中mach_loader會(huì)自動(dòng)解析MachO文件中的LC_CODE_SIGNATURE并進(jìn)行校驗(yàn),可以參考mach_loader的代碼 bsd/kern/mach_loader.c

load_code_signature在解析完簽名的數(shù)據(jù)后會(huì)調(diào)用mac_vnode_check_singature函數(shù)進(jìn)行驗(yàn)證,而這個(gè)函數(shù)會(huì)被名為AFMI(AppleMobileFileIntegrity)的內(nèi)核擴(kuò)展(kext)通過Hook的方式接管,而AFMI只是一層殼,最終也是調(diào)用了libmis.dylib來實(shí)現(xiàn)簽名的校驗(yàn),這一校驗(yàn)過程基本與安裝時(shí)一致,防止安裝后的篡改。
需要注意的是,加載過程中為了提升加載效率,簽名校驗(yàn)并不會(huì)去檢查Code Directory與實(shí)際的代碼是否匹配,僅僅只檢查了CMS Signature及CDHash的合法性。
運(yùn)行時(shí)
當(dāng)一頁代碼被加載到虛擬內(nèi)存后,會(huì)立即觸發(fā)page fault,此時(shí)內(nèi)核中的vm_fault函數(shù)會(huì)被調(diào)用,緊接著調(diào)用vm_fault_enter,在vm_fault_enter的實(shí)現(xiàn)中會(huì)判斷代碼頁是否需要簽名校驗(yàn),并執(zhí)行校驗(yàn)的操作,參考代碼osfmk/vm/vm_fault.c
kern_return_t vm_fault_enter(...) {
// ...
/* Validate code signature if necessary. */
if (VM_FAULT_NEED_CS_VALIDATION(pmap, m, object)) {
vm_object_lock_assert_exclusive(object);
if (m->cs_validated) {
vm_cs_revalidates++;
}
/* VM map is locked, so 1 ref will remain on VM object -
* so no harm if vm_page_validate_cs drops the object lock */
vm_page_validate_cs(m);
}
// ...
}
對(duì)于宏VM_FAULT_NEED_CS_VALIDATION的解釋是
/*
* CODE SIGNING:
* When soft faulting a page, we have to validate the page if:
* 1. the page is being mapped in user space
* 2. the page hasn't already been found to be "tainted"
* 3. the page belongs to a code-signed object
* 4. the page has not been validated yet or has been mapped
for write. */
#define VM_FAULT_NEED_CS_VALIDATION(pmap, page)
((pmap) != kernel_pmap /*1*/ && !(page)->cs_tainted /*2*/ && (page)->object->code_signed /*3*/ && (!(page)->cs_validated || (page)->wpmapped /*4*/))
vm_page_validate_cs會(huì)計(jì)算當(dāng)前代碼頁的哈希值,并與簽名中CodeDirectory記錄的值進(jìn)行比對(duì),完成代碼簽名的驗(yàn)證。如果不符,且不滿足系統(tǒng)預(yù)設(shè)的例外條件,則會(huì)向內(nèi)核發(fā)出CS_KILL指令,將進(jìn)程結(jié)束。
至此簽名的校驗(yàn)流程就全部完成了。
0x07 越獄與重簽名
越獄
越獄之后,簽名校驗(yàn)機(jī)制會(huì)被破壞掉,否則用于實(shí)現(xiàn)越獄的代碼自身就無法運(yùn)行。比如在iOS6/7時(shí)代,典型的方式是替換 libmis.dylib中的_MISValidateSignature函數(shù),使其永遠(yuǎn)返回驗(yàn)證成功,簡(jiǎn)單粗暴但很有效,因此越獄的設(shè)備可以不受簽名限制運(yùn)行任意程序。但是單純解決掉這個(gè)函數(shù)只是解決了MachO文件的Load問題,運(yùn)行時(shí)仍然會(huì)有沙盒和Code Directory的校驗(yàn),想要對(duì)系統(tǒng)完全的控制權(quán)必須同時(shí)解決掉這兩個(gè)問題。
由于沙盒機(jī)制的實(shí)現(xiàn)分散在系統(tǒng)的各個(gè)角落,沒有簡(jiǎn)單的方式可以將沙盒一刀切地屏蔽掉,因此一般越獄并不會(huì)破壞掉沙盒。但因?yàn)樵姜z設(shè)備簽名校驗(yàn)機(jī)制被繞過,不再會(huì)根據(jù)embedded.mobileprovision文件檢查Entitlements的合法性,因此我們可以在沙盒范圍內(nèi),聲明任意的權(quán)限。Code Directory的校驗(yàn)在內(nèi)核層,破解難度相對(duì)較大,并且完全沒有必要進(jìn)行破解,因?yàn)镃ode Directory只是單純地校驗(yàn)未加密的哈希值而已,只需要按照代碼簽名的格式做好Code Directory即可。
越獄之父Saurik為此創(chuàng)造了ldid這個(gè)工具,用于給越獄設(shè)備上的程序制造"假"的簽名。使用ldid進(jìn)行簽名只需要指定一個(gè)可選的Entitlements文件,簽名之后,產(chǎn)生的LC_CODE_SIGNATURE中只會(huì)兩個(gè)有效的Blob,分別是 Code Directory和 Entitlements,并沒有最重要的CMS Signature部分,因?yàn)?code>_MISCalidateSignature永遠(yuǎn)都會(huì)告訴系統(tǒng)簽名是正確的。
$ cp TestCodeSign TestCodeSign.ldid
$ ldid -Sxxx.entitlements TestCodeSign.ldid
$ jtool --sig TestCodeSign.ldid -arch arm64
Blob at offset: 54016 (928 bytes) is an embedded signature
Code Directory (442 bytes)
...
Empty requirement set (12 bytes)
Entitlements (424 bytes) (use --ent to view)
重簽名
有的時(shí)候出于各種原因,我們需要對(duì)一個(gè)App進(jìn)行重簽名,然后在自己的設(shè)備上進(jìn)行測(cè)試?;仡櫼幌潞灻谋貍錀l件:
- 開發(fā)者證書,以及對(duì)應(yīng)的密鑰
- Entitlements文件
- embedded.mobileprovision
開發(fā)者證書和密鑰我們已經(jīng)有了,對(duì)于Entitlements和embedded.mobileprovision文件,為了確保重簽后的App能夠正常運(yùn)行,必須使用和原App相同或者至少包含原App所需權(quán)限的Entitlements文件。這個(gè)并不難操作,只需要新建一個(gè)工程,開啟相應(yīng)的功能,讓Xcode自動(dòng)為我們生成即可。但是Entitlements文件中還有一些跟Team ID和App ID相關(guān)的配置,這兩個(gè)是沒有辦法偽造的,因?yàn)槲覀儾荒苁褂靡呀?jīng)被其他開發(fā)者注冊(cè)過的ID。使用自己的ID一般也不會(huì)有什么問題,但在某些情況下可能導(dǎo)致最終的程序邏輯出現(xiàn)異常,這根具體的代碼實(shí)現(xiàn)細(xì)節(jié)有關(guān)。
現(xiàn)在,只要確保有正確的Entitlements文件,Provisioning Profile與Entitlements文件匹配,且包含重簽時(shí)使用的證書及目標(biāo)設(shè)備的UUID,就可以進(jìn)行重簽名了,如果重簽名后無法安裝,請(qǐng)檢查Provisioning Profile文件是否滿足上述條件。
Entitlements文件中還標(biāo)識(shí)了application-identifier,也就是Bundle ID,正常簽名的App中,這個(gè)值和Info.plist中的CFBundleIdentifier的值是相同的,但實(shí)際在簽名校驗(yàn)過程中,系統(tǒng)并不會(huì)檢查二者是否一致。因此即使Entitlements中與Info.plist文件使用了不同的Bundle ID,理論上也不會(huì)影響重簽名之后的運(yùn)行。
需要注意,App中除了可執(zhí)行程序文件外,還會(huì)可能會(huì)有Frameworks及Plugins,里面都會(huì)包含二進(jìn)制的代碼文件,他們的哈希值也會(huì)被存儲(chǔ)在 _CodeSignature/CodeResources中。所有的二進(jìn)制代碼都必須進(jìn)行簽名,而簽名后二進(jìn)制文件的哈希值就會(huì)產(chǎn)生變化,因此需要先對(duì)這兩個(gè)文件夾下的二進(jìn)制文件進(jìn)行簽名,再對(duì)App進(jìn)行簽名。
重簽名的基本流程,使用-f參數(shù)可以強(qiáng)制覆蓋掉已有的簽名
$ # 對(duì)Frameworks及Plugins中的每一個(gè)文件進(jìn)行簽名,此時(shí)不需要指定entitlements
$ codesign -f -s "證書名稱或者SHA1值" Target.app/Frameworks/xxxxx.framework
$ codesign -f -s "證書名稱或者SHA1值" Target.app/Frameworks/libxxxx.dylib
$ ...
$ # 將準(zhǔn)備好的Provisioning Profile拷貝到App根目錄
$ cp ~/Library/MobileDevice/Provisioning\ Profiles/xxxxx.mobileprovision Target.app/embedded.mobileprovision
$ # 對(duì)App進(jìn)行簽名
$ codesign -f -s "證書名稱或者SHA1值" --entitlements resign.entitlements Target.app
0x08 References
| reference | link |
|---|---|
| Code Signing Guide | https://developer.apple.com/... |
| ASN.1 JavaScript decoder | http://lapo.it/asn1js/ |
| Cryptographic Message Syntax (CMS) | https://www.ietf.org/rfc/rfc3852.txt |
| iSign in python | https://github.com/saucelabs/isign |
| CodeSigning (RSACon 2015) | http://newosxbook.com/articles/CodeSigning.pdf |
| jtool | http://www.newosxbook.com/tools/jtool.html |
| mistool | http://newosxbook.com/tools/mistool.html |
| evasi0n7 jailbreak writeup | https://geohot.com/e7writeup.html |
| iOS hacker's handbook | https://books.google.com.hk/books?id=1kDcjKcz9GwC |