在我們深入討論之前,請(qǐng)注意:
話雖如此,這個(gè)bug還是可以遠(yuǎn)程觸發(fā)的,而且可以導(dǎo)致受影響的設(shè)備上任何正在處理遠(yuǎn)程消息(iMessage、Facebook Messenger、WhatsApp等)的iOS應(yīng)用程序都會(huì)崩潰!
“Patrick,我想中國(guó)黑了我的iPhone”
雖然我通常會(huì)對(duì)這種難以置信的情景一笑置之,但我正在研究我的情緒智力,耐心地詢問為什么我的臺(tái)灣朋友會(huì)這樣想。
她聲稱,每當(dāng)她輸入臺(tái)灣或更糟的詞時(shí),都會(huì)收到一條帶有臺(tái)灣旗幟(????)的消息,這會(huì)讓她的iOS設(shè)備上的應(yīng)用程序崩潰。
我仍然有點(diǎn)懷疑,但如下所示,這正是正在發(fā)生的事情!
我給她發(fā)了多個(gè)臺(tái)灣旗幟,導(dǎo)致她的iMessage、Facebook Messenger和WhatsApp持續(xù)崩潰。
在這篇文章中,我們將說明如何分析和跟蹤這個(gè)遠(yuǎn)程iOS缺陷的根本原因。
崩潰的設(shè)備是運(yùn)行iOS 11.3(當(dāng)時(shí)最新版本的iOS)的iPhone 7:
Device: iPhone 7, iPhone9,1 (US)
iOS: 11.3 (15E216)
Language: English, followed by Chinese
Region: United States
Jailbroken: No
以下是iMessenger(MobileSMS)提供的經(jīng)過刪減的崩潰報(bào)告。它是從設(shè)備(通過Settings -> Privacy, Analytics, Analytics Data)獲取的:
{"app_name":"MobileSMS","timestamp":"2018-04-18 22:27:25.13 -0700","app_version":"5.0",
"slice_uuid":"feac9bde-20a2-37c2-86e0-119fb8b9b650","adam_id":0,"build_version":"1.0",
"bundleID":"com.apple.MobileSMS","share_with_app_devs":false,"is_first_party":true,
"bug_type":"109","os_version":"iPhone OS 11.3
(15E216)","incident_id":"9EE5610B-7A0C-4558-895F-CF876DEB6B07","name":"MobileSMS"}
Incident Identifier: 9EE5610B-7A0C-4558-895F-CF876DEB6B07
CrashReporter Key: 69340bb1126c092b97b9af069f4f6f037466ee0c
Hardware Model: iPhone9,1
Process: MobileSMS [10417]
Path: /Applications/MobileSMS.app/MobileSMS
Identifier: com.apple.MobileSMS
Version: 1.0 (5.0)
Code Type: ARM-64 (Native)
Role: Foreground
Parent Process: launchd [1]
Coalition: com.apple.MobileSMS [2015]
Date/Time: 2018-04-18 22:27:24.9896 -0700
Launch Time: 2018-04-18 22:26:16.9044 -0700
OS Version: iPhone OS 11.3 (15E216)
Baseband Version: 3.66.00
Report Version: 104
Exception Type: EXC_BAD_ACCESS (SIGSEGV)
Exception Subtype: KERN_INVALID_ADDRESS at 0x0000000000000000
Termination Signal: Segmentation fault: 11
Termination Reason: Namespace SIGNAL, Code 0xb
Terminating Process: exc handler [0]
Triggered by Thread: 6
Filtered syslog:
None found
Thread 0 name: Dispatch queue: com.apple.main-thread
Thread 0:
0 libsystem_kernel.dylib 0x00000001824b3e08 0x1824b3000 + 3592
1 libsystem_kernel.dylib 0x00000001824b3c80 0x1824b3000 + 3200
2 CoreFoundation 0x00000001829f6e40 0x182909000 + 974400
3 CoreFoundation 0x00000001829f4908 0x182909000 + 964872
4 CoreFoundation 0x0000000182914da8 0x182909000 + 48552
5 GraphicsServices 0x00000001848f7020 0x1848ec000 + 45088
6 UIKit 0x000000018c8f578c 0x18c5d8000 + 3266444
7 MobileSMS 0x0000000100e1867c 0x100df8000 + 132732
8 libdyld.dylib 0x00000001823a5fc0 0x1823a5000 + 4032
....
Thread 6 name: Dispatch queue: com.apple.ResponseKit
Thread 6 Crashed:
0 CoreFoundation 0x0000000182922efc 0x182909000 + 106236
1 CoreEmoji 0x00000001886b2354 0x1886a6000 + 50004
2 CoreEmoji 0x00000001886b2354 0x1886a6000 + 50004
3 CoreEmoji 0x00000001886b2c80 0x1886a6000 + 52352
4 CoreEmoji 0x00000001886a8ebc 0x1886a6000 + 11964
5 ResponseKit 0x00000001968754ac 0x19683d000 + 230572
6 ResponseKit 0x0000000196872e9c 0x19683d000 + 220828
7 ResponseKit 0x00000001968739b4 0x19683d000 + 223668
8 ResponseKit 0x0000000196862e78 0x19683d000 + 155256
9 ResponseKit 0x0000000196862c00 0x19683d000 + 154624
10 ResponseKit 0x00000001968619f0 0x19683d000 + 150000
11 libdispatch.dylib 0x0000000182340b24 0x18233f000 + 6948
12 libdispatch.dylib 0x0000000182340ae4 0x18233f000 + 6884
13 libdispatch.dylib 0x000000018234aa38 0x18233f000 + 47672
14 libdispatch.dylib 0x000000018234b380 0x18233f000 + 50048
15 libdispatch.dylib 0x000000018234bd4c 0x18233f000 + 52556
16 libdispatch.dylib 0x000000018235411c 0x18233f000 + 86300
17 libsystem_pthread.dylib 0x0000000182673e70 0x182673000 + 3696
18 libsystem_pthread.dylib 0x0000000182673b08 0x182673000 + 2824
Thread 6 crashed with ARM Thread State (64-bit):
x0: 0x0000000000000000 x1: 0x00000001add1ad38 x2: 0x0000000000000000 x3: 0x00000001ad364438
x4: 0x0000000000000000 x5: 0x0000000000000001 x6: 0x0000000000000000 x7: 0x0000000000000000
x8: 0x0000000000000000 x9: 0x00000001b4e15930 x10: 0x0000000ffffffff8 x11: 0x0000000000000040
x12: 0xffffffffffffffff x13: 0x0000000000000001 x14: 0x0000000000000000 x15: 0x00002d0000002d00
x16: 0x0000000000000000 x17: 0x0000000000002d00 x18: 0x0000000000000000 x19: 0x0000000000000000
x20: 0x00000001add1ad38 x21: 0x0000000000000000 x22: 0x0000000000000000 x23: 0x00000001c4864cc0
x24: 0x00000001000404ef x25: 0x0000000000050000 x26: 0x0000000103d059e4 x27: 0x0000000103d059e4
x28: 0x0000000000000000 fp: 0x000000016f1a5b20 lr: 0x00000001886b2354
sp: 0x000000016f1a5b00 pc: 0x0000000182922efc cpsr: 0x80000000
Binary Images:
0x100df8000 - 0x100e43fff MobileSMS arm64 <feac9bde20a237c286e0119fb8b9b650> /Applications/MobileSMS.app/MobileSMS
0x182909000 - 0x182c9ffff CoreFoundation arm64 <cf162b3ca2883453b2914300d4f19612> /System/Library/Frameworks/CoreFoundation.framework/CoreFoundation
0x1886a6000 - 0x1886b7fff CoreEmoji arm64 <6d18237f09d23ce6aa6abb287d7aa515> /System/Library/PrivateFrameworks/CoreEmoji.framework/CoreEmoji
0x19683d000 - 0x19693ffff ResponseKit arm64 <4f7abc9a8f803cb2bff0172b8c69f13e> /System/Library/PrivateFrameworks/ResponseKit.framework/ResponseKit
我們將更詳細(xì)地討論這個(gè)問題,但是簡(jiǎn)單說就是在地址0x00000000000000處發(fā)生一個(gè)EXC_BAD_ACCESS (SIGSEGV) (Exception Subtype: KERN_INVALID_ADDRESS)。
這類崩潰通常表示空指針解引用(NULL-pointer dereference)。在這里,這個(gè)錯(cuò)誤似乎是在iOS執(zhí)行某種類型的表情處理時(shí)觸發(fā)的(這與iMessenger接收到的臺(tái)灣標(biāo)志的觸發(fā)相吻合)。
iOS系統(tǒng)崩潰報(bào)告中的其他相關(guān)信息包括:
我們的目標(biāo)是現(xiàn)在追查崩潰的原因。也就是說,為什么0x0000000182922efc上的指令取消引用一個(gè)空指針?
我們將從反轉(zhuǎn)調(diào)用堆棧中出現(xiàn)的動(dòng)態(tài)庫(ResponseKit、CoreEmoji和CoreFoundation)開始。具體來說,我們將檢查在調(diào)用堆棧中出現(xiàn)的這些dylib中的地址的代碼。
因?yàn)槲遗笥训氖謾C(jī)沒有越獄,所以我們只能從設(shè)備中獲取dylib二進(jìn)制文件.我們必須從其他地方獲取它們。事實(shí)證明,最簡(jiǎn)單的是來自于iOS 11.3的恢復(fù)映像。這類還原映像包含iOS系統(tǒng)二進(jìn)制文件,例如我們要尋找的dylib。我們可以從ipsw.me獲取iOS 11.3還原映像(iPhone_4.7_P3_11.0_11.3_15E216_Restore.ipsw)
下載這個(gè)文件后,我們就可以通過hdiutil命令掛載058-97716-127.dmg磁盤映像:
$ hdiutil attach iPhone_4.7_P3_11.0_11.3_15E216_Restore/058-97716-127.dmg expected CRC32 $BDE79F12 /dev/disk2 GUID_partition_scheme
/dev/disk2s1 EFI
/dev/disk2s2 Apple_APFS
/dev/disk3 EF57347C-0000-11AA-AA11-0030654 /dev/disk3s1 41504653-0000-11AA-AA11-0030654 /Volumes/Emet15E216.D10D101D20D201OS
由于我們要尋找的dylib被嵌入到dyld共享緩存(dyld_shared_cache_arm64)中,所以我們必須提取它們。
使用jtool,這是最直接的,只需指定要提取的dylib的名稱(例如CoreEmoji),然后指定共享緩存的路徑:
jtool -e "CoreEmoji" /Volumes/Emet15E216.D10D101D20D201OS/System/Library/Caches/com.apple.dyld/dyld_shared_cache_arm64
Extracting /System/Library/PrivateFrameworks/CoreEmoji.framework/CoreEmoji at 0x6b4e000 into dyld_shared_cache_arm64.CoreEmoji
提取了以下dylib(在崩潰線程的調(diào)用堆棧中分別引用了這些dylib):
yld_shared_cache_arm64_CoreEmoji
version: 69.3.0
sha1: 20F6BECF7C76A3FEAFEB8D2321F593388A3CB9B6
dyld_shared_cache_arm64_CoreFoundation
version: 1452.23.0
sha1: AD3A226884BB3612694B9AB37DF18F42452D5139
dyld_shared_cache_arm64_ResponseKit
version 109.0.0
sha1: BDA7F1F329321C20539499EAF1C36693823CF60E
不幸的是,我們必須符號(hào)化這些dylib,因?yàn)樗麄兊拇蠖鄶?shù)符號(hào)都被“編輯”了:
$ nm dyld_shared_cache_arm64_ResponseKit
0000000194ce6f9c t <redacted>
0000000194ce701c t <redacted>
0000000194ce7090 t <redacted>
0000000194ce70c4 t <redacted>
0000000194ce71e4 t <redacted>
0000000194ce72e0 t <redacted>
0000000194ce72e8 t <redacted>
0000000194ce72f0 t <redacted>
0000000194ce735c t <redacted>
0000000194ce7444 t <redacted>
...
不過,首先讓我們對(duì)提取的iOS dylib進(jìn)行重新定位,以便它們的地址(在反匯編程序/反編譯器中查看時(shí))與崩潰報(bào)告中的地址相匹配。
要對(duì)每個(gè)提取的dylib進(jìn)行重新定位,我們使用崩潰報(bào)告中的基地址(例如CoreEmoji的0x1886a6000)。在Hopper中,可以通過Modify -> Change File Base Address…來重新建立二進(jìn)制文件的基地址:
一旦重設(shè)好了,我們就可以解決符號(hào)的問題了。
我不確定這樣做的最佳方式,所以我只是利用了每個(gè)dylib的MacOS版本。具體來說,我將符號(hào)化的x64匯編(MacOS)與無符號(hào)的ARM64匯編(iOS)“匹配”。雖然有手動(dòng)過程,但這工作得很好!
例如,以地址0x0000000196862c00(來自調(diào)用堆棧中的第9幀)為例。下面是方法的完整反編譯(在IOS ResponseKit dylib中),其中包含地址0x00000196862c00:
//iOS ResponseKit
int <redacted>_194d0ab58(int arg0) {
r25 = loc_19147d5e0(r2);
r22 = loc_19147d5e0(r4);
loc_19147d5e0(r5);
r27 = *_RKMessageResponseDontOverrideLanguageID | r7;
loc_19147d5e0(r6);
loc_19147d5d8(arg0, 0x1b3e37900, r25, r3, r22, r5, &var_58, 0x0, r27);
loc_19147d5e8();
loc_19147d5dc(r26);
loc_19147d5dc(r22);
loc_19147d5dc(r25);
loc_19147d5e0(var_58);
loc_19147d5dc(r20);
loc_19147d5dc(r21);
r0 = loc_19147d7cc(r19);
return r0;
}
如果我們反編譯MacOS的ResponseKit(/System/Library/PrivateFrameworks/ResponseKit.framework),我們可以在RKMessageResponseManager responsesForMessageImp:maximumResponses:forConversationHistory:forContext:withLanguage:options:方法中找到“匹配的”x64反編譯(請(qǐng)注意對(duì)RKMessageResponseDontOverrideLanguageID符號(hào)的引用):
* @class RKMessageResponseManager */
-(void *)responsesForMessageImp:(void *)arg2 maximumResponses:(unsigned long long)arg3 forConversationHistory:(void *)arg4 forContext:(void *)arg5 withLanguage:(void *)arg6 options:(unsigned long long)arg7 {
r14 = [arg2 retain];
r15 = [arg4 retain];
var_38 = [arg5 retain];
var_30 = arg6;
rbx = *_RKMessageResponseDontOverrideLanguageID;
r13 = [arg6 retain];
rax = [self responsesForMessageWithLanguageDetectionImp:r14 maximumResponses:arg3 forConversationHistory:r15 forContext:arg5 withLanguage:&var_30 inputModes:0x0 options:rbx | arg7];
[var_38 release];
[r15 release];
[r14 release];
r14 = [rax retain];
rbx = [var_30 retain];
[r13 release];
[rbx release];
rax = [r14 autorelease];
return rax;
}
現(xiàn)在我們知道iOS ResponseKit dylib中的int_194d0ab58方法實(shí)際上是RKMessageResponseManager responsesForMessageImp:maximumResponses:forConversationHistory:forContext:withLanguage:options:方法(注意對(duì)RKMessageResponseDontOverrideLanguageID符號(hào)的引用)。
一旦對(duì)dylib進(jìn)行了重新基和(手動(dòng))符號(hào)化,就更容易理解這個(gè)bug了,因?yàn)榉椒麑?duì)它們的用途有相當(dāng)?shù)拿枋鲂浴?/p>
我們將從分析崩潰線程(線程6)的調(diào)用堆棧中的地址開始,以揭示這個(gè)遠(yuǎn)程iOS bug的根本原因。
跳過對(duì)libDispatch.dylib的調(diào)用,從堆棧幀#10開始,并將每個(gè)地址映射到它所屬的符號(hào)化方法(或塊):
#10 ResponseKit 0x00000001968619f0
-[RKMessageResponseManager responsesForMessage:maximumResponses:forContext:withLanguage:options:completionBlock:]
#9 ResponseKit 0x0000000196862c00
-[RKMessageResponseManager responsesForMessageImp:maximumResponses:forConversationHistory:forContext:withLanguage:options:]
#8 ResponseKit 0x0000000196862e78
-[RKMessageResponseManager responsesForMessageWithLanguageDetectionImp:maximumResponses:forConversationHistory:forContext:withLanguage:inputModes:options:]:
#7 ResponseKit 0x00000001968739b4
+[RKMessageClassifier messageClassification:withLanguageIdentifier:conversationTurns:]:
#6 ResponseKit 0x0000000196872e9c
-[NSLinguisticTagger languageOfRange:withAdditionalContext:withPreferredLanguages:]
#5 ResponseKit 0x00000001968754ac
+[RKUtilities removeEmoji:]
#4 CoreEmoji 0x00000001886a8ebc
CEMStringContainsEmoji
#3 CoreEmoji 0x00000001886b2c80
unnamed subroutine
#2 CoreEmoji 0x00000001886b2354
unnamed subroutine
#1 CoreEmoji 0x00000001886b2354
unnamed subroutine
#0 CoreFoundation 0x0000000182922efc
CFStringCompare + 0x38
好吧發(fā)生什么事了?看起來是這樣的,當(dāng)接收到消息時(shí),ResponseKit會(huì)對(duì)消息進(jìn)行分類,并且(如果某些分類是真的話)調(diào)用+[RKUtilities removeEmoji:]方法。這方法調(diào)用CoreEmoji動(dòng)態(tài)庫來執(zhí)行實(shí)際的表情符號(hào)移除。
iOS為什么要?jiǎng)h除表情符號(hào)?我們很快就會(huì)講到的!
在調(diào)用一些未命名的子程序后,CoreEmoji調(diào)用CFStringCompare函數(shù),該函數(shù)在地址0x0000000182922efc處的指令處崩潰。
地址0x0000000182922efc是故障指令的地址。它是調(diào)用堆棧(即幀#0)中的最終地址,也是崩潰報(bào)告“ARM線程狀態(tài)”部分中的pc(程序計(jì)數(shù)器)寄存器中的最終地址。
CFStringCompare在0x0000000182922efc有什么指令?
0000000180dcaefc ldr x8, [x21]
ldr arm指令將“程序相對(duì)偏移或外部地址加載到寄存器”(arm)。在這里,它試圖取消引用,并將值從x21寄存器加載到x8寄存器中。
查看崩潰報(bào)告中的“ARM線程狀態(tài)”部分,可以看到x21寄存器在崩潰時(shí)是空的(0x00000000000000):
Thread 6 crashed with ARM Thread State (64-bit):
x0: 0x0000000000000000 x1: 0x00000001add1ad38 x2: 0x0000000000000000 x3: 0x00000001ad364438
x4: 0x0000000000000000 x5: 0x0000000000000001 x6: 0x0000000000000000 x7: 0x0000000000000000
x8: 0x0000000000000000 x9: 0x00000001b4e15930 x10: 0x0000000ffffffff8 x11: 0x0000000000000040
x12: 0xffffffffffffffff x13: 0x0000000000000001 x14: 0x0000000000000000 x15: 0x00002d0000002d00
x16: 0x0000000000000000 x17: 0x0000000000002d00 x18: 0x0000000000000000 x19: 0x0000000000000000
x20: 0x00000001add1ad38 x21: 0x0000000000000000 x22: 0x0000000000000000 x23: 0x00000001c4864cc0
x24: 0x00000001000404ef x25: 0x0000000000050000 x26: 0x0000000103d059e4 x27: 0x0000000103d059e4
x28: 0x0000000000000000 fp: 0x000000016f1a5b20 lr: 0x00000001886b2354
sp: 0x000000016f1a5b00 pc: 0x0000000182922efc cpsr: 0x80000000
如果試圖取消引用空地址(指針),這將與EXC_BAD_ACCESS(SIGSEGV)一起崩潰,這正是崩潰報(bào)告中給出的確切原因:
Exception Type: EXC_BAD_ACCESS (SIGSEGV)
Exception Subtype: KERN_INVALID_ADDRESS at 0x0000000000000000
那么x21中的值應(yīng)該是什么呢?很明顯,一個(gè)有效地址。
但是,查看故障指令之前的指令(在CFStringCompare函數(shù)中)的arm64反匯編代碼,我們可以看到它是傳遞給CFStringCompare的第一個(gè)參數(shù)。
_CFStringCompare:
0000000182922ec4 stp x22, x21, [sp, #-0x30]!
0000000182922ec8 stp x20, x19, [sp, #0x10]
0000000182922ecc stp x29, x30, [sp, #0x20]
0000000182922ed0 add x29, sp, #0x20
0000000182922ed4 mov x19, x2
0000000182922ed8 mov x20, x1
0000000182922edc mov x21, x0
0000000182922ee0 tbz x21, 0x3f, loc_182922efc ;take this
loc_182922ee4:
0000000182922ee4 adrp x8, #0x1b3519000
0000000182922ee8 ldr x1, [x8, #0x308]
0000000182922eec mov x0, x21
0000000182922ef0 bl 0x181c1c900
0000000182922ef4 mov x3, x0
0000000182922ef8 b loc_182922fd0
loc_182922efc:
0000000182922efc ldr x8, [x21] ; b00m, we crash as x21 is NULL
也許反編譯更能說明問題:
_CFStringCompare
r21 = theString1;
if ((r21 & 0xffffffff80000000) != 0x0) {
r3 = loc_181c1c900(r21, *0x1b3519308);
}
else
{
r8 = *r21; //b00m, we crash as this is NULL
}
反匯編0x0000000182922edc,我們可以看到第一個(gè)參數(shù)(在X0寄存器中傳遞)被移動(dòng)到x21寄存器中:
mov x21, x0
在0x0000000182922ee0(檢測(cè)指針是否被“標(biāo)記”)的測(cè)試之后,代碼跳轉(zhuǎn)到地址0x0000000182922efc,在那里取消對(duì)空x21寄存器的引用,從而導(dǎo)致崩潰。
寄存器x21上的檢查(在此程序集指令中實(shí)現(xiàn);TBZ x21、0x3f、loc_182922efc)是檢查指針是否“標(biāo)記”的檢查。
標(biāo)記指針是在iOS 7和MacOSX10.7中為64位架構(gòu)引入的.帶標(biāo)記的指針是一種特殊的指針,它將數(shù)據(jù)直接存儲(chǔ)到指針中,而不是進(jìn)行內(nèi)存分配。這具有明顯的性能優(yōu)勢(shì)。blog.timac.org
CFStringCompare的函數(shù)定義是:
CFComparisonResult CFStringCompare(CFStringRef theString1, CFStringRef theString2, CFStringCompareFlags compareOptions);
第一個(gè)參數(shù)是一個(gè)命名為theString1的CFStringRef。由于崩潰是對(duì)第一個(gè)參數(shù)(X0,移動(dòng)到x21)的取消引用,我們現(xiàn)在知道有些東西正在傳入theString1參數(shù)的空值!
好了,我們已經(jīng)找到了崩潰的直接原因:傳遞給CFStringCompare的空字符串。
讓我們回顧一下調(diào)用堆棧跟蹤,找出為什么會(huì)錯(cuò)誤地傳入這樣一個(gè)空值!
回想一下,CFStringCompare是由地址為0x00000001886b22ec的CoreEmoji.dylib(dyld_shared_cache_arm64_CoreEmoji)中的一個(gè)未命名函數(shù)調(diào)用的。
由于提取的CoreEmoji二進(jìn)制文件(來自dyld共享緩存)不是符號(hào)化的,因此只需從dylib的MacOS版本中刪除這個(gè)子例程的分解代碼就更簡(jiǎn)單了。
下面是這兩個(gè)版本的反編譯代碼(為了說明MacOS和iOS版本中的代碼是相同的):
//iOS (arm64)
int <redacted>_186b5a2ec {
var_10 = r20;
stack[-24] = r19;
r31 = r31 + 0xffffffffffffffe0;
saved_fp = r29;
stack[-8] = r30;
if (*qword_1b1c9baf8 != -0x1) {
dispatch_once(0x1b37f3af8, 0x1add1a6f8);
}
r20 = loc_182938048();
r19 = loc_1829387c8();
loc_1829111e8(r20);
if (*(int8_t *)byte_1b1c9bb00 != 0x0) {
r0 = 0x0;
}
else {
r0 = loc_182922ec4(r19, 0x1add1ad38, 0x0);
if (r0 != 0x0) {
if (CPU_FLAGS & NE) {
r0 = 0x1;
}
}
}
return r0;
}
//macOS (x64)
int sub_b9fe() {
if (*qword_128e8 != -1)
{
dispatch_once(qword_128e8, ^ {/* block implemented at sub_ba72 */ } });
}
rbx = CFLocaleCopyCurrent();
r14 = CFLocaleGetValue(rbx, **_kCFLocaleCountryCode);
CFRelease(rbx);
if (*(int8_t *)byte_128f0 != 0x0) {
rax = 0x0;
}
else {
rax = CFStringCompare(r14, @"CN", 0x0);
rax = rax != 0x0 ? 0x1 : 0x0;
}
return rax;
}
在arm64反編譯中,以下行表示對(duì)CFStringCompare的調(diào)用:
r0 = loc_182922ec4(r19, 0x1add1ad38, 0x0);
寄存器r19是第一個(gè)空參數(shù)(theString1),因此觸發(fā)了崩潰。
再查找?guī)仔校覀兛梢钥吹絩19被設(shè)置為調(diào)用loc_1829387c8()的返回值;
r19 = loc_1829387c8();
多虧了MacOS符號(hào)化的反編譯,我們可以看到這是對(duì)CFLocaleGetValue()的調(diào)用。
Apple記錄了以下功能:
CFTypeRef CFLocaleGetValue(CFLocaleRef locale, CFLocaleKey key);返回區(qū)域設(shè)置的鍵值對(duì)的給定鍵的對(duì)應(yīng)值。
通過反編譯,我們可以確定locale是來自CFLocaleCopyCurrent()的返回值,而鍵是_kCFLocaleCountryCode。
因此,源代碼看起來可能如下所示:
CFLocaleRef locale = CFLocaleCopyCurrent();
CFStringRef countryCode = CFLocaleGetValue (locale, kCFLocaleCountryCode);
這段代碼后面緊接著是對(duì)布爾標(biāo)志的檢查(iOS:byte_1b1c9bb00,MacOS:byte_128f0)。
堅(jiān)持使用符號(hào)化的macOS dylib,我們可以找到這個(gè)值的交叉引用(x-ref),以確定它設(shè)置在哪里(sub_ba72):
void sub_ba72(void * _block) {
rbx = CFPreferencesCopyValue(@"Country", **_kCFPreferencesAnyApplication, **_kCFPreferencesAnyUser, **_kCFPreferencesCurrentHost);
if (rbx != 0x0) {
r14 = CFEqual(rbx, @"CN") != 0x0 ? 0x1 : 0x0;
CFRelease(rbx);
}
else {
r14 = 0x0;
}
*(int8_t *)byte_128f0 = r14;
return;
}
這段代碼(sub_ba72)確定用戶當(dāng)前的‘國(guó)家’首選項(xiàng)。
如果不是中國(guó)(“cn”),則該標(biāo)志設(shè)置為0x1(True)。如果國(guó)家是中國(guó),或者CFPreferencesCopyValue()中止并返回NULL,則標(biāo)志設(shè)置為0x0(False)。
我朋友手機(jī)的區(qū)域和語言沒有設(shè)置為“cn”,所以這個(gè)標(biāo)志(AFAIK)應(yīng)該設(shè)置為0x1(真):
但是,由于代碼用了else(反過來調(diào)用CFStringCompare(),它將指示該標(biāo)志必須為0x0。
//check some flag ('CN')
if (*(int8_t *)byte_1b1c9bb00 != 0x0) {
r0 = 0x0;
}
//we take this path
else {
//call to CFStringCompare() that crashes
r0 = loc_182922ec4(r19, 0x1add1ad38, 0x0);
...
}
一種解釋是,由于某些原因,對(duì)CFPreferencesCopyValue(@“Country”.)調(diào)用失敗,會(huì)將標(biāo)志設(shè)置為0x0。或者代碼認(rèn)為由于某種(未知的)原因,手機(jī)的區(qū)域設(shè)置為“cn”?
無論如何,調(diào)用CFStringCompare時(shí),第一個(gè)參數(shù)(寄存器r19)設(shè)置為NULL:
//call CFStringCompare()
// first parameter is NULL, and thus crashes
// second parameter is @"CN"
r0 = loc_182922ec4(r19, 0x1add1ad38, 0x0);
請(qǐng)注意,只有在對(duì)CFLocaleGetValue()的調(diào)用失敗(即返回NULL)時(shí),r19寄存器才能為空。
一種解釋是,對(duì)CFLocaleCopyCurrent的調(diào)用返回null,這反過來會(huì)導(dǎo)致CFLocaleGetValue也返回null(這反過來會(huì)將null傳遞給CFStringCompare(),從而導(dǎo)致崩潰)。
如果我們查看蘋果代碼中的其他地方,比如它們的CFStringCompareWithOptionsAndLocale函數(shù),可以看到它們?cè)谶@里檢查CFLocaleCopyCurrent()的返回值:
locale = CFLocaleCopyCurrent();
langCode = ((NULL == locale) ? NULL : (const uint8_t *)_CFStrGetLanguageIdentifierForLocale(locale));
這意味著CFLocaleCopyCurrent()確實(shí)可能失敗,并返回NULL(因此應(yīng)該檢查!)
不幸的是,我的理解只到這一點(diǎn)上了。也就是說,我不知道為什么以及在什么條件下:CFLocaleGetValue(CFLocaleCopyCurrent(),kCFLocaleNationalCode)可以返回NULL。但是它可以,而且這是不做檢查的!因此,用NULL調(diào)用CFStringCompare(),應(yīng)用程序就會(huì)崩潰!
蘋果強(qiáng)調(diào):
在某些情況下,如果設(shè)備的語言/區(qū)域設(shè)置不正確,即缺少區(qū)域代碼,則可以返回NULL。若要觸發(fā)此操作,必須將設(shè)備設(shè)置為不支持區(qū)域的無支持狀態(tài)。
兩年多來,她的手機(jī)一直無法輸入“臺(tái)灣”,或者每當(dāng)手機(jī)收到臺(tái)灣旗幟表情符號(hào)時(shí),她的手機(jī)就會(huì)被“遠(yuǎn)程攻擊”,只不過是把這個(gè)地區(qū)從美國(guó)、中國(guó)大陸,再回到美國(guó),一目了然。
我不能百分之百地確定為什么(或者如何修復(fù)它),但我猜它要么將‘Country’值設(shè)置為‘us’,所以現(xiàn)在boolan標(biāo)志(在byte_1b1c9bb00處)被設(shè)置為0x1,這意味著CFStringCompare()從來沒有被調(diào)用過,或者,對(duì)CFLocaleCopyCurrent()/CFLocaleGetValue()的調(diào)用不再返回null,這意味著一個(gè)有效的字符串被傳遞給了CFStringCompare()。
由于我不確定還有多少其他iOS用戶受到影響,我也向蘋果報(bào)告了這個(gè)問題。他們給它分配了CVE-2018-4290,并在iOS 11.4.1中對(duì)其進(jìn)行了修復(fù):
我還沒有機(jī)會(huì)研究蘋果的補(bǔ)丁,但我提出以下建議作為一個(gè)簡(jiǎn)單的解決方案:
為了避免這種崩潰,代碼應(yīng)該只檢查調(diào)用CFLocaleGetValue()的結(jié)果,如果調(diào)用失敗(即返回NULL),則跳過對(duì)CFStringCompare()的調(diào)用:
CFLocaleRef locale = CFLocaleCopyCurrent();
CFStringRef countryCode = CFLocaleGetValue (locale, kCFLocaleCountryCode);
//fix!
// make sure to check this!!
if(NULL != countryCode)
{
CFStringCompare(countryCode, @"CN", 0x0);
}
//otherwise handle case where `countryCode` is NULL
else
{
....
}
到目前為止,本文深入研究揭示并解釋了一個(gè)(遠(yuǎn)程)iOS崩潰的技術(shù)原因。然而,仍然存在一個(gè)尚未回答但相當(dāng)有趣的問題:“不管怎么說,這段代碼到底想要完成什么?”
回顧:
這么多中國(guó)!嗯,那又有什么用呢!
答案可以在表情包網(wǎng)站上找到,上面寫著:
此標(biāo)志隱藏在設(shè)置為中國(guó)的iOS設(shè)備上的表情符號(hào)鍵盤上。中國(guó)的iPhone不會(huì)顯示這個(gè)標(biāo)志,而是會(huì)顯示一個(gè)缺失的字符豆腐(?)。
在本文中,我們找到了遠(yuǎn)程iOS缺陷的原因。
盡管它的影響僅限于拒絕服務(wù)(空指針取消引用),但它為分析iOS代碼提供了一個(gè)有趣的案例研究。