Instagram应用代码执行漏洞详解(二)

admin 2021年4月13日01:05:15Instagram应用代码执行漏洞详解(二)已关闭评论64 views字数 7328阅读24分25秒阅读模式

在上一篇文章中,我们为读者详细介绍了Instagram应用代码执行漏洞有关的背景知识,以及通过模糊测试在Mozjpeg项目中发现的安全漏洞,在本文中,我们将为读者介绍wildcopy型漏洞的利用方法等精彩内容。

Wild Copy型漏洞的利用

要导致内存损坏,我们必须要溢出到决定分配的内存块的大小的整数的边界之外;我们的计算表明,这个整数肯定超过32位。由于我们面对的是wildcopy,这意味着我们将尝试复制大于2^32(4GB)的数据。因此,当循环触及未映射页面时,程序崩溃的概率是极高的:

Instagram应用代码执行漏洞详解(二)
图5 wildcopy型漏洞导致的段故障

那么我们该如何利用这一点呢?

在我们深入研究wildcopy型漏洞的利用技术之前,我们需要将我们的情况与典型的wildcopy型漏洞情况(如Stagefright漏洞)区分开来。经典的情况通常涉及一个写入4GB的数据的memcpy调用。

但是,在我们的例子中有一个for循环,它每次复制X字节,共复制Y次,而X*Y正好等于4GB。

当我们试图利用这种内存损坏漏洞时,需要问自己几个重要的问题:

  • 我们能控制(至少部分控制)被破坏的数据内容吗?
  • 我们能控制要破坏的数据的长度吗?
  • 我们能控制要溢出的待分配内存块的大小吗?

最后一个问题特别重要,因为在Jemalloc/LFH(或者每一个基于存储桶的分配函数)中,如果我们无法控制我们想要破坏的内存块的大小,那么可能很难对堆进行重塑,从而破坏一个特定的目标结构(如果这个结构的大小明显不同的话)。

乍一看,第一个问题的答案好像是肯定的,因为我们控制了图像数据的内容,也就是我们具有控制数据内容的能力。

现在,进入第二个问题:控制要破坏的数据的长度。这里的答案显然也是肯定的,因为memcpy循环会逐行复制文件,而复制的每一行的大小是由width参数和output_component参数的乘积决定的,而这两个参数完全处于攻击者的控制之下。

现在,我们要回答的是第三个问题,即关于我们能否破坏缓冲区的大小,答案也很明显。

因为这个缓冲区的大小是由“width * height * cinfo->output_components”控制的。为此,我们编写了一个Python脚本,它可以根据我们希望分配的内存块的大小,并结合整数溢出的影响,来给出这3个参数的合适大小。
```
import sys
def main(low=None, high=None):
res = []
print("brute forcing...")
for a in range(0xffff):
for b in range(0xffff):
x = 4 * (a+1) * (b+1) - 2**32
if 0 < x <= 0x100000:#our limit
if (not low or (x > low)) and (not high or x <= high):
res.append((x, a+1, b+1))
for s, x, y in sorted(res, key=lambda i: i[0]):
print "0x%06x, 0x%08x, 0x%08x" % (s, x, y)
if name == 'main':
high = None
low = None
if len(sys.argv) == 2:
high = int(sys.argv[1], 16)
elif len(sys.argv) == 3:
high = int(sys.argv[2], 16)
low = int(sys.argv[1], 16)
main(low, high)

```

现在我们已经有了利用wildcopy型漏洞的先决条件,接下来,让我们看看该如何利用它们。

为了触发该漏洞,我们必须指定一个大于2^32字节的内存块长度。在实践中,我们需要在到达未映射的内存之前停止wildcopy操作。

为此,我们有很多选择:

  • 依靠竞态条件——当wildcopy操作破坏了一些有用的目标结构或内存时,我们可以在wildcopy崩溃之前,让另一个线程使用现在被破坏的数据来做一些事情(例如,构造其他原语,终止wildcopy,等等)。
  • 如果wildcopy循环具有某种逻辑,可以在某些情况下停止循环,那么我们可以在破坏足够的数据后触发这些条件,使得循环停止。
  • 如果wildcopy循环每次迭代中都调用一个虚函数,并且指向该函数的指针位于堆内存中的结构中(或位于我们在wildcopy期间破坏的另一个内存地址中),则攻击者可以通过该循环在wildcopy过程中覆盖该指针从而实现控制权的转移。

遗憾的是,第一个选项在这里并不适用,因为我们是从一个图像向量发动攻击的。因此,我们无法控制线程,所以竞态条件选项没有任何帮助。

