如何远程利用 Android GraphicBuffer::unflatten() 漏洞 (CVE-2015-1474)?

信息安全 开发 移动的 安卓 爪哇 拒绝服务
2021-08-22 12:44:15

我一直在阅读 android 中的 DoS(拒绝服务)攻击,尽管我了解以下攻击,这些攻击利用了常规编程功能等。

  • (Android Web 浏览器)当 Android 设备访问特定网页时,该网页可以保留 JavaScript,该 JavaScript 可以使用大循环推送 Android 设备以打开链接的应用程序,例如 Android Market Place、电子邮件、消息传递等,多次证明崩溃 http://packetstormsecurity.com/files/118539/Android-4.0.3-Browser-Crash.html

我无法理解的漏洞

漏洞详情

CVE-2015-1474

从 Android 到 5.0 的 platform/frameworks/native/libs/ui/GraphicBuffer.cpp 中的 GraphicBuffer::unflatten 函数中的多个整数溢出允许攻击者通过触发大量的向量获得特权或导致拒绝服务(内存损坏) (1) 文件描述符或 (2) 整数值。

http://www.cvedetails.com/cve/CVE-2015-1474/

a

GraphicBuffer::unflatten() 中的 Google Android 整数溢出让远程用户执行任意代码

SecurityTracker 警报 ID: 1031875

安全追踪器网址: http://securitytracker.com/id/1031875

CVE 参考: CVE-2015-1474(链接到外部站点)

日期: 2015 年 3 月 10 日

影响: 通过网络执行任意代码、用户通过网络访问

修复可用: 是供应商确认:是

描述: 在 Google Android 中报告了一个漏洞。远程用户可以在目标系统上执行任意代码。

远程用户可以发送特制数据以触发 GraphicBuffer::unflatten() 中的整数溢出,并可能在目标系统上执行任意代码。代码将以目标应用程序的权限运行。

影响: 远程用户可以在目标系统上执行任意代码。

解决方案: 供应商已发布源代码修复,可在以下位置获得:https://android.googlesource.com/platform/frameworks/native/+/38803268570f90e97452cd9a30ac831661829091

供应商 URL:( android.googlesource.com/链接到外部站点)

原因: 边界错误

底层操作系统:

消息历史: 无。

http://www.securitytracker.com/id/1031875

一个

参考:

    http://www.cvedetails.com/cve/CVE-2015-1474/
    http://www.securitytracker.com/id/1031875

我发现上述问题(使用特定的操作系统框架等)更难理解。

我可以从上面收集到的是:

该攻击允许攻击者:

  • 获得特权
  • 导致拒绝服务(内存损坏)

但我不明白究竟是什么让操作系统面临这些问题?例如,这究竟是什么意思:远程用户可以发送特制数据来触发 GraphicBuffer::unflatten() 中的整数溢出

攻击者如何远程执行此类操作?GraphicBuffer::unflatten()是什么意思?

或者

用户将如何执行这个任意代码,这是什么类型的代码?

我错过了什么?


关于答案的小问题:

首先,函数native_handle_create和变量native_handle在哪里声明(我可以看到它们的用途,但既不是局部声明也不是全局声明)。

其次,您如何确定缓冲区的实际大小应该是多少(因此要确定numFdsnumInts的当前大小是否不正确,即不应为-1)?

第三, h->data是什么意思,因为我找不到名为data的变量的声明,而且我不知道->是什么意思?什么是 sizeof(int)?

最后我认为 numFds 是 unsigned const size_t numFds = buf[8]; ,如果 numFds 是size_t类型,我认为它不应该能够保存负数(除非我错过了什么)?另外,我不明白我们如何从创建堆损坏或数组索引越界异常等转移到任意代码执行?

如果这些进一步的询问令人恼火,我深表歉意,我只是想正确地理解它。再次感谢 :-)

2个回答

了解该漏洞的最简单方法是查看差异,挖掘代码,并找出如何利用它。

易受攻击的方法的签名如下所示:

status_t GraphicBuffer::unflatten(
    void const*& buffer, size_t& size, int const*& fds, size_t& count) {

这里的重要参数是void const*& buffer,它是用于展开的数据缓冲区,以及size_t& size,它指定缓冲区的大小。

请记住这size_t是 typedef unsigned int,这意味着它是一个无符号整数。这意味着它不能代表负值。

该方法开始如下:

// if the size of the buffer is less than 8 integers, return because it's too small
if (size < 8*sizeof(int)) return NO_MEMORY;

// cast the buffer to an integer pointer type (think "array of ints")
// remember that the size argument is NOT an int, but a size_t - therefore unsigned int.
int const* buf = static_cast<int const*>(buffer);

// check that the buffer contains the right magic number at the start
if (buf[0] != 'GBFR') return BAD_TYPE;

// populate two size_t variables - these are the ones which cause the integer overflow later!
// note that this data is supplied by the calling application, so it can control it.
const size_t numFds  = buf[8];
const size_t numInts = buf[9];

因此,正如您所看到的,有一些粗略的检查以查看缓冲区的大小是否正确。然后它继续从缓冲区读取更多数据并填充两个计数(Fds 和 Ints)。这些数字指定在标题数据之后有多少个值。

让我们继续...

// check that the buffer is large enough to contain the number of ints specified.
// notice that 10 is the number of ints read so far (buf[9] was our last access)
// so in order to store numInts ints, the buffer needs to be 10 + numInts ints long.
const size_t sizeNeeded = (10 + numInts) * sizeof(int);
// if the buffer isn't big enough, fail
if (size < sizeNeeded) return NO_MEMORY;

// if the count argument is less than zero, fail
// this is meaningless because count is size_t, so can't be negative
size_t fdCountNeeded = 0;
if (count < fdCountNeeded) return NO_MEMORY;

if (handle) {
    // free previous handle if any
    free_handle();
}

到目前为止一切顺利,主要是。检查缓冲区是否足够大,可以容纳标头声明的“整数”数量。对负数进行毫无意义的检查,但无论如何这是不可能的。最后,它会清理任何旧手柄以防万一,以防止手柄泄漏。

现在漏洞来了...

// if either numFds or numInts is nonzero...
if (numFds || numInts) {
    // read a bunch of fields...
    width  = buf[1];
    height = buf[2];
    stride = buf[3];
    format = buf[4];
    usage  = buf[5];

    // native_handle_create is just a wrapper for malloc'ing a struct which
    // contains a few metadata fields and the fds/ints data.
    // the function takes two ints as its parameters, so it can take negatives.
    // the underlying code does malloc(...+((numFds+numInts)*sizeof(int)))
    // and since nothing has checked if numFds < 0, this will make a buffer
    // which is smaller than expected!
    //
    // example: if numFds is -1 and numInts is 20, the allocated data buffer
    // will be 19*sizeof(int) bytes long.
    //
    // this is potentially bad already, but it gets worse.
    native_handle* h = native_handle_create(numFds, numInts);

    // this will copy the list of file descriptors (fds) from the fds pointer
    // to the h->data pointer. the number of bytes to copy is numFds*sizeof(int).
    // it is important to note that the third argument of memcpy takes a size_t,
    // which is unsigned. numFds is a signed int, but when you multiply it with
    // sizeof(int), which is type size_t, you get a size_t, which is interpreted
    // as an UNsigned integer. as such, -1 (0xFFFFFFFF) in signed becomes
    // 4294967295 when translated over to size_t. so, it attempts to copy way
    // too much data from fds to h->data, leading to heap corruption.
    memcpy(h->data,          fds,     numFds*sizeof(int));

    // <-- CRASH HERE, HEAP IS CORRUPTED

    memcpy(h->data + numFds, &buf[10], numInts*sizeof(int));

    handle = h;
} else {
    width = height = stride = format = usage = 0;
    handle = NULL;
}

本质上,问题在于文件描述符的数量(numFds)被读取为有符号整数,但随后被转换为 memcpy 的无符号整数,这会将一个小的负数变成一个大的正数并超出堆缓冲区。这都是由于缺少对 和 的值的检查造成numIntsnumFds

根据FullDisclosure 帖子,您可以通过制作一个派生自 的类来利用它,该类可以BufferQueue访问最终传递给unflatten'buffer参数的数据。

现在,由于所有这些易受攻击的代码都位于在用户system_server下运行的进程内,因此system从用户应用程序(即应用程序)破坏其堆是一件大事。如果您可以覆盖堆上的结构或类中的函数指针(例如,来自回调或事件),则可以在 中获得任意代码执行system_server,从而获得特权。


要回答您的澄清请求...

首先,函数 native_handle_create 和变量 native_handle 在哪里声明(我可以看到它们的用途,但既不是局部声明也不是全局声明)。

Android 交叉参考对于查找这类东西很有用。该函数在native_handle.c中声明,native_handle结构(不是变量 -h是 type 的指针变量native_handle)在native_handle.h中声明。

其次,您如何确定缓冲区的实际大小应该是多少(以便确定 numFds 或 numInts 的当前大小是否不正确,即不应为-1)?

好吧,我们知道列表中不能有 -1 项,所以这是无效的。如果您查看补丁 diff,他们会添加一些检查:

// compute the maximum possible number of ints that you can store in a buffer
// whose size is represented by a size_t.
const size_t maxNumber = UINT_MAX / sizeof(int);
// if numFds exceeds the max number of possible ints (e.g. if 0xFFFFFFFF is passed)
// this check will fail. the second check is the same, except it accounts for the
// fact that 10 ints in the buffer are already used for other purposes.
if (numFds >= maxNumber || numInts >= (maxNumber - 10)) {
    // clear data and fail
    width = height = stride = format = usage = 0;
    handle = NULL;
    ALOGE("unflatten: numFds or numInts is too large: %d, %d",
            numFds, numInts);
    return BAD_VALUE;
}

稍后我们可以看到更多的检查...

 // originally this was set to 0, which essentially did nothing.
 size_t fdCountNeeded = numFds;
 if (count < fdCountNeeded) return NO_MEMORY;

调用后的最终检查native_handle_create可防止分配失败导致问题 -即使为 null,它也会执行memcpyto ,从而导致 null 取消引用错误(DoS 条件)。h->datah

因此,这些检查确保输入值不会超出范围,因此不能像以前那样被利用。

第三,h->data 是什么意思,因为我找不到名为 data 的变量的声明,而且我不知道 -> 是什么意思?什么是 sizeof(int)?

->运算符是取消引用左边的操作数是指向结构的指针。所以h->datadereferences h,它是一个指向类型结构的指针native_handle(我们之前提到过)并访问该data结构的字段。相比之下,如果h不是指针而只是一个普通的结构,你会这样做h.data

sizeof宏为您提供类型的大小(以字节为单位)。因此将为您的编译器实现和目标平台sizeof(int)返回本机大小。int在大多数情况下,它应该返回 4 或 8 来指定 32 位或 64 位整数。

最后我认为 numFds 是 unsigned const size_t numFds = buf[8];,如果 numFds 是 size_t 类型,我认为它不应该能够保持负数开始(除非我错过了什么)?

是的,numFdssize_t,但这只是意味着该值中的原始字节被解释为无符号整数。如果它包含 0xFFFFFFFF,则其直接解释的十进制值将是 4294967295。但是,当您将 asize_t转换为 an时,问题就出现了int,此时其中的原始字节被解释为有符号整数,在这种情况下,0xFFFFFFFF 突然变成 -1 .

另外,我不明白我们如何从创建堆损坏或数组索引越界异常等转移到任意代码执行?

这是一个复杂的话题,但我会给你一个总结。堆是您的大部分应用程序数据所在的位置。这意味着当您制作缓冲区和类实例以及其他各种东西时,大部分数据都在堆中。现在,假设您有一个带有事件的类 - 让我们称之为Dog,并将事件称为Bark当某些条件出现时,该类的一个实例将引发该Bark事件,以便其他对象可以对该树皮作出反应。事件只是一个指针。其他对象通过为其分配一个指向方法的指针(即指向某些代码的指针)来处理该事件。因此,当Dog实例想要 raiseBark时,它会查看Bark指针并开始执行那里的代码。在汇编中,这看起来像call [eax+0x0...].

由于Dog实例存在于内存中,在堆上,它的Bark指针也存在。如果您可以覆盖Bark指向其他可执行内存的指针,然后导致Dog实例引发事件,它将在其他位置运行代码。如果您控制该位置的代码(例如,通过用代码填充缓冲区并将该地址用作指针),那么您将获得任意代码执行。

您可以通过简单地用可能以有意义的方式覆盖目标数据的数据结构来充斥堆来利用这一点。它并不总是 100% 可靠,但有时就是这样。

NX 和 ASLR 让事情变得更加复杂,您必须使用指针泄漏和 ret2libc/ROP 来利用它,但这是一个更大的话题。

如果你想进入漏洞利用写作,我强烈推荐Corelan 的漏洞利用写作教程它们有点过时了——你需要一个禁用硬件 NX 的 WinXP VM——但它仍然是一个强大的资源。

虽然我不熟悉这个特定的漏洞,但我可以笼统地回答这个问题。

这究竟是什么意思:远程用户可以发送特制数据来触发 GraphicBuffer::unflatten() 中的整数溢出

这并不意味着远程攻击者可以以某种方式对GraphicBuffer::unflatten. 这意味着远程用户有(甚至“可能”有)一条路径可以将数据发送到您的 Android 设备,以便最终将数据传递GraphicBuffer::unflatten给可能导致整数溢出。例如,假设这GraphicBuffer::unflatten与渲染图像有关,攻击者可以创建格式错误的图像并将其插入他们希望在您的 Android 设备上打开的电子邮件或网页中。

用户将如何执行这个任意代码,这是什么类型的代码?

除非研究人员发现此整数溢出漏洞的特定利用,否则它不太可能导致任意任意代码执行。也就是说,可以想象存在可能导致任意代码执行的代码路径。

我知道上述文本中有很多“如果”和“可能”,但为了对漏洞披露负责,公司试图记录可想象的最坏情况。Adobe 的 Acrobat Reader 漏洞列表就是一个很好的例子虽然他们将大多数漏洞归类为具有远程访问权限,但我怀疑这主要是由于在本地 PDF 上下载和运行阅读器所致。

不幸的是,由于漏洞披露旨在试图向攻击者隐藏详细信息,因此它们经常让客户感到困惑并想要更多信息。

注意:无论是实际的远程执行威胁还是只是理论上的威胁,您都应该尽快通过修补来做出响应。