来源:Flanker Sky
作者:flanker017
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:]
, 随后IIOImageSource
将图像的解析根据图像头特征分配到对应的plugin中。注意这里并不是基于文件扩展名做的判断,所以后续我们可以通过这个特性绕过过滤实现利用。
如果把它拖动到任意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宽度和高度。
对于终端用户来讲,当然升级系统是最好的办法了。