为了使用第二种方法,我们需要寻找一个kill-switch来停止wildcopy。为此,我们尝试将文件切成两半,同时保持图像头部的大小不变。然而,我们发现,如果该库到达一个EOF标记,它就会添加另一个EOF标记,这样的话,我们最终会陷入一个EOF标记的无限循环。

我们还试图寻找一个ERREXIT函数,可以在运行时停止解压过程,但我们了解到,无论我们怎么做,在这段代码中,我们永远无法到达一条通往ERREXIT的路径。因此,第二个选项也不适用。

要使用第三个选项,我们需要寻找一个在wildcopy循环的每次迭代中都会调用的虚函数。

现在,让我们来考察执行memcpy拷贝的循环逻辑:
```
while ( 1 ){
output_scanline = cinfo->output_scanline;
if ( (unsigned int)output_scanline >= cinfo->output_height )
break;
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)
bytes_copied += width * output_component;
}
}

```

我们可以看到,除了执行覆盖任务的memcpy函数之外,只有一个函数jpeg_read_scanlines将在每次迭代中被调用!

下面,让我们来考察一下jpeg_read_scanlines函数的代码:
```
GLOBAL(JDIMENSION)
jpeg_read_scanlines(j_decompress_ptr cinfo, JSAMPARRAY scanlines,
JDIMENSION max_lines)
{
JDIMENSION row_ctr;
if (cinfo->global_state != DSTATE_SCANNING)
ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state);
if (cinfo->output_scanline >= cinfo->output_height) {
WARNMS(cinfo, JWRN_TOO_MUCH_DATA);
return 0;
}
/ Call progress monitor hook if present /
if (cinfo->progress != NULL) {
cinfo->progress->pass_counter = (long)cinfo->output_scanline;
cinfo->progress->pass_limit = (long)cinfo->output_height;
(cinfo->progress->progress_monitor) ((j_common_ptr)cinfo);
}
/
Process some data /
row_ctr = 0;
(
cinfo->main->process_data) (cinfo, scanlines, &row_ctr, max_lines);
cinfo->output_scanline += row_ctr;
return row_ctr;
}

```

如您所见,每次调用jpeg_read_scanlines从文件中读取另一行时,我们都会调用一个虚函数process_data。

从文件中读取的行被复制到名为CINFO的结构体中名为“row_ctr”的缓冲区中。
```
(*cinfo->main->process_data) (cinfo, scanlines, &row_ctr, max_lines);

proccess_data points to another function called process_data_simple_main:
process_data_simple_main(j_decompress_ptr cinfo, JSAMPARRAY output_buf,
JDIMENSION out_row_ctr, JDIMENSION out_rows_avail)
{
my_main_ptr main_ptr = (my_main_ptr)cinfo->main;
JDIMENSION rowgroups_avail;
/
Read input data if we haven't filled the main buffer yet /
if (!main_ptr->buffer_full) {
if (!(
cinfo->coef->decompress_data) (cinfo, main_ptr->buffer))
return;

main_ptr->buffer_full = TRUE;

}
rowgroups_avail = (JDIMENSION)cinfo->_min_DCT_scaled_size;
/ Feed the postprocessor /
(*cinfo->post->post_process_data) (cinfo, main_ptr->buffer,
&main_ptr->rowgroup_ctr, rowgroups_avail,
output_buf, out_row_ctr, out_rows_avail);

/ Has postprocessor consumed all the data yet? If so, mark buffer empty /
if (main_ptr->rowgroup_ctr >= rowgroups_avail) {
main_ptr->buffer_full = FALSE;
main_ptr->rowgroup_ctr = 0;
}
}

```

从process_data_simple_main中,我们还可以发现有两个在每次迭代中被调用的虚函数。它们有一个共同之处,即都含有cinfo结构体。

什么是cinfo结构体?

Cinfo是一个在Mozjpeg各种函数间传递的结构体。它保存着关键的成员、函数指针和图像元数据。

让我们看看Jpeglib.h文件中cinfo结构体的定义:
```
struct jpeg_decompress_struct {
struct jpeg_error_mgr err;

struct jpeg_memory_mgr mem;

struct jpeg_progress_mgr progress;
void
client_data;

boolean is_decompressor;

int global_state
struct jpeg_source_mgr src;
JDIMENSION image_width;
JDIMENSION image_height;
int num_components;
...
J_COLOR_SPACE out_color_space;
unsigned int scale_num
...
JDIMENSION output_width;

JDIMENSION output_height;

int out_color_components;

int output_components;

int rec_outbuf_height;
int actual_number_of_colors;

...
boolean saw_JFIF_marker;

UINT8 JFIF_major_version;

UINT8 JFIF_minor_version;
UINT8 density_unit;

UINT16 X_density;

UINT16 Y_density;

...

...
int unread_marker;
struct jpeg_decomp_master
master;

struct jpeg_d_main_controller main; <<-- there’s a function pointer here
struct jpeg_d_coef_controller
coef; <<-- there’s a function pointer here
struct jpeg_d_post_controller post; <<-- there’s a function pointer here
struct jpeg_input_controller
inputctl;
struct jpeg_marker_reader marker;
struct jpeg_entropy_decoder
entropy;
. . .
struct jpeg_upsampler upsample;
struct jpeg_color_deconverter
cconvert
. . .
};

```

在cinfo结构体中,我们可以看到3个指向函数的指针,我们可以在实现覆盖功能的循环中尝试覆盖这些指针,从而转移执行流程。

事实证明,第三种选择是适用于我们的情况的!

Jemalloc 101

在深入研究Jemalloc的利用方法之前,我们需要了解Android的堆分配器是如何工作的,以及我们在下文中涉及的术语:Chunk、Run与Region。

Jemalloc是一个基于bucket的内存分配器,它将内存划分为大小相等的chunk,并使用这些chunk来存储所有其他数据结构(以及用户请求的内存)。Chunk又被进一步划分为“run”,它们负责请求/分配具有一定大小的内存块。run会跟踪空闲和已使用的“region”。Region是用户(通过malloc调用)分配内存时返回的堆元素(heap items)。最后,每个run都与一个“bin”相关联。bin是负责存储空闲region的结构体。

Instagram应用代码执行漏洞详解(二)
图6 Jemalloc的基本构造

控制PC寄存器

我们发现了3个可以利用的函数指针,我们可以在wildcopy期间通过覆盖它们来转移执行流程,从而控制PC寄存器。

结构体cinfo的成员如下所示:
```
struct jpeg_d_post_controller post
struct jpeg_d_main_controller
main
struct jpeg_d_coef_controller *coef

```

上面这些结构体的定义位于Jpegint.h文件中:
```
/ Main buffer control (downsampled-data buffer) /
struct jpeg_d_main_controller {
void (start_pass) (j_decompress_ptr cinfo, J_BUF_MODE pass_mode);
void (
process_data) (j_decompress_ptr cinfo, JSAMPARRAY output_buf,
JDIMENSION *out_row_ctr, JDIMENSION out_rows_avail);
};

/ Coefficient buffer control /
struct jpeg_d_coef_controller {
void (start_input_pass) (j_decompress_ptr cinfo);
int (
consume_data) (j_decompress_ptr cinfo);
void (start_output_pass) (j_decompress_ptr cinfo);
int (
decompress_data) (j_decompress_ptr cinfo, JSAMPIMAGE output_buf);
jvirt_barray_ptr *coef_arrays;
};

/ Decompression postprocessing (color quantization buffer control) /
struct jpeg_d_post_controller {
void (start_pass) (j_decompress_ptr cinfo, J_BUF_MODE pass_mode);
void (
post_process_data) (j_decompress_ptr cinfo, JSAMPIMAGE input_buf,
JDIMENSION in_row_group_ctr,
JDIMENSION in_row_groups_avail,
JSAMPARRAY output_buf, JDIMENSION
out_row_ctr,
JDIMENSION out_rows_avail);

```

我们需要找到这3个结构体在堆内存中的位置,这样我们就可以覆盖其中至少一个结构体来获得PC寄存器的控制权。

为了弄清楚这一点,我们需要知道当我们使用Mozjpeg解压一张图片时,堆是什么样子的。

小结

在本文中,我们为读者介绍wildcopy型漏洞的利用方法等内容,在接下来的文章中,我们将继续为读者介绍Mozjpeg内部的内存管理器以及相关堆布局等知识。

原文地址:https://research.checkpoint.com/2020/instagram_rce-code-execution-vulnerability-in-instagram-app-for-android-and-ios/

相关推荐: macOS版本的本地提权漏洞分析

译文声明 本文是翻译文章,文章原作者Csaba Fitzl,文章来源:https://www.offensive-security.com 原文地址:https://www.offensive-security.com/offsec/microsoft-tea…

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2021年4月13日01:05:15
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   Instagram应用代码执行漏洞详解(二)https://cn-sec.com/archives/246735.html