iOS 表情符号拒绝服务漏洞详情披露(CVE-2018-4290)

前言

在我们深入讨论之前,请注意:

  • 这个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…来重新建立二进制文件的基地址:

t012c57ac7295f94a20

一旦重设好了,我们就可以解决符号的问题了。

我不确定这样做的最佳方式,所以我只是利用了每个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代码提供了一个有趣的案例研究。

原文地址:https://objective-see.com/blog/blog_0x34.html

上一篇:中国互联网大会共话防范打击通讯信息诈骗 官方点赞腾讯神荼

下一篇:沙箱:挖掘你的安全隐患