iOS/macOS Remote code execution triggered by malformed GIF in ImageIO framework(CVE-2017-2416)

2017-04-07T00:00:00
ID SSV:92924
Type seebug
Reporter Root
Modified 2017-04-07T00:00:00

Description

> ImageIO Available for: iPhone 5 and later, iPad 4th generation and later, iPod touch 6th generation and later > > Impact: Processing a maliciously crafted image may lead to arbitrary code execution > > Description: A memory corruption issue was addressed through improved input validation. > > CVE-2017-2416: flanker_hqd of KeenLab, Tencent

Abstract

Recently I’ve switched my main research focus back from Apple stuff to Android and browsers. While I was auditing a custom image parsing library written by some ppls, I transferred the test case image manipulated by 010editor via a popular IM messenger, and all of a sudden, the app crashed. I investigated the crash and found it is a issue in ImageIO library, and can be automatically triggered in all kinds of iOS/macOS apps that receives GIF images, especially the ones for instant messaging, such as Signal, Telegram, Slack, iMessage etc and Email clients such as Mail, Outlook, Inbox, Gmail, etc and even financial apps that want to be an IM such as Alipay. All these apps will crash on receiving the malicious GIF.

I haven’t test Twitter, but should you find a way to post the malformed GIF online (which I think can be done by manipulated the post stream to bypass the frontend filtering, but I was too busy to try that), the client should also crash as well.

What make things worse is that many clients will automatically reload and reparse the image on open, triggering the vulnerability again and again, lead to infinite loop and eliminating the need for attacker to persistent – -b

DEMO video1

The first video demonstrates receiving malformed gif file via iMessage lead to crash

https://blog.flanker017.me/wp-content/uploads/2017/04/crash1.mp4

DEMO video2

the second video demonstrates persistence (user cannot open iMessage anymore…)

https://blog.flanker017.me/wp-content/uploads/2017/04/crash123.m4v

Crash trace

`` * thread #1: tid = 0x17570, 0x00007fff9557f1ab ImageIOIIOReadPlugin::IIOReadPlugin(CGImagePlus*, unsigned int, unsigned int, long long, unsigned char) + 67, queue = 'com.apple.main-thread', stop reason = EXC_BAD_ACCESS (code=EXC_I386_GPFLT) frame #0: 0x00007fff9557f1ab ImageIOIIOReadPlugin::IIOReadPlugin(CGImagePlus*, unsigned int, unsigned int, long long, unsigned char) + 67 ImageIOIIOReadPlugin::IIOReadPlugin: -> 0x7fff9557f1ab <+67>: mov al, byte ptr [rdi + 0x40] 0x7fff9557f1ae <+70>: mov qword ptr [rbx + 0x20], rdi 0x7fff9557f1b2 <+74>: mov byte ptr [rbx + 0xc8], al 0x7fff9557f1b8 <+80>: xor eax, eax

Thread 0 Crashed:: Dispatch queue: com.apple.main-thread 0 com.apple.ImageIO.framework 0x00007fffa144d1ab IIOReadPlugin::IIOReadPlugin(CGImagePlus, unsigned int, unsigned int, long long, unsigned char) + 67 1 com.apple.ImageIO.framework 0x00007fffa14b8c93 GIFReadPlugin::InitProc(CGImagePlugin, unsigned long, unsigned long) + 59 2 com.apple.ImageIO.framework 0x00007fffa14177da IIOImageSource::makeImagePlus(unsigned long, CFDictionary const*) + 252 3 com.apple.ImageIO.framework 0x00007fffa141918b IIOImageSource::getPropertiesAtIndexInternal(unsigned long, CFDictionary const) + 57 4 com.apple.ImageIO.framework 0x00007fffa141911c IIOImageSource::copyPropertiesAtIndex(unsigned long, __CFDictionary const) + 98 5 com.apple.ImageIO.framework 0x00007fffa13f03ca CGImageSourceCopyPropertiesAtIndex + 181 6 com.apple.AppKit 0x00007fff9cfdbcae +[NSBitmapImageRep _imagesWithData:hfsFileType:extension:zone:expandImageContentNow:includeAllReps:] + 543 7 com.apple.AppKit 0x00007fff9cfdba68 +[NSBitmapImageRep _imageRepsWithData:hfsFileType:extension:expandImageContentNow:] + 93 8 com.apple.AppKit 0x00007fff9d4bf08e -[NSImage _initWithData:fileType:hfsType:] + 479

