前言
在我们深入讨论之前,请注意:
- 这个bug只是拒绝服务(相对于远程编码执行)。
- 这个bug仅影响某些“无区域”配置中的iOS设备。
- 这个bug在iOS 11.4.1已被修补为CVE-2018-4290。
话虽如此,这个bug还是可以远程触发的,而且可以导致受影响的设备上任何正在处理远程消息(iMessage、Facebook Messenger、WhatsApp等)的iOS应用程序都会崩溃!
背景
“Patrick,我想中国黑了我的iPhone”
虽然我通常会对这种难以置信的情景一笑置之,但我正在研究我的情绪智力,耐心地询问为什么我的台湾朋友会这样想。
她声称,每当她输入台湾或更糟的词时,都会收到一条带有台湾旗帜(??)的消息,这会让她的iOS设备上的应用程序崩溃。
我仍然有点怀疑,但如下所示,这正是正在发生的事情!
我给她发了多个台湾旗帜,导致她的iMessage、Facebook Messenger和WhatsApp持续崩溃。?
在这篇文章中,我们将说明如何分析和跟踪这个远程iOS缺陷的根本原因。
崩溃
崩溃的设备是运行iOS 11.3(当时最新版本的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)提供的经过删减的崩溃报告。它是从设备(通过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
我们将更详细地讨论这个问题,但是简单说就是在地址0x00000000000000处发生一个EXC_BAD_ACCESS (SIGSEGV) (Exception Subtype: KERN_INVALID_ADDRESS)。
这类崩溃通常表示空指针解引用(NULL-pointer dereference)。在这里,这个错误似乎是在iOS执行某种类型的表情处理时触发的(这与iMessenger接收到的台湾标志的触发相吻合)。
iOS系统崩溃报告中的其他相关信息包括:
- 故障指令地址(0x0000000182922efc)
- 崩溃前线程(#6)的调用堆栈
- 相关的dylib(即调用堆栈中的那些)
崩溃分析
我们的目标是现在追查崩溃的原因。也就是说,为什么0x0000000182922efc上的指令取消引用一个空指针?
我们将从反转调用堆栈中出现的动态库(ResponseKit、CoreEmoji和CoreFoundation)开始。具体来说,我们将检查在调用堆栈中出现的这些dylib中的地址的代码。
因为我朋友的手机没有越狱,所以我们只能从设备中获取dylib二进制文件.我们必须从其他地方获取它们。事实证明,最简单的是来自于iOS 11.3的恢复映像。这类还原映像包含iOS系统二进制文件,例如我们要寻找的dylib。我们可以从ipsw.me获取iOS 11.3还原映像(iPhone_4.7_P3_11.0_11.3_15E216_Restore.ipsw)
下载这个文件后,我们就可以通过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(在崩溃线程的调用堆栈中分别引用了这些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
不幸的是,我们必须符号化这些dylib,因为他们的大多数符号都被“编辑”了:
$ 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>
...
不过,首先让我们对提取的iOS dylib进行重新定位,以便它们的地址(在反汇编程序/反编译器中查看时)与崩溃报告中的地址相匹配。
要对每个提取的dylib进行重新定位,我们使用崩溃报告中的基地址(例如CoreEmoji的0x1886a6000)。在Hopper中,可以通过Modify -> Change File Base Address…来重新建立二进制文件的基地址:
一旦重设好了,我们就可以解决符号的问题了。
我不确定这样做的最佳方式,所以我只是利用了每个dylib的MacOS版本。具体来说,我将符号化的x64汇编(MacOS)与无符号的ARM64汇编(iOS)“匹配”。虽然有手动过程,但这工作得很好!
例如,以地址0x0000000196862c00(来自调用堆栈中的第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反编译(请注意对RKMessageResponseDontOverrideLanguageID符号的引用):
* @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;
}
现在我们知道iOS ResponseKit dylib中的int_194d0ab58方法实际上是RKMessageResponseManager responsesForMessageImp:maximumResponses:forConversationHistory:forContext:withLanguage:options:方法(注意对RKMessageResponseDontOverrideLanguageID符号的引用)。
一旦对dylib进行了重新基和(手动)符号化,就更容易理解这个bug了,因为方法名对它们的用途有相当的描述性。
我们将从分析崩溃线程(线程6)的调用堆栈中的地址开始,以揭示这个远程iOS bug的根本原因。
跳过对libDispatch.dylib的调用,从堆栈帧#10开始,并将每个地址映射到它所属的符号化方法(或块):
#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
好吧发生什么事了?看起来是这样的,当接收到消息时,ResponseKit会对消息进行分类,并且(如果某些分类是真的话)调用+[RKUtilities removeEmoji:]方法。这方法调用CoreEmoji动态库来执行实际的表情符号移除。
iOS为什么要删除表情符号?我们很快就会讲到的!
在调用一些未命名的子程序后,CoreEmoji调用CFStringCompare函数,该函数在地址0x0000000182922efc处的指令处崩溃。
地址0x0000000182922efc是故障指令的地址。它是调用堆栈(即帧#0)中的最终地址,也是崩溃报告“ARM线程状态”部分中的pc(程序计数器)寄存器中的最终地址。
CFStringCompare在0x0000000182922efc有什么指令?
0000000180dcaefc ldr x8, [x21]
ldr arm指令将“程序相对偏移或外部地址加载到寄存器”(arm)。在这里,它试图取消引用,并将值从x21寄存器加载到x8寄存器中。
查看崩溃报告中的“ARM线程状态”部分,可以看到x21寄存器在崩溃时是空的(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)一起崩溃,这正是崩溃报告中给出的确切原因:
Exception Type: EXC_BAD_ACCESS (SIGSEGV)
Exception Subtype: KERN_INVALID_ADDRESS at 0x0000000000000000
那么x21中的值应该是什么呢?很明显,一个有效地址。
但是,查看故障指令之前的指令(在CFStringCompare函数中)的arm64反汇编代码,我们可以看到它是传递给CFStringCompare的第一个参数。
_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,我们可以看到第一个参数(在X0寄存器中传递)被移动到x21寄存器中:
mov x21, x0
在0x0000000182922ee0(检测指针是否被“标记”)的测试之后,代码跳转到地址0x0000000182922efc,在那里取消对空x21寄存器的引用,从而导致崩溃。
寄存器x21上的检查(在此程序集指令中实现;TBZ x21、0x3f、loc_182922efc)是检查指针是否“标记”的检查。
标记指针是在iOS 7和MacOSX10.7中为64位架构引入的.带标记的指针是一种特殊的指针,它将数据直接存储到指针中,而不是进行内存分配。这具有明显的性能优势。blog.timac.org
CFStringCompare的函数定义是:
CFComparisonResult CFStringCompare(CFStringRef theString1, CFStringRef theString2, CFStringCompareFlags compareOptions);
第一个参数是一个命名为theString1的CFStringRef。由于崩溃是对第一个参数(X0,移动到x21)的取消引用,我们现在知道有些东西正在传入theString1参数的空值!
好了,我们已经找到了崩溃的直接原因:传递给CFStringCompare的空字符串。
让我们回顾一下调用堆栈跟踪,找出为什么会错误地传入这样一个空值!
回想一下,CFStringCompare是由地址为0x00000001886b22ec的CoreEmoji.dylib(dyld_shared_cache_arm64_CoreEmoji)中的一个未命名函数调用的。
由于提取的CoreEmoji二进制文件(来自dyld共享缓存)不是符号化的,因此只需从dylib的MacOS版本中删除这个子例程的分解代码就更简单了。
下面是这两个版本的反编译代码(为了说明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反编译中,以下行表示对CFStringCompare的调用:
r0 = loc_182922ec4(r19, 0x1add1ad38, 0x0);
寄存器r19是第一个空参数(theString1),因此触发了崩溃。
再查找几行,我们可以看到r19被设置为调用loc_1829387c8()的返回值;
r19 = loc_1829387c8();
多亏了MacOS符号化的反编译,我们可以看到这是对CFLocaleGetValue()的调用。
Apple记录了以下功能:
CFTypeRef CFLocaleGetValue(CFLocaleRef locale, CFLocaleKey key);返回区域设置的键值对的给定键的对应值。
通过反编译,我们可以确定locale是来自CFLocaleCopyCurrent()的返回值,而键是_kCFLocaleCountryCode。
因此,源代码看起来可能如下所示:
CFLocaleRef locale = CFLocaleCopyCurrent();
CFStringRef countryCode = CFLocaleGetValue (locale, kCFLocaleCountryCode);
这段代码后面紧接着是对布尔标志的检查(iOS:byte_1b1c9bb00,MacOS:byte_128f0)。
坚持使用符号化的macOS dylib,我们可以找到这个值的交叉引用(x-ref),以确定它设置在哪里(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)确定用户当前的‘国家’首选项。
如果不是中国(“cn”),则该标志设置为0x1(True)。如果国家是中国,或者CFPreferencesCopyValue()中止并返回NULL,则标志设置为0x0(False)。
我朋友手机的区域和语言没有设置为“cn”,所以这个标志(AFAIK)应该设置为0x1(真):
但是,由于代码用了else(反过来调用CFStringCompare(),它将指示该标志必须为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);
...
}
一种解释是,由于某些原因,对CFPreferencesCopyValue(@“Country”.)调用失败,会将标志设置为0x0。或者代码认为由于某种(未知的)原因,手机的区域设置为“cn”?
无论如何,调用CFStringCompare时,第一个参数(寄存器r19)设置为NULL:
//call CFStringCompare()
// first parameter is NULL, and thus crashes
// second parameter is @"CN"
r0 = loc_182922ec4(r19, 0x1add1ad38, 0x0);
请注意,只有在对CFLocaleGetValue()的调用失败(即返回NULL)时,r19寄存器才能为空。
一种解释是,对CFLocaleCopyCurrent的调用返回null,这反过来会导致CFLocaleGetValue也返回null(这反过来会将null传递给CFStringCompare(),从而导致崩溃)。
如果我们查看苹果代码中的其他地方,比如它们的CFStringCompareWithOptionsAndLocale函数,可以看到它们在这里检查CFLocaleCopyCurrent()的返回值:
locale = CFLocaleCopyCurrent();
langCode = ((NULL == locale) ? NULL : (const uint8_t *)_CFStrGetLanguageIdentifierForLocale(locale));
这意味着CFLocaleCopyCurrent()确实可能失败,并返回NULL(因此应该检查!)
不幸的是,我的理解只到这一点上了。也就是说,我不知道为什么以及在什么条件下:CFLocaleGetValue(CFLocaleCopyCurrent(),kCFLocaleNationalCode)可以返回NULL。但是它可以,而且这是不做检查的!因此,用NULL调用CFStringCompare(),应用程序就会崩溃!
苹果强调:
在某些情况下,如果设备的语言/区域设置不正确,即缺少区域代码,则可以返回NULL。若要触发此操作,必须将设备设置为不支持区域的无支持状态。
修复
两年多来,她的手机一直无法输入“台湾”,或者每当手机收到台湾旗帜表情符号时,她的手机就会被“远程攻击”,只不过是把这个地区从美国、中国大陆,再回到美国,一目了然。
我不能百分之百地确定为什么(或者如何修复它),但我猜它要么将‘Country’值设置为‘us’,所以现在boolan标志(在byte_1b1c9bb00处)被设置为0x1,这意味着CFStringCompare()从来没有被调用过,或者,对CFLocaleCopyCurrent()/CFLocaleGetValue()的调用不再返回null,这意味着一个有效的字符串被传递给了CFStringCompare()。
由于我不确定还有多少其他iOS用户受到影响,我也向苹果报告了这个问题。他们给它分配了CVE-2018-4290,并在iOS 11.4.1中对其进行了修复:
我还没有机会研究苹果的补丁,但我提出以下建议作为一个简单的解决方案:
为了避免这种崩溃,代码应该只检查调用CFLocaleGetValue()的结果,如果调用失败(即返回NULL),则跳过对CFStringCompare()的调用:
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
{
....
}
回顾
到目前为止,本文深入研究揭示并解释了一个(远程)iOS崩溃的技术原因。然而,仍然存在一个尚未回答但相当有趣的问题:“不管怎么说,这段代码到底想要完成什么?”
回顾:
- 这起崩溃事件是由键入台湾或接收任何带有台湾旗帜(??)的信息引发的。
- 导致崩溃的方法(例如,RemoveEmoji)似乎与从接收到的消息中删除表情符号有关。
- 故障指令之前的各种代码正在检查中国(“CN”)用户设备的语言/地区设置。
- 传递给CFStringCompare(崩溃的函数)的第二个参数设置为(“cn”)。
这么多中国!嗯,那又有什么用呢!
答案可以在表情包网站上找到,上面写着:
此标志隐藏在设置为中国的iOS设备上的表情符号键盘上。中国的iPhone不会显示这个标志,而是会显示一个缺失的字符豆腐(☒)。
结论
在本文中,我们找到了远程iOS缺陷的原因。
尽管它的影响仅限于拒绝服务(空指针取消引用),但它为分析iOS代码提供了一个有趣的案例研究。
审核人:yiwang 编辑:边边