Before we dive into, please note:
this bug is only a denial of service (vs. remote coded execution)
this bug only affects iOS devices in certain "region-less" configurations
this bug has been patched as CVE-2018-4290 in iOS 11.4.1
That having been said, this bug is (was) remotely triggerable and on an affected device, will crash any iOS application that is processing remote messages (iMessage
, Facebook Messenger
, WhatsApp
, etc.)!
"Patrick I think China has hacked my iPhone"
Though I'd normally laugh off this rather implausible scenario, I'm working on my emotional intelligence (ha!) so patiently inquired as to why my Taiwanese friend thought this was the case.
She claimed that any time she typed the word Taiwan
or worse, received a message with a Taiwanese flag (🇹🇼) it would crash the application on her (fully patched) iOS device.
I remained a little skeptical but as the following gif shows, this was exactly what was happening...consistently!
I quickly reverted to my rather immature self and sent her multiple Taiwanese flags causing her
iMessage
,Facebook Messenger
, and
In this blog post, we'll illustrate how to analyze and track down the underlying cause of this remote iOS flaw (without requiring a jailbroken 'research' device).
The crashing device was an iPhone 7, running iOS 11.3
(the latest version of iOS at the time):
Device: iPhone 7, iPhone9,1 (US)iOS: 11.3 (15E216)Language: English, followed by ChineseRegion: United StatesJailbroken: No
The following is an abridged crash report, from iMessenger
(MobileSMS
). It was pulled off the device (via 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-CF876DEB6B07CrashReporter Key: 69340bb1126c092b97b9af069f4f6f037466ee0cHardware Model: iPhone9,1Process: MobileSMS [10417]Path: /Applications/MobileSMS.app/MobileSMSIdentifier: com.apple.MobileSMSVersion: 1.0 (5.0)Code Type: ARM-64 (Native)Role: ForegroundParent Process: launchd [1]Coalition: com.apple.MobileSMS [2015]Date/Time: 2018-04-18 22:27:24.9896 -0700Launch Time: 2018-04-18 22:26:16.9044 -0700OS Version: iPhone OS 11.3 (15E216)Baseband Version: 3.66.00Report Version: 104Exception Type: EXC_BAD_ACCESS (SIGSEGV)Exception Subtype: KERN_INVALID_ADDRESS at 0x0000000000000000Termination Signal: Segmentation fault: 11Termination Reason: Namespace SIGNAL, Code 0xbTerminating Process: exc handler [0]Triggered by Thread: 6Filtered syslog:None foundThread 0 name: Dispatch queue: com.apple.main-threadThread 0:0 libsystem_kernel.dylib 0x00000001824b3e08 0x1824b3000 + 35921 libsystem_kernel.dylib 0x00000001824b3c80 0x1824b3000 + 32002 CoreFoundation 0x00000001829f6e40 0x182909000 + 9744003 CoreFoundation 0x00000001829f4908 0x182909000 + 9648724 CoreFoundation 0x0000000182914da8 0x182909000 + 485525 GraphicsServices 0x00000001848f7020 0x1848ec000 + 450886 UIKit 0x000000018c8f578c 0x18c5d8000 + 32664447 MobileSMS 0x0000000100e1867c 0x100df8000 + 1327328 libdyld.dylib 0x00000001823a5fc0 0x1823a5000 + 4032....Thread 6 name: Dispatch queue: com.apple.ResponseKitThread 6 Crashed:0 CoreFoundation 0x0000000182922efc 0x182909000 + 1062361 CoreEmoji 0x00000001886b2354 0x1886a6000 + 500042 CoreEmoji 0x00000001886b2354 0x1886a6000 + 500043 CoreEmoji 0x00000001886b2c80 0x1886a6000 + 523524 CoreEmoji 0x00000001886a8ebc 0x1886a6000 + 119645 ResponseKit 0x00000001968754ac 0x19683d000 + 2305726 ResponseKit 0x0000000196872e9c 0x19683d000 + 2208287 ResponseKit 0x00000001968739b4 0x19683d000 + 2236688 ResponseKit 0x0000000196862e78 0x19683d000 + 1552569 ResponseKit 0x0000000196862c00 0x19683d000 + 15462410 ResponseKit 0x00000001968619f0 0x19683d000 + 15000011 libdispatch.dylib 0x0000000182340b24 0x18233f000 + 694812 libdispatch.dylib 0x0000000182340ae4 0x18233f000 + 688413 libdispatch.dylib 0x000000018234aa38 0x18233f000 + 4767214 libdispatch.dylib 0x000000018234b380 0x18233f000 + 5004815 libdispatch.dylib 0x000000018234bd4c 0x18233f000 + 5255616 libdispatch.dylib 0x000000018235411c 0x18233f000 + 8630017 libsystem_pthread.dylib 0x0000000182673e70 0x182673000 + 369618 libsystem_pthread.dylib 0x0000000182673b08 0x182673000 + 2824Thread 6 crashed with ARM Thread State (64-bit):x0: 0x0000000000000000 x1: 0x00000001add1ad38 x2: 0x0000000000000000 x3: 0x00000001ad364438x4: 0x0000000000000000 x5: 0x0000000000000001 x6: 0x0000000000000000 x7: 0x0000000000000000x8: 0x0000000000000000 x9: 0x00000001b4e15930 x10: 0x0000000ffffffff8 x11: 0x0000000000000040x12: 0xffffffffffffffff x13: 0x0000000000000001 x14: 0x0000000000000000 x15: 0x00002d0000002d00x16: 0x0000000000000000 x17: 0x0000000000002d00 x18: 0x0000000000000000 x19: 0x0000000000000000x20: 0x00000001add1ad38 x21: 0x0000000000000000 x22: 0x0000000000000000 x23: 0x00000001c4864cc0x24: 0x00000001000404ef x25: 0x0000000000050000 x26: 0x0000000103d059e4 x27: 0x0000000103d059e4x28: 0x0000000000000000 fp: 0x000000016f1a5b20 lr: 0x00000001886b2354sp: 0x000000016f1a5b00 pc: 0x0000000182922efc cpsr: 0x80000000Binary Images:0x100df8000 - 0x100e43fff MobileSMS arm64 /Applications/MobileSMS.app/MobileSMS0x182909000 - 0x182c9ffff CoreFoundation arm64 /System/Library/Frameworks/CoreFoundation.framework/CoreFoundation0x1886a6000 - 0x1886b7fff CoreEmoji arm64 /System/Library/PrivateFrameworks/CoreEmoji.framework/CoreEmoji0x19683d000 - 0x19693ffff ResponseKit arm64 /System/Library/PrivateFrameworks/ResponseKit.framework/ResponseKit
We'll walk thru this in more detail, but the tl;dr version is that a EXC_BAD_ACCESS (SIGSEGV)
(Exception Subtype: KERN_INVALID_ADDRESS
) occurred at address 0x0000000000000000
.
Such crashes are usually indicative of a NULL
-pointer dereference. Here, it appears that the fault was triggered when iOS was performing some type of emoji processing (which fits the trigger of a Taiwanese flag being received by iMessenger
).
Other relevant information in the iOS crash report includes:
0x0000000182922efc
)Our goal is to now track down the cause of the crash. That is say, why did the instruction at 0x0000000182922efc
dereference a NULL
-pointer?
We'll start by reversing the dynamic libraries that appear in the call stack, (ResponseKit
, CoreEmoji
, and CoreFoundation
). Specifically we'll examine the code at the addresses within these dylibs that appear in the call stack (stack backtrace).
Since my friend's phone was not jailbroken, we can't just grab the dylib binaries from the device...we have to get them from elsewhere. Turns out the easiest is from the iOS 11.3
restore image. Such restore images contain the iOS system binaries, such as the dylibs we're after. We can grab the iOS 11.3
restore image (iPhone_4.7_P3_11.0_11.3_15E216_Restore.ipsw
) from ipsw.me.
Once this file is downloaded, we can mount the 058-97716-127.dmg
disk image via the hdiutil
command:
$ 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
Since the dylibs we're after are embedded in the dyld
shared cache (dyld_shared_cache_arm64
) we have to extract them.
Using jtool this is fairly straight forward. Simply specify the name of the dylib to extract (e.g.CoreEmoji
) and then the path to the shared cache:
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
The following dylibs (which were each referenced in the call stack of the crashing thread) were extracted:
dyld_shared_cache_arm64_CoreEmojiversion: 69.3.0sha1: 20F6BECF7C76A3FEAFEB8D2321F593388A3CB9B6dyld_shared_cache_arm64_CoreFoundationversion: 1452.23.0sha1: AD3A226884BB3612694B9AB37DF18F42452D5139dyld_shared_cache_arm64_ResponseKitversion 109.0.0sha1: BDA7F1F329321C20539499EAF1C36693823CF60E
Unfortunately, we have to symbolicate these dylibs as the majority of their symbols have all been 'redacted':
$ 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>
...
First though, let's rebase the extracted iOS dylibs so that their addresses (when viewed in a disassembler/decompiler) will match those in the crash report.
To rebase each extracted dylib, we use the base addresses found in the crash report (for example 0x1886a6000
for CoreEmoji
). In Hopper
, one can rebase a binary via the Modify
-> Change File Base Address...
:
Once the dylibs are rebased, we can tackle the symbolication (or lack thereof) issue.
I wasn't sure the 'best' way to do this, so I simply leveraged the macOS versions of each dylib. Specifically, I 'matched' the symbolicated x64 disassembly (macOS) to the unsymbolicated arm64 disassembly (iOS). Though somewhat of a manual process, this worked great!
For example, take the addresses 0x0000000196862c00
(from frame #9, in the call stack). Here's the full decompilation of the method (within the iOS ResponseKit
dylib) which contains address 0x0000000196862c00
:
//iOS ResponseKitint <redacted>
If we decompile macOS's ResponseKit
(/System/Library/PrivateFrameworks/ResponseKit.framework
) we can find the "matching" x64 decompilation in the RKMessageResponseManager
responsesForMessageImp:maximumResponses:forConversationHistory:forContext:withLanguage:options:
method (note the reference to the RKMessageResponseDontOverrideLanguageID
symbol):
/* @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
Now we know the int <redacted>_194d0ab58
method in the iOS ResponseKit
dylib, is really the RKMessageResponseManager
responsesForMessageImp:maximumResponses:forConversationHistory:forContext:withLanguage:options:
method.
Once the dylibs are rebased and (manually) symbolicated, it makes understanding the bug a lot easier, as the method names are fairly descriptive as to their purpose(s).
We'll start by analyzing the addresses in the call stack of the crashing thread (thread #6) in order to uncover the root cause of this remote iOS bug.
Skipping over the (likely spurious) call into libdispatch.dylib
we start at stack frame #10 and map each address to the symbolicated method (or block) it falls into:
#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 0x00000001886a8ebcCEMStringContainsEmoji#3 CoreEmoji 0x00000001886b2c80unnamed subroutine#2 CoreEmoji 0x00000001886b2354unnamed subroutine#1 CoreEmoji 0x00000001886b2354unnamed subroutine#0 CoreFoundation 0x0000000182922efcCFStringCompare + 0x38
Ok so what's going on? Well looks like when a message is received, ResponseKit
classifies the message, and (if some classification is true?) invokes the +[RKUtilities removeEmoji:]
method. This method calls into the CoreEmoji
dylib in order to apparently perform the actual removal of the emoji(s).
Why would iOS want to remove an emoji? We'll get to that shortly!
After calling into some unnamed subroutines, CoreEmoji
invokes the CFStringCompare
function, which crashed at an instruction found at address 0x0000000182922efc
The address
0x0000000182922efc
is the address of the faulting instruction.
It is the final address in the call stack (i.e. frame #0), as well as in thepc
(program counter) register in the "ARM Thread State" section of the crash report.
What is instruction within CFStringCompare
at 0x0000000182922efc
?
0000000180dcaefc ldr x8, [x21]
The ldr
arm instruction loads a "program-relative or external address into a register" (arm). Here it's attempting to dereference and load the value from the x21
register into the x8
register.
Looking at the "ARM Thread State" section of the crash report, shows us that the x21
register is NULL
(0x0000000000000000
) at the time of the crash:
Thread 6 crashed with ARM Thread State (64-bit):x0: 0x0000000000000000 x1: 0x00000001add1ad38 x2: 0x0000000000000000 x3: 0x00000001ad364438x4: 0x0000000000000000 x5: 0x0000000000000001 x6: 0x0000000000000000 x7: 0x0000000000000000x8: 0x0000000000000000 x9: 0x00000001b4e15930 x10: 0x0000000ffffffff8 x11: 0x0000000000000040x12: 0xffffffffffffffff x13: 0x0000000000000001 x14: 0x0000000000000000 x15: 0x00002d0000002d00x16: 0x0000000000000000 x17: 0x0000000000002d00 x18: 0x0000000000000000 x19: 0x0000000000000000x20: 0x00000001add1ad38 x21: 0x0000000000000000 x22: 0x0000000000000000 x23: 0x00000001c4864cc0x24: 0x00000001000404ef x25: 0x0000000000050000 x26: 0x0000000103d059e4 x27: 0x0000000103d059e4x28: 0x0000000000000000 fp: 0x000000016f1a5b20 lr: 0x00000001886b2354sp: 0x000000016f1a5b00 pc: 0x0000000182922efc cpsr: 0x80000000
If one tries to dereference a NULL
address (pointer), this will crash with a EXC_BAD_ACCESS
(SIGSEGV)` ...which is the exact reason given in the crash report:
Exception Type: EXC_BAD_ACCESS (SIGSEGV)Exception Subtype: KERN_INVALID_ADDRESS at 0x0000000000000000
So what is the value in x21
supposed to be? ...well, obviously a valid address of something.
But what? Looking at the arm64 disassembly of the instructions (within the CFStringCompare
function) that precede the faulting instruction, we can see it's the first parameter to passed in to CFStringCompare
.
_CFStringCompare:0000000182922ec4 stp x22, x21, [sp, #-0x30]!0000000182922ec8 stp x20, x19, [sp, #0x10]0000000182922ecc stp x29, x30, [sp, #0x20]0000000182922ed0 add x29, sp, #0x200000000182922ed4 mov x19, x20000000182922ed8 mov x20, x10000000182922edc mov x21, x00000000182922ee0 tbz x21, 0x3f, loc_182922efc ;take thisloc_182922ee4:0000000182922ee4 adrp x8, #0x1b35190000000000182922ee8 ldr x1, [x8, #0x308]0000000182922eec mov x0, x210000000182922ef0 bl 0x181c1c9000000000182922ef4 mov x3, x00000000182922ef8 b loc_182922fd0loc_182922efc:0000000182922efc ldr x8, [x21] ; b00m, we crash as x21 is NULL
Maybe the decompilation is more illustrative:
_CFStringComparer21 = theString1;if ((r21 & 0xffffffff80000000) != 0x0)else
In the disassembly at 0x0000000182922edc
we can see that the first parameter (passed in the x0
register) is moved into the x21
register:
mov x21, x0
Following a test at 0x0000000182922ee0
(to detect if the pointer is 'tagged'), the code jumps to address 0x0000000182922efc
where it dereferences the NULL
x21
register, and thus crashes.
The check on the register
x21
(implemented in this assembly instruction;tbz x21, 0x3f, loc_182922efc
) is a check to see if the pointer is 'tagged'.
"Tagged pointers were introduced in iOS 7 and Mac OS X 10.7 for 64-bit architectures. A tagged pointer is a special pointer with data stored directly into the pointer instead of doing memory allocations. This has obvious performance advantages." blog.timac.org
The function definition for CFStringCompare
is:
CFComparisonResult CFStringCompare(CFStringRef theString1, CFStringRef theString2, CFStringCompareFlags compareOptions);
The first parameter is a CFStringRef
creatively named theString1
. Since the crash is a dereference of this first parameter (x0
, moved into x21
), we now know that something is passing in a NULL
value for the theString1
parameter!
Hooray, we've figured the immediate cause of the crash: a NULL
string passed to CFStringCompare
.
Let's look back up the call stack trace to find out why such a NULL
value is being erroneously passed in!
Turns out we don't have to look far. Recall that CFStringCompare
is invoked by an unnamed function in CoreEmoji.dylib
(dyld_shared_cache_arm64_CoreEmoji
) at address 0x00000001886b22ec
As the the extracted CoreEmoji
binary (from the dyld shared cache) is not symbolicated, it's simpler just work off the disassembled code of this subroutine from macOS version of the dylib.
Below is the decompilation of both (to illustrate the code in the macOS and iOS versions are the same):
//iOS (arm64)int <redacted>_186b5a2ec
//macOS (x64)int
In the arm64 decompilation, the following line represents the call into CFStringCompare
(which crashes):
r0 = loc_182922ec4(r19, 0x1add1ad38, 0x0);
The register r19
is the first parameter (theString1
) that was NULL
, and thus triggered the crash.
Looking up a few lines we can see r19
is set as the return value of a call to loc_1829387c8()
;
r19 = loc_1829387c8();
Thanks to the macOS symbolicated decompilation, we can see this is a call to CFLocaleGetValue()
.
Apple documents this function:
CFTypeRef CFLocaleGetValue(CFLocaleRef locale, CFLocaleKey key);
Returns the corresponding value for the given key of a locale’s key-value pair.
From the decompilation we can determine the locale is the return value from CFLocaleCopyCurrent()
, while the key is the _kCFLocaleCountryCode
.
Thus, the original source code likely looked something like this:
CFLocaleRef locale = ;CFStringRef countryCode = ;
Immediately after this code is a check against a boolean flag (byte_1b1c9bb00
on iOS, byte_128f0
on macOS).
Sticking with the symbolicated macOS dylib, we can find a cross-reference (x-ref) to this value to figure out where it's set (sub_ba72
):
void
This code (sub_ba72
) determines the user's current 'Country' preference.
If it's not equal to China ("CN"
) the flag is set to 0x1
(true).
If the country is China, or the CFPreferencesCopyValue()
failed and returned NULL
, the flag is set to 0x0
(false).
My friend's phone's region and language were not set to "CN"
(a least in the UI) - so this flag should (AFAIK) be set to 0x1
(true):
...but since the code took the else
clause (in turn calling CFStringCompare()
), it would indicate that this flag would have to be 0x0
.
//check some flag ('CN')if (*(int8_t *)byte_1b1c9bb00 != 0x0)//we take this pathelse
One explanation is that the call to CFPreferencesCopyValue(@"Country" ...)
failed for some reason which would also set the flag to 0x0
. Or the code thought the phone's locale was set to "CN"
for some (unknown) reason?
Regardless, CFStringCompare
was called with a first parameter (register r19
) set to NULL
:
//call CFStringCompare()// first parameter is NULL, and thus crashes// second parameter is @"CN"r0 = ;
Again note, the r19
register can only be NULL
if the call to CFLocaleGetValue()
fails (i.e returns NULL
).
One explanation is that the call to CFLocaleCopyCurrent
returned NULL
, which in turn would cause CFLocaleGetValue
to also return NULL
(which in turn would pass NULL
to CFStringCompare()
and thus crash).
If we look at other places in Apple's code such as their CFStringCompareWithOptionsAndLocale
function, we can see here they check the return value of CFLocaleCopyCurrent()
:
locale = ;langCode = ((NULL == locale) ? NULL : (const uint8_t *));
...this implies CFLocaleCopyCurrent()
can indeed fail, and return NULL
(and thus should be checked!)
Unfortunately at this point, my understanding of the situation comes to an end. That is to say, I'm not sure why under what conditions: CFLocaleGetValue(CFLocaleCopyCurrent(), kCFLocaleCountryCode)
can return NULL
....but it can, and this is not checked! And thus CFStringCompare()
is invoked with NULL
and the application comes crashing down!
Apple weights in here, noting:
Under certain conditions if a device’s language/locale settings were set incorrectly, i.e. missing regionCode, it can return NULL. To trigger this the device must be set in a unsupported region-less state.
After two+ years of being unable to type 'Taiwan' or being remotely DOS'd anytime her phone received an Taiwanese flag emoji the fix (kudos to my friend Josh S. for the idea!), was simply to toogle the region from US, to China, then back to US.
I'm not 100% sure why (or how this fixed it), but I'm guessing it either set the 'Country' value to 'US' so the boolan flag (at byte_1b1c9bb00
) was set now to 0x1
, meaning CFStringCompare()
was never called....or, that the calls to CFLocaleCopyCurrent()/CFLocaleGetValue()
no longer returned NULL
, meaning a valid string was passed to CFStringCompare()
.
Since I wasn't sure how many other iOS users were affected, I also reported the bug to Apple. They assigned it CVE-2018-4290
and patched it in iOS 11.4.1
:
I haven't had a chance to reverse Apple's patch, but I suggested the following as a simple fix:
To avoid this crash the code should likely just check the result of the call to CFLocaleGetValue()
and if its fails (i.e. returns NULL
), skip the call to CFStringCompare()
:
CFLocaleRef locale = ;CFStringRef countryCode = ;//fix!// make sure to check this!!if(NULL != countryCode)//otherwise handle case where `countryCode` is NULLelse
So far this blog has been a technical deep-dive which uncovered and explained the technical reason of a (remote) iOS crash. However, there still remains an unanswered but rather intriguing question: "What was this code trying to accomplish anyways?"
Recall that:
the crash was trigged by typing Taiwan
or by receiving any message with a Taiwanese flag (🇹🇼).
the methods leading up to the crash (e.g. removeEmoji
) seem to be related to the removal of emojis from a received message.
various code (just prior) to the faulting instruction is checking the user's device’s language/locale settings for China ("CN"
).
the second parameter passed to CFStringCompare
(the function that crashes), is set to ("CN"
).
So much China! ...hrmm so what gives!
The (apparent) answer can be found on the emojipedia website which states:
This flag is hidden from the emoji keyboard on iOS devices where the region is set to China.
Chinese iPhones won't display this flag and will instead show a missing character tofu (☒).
A forum on the MacRumors website, titled, "Apple censors Taiwan flag on iPhones in China" discusses this in more details and provides proof, confirming that on "iPhones in China" the Taiwanese flag is indeed, not shown:
You can test this too. Just change your Region to China (Via
System Prefences
,General
,Language & Region
,Region
). Taiwanese flags will now be rendered as blank white box with an 'X' thru it!
Does Apple really add code to iOS to appease the Chinese government? Of course! And when that code is buggy, their users suffer.
Though Apple loves to exude an aura of 'users first', the reality is they are first and foremost a corporation. As such their primary objective is always profit 🤑 #truth
In this blog post, we tracked down the cause of a remote iOS flaw.
Though its impact was limited to a denial of service (NULL
-pointer dereference) it made for an interesting case study of analyzing iOS code.
...and if Apple hadn't tried to appease the Chinese government in the first place, there would be no bug!