in ,

Remote iPhone Exploitation Part 2: Bringing Light into the Darkness – a Remote ASLR Bypass, Hacker News

Remote iPhone Exploitation Part 2: Bringing Light into the Darkness – a Remote ASLR Bypass, Hacker News

[ ]

Posted by Samuel Groß, Project ZeroThis post is the second in a series about a remote, interactionless iPhone exploit over iMessage.The first blog post, which introduced the exploited vulnerability , can be found)[ ] **************** [!]

[ ] The initial primitive gained from the vulnerability. is an absolute address dereference in which the read value is afterwards used as an ObjC object. As such, some knowledge of the target address space is required in order to exploit this vulnerability for remote code execution. This blog post describes a way to defeat ASLR remotely without any additional information disclosure vulnerabilities.

First off, the effectiveness of an old technique, heap spraying, is evaluated. Afterwards, a technique is described through which it is possible to infer the base address of the dyld shared cache region given only a memory corruption bug. The released code implements the presented attack and can infer the shared cache base address remotely on vulnerable devices within a couple of minutes.

Heap Spraying on iOS

The goal of heap spraying is to put data of the attacker’s choosing at a known address so that it can be referenced during the exploitation process. Heap spraying should be less effective today than

it was (years ago) ******************** […] ********************** (due to ASLR and a) – bit address space. However, it turns out that on iOS the technique is still fairly usable. In fact, only about 319 MB of data need to be sprayed to put controlled data at a known address. This can easily be demonstrated with the following code snippet: [ ]

************** […] ********************** (


size_t size=0x [!] ;

************** const [key isEqual:] ************

size_t count=([…] * 1505 * 1505) / size; [!]

**************** for […] for [key isEqual:] ************ ([!]) ([…]

inti=0; i

**************** int int [key isEqual:] ************

* chunk=malloc (size); […]

* chunk=0x (**************************************************************************************;

[ ] **************** […] [597]

// Now look at the memory at 0x (********************** […]

Running this in on iOS (eg as part of a custom app) will put 0x at address 0x (************************************************************************************: ********************** [!]

(lldb) x / gx 0x

0x (*************************************************************************************: 0x 0000000 (************************************************************************************************************************************************************************************************************************ 188000000Heap Spraying via iMessage

The next question then is how to best spray a few hundred megabytes of data remotely over iMessage. Basically, there are two ways to do that:

    ************************ [ ] By abusing a memory leak (not an information leak!), A bug in which a chunk of memory is “forgotten” and never freed, and triggering it multiple times until the desired amount of memory has been leaked.

      [ ]By finding and abusing an “amplification gadget”: a piece of code that takes an existing chunk of data and copies it, potentially multiple times, thus allowing the attacker to spray a large amount of memory by only sending a relatively small number of bytes.

        [!]As it turns out, the NSKeyedUnarchiver API provides both primitives and this exploit actually combines the two: it sends messages of around kB, which seems to be the maximum size of inline data, and with that sprays around MB of heap data. It then leaks those 31 MB and repeats the procedure a couple of times to perform the full heap spray.

        However, it is likely possible to Perform the entire spray in a single message by using downloaded attachments (the pbdi key). That way the memory leak wouldn’t be necessary and the bug could be triggered multiple times if necessary. This is left as an exercise for the reader. [!]

        There are many places in the NSKeyedUnarchiver subsystem where memory is leaked. One example are cyclic object graphs, which will never be cleaned up as they keep each other alive – a reference cycle. There is also another way: __NSKeyedCoderOldStyleArrays, which are used during decoding of

        NSValues ​​**************************, and can, due to a quirk in the NSKeyedUnarchiver, always be deserialized regardless of the set of allowed classes. A __NSKeyedCoderOldStyleArray stores its size and a pointer to a malloced chunk containing a number of same-type values, e.g. integers, c-strings, or ObjC objects. Interestingly, when the __NSKeyedCoderOldStyleArray is destroyed, it only frees its backing memory but does not recursively free the objects contained in it. As such, if the array contains pointers to other memory chunks, such as ObjC objects, memory is leaked. This is normally fine, as a __NSKeyedCoderOldStyleArray is only used to temporarily decode the array values ​​of an NSValue. However, as it can also be decoded as a standalone object, it becomes possible to leak a large number of ObjC objects with it.

        [ ] As for the amplification gadget, ACZeroingString instances appear to be the perfect candidate, having already been (ab) used in Natalie’sexploit for CVE – –

        (*******************. As part of its initWithCoder, an ACZeroingString will take an existing NSData object and copy its content into a newly malloc’ed memory chunk.

        [ ] With that, the object graph to spray around 32 MB of heap data looks like this:

        ****************************** (********************

        Here, each ACZeroingString instance copies the NSData’s content into a new malloc buffer and the __NSKeyedCoderOldStyleArray keeps all those ACZeroingString instances alive even after the current message has been fully processed. After sending around 8 of these messages, controlled data will be located at 0x 110000000.**************(************************************ (************************************** [!] ************************

        Having controlled data at a known address is a first step, but likely not enough. Given the existing exploit primitive, it would now be possible to create fake ObjC objects in the heapspray region and have them be used in

        [NSSharedKeySet indexForKey:]

        . However, as the heapspray content is not executable, it is necessary to know the address of code pages before faking objects really becomes possible. How this can be achieved will be discussed next, after a short introduction to some ObjC internals.Objective-C for the ASLR Bypasser

        Shown next is the relevant code snippet from

        [NSSharedKeySet indexForKey:]

        in which the read from a controlled address happens:

        // index is fully controlled, _keys is nullptr

        id candidate=self ->_ keys [index]; [ ]

        if (candidate!=Null) {

        if ([key isEqual:candidate] ({

        return prevLength index;

        [ ] **************** […]

        [ ] **************** […]

        [ ] The

        id [ ] type that is used here represents a reference to an ObjC object for which no more type information is available, not unlike a void * in C. However, unlike C, ObjC has runtime type information, so it is always possible to determine the exact type of an object at runtime, for example through the


        Further, ObjC supports pointer tagging and so a pointer to an ObjC object, for example an id, can generally be one of two things:

          ************************ [ ] An actual pointer to an ObjC object [ ]

            [ ]A pointer-sized value containing both the type and value information

              [!]The layout of objects will be discussed in the next post, where it will be relevant for gaining code execution. However, for this blog post it is already necessary to take a closer look at ObjC tagged pointers.

              NSNumbers [!] ************ […] and very short [ ] **************************************NSStringsare examples for tagged pointers. On Arm 79, an id is considered a tagged pointer if************************************

              its MSB is set [!] ****************. In that case, the corresponding class isstored in a global tablewhich which index is encoded in the tagged pointer. With that, an NSNumber instance storing a 40 – bit integer (in ObjC: `[key isEqual:] NSNumber * n=@ […] `) would roughly be represented as

              0xb a2 [!] a2 […] [597]

              (1 0 11 … ******************************************************************************** […]

              Where the MSB is 1, indicating a tagged pointer and the following 3 bits identify the index of the class, in this case 3, corresponding to __NSCFNumber. In the case of __NSCFNumber, the (bit value is stored in bits 8 ..) while the lowest byte indicates the specific number type, in this casekCFNumberSInt (Type) ******************** [ ] **************** [!]

              The APIs operating on ObjC id’s ( objc_msgSend, objc_retain, objc_xyz) will usually first check the tag bit and proceed accordingly:[coder decodeObjectOfClasses:classes 

                                                forKey:@”NS.configDict”] ****************

              if (arg & 0x) {

              // handle tagged pointer[597]

              } else {

              // handle real pointer

              [ ] **************** […]

              Likely in order to break

              exploit techniques that abuse tagged pointers, tagged pointer values ​​are now also XORed with a per-process random value. As such, the actual value used at runtime would now be

              0xb 0000000000002 a2 ^ objc_debug_taggedpointer_obfuscator

              [NSCFString isEqual:]

              Where objc_debug_taggedpointer_obfuscator seems to be a fully random number except for the MSB, which must be zero (to preserve the pointer tagging bit). The following experiment with lldb and a simple iOS app demonstrates this: [!]

              (lldb) pn [!]

              (__ NSCFNumber $ 0=0xf a (a) *************************************************************************************************************************************************************************************** (int)

              (lldb) p objc_debug_taggedpointer_obfuscator

              (void $ 1=0x (a) *********************************************************************************************** […]

              (lldb) p / x ( uintptr_t) n ^ (uintptr_t) objc_debug_taggedpointer_obfuscator

              [ ]

              (unsigned long) $ 9=0xb (a2) **********************************The Dyld Shared Cache**************************

              On iOS (as well as on macOS), most system libraries are prelinked into one giant binary blob, called the[key isEqual:] ************************************** dyld_shared_cache[ ]. Amongst other benefits, this improves program load times as it reduces the runtime overhead of symbol resolution. One security relevant aspect of the shared cache is that it is mapped at the same address in every process, with its exact location only being randomized once during device boot. This is likely due to the shared cache being mapped into all userspace processes (thus reducing overall system memory usage) but also containing absolute pointers to itself, making it not

              position independent[!] . As such, once the base address of the shared cache is known, the addresses of pretty much all libraries in any userspace process on that device, including thousands of ROP gadgets, all ObjC Classes, various strings, and much more, are also known. This is sufficient for an RCE exploit. [ ]

              On the latest iOS versions, the dyld_shared_cache will be mapped somewhere in the address range 0x (to 0x) ********************************************************************************, providing for roughly possible base addresses as the cache itself is around 1GB in size and the page size (the smallest granularity for the ASLR shift) is 0x bytes. As such, it would be possible to find the base address by sheer brute force. However, as every wrong guess would likely cause a crash, the targeted imagent process would soon be subject to restart limiting enforced by launchd if a service crashes too quickly. This can be avoided by only crashing once roughly every 10 seconds. With that, a full brute force attack would then take around 3-4 weeks. While such an attack is not completely impossible given that mobile devices are rarely rebooted, it is probably not a realistic one either. The remaining part of this blog post describes how the base address can be inferred within 5 minutes instead.

              Defeating ASLR with a Crash Oracle

              One outstanding aspect ofCVE – 2682 –is that It creates an additional communication channel between the victim device and the attacker. It appears that this is a rare situation which should also be mitigatable to a large degree by properly sandboxing the process in question to prevent it from doing any kind of network activity. As such, one of the main questions motivating this research was the following: given only a remote memory corruption vulnerability, would it be possible to infer the base address of the shared cache somehow? To achieve this, some kind of communication channel had to first be found, which exists in the form of iMessage receipts.

              [ ] iMessage supports two different types of message receipts: delivery receipts and read receipts. The latter one can be disabled in Settings while the former will always be sent. The following screenshot from an iMessage chat shows how these receipts are used.

              (************************************************** [!]

              ****************************** (********************

              Here, the sender received a delivery receipt and a read receipt for the message “Foo”, a delivery receipt for the message “Bar” , and no receipt at all for the message “Baz”. Delivery receipts are interesting as they are sent automatically without any user interaction. Even more interesting is the time they are sent out by imagent, as shown in the following pseudocode snippet of imagent’s logic for pr ocessing incoming iMessages:**************(************************************ (************************************** [!]processIncomingMessage (message): […]

              msgPlist=decodeIntoPlist (message)

              # extract some values ​​from the plist …

              atiData=msgPlist [‘ATI’][ ]

              ati=NSKeyedUnarchive (atiData) [1]

              # more stuff …

              [ ] sendDeliveryReceipt ()

              # yet more stuff …

              Any bug in the NSKeyedUnarchiver API will trigger at [1]. With that it is possible to build a “crash oracle”: if a crash is triggered during the NSKeyedUnarchiving, no delivery receipt will be sent, otherwise one will be sent. This in turn allows the sender to infer whether or not the payload caused a crash in imagent on the remote device. What remains now is to turn the bug into an oracle function so that a useful bit of information can be extracted from the outcome of each oracle query.

              The ideal oracle would be

              (def def oracle (addr):

              if isMapped (addr):

              nocrash ()

              **************** […]

              crash ()

              Given that, breaking ASLR remotely would be rather straightforward:

    What do you think?

    Leave a Reply

    Your email address will not be published. Required fields are marked *

    GIPHY App Key not set. Please check settings

    Hacker News Classics (2018), Hacker News

    Bitcoin price breaks above $8,000: Will it hold?