```

Almost all image related functions on Apple platform calls down to [NSImage _initWithData:fileType:hfsType:], and IIOImageSource dispatches image parsing to corresponding plugin based on signature detection (note: not based on file extension). This feature will be useful afterwards.

Sample file to test if you’re vulnerable

Test image sample:

Sample PNG Sample GIF

Grab an image file and change the width/height field to both negative short whose unsigned form value larger than 0xff00.

Drag it into /send to any macos/iOS application and if it crashes, you’re vulnerable.

Analysis

The root cause seems to be at GIFReadPlugin::init function, in the following decompiled snippet:

``` v32 = (signed int16)width * (signed int64)height; if ( v32 > filesize * 1100 * v29 ) { LOBYTE(aspectbyte) = 0; v15 = 0LL; if ( this->gapC0[8] ) { LOBYTE(aspectbyte) = 0; LogError( "init", 498, "malformed GIF file (%d x %d) - [canvasSize: %ld fileSize: %ld ratio: %d] \n", (unsigned int)(signed int16)width, (unsigned int)(height), // width >> 16 is height (signed int16)width * (signed __int64)SHIWORD(width), filesize, v32 / filesize); v15 = 0LL; } goto LABEL_71; }

text:00000000000CC51F movsx rax, r9w text:00000000000CC523 mov ecx, r9d text:00000000000CC526 shr ecx, 10h text:00000000000CC529 movsx rbx, cx text:00000000000CC52D imul rbx, rax text:00000000000CC531 imul rdx, r12, 44Ch text:00000000000CC538 mov rax, rdx text:00000000000CC53B imul rax, rsi __text:00000000000CC53F cmp rbx, rax

```

An attacker can craft an image of negative height and weight, thus bypassing the check comparing to file size, lead to following out-of-bound. As I have mentioned above, the dispatching is based on file signature rather than file extension. I noticed some applications’ web interfaces have check on the size of GIF images, preventing me from spreading this POC to mobile apps. However they do not have check on PNG extension, allowing me to upload the malformed GIF image in PNG extension, bypassing the check and crashes whoever receives it.

While this does make sense, after Apple releases the fix I checked the new ImageIO binary and found the fix actually goes another way. Recall the crash happens in IIOReadPlugin::IIOReadPlugin, in the following pseudo code at 10.11.2/3:

``` bool fastcall IIOReadPlugin::IIOReadPlugin(IIOReadPlugin *a1, int64 a2, int a3, int a4, int64 a5, unsigned int8 a6) { unsigned int8 v6; // r14@1 IIOReadPlugin *this; // rbx@1 int64 v8; // rax@1 int64 sessionwrap; // rdi@1 IIOImageReadSession session; // rax@2 IIOImageRead v11; // rdi@2 int64 v12; // rax@2 int64 *v13; // rcx@5 int64 v14; // rdx@5 bool result; // al@5

v6 = a6;
this = a1;
a1-&gt;vt = (__int64)off_1659D0;
a1-&gt;field_8 = a2;
v8 = *(_QWORD *)(a2 + 24);
a1-&gt;field_10 = v8;
a1-&gt;field_38 = a3;
a1-&gt;field_3c = a4;
a1-&gt;field_30 = a5;
sessionwrap = *(_QWORD *)(v8 + 24);
if ( sessionwrap )
{
    session = (IIOImageReadSession *)CGImageReadSessionGetSession(sessionwrap); //session is invalid
    this-&gt;session = session;
    v11 = (IIOImageRead *)session-&gt;imageread; //oob happens here and lead to crash
    LOBYTE(session) = v11-&gt;field_40;
    this-&gt;field_20 = (__int64)v11;
    this-&gt;field_c8 = (char)session;
    v12 = 0LL;
    if ( v11 )
        v12 = IIOImageRead::getSize(v11);
}
else
{
    this-&gt;field_20 = 0LL;
    this-&gt;session = 0LL;
    this-&gt;field_c8 = 1;
    v12 = 0LL;
}

And now apple changes the if-block in 10.12.4:

a1->field_8 = cgimgplus; imageplus = CGImagePlusGetIPlus(cgimgplus); a1->field_10 = imageplus; a1->field_38 = v9; a1->field_3c = v8; a1->field_30 = v7; v12 = (_QWORD )(imageplus + 32); a1->field_18 = v12; imageread = (IIOImageRead )(v12 + 32); if ( imageread ) { v10->field_c8 = ((_BYTE *)imageread + 64); v10->field_20 = (__int64)imageread; v14 = IIOImageRead::getSize(imageread); } else { v10->field_c8 = 0; v10->field_20 = 0LL; v14 = 0LL; }

```

Removing the usage of IIOImageReadSession in this function. Is it better than fixing the size change? Dunno.

Custom fix?

For app developers who want to mitigate this issue for users staying at old versions, I suggest check for negative width and height before passing to NSImage.

I believe this vulnerability is introduced in iOS 10, so iOS 9/OSX 10.11 users are not affected (how many ppls are still using iOS9? Raise your hands). For iOS 10/macOS 10.12 users, please upgrade to 10.3/10.12.4 for the official fix.

Timeline

  • 2017.1.10 Initial discovery
  • 2017.1.16 Report to Apple
  • 2017.1.24 Apple responds on they are working on a fix
  • 2017.3.23 CVE-2017-2416 assigned
  • 2017.3.28 Advisory published at https://support.apple.com/en-us/HT207617
  • 2017.4.6 Public disclosure