- ImageIO
- 适用于:iPhone 5 及更新机型、iPad 第 4 代及更新机型、iPod touch 第 6 代及更新机型
- 影响:处理恶意制作的图像可能会导致任意代码执行
- 说明:内存损坏问题已通过改进输入验证得到解决。
- CVE-2017-2416:腾讯科恩实验室的 @flanker_hqd
(For English version see https://blog.flanker017.me/cve-2017-2416-gif-remote-exec/)
前段时间偶然发现了一个 ImageIO.framework 中的图像解析漏洞,通过发送这个恶意图片,可以在任何有图片显示功能的应用中直接触发该漏洞,特别是各种 IM 应用(例如 iMessage, Telegram, Slack, iMessage 和国产流行 IM,以及邮件应用例如 Mail, Outlook, Inbox, Gmail,还有一些想做 IM 的金融应用例如 alipay 等),导致应用崩溃。在精心布置的内存布局下还有远程代码执行的可能。
让问题变得更蛋疼的是,很多客户端通常会在启动的时候再去尝试恢复加载之前的记录,也包括图片,这导致每次启动的时候该漏洞都会被触发,自动地成为了一个可持续的漏洞 – -b 例如 iMessage 和 Mail 即是如此。通过 iMessage 给一个没有升级到 10.12.4 的人发送攻击图片,其 iMessage 就再也打不开了。
第一个视频展示了发送一条恶意 imessage 就导致对方崩溃的过程
然后被攻击的设备就再也打不开 imessage 了
- * thread #1: tid = 0x17570, 0x00007fff9557f1ab ImageIO`IIOReadPlugin::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 ImageIO`IIOReadPlugin::IIOReadPlugin(CGImagePlus*, unsigned int, unsigned int, long long, unsigned char) + 67
- ImageIO`IIOReadPlugin::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
在苹果平台上,基本所有的图像解析功能最后都会调用
, 随后
- [NSImage _initWithData:fileType:hfsType:]
将图像的解析根据图像头特征分配到对应的 plugin 中。注意这里并不是基于文件扩展名做的判断,所以后续我们可以通过这个特性绕过过滤实现利用。
- IIOImageSource
如果把它拖动到任意 macos/iOS app 中的时候崩溃了,那么你的系统受该漏洞影响,赶快升级吧。 测试样例文件下载:Sample PNG Sample GIF 仅供自测使用,请勿用于非法用途例如发送给他人。
漏洞的一个源头在
函数中,观察如下反汇编代码:
- GIFReadPlugin::init
- 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
一个攻击者可以构造负数的高度和长度,bypass 掉对 filesize 的比较,造成后续内存越界访问。一般来讲攻击者可以通过手动构造图片输入流 / hook 进行发送,或者通过 app 服务自身提供的 web 服务来进行发送。前面提到过 ImageIO 解析图片的时候并不是通过判断扩展名来进行的,通过这个特性我们可以同样 bypass 一些 web 图片上传界面的过滤,将恶意图片成功发送到对方设备上,粗发漏洞。
相对来讲稍微令人诧异的是苹果的修复。补丁并没有打在 size 比较这里,而是打在了 IIOReadPlugin 这里。在补丁之前,IIOReadPlugin 的关键代码如下所示:
- 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->vt = (__int64)off_1659D0;
- a1->field_8 = a2;
- v8 = *(_QWORD *)(a2 + 24);
- a1->field_10 = v8;
- a1->field_38 = a3;
- a1->field_3c = a4;
- a1->field_30 = a5;
- sessionwrap = *(_QWORD *)(v8 + 24);
- if ( sessionwrap )
- {
- session = (IIOImageReadSession *)CGImageReadSessionGetSession(sessionwrap); //session is invalid
- this->session = session;
- v11 = (IIOImageRead *)session->imageread; //oob happens here and lead to crash
- LOBYTE(session) = v11->field_40;
- this->field_20 = (__int64)v11;
- this->field_c8 = (char)session;
- v12 = 0LL;
- if ( v11 )
- v12 = IIOImageRead::getSize(v11);
- }
- else
- {
- this->field_20 = 0LL;
- this->session = 0LL;
- this->field_c8 = 1;
- v12 = 0LL;
- }
在 10.12.4 中,if 分支语句变成了如下所示:
- 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;
- }
IIOImageReadSession 的使用在这里被移除了。这是否从根源上解决了问题?让我们拭目以待。
对于想自行防御这个问题的开发者来说(毕竟有很多用户没有升级到最新版,锅还是会被他们扣在开发者头上),我建议在图片显示前先自行检查下 GIF 宽度和高度。
对于终端用户来讲,当然升级系统是最好的办法了。
来源: http://www.tuicool.com/articles/miuY3mj