背景知识
Instagram是当下最受欢迎的社交媒体平台之一,用户每天在该平台上传超过1亿多张照片。为此,我们决定对Android和iOS操作系统上的Instagram应用进行安全审计,结果发现了一个高危漏洞:攻击者可以利用它在受害者的手机上远程执行代码。
我们这次研究采用的方法是检查Instagram使用的第三方项目。
如今,几乎所有软件开发者都会在软件中利用开源项目。因此,我们就沿着这种思路,考察Instagram使用的开源项目,最终在Mozjpeg中发现了一个安全漏洞。而Mozjpeg就是一个JPEG格式解码器的开源项目。
在我们下面描述的攻击场景中,攻击者只需通过电子邮件、WhatsApp或其他媒体交换平台向受害者发送一张图片即可。当受害者打开Instagram应用时,该漏洞就会被触发。
我们都知道,如今即使是最大的公司也依赖于公共开源项目,而且这些项目几乎无需修改就能集成到他们的应用程序中。
大多数使用第三方开源项目的公司都会声明这一点,但并不是所有的库都会出现在应用程序的“About”页面中。要了解Instagram应用程序所涉及的所有库的最好方法,就是亲自打开该应用的lib-superpack-zstd文件夹进行查看。
::: hljs-center
图1 Instagram使用的共享对象
:::
在下图中,你可以看到使用Instagram上传图片时,会加载三个共享对象:libfb_mozjpeg.so、libjpegutils_moz.so和libcj_moz.so。
::: hljs-center
图2 Mozjpeg的共享对象
:::
其中,后缀“moz”是“mozjpeg(也就是Mozilla JPEG Encoder Project)”的简称,但是,这些模块到底是干什么的呢?
关于Mozjpeg
我们先来简单介绍一下JPEG格式的历史。JPEG是一种从20世纪90年代初就已经面试的图像文件格式,这种格式是基于有损压缩概念的,这意味着在压缩过程中会损失一些信息,但这种信息损失对人眼来说可以忽略不计。而Libjpeg则是Windows、Mac和Linux操作系统中内置的基线JPEG编码器,它是由一个非正式的独立小组维护的。这个库试图在编码速度、质量与文件大小之间取得平衡。
相比之下,Libjpeg-turbo则是libjpeg库的更高性能替代品,是大多数Linux发行版的默认库。这个库被设计成在编码和解码过程中使用更少的CPU时间。
2014年3月5日,Mozilla宣布了“Mozjpeg”项目,这是一个建立在libjpeg-turbo基础之上的JPEG编码器,以牺牲性能为代价,为网络图像提供更好的压缩性能。
这个开源项目是专门用于Web上的图片的。Mozilla公司在2014年推出了libjpeg-turbo,这样他们就可以专注于减少文件尺寸,以降低带宽,从而更快地加载Web图片了。
Instagram决定将mozjpeg库拆分成3个不同的共享对象:
- libfb_mozjpeg.so:负责Mozilla特有的解压导出API。
- libcj_moz.so:即libjeg-turbo,它负责解析图像数据。
- libjpegutils_moz.so:两个共享对象之间的连接器。它保存JNI调用的导出API,以触发Java应用端的解压操作。
模糊测试
我们在CPR的团队建立了一个多处理器模糊测试实验室,结合Adobe Research给我们带来了惊人的结果,所以,我们决定将我们的模糊测试工作扩展到Mozjpeg库上面。
由于libjpeg-turbo库已经过了充分的模糊测试的考验,所以我们把重点放在Mozjpeg库上。
Mozilla在libjpeg-turbo的基础上增加的主要部分是压缩算法,所以,我们把目光投向了这些算法上面。
AFL是我们的首选武器,所以,我们自然要为它写一个harness。
为了写出这个harness,我们必须了解如何对Mozjpeg的解压函数进行检测。
幸运的是,我们可以通过Mozjpeg自带的示例来了解该库的用法。
```
METHODDEF(int)
do_read_JPEG_file(struct jpeg_decompress_struct cinfo, char filename)
{
struct my_error_mgr jerr;
/ More stuff /
FILE infile; / source file /
JSAMPARRAY buffer; / Output row buffer /
int row_stride; / physical row width in output buffer /
if ((infile = fopen(filename, "rb")) == NULL) {
fprintf(stderr, "can't open %sn", filename);
return 0;
}
/ Step 1: allocate and initialize JPEG decompression object /
/ We set up the normal JPEG error routines, then override error_exit. /
cinfo->err = jpeg_std_error(&jerr.pub);
jerr.pub.error_exit = my_error_exit;
/ Establish the setjmp return context for my_error_exit to use. /
if (setjmp(jerr.setjmp_buffer)) {
jpeg_destroy_decompress(cinfo);
fclose(infile);
return 0;
}
/ Now we can initialize the JPEG decompression object. /
jpeg_create_decompress(cinfo);
/ Step 2: specify data source (eg, a file) /
jpeg_stdio_src(cinfo, infile);
/ Step 3: read file parameters with jpeg_read_header() /
(void)jpeg_read_header(cinfo, TRUE);
/ Step 4: set parameters for decompression /
/ In this example, we don't need to change any of the defaults set by
* jpeg_read_header(), so we do nothing here.
/
/ Step 5: Start decompressor /
(void)jpeg_start_decompress(cinfo);
/ JSAMPLEs per row in output buffer /
row_stride = cinfo->output_width * cinfo->output_components;
/ Make a one-row-high sample array that will go away when done with image /
buffer = (cinfo->mem->alloc_sarray)
((j_common_ptr)cinfo, JPOOL_IMAGE, row_stride, 1);
/ Step 6: while (scan lines remain to be read) /
/ jpeg_read_scanlines(...); /
while (cinfo->output_scanline < cinfo->output_height) {
(void)jpeg_read_scanlines(cinfo, buffer, 1);
/ Assume put_scanline_someplace wants a pointer and sample count. /
put_scanline_someplace(buffer[0], row_stride);
}
/ Step 7: Finish decompression /
(void)jpeg_finish_decompress(cinfo);
/ Step 8: Release JPEG decompression object /
jpeg_destroy_decompress(cinfo);
fclose(infile);
return 1;
}
```
然而,为了确保我们在Mozjpeg中发现的任何崩溃都会影响Instagram本身,我们需要知道Instagram是如何将Mozjpeg集成到他们的代码中的。
幸运的是,下面你可以看到,Instagram对于该库的使用简直就是复制粘贴的典范:
::: hljs-center
::: hljs-center
图3 Instagram使用Mozjpeg的方式
:::
正如你所看到的,他们唯一的改动之处就是将示例代码中的put_scanline_someplace虚函数替换为使用memcpy函数的read_jpg_copy_loop函数。
我们的harness从AFL接收生成的图像文件,并将它们发送到经过封装的Mozjpeg解压缩函数。
我们只用30个CPU核心运行了一天的fuzzer,AFL就向我们通报了447个“独特的”崩溃。
在对结果进行筛选后,我们发现了一个有趣的崩溃,与JPEG的图像尺寸解析有关。该崩溃是由于越界写入导致的,我们决定对其进行深入研究。
CVE-2020-1895漏洞
实际上,存在问题的函数是read_jpg_copy_loop,它会导致解压过程中发生整数溢出。
::: hljs-center
图4 截取自IDA的read_jpg_copy_loop代码片段
:::
该存在漏洞的函数在解析JPEG图像文件时需要处理图像尺寸。下面是描述该漏洞的伪代码:
```
width = rect->right - rect->bottom;
height = rect->top - rect->left;
allocated_address = __wrap_malloc(widthheightcinfo->output_components);// <---Integer overflow
bytes_copied = 0;
while ( 1 ){
output_scanline = cinfo->output_scanline;
if ( (unsigned int)output_scanline >= cinfo->output_height )
break;
//reads one line from the file into the cinfo buffer
jpeg_read_scanlines(cinfo, line_buffer, 1);
if ( output_scanline >= Rect->left && output_scanline < Rect->top )
{
memcpy(allocated_address + bytes_copied , line_buffer, width*output_component);// <--Oops
bytes_copied += width * output_component;
}
}
```
首先,我们来了解一下这段代码的作用。
其中,_wrap_malloc函数会根据3个参数(即图形尺寸)来分配一个内存块。其中,width和height这两个参数都是从文件中解析出来的16位整数(uint16_t)。
而参数cinfo->output_component则用于指出每个像素对应于多少个字节。
这个变量的取值可能是1(灰度)、3(RGB)或4(RGB+AlphaCMYK等)。
除了width和height之外,参数output_component也完全处于攻击者的控制之下。因为它也是从文件中解析出来的,并且没有对文件中的剩余数据进行验证。
并且,__warp_malloc的预期是:其参数是通过32位寄存器进行传递的! 这意味着,如果我们能够设法让分配的内存块的大小超过(2^32)字节,就会导致整数溢出,这样的话,实际分配的内存块就会比预期的尺寸小得多。
实际上,分配的内存块的大小是通过将图像的宽度、高度和output_components相乘得到的。问题在于,软件并没有对这些尺寸没有进行相应的检测,并且完全处于攻击者的控制之下。当它们被攻击者利用时,就会导致整数溢出。
```
__wrap_malloc(width * height * cinfo->output_components);// <---- Integer overflow
```
更让攻击者喜出望外的是,这个缓冲区将会传递给memcpy函数,从而导致基于堆的缓冲区溢出。
内存分配完毕后,将调用memcpy函数,从而将图像数据复制到分配的内存中。
图像的复制是逐行进行的。
```
memcpy(allocated_address + bytes_copied ,line_buffer, width*output_component);//<--Oops
```
也就是说,每次复制的数据量为width*output_component,复制的次数为height。
从利用漏洞的角度来看,这是一个很有“前途”的漏洞:线性堆溢出使攻击者能够控制分配的内存块的大小、溢出的数量以及溢出内存区域的内容。
小结
在本文中,我们为读者详细介绍了Instagram应用代码执行漏洞有关的背景知识,以及通过模糊测试在Mozjpeg项目中发现的安全漏洞,在下一篇文章中,我们将为读者介绍wildcopy型漏洞的利用方法等内容。
原文地址:https://research.checkpoint.com/2020/instagram_rce-code-execution-vulnerability-in-instagram-app-for-android-and-ios/
0x01 Windows certutil certutil是Windows自带的工具,具有下载文件,校验文件MD5、SHA1、SHA256,文件base64编码等功能。 使用certutil下载文件,保存在当前路径,文件名称与下载文件名称相同: certut…
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论