现实世界的堆漏洞利用图鉴

GitHub Link

https://github.com/hac425xxx/heap-exploitation-in-real-world

概述

本文将以多个真实的堆相关的漏洞以及一些高质量的研究文章为例,介绍在真实漏洞场景下的堆利用技巧,主要是介绍各种现实漏洞场景的堆布局技巧。

vuln obj : 存在漏洞的对象,比如存在堆溢出的对象。

target obj: 漏洞利用时需要篡改的对象,比如利用堆溢出时,我们想覆盖的对象。

A tale of two mallocs

当来利用堆溢出的时候,我们需要找一个 gadget object 来进行利用

常用的 gadget object 类型:

  1. 有函数指针,虚表指针,可用于劫持控制流。
  2. 对象中有 size/length 字段,修改这个字段可以导致后续使用对象时发送越界。
  3. 对象中的指针在后面的逻辑中会被用于读、写内存等操作,利用程序的逻辑可以完成一些漏洞利用的原语。

此外利用堆漏洞非常重要的是进行堆布局,为了进行堆布局我们需要找到外部输入可控的内存操作原语,搜索内存操作原语时需要关注三个要素:

  1. 内存分配的大小是否可控
  2. 内存中的内容是否可控
  3. 分配到的内存的生命周期是否可控,即我们能否控制其申请和释放的时机

然后根据不同的内存控制原语采用合适方式进行堆布局,常见搜索内存操作原语的思路是在代码中找含有malloc,new,realloc,std::vector,std::string的调用点,然后判断是否可以由外部输入触发。

作者提到进行堆布局的时候,可以选用占位式堆喷,如图所示:

此外为了提升溢出到 gadget 的概率我们可以先把内存碎片清理了,然后再进行占位式堆喷,这样 vuln obj 和 gadget obj 大概率会相邻分配。

如果分配 gadget object 的时候会有一些小对象的分配从而导致影响内存布局,我们可以先在堆上占位一些小的内存块,然后在分配 gadget 前释放一些小的内存块,这样小对象就会落在之前占位的小内存块中,避免影响 vuln obj 和 gadget object 之间的布局。

Assuming the gadget object is the one which allocates the unwanted allocations, one way to deal with this issue is to do the following:

  1. First, at the beginning of the exploit, we allocate a bunch of smaller blocks, let’s say 100 blocks of 0x100 bytes each
  2. We then allocate all the placeholder sets (表示一组相邻的 overflowable 和 gadget 占位对象)
  3. Then, for each placeholder set, we first free the gadget placeholder.
  4. Then we free a few of the small filter allocations. These will be added to the free bins
  5. Then we allocate the gadget itself, the smaller unwanted allocations will use the filter allocations we just freed, and the gadget itself will fall on the placeholder.

CVE-2015-3864

漏洞是一个整数溢出导致的堆溢出

case FOURCC('t', 'x', '3', 'g'):
{
    uint32_t type;
    const void *data;
    size_t size = 0;
    if (!mLastTrack->meta->findData(
            kKeyTextFormatData, &type, &data, &size)) {
        size = 0;
    }
    if (SIZE_MAX - chunk_size <= size) { // <---- attempt to prevent overflow 
        return ERROR_MALFORMED;
    }
    uint8_t *buffer = new uint8_t[size + chunk_size];
    if (size > 0) {
        memcpy(buffer, data, size);
    }
    if ((size_t)(mDataSource->readAt(*offset, buffer + size, chunk_size))
            < chunk_size) {
        delete[] buffer;
        buffer = NULL;
        return ERROR_IO;
    }

在分配内存前有一个检查,但由于 chunk_size 在 32 位系统下下也是 uint64_t 的,所以当 chunk_size > SIZE_MAX 时(比如 0xFFFFFFFFFFFFFFFF),会通过检查,然后下面分配的时候就会整数溢出。

从文件中解析 chunk_size 的代码如下

status_t MPEG4Extractor::parseChunk(off64_t *offset, int depth) {
    uint32_t hdr[2];
    if (mDataSource->readAt(*offset, hdr, 8) < 8) {
        return ERROR_IO;
    }
    uint64_t chunk_size = ntohl(hdr[0]);
    uint32_t chunk_type = ntohl(hdr[1]);
    off64_t data_offset = *offset + 8;

    if (chunk_size == 1) {
        if (mDataSource->readAt(*offset + 8, &chunk_size, 8) < 8) {
            return ERROR_IO;
        }
        chunk_size = ntoh64(chunk_size); // 从文件中获取 8 字节的 chunk_size

因此通过这个漏洞我们可以实现一个堆溢出,vuln obj 的大小可控,溢出的内容可控,由于是整数溢出后面可能会拷贝大量的数据(比如 0xFFFFFFFFFFFFFFFF),最终可能会由于访问到没有映射的内存导致进程崩溃。

因此,利用这种类型的整数溢出漏洞,我们需要找到一些路径,让溢出发生后,不会拷贝过量的数据,对于这个漏洞来说我们可能有以下两种方式:

  1. 内存分配后,程序会调用 readAt 从文件中读取 chunk_size 的数据,如果能让 readAt 返回值小于 chunk_size (比如控制文件大小),就能避免溢出过量的数据。
  2. 通过覆盖在具体数据拷贝之前调用的函数指针完成利用,从而避免了溢出过量数据。

该漏洞的利用采取的是第二种方式,即溢出到 mDataSource 对象 (其类型 MPEG4DataSource ),然后把该对象的虚表劫持,从而劫持 readAt 这个虚函数调用。

那么利用该漏洞的 target obj 就是 mDataSource 对象,如果要利用该漏洞,buffer 和 mDataSource 的内存布局如下:

下面就介绍如何利用程序中的逻辑来整理堆的布局,实现上述的布局,首先需要分析源码定位 vuln obj 和 target obj 的分配方式,当解析到 stbl 标签时,会分配 mDataSource 对象(target obj),当解析 tx3g 标签时就会分配 vuln obj 并触发溢出。

然后再看看程序用的内存分配器的一些特点,其使用的是 jemalloc ,jemalloc的大概思路是把内存块分成大小相同的 object,然后返回给申请者,类似于内核的 slub 分配器。

由于堆分配器的实现,堆在初始情况下先分配的对象的地址会比后分配的对象的地址小,这时如果直接先分配 target obj 的话,vuln obj 就会在 target obj 的后面,因此无法溢出到 target obj.

假如先分配 vuln obj 的话,由于漏洞触发点的内存分配和数据溢出是一起发生的,也是无法覆盖到 mDataSource

或者我们可以利用jemalloc的LIFO特性,先把后面的块释放了,然后把前面的块释放了,然后再先分配 target obj 后分配 vuln obj,也可以实现上述的布局。

exploit 里面的思路是采用占位对象,然后通过控制占位对象的分配与释放,来完成布局,示意图如下:

  1. 首先分配两个占位对象 A 和 B,且 A 和 B 的前后都是被分配出去的内存,然后释放B,再利用 stbl 标签分配 target 对象,这样 mDataSource 就会落在 B 的位置。
  2. 然后释放 A ,并利用 tx3g 标签触发漏洞,分配buffer到 A 的位置,就能完成我们需要的布局,这样一溢出就可以覆盖到 mDataSource 对象。

要完成上述布局,关键点在于需要找到可以通过用户输入(mp4文件)控制 分配/释放 的代码逻辑,在 libstagefright 中我们可以利用 avcChvcC 来进行占位,通过这两个标签的特点为:

  1. 分配任意大小的内存,内存中的内容可控(从文件读取)
  2. 内存释放的时机可以通过输入文件中的特定标签控制

利用 avcChvcC 来占位的示例图如下:

为了稳定的完成堆布局,还需要解决内存碎片的问题,上述布局成立的前提是 avcC 和 hvcC 对象是相邻的,如果内存中存在内存碎片的话,这两个对象是有可能不相邻的。

为了解决这个问题常用的方式是首先把之前的内存碎片清掉(即大量分配内存,把之前堆中碎片的内存块申请完),之后再申请内存的时候,我们分配到的内存块的顺序就会符合我们的预期了。

exploit 中使用的是 pssh 标签来清理内存碎片,原因是程序在解析 pssh 标签时会根据标签中的数据分配内存,内存的大小和内容可控,且分配出来的内存在整个文件解析完成后才会被释放,因此直接在输入文件中放置多个 pssh 标签就可以完成内存碎片的清理工作。

完成堆布局后现在就可以溢出到 mDataSource 对象 (其类型 MPEG4DataSource ),接下来的利用思路就是覆盖对象的虚表,把虚表劫持到我们伪造的需要,然后对象进行虚函数调用的时候就可以劫持pc。

目前的问题是如何知道 伪造的虚表 的地址,获取地址一般有两种方式:

  1. 找一个信息泄漏漏洞,或者利用漏洞构造信息泄漏,从而可以拿到堆的地址,然后把虚表指针指向堆上的可控数据位置即可。
  2. 利用堆喷射技术,在某个固定的地址布置伪造的虚表数据,然后把虚表指针指向该位置即可。

这里使用的是堆喷射技术,以32位系统为例,堆喷射的思路大概是由于进程虚拟地址空间的大小限制,当程序分配大量内存时(比如 0xff000 字节时), 尽管存在随机化,我们依然可以以极大的概率在某个具体的地址(0xf7500000)上布置我们的数据。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <unistd.h>
#include <sys/mman.h>

#define ALLOC_SIZE  0xff000
#define ALLOC_COUNT 0x1

int main(int argc, char** argv) {
  int i = 0;
  char* min_ptr = (char*)0xffffffff;
  char* max_ptr = (char*)0;
  
  for (i = 0; i < ALLOC_COUNT; ++i) {
    char* ptr = mmap(NULL, ALLOC_SIZE, 
                     PROT_READ | PROT_WRITE | PROT_EXEC, 
                     MAP_PRIVATE | MAP_ANONYMOUS,
                     -1, 0);
    if (ptr < min_ptr) {
      fprintf(stderr, "new min: %p\n", ptr);
      min_ptr = ptr;
    }
    if (ptr + ALLOC_SIZE > max_ptr) {
      fprintf(stderr, "new max: %p\n", ptr + ALLOC_SIZE);
      max_ptr = ptr + ALLOC_SIZE;
    }
    
    memset(ptr, '\xcc', ALLOC_SIZE);
  }
  
  fprintf(stderr, "finished min: %p max %p\n", min_ptr, max_ptr);
  ((void(*)())0xf7500000)();
}

利用大量的内存分配可以让进程的某个地址存放我们的数据,数据的内容如何控制呢,按照我的理解,我们在进行堆喷的时候尽量按页对齐去分配,然后以页为单位进行数据布局,就可以实现在可预测的地址,布置可控的数据,具体的偏移和布局还需要调试确认。

exploit 中的堆喷射思路如下

小结

该漏洞的利用非常经典,主要的特点如下:

  1. 触发整数溢出后,通过劫持后续可能会使用的虚函数调用来避免整数溢出后的内存拷贝访问到不可访问的内存.
  2. 通过分析程序对文件的解析流程,找到了内存申请和释放的原语(avcC 和 hvcC),随后使用这两个标签进行占位并完成了 vuln obj 和 target obj 的布局。
  3. 为了增大占位成功的概率,使用了 pssh 标签分配大量对象,清理内存碎片。
  4. 为了在没有信息泄露的情况下劫持虚表,利用 pssh 进行堆喷射,在可预测的地址处放置 rop gadget.

主要可以学习的思路有:

  1. 触发漏洞后,要思考后续的步骤,比如后续要覆盖什么,为了完成覆盖,需要什么样的内存布局。
  2. 然后去程序中查找是否存在内存控制的原语,比如内存申请/释放原语,申请的大小、内存的内容、释放的时机是否可控等。
  3. 然后就根据堆分配器的特性来尝试堆布局。
  4. 堆喷射在32位下还是很有用的,64位下在一些情况下面也是可以用的。
  5. 占位型堆布局和内存碎片的清理也非常的经典。

CVE-2017-0781

漏洞是8字节的溢出, vuln obj 的大小可控。

作者通过不断调整 vuln obj 的大小来触发漏洞,然后分析 crash 的上下文,最后在32字节的run中找到了合适的 target obj (fixed_queue_t),该对象中有个函数指针,可以用于劫持控制流。

为了实现控制 pc,还利用堆喷在可预测的地址上布置了数据,布置的过程主要靠尝试和调试。

小结

该漏洞利用最直接借鉴的思想是通过不断尝试 vuln obj 的大小,来找到潜在的 target obj,这种思路比较有通用性,也可以减少人工搜索 target obj 的工作量,甚至会发现一些比较神奇的对象。

还有就是 jemalloc 的堆喷可以通过观察 regions 的内存布局来提升成功率。

Take Down MacOS Bluetooth with Zero-click RCE

这组漏洞利用包含两个漏洞,一个信息泄露漏洞(CVE-2020-3847)和一个堆溢出漏洞(CVE-2020-3848)。

CVE-2020-3847 的成因是内存申请的大小和访问内存时的偏移检查有问题,漏洞触发流程:

  1. 首先发一个请求,让 ServiceAttributeResults 分配 16 字节的内存
  2. 然后再发一个请求,设置一个比较大的偏移,就可以 泄露出 ServiceAttributeResults 后面的内存数据

CVE-2020-3848 的漏洞成因是分配内存时分配的是 0x20 字节,但是拷贝数据时最多可以拷贝 0xff 字节。

为了实现 Zero Click 的漏洞利用,作者经过分析发现只能通过创建 SDP 连接来让目标分配内存,且一个设备最多只能创建 30 个 SDP 连接,创建 30 个 SDP 连接后实际会分配的对象和关系如下图

随后作者通过释放其中的几个 SDP 连接对象,并结合信息泄露漏洞来检查当前堆布局是否可以用于利用,即vuln obj 后面是否存在可以用于利用的对象。

大概思路应该是通过一些内存的申请释放看能否让 vuln obj 后面放置可利用的对象。

小结

主要启示在于:

  1. 在审计代码的时候,要注意审计内存申请和使用不是在同一次请求、解析过程中处理的情况,两者的不一致就有可能导致漏洞。
  2. 该漏洞利用的堆布局有点撞运气的感觉,不过由于有信息泄露,我们可以多次尝试,直到符合预期的堆布局出现再触发堆溢出漏洞。
  3. 有限的堆操作原语(只能控制某些特定对象的申请于释放)也是非常有用的。

BleedingTooth: Linux Bluetooth Zero-Click Remote Code Execution

这篇文章涉及3个漏洞,本节主要涉及其中2个被利用的漏洞,即 CVE-2020-12352 和 CVE-2020-12351,比较有意思的是作者在编写 CVE-2020-12352 的 POC 时,触发了 CVE-2020-12351.

CVE-2020-12351 是一个栈变量未初始化漏洞导致的信息泄露,作者通过随机的发送一些报文进行尝试,最终可以通过该漏洞泄露出 内核和堆的地址。

CVE-2020-12352 是一个类型混淆漏洞,漏洞的成因是把 struct amp_mgr 对象当作了 struct sock 对象传入了 sk_filter 函数进行处理。

经过分析,通过控制 sock 结构体 (即被类型混淆的 amp_mgr 对象)的 sk_filter 指针可以完成漏洞利用, struct sock 的结构体布局如下:

// pahole -E -C sock --hex bluetooth.ko
struct sock {
	struct sock_common {
		...
		short unsigned int skc_family;       /*  0x10   0x2 */
		...
	} __sk_common; /*     0  0x88 */
	...
	struct sk_filter *         sk_filter;   /* 0x110   0x8 */

可以看到 sk_filter 字段位于 sock 结构体偏移 0x110 处,但是 struct amp_mgr 的大小为 0x70,由于 slub 分配器的特性, amp_mgr 结构体实际会在 kmalloc-128 处分配,所以 sk_filter 字段实际位于 amp_mgr 结构体后面第二个内存块中,如下图所示:

amp_mgr 分配在 kmalloc-128 中,当 amp_mgr 被当作 sock 结构传入 sk_filter 函数处理时,其访问 sk->sk_filter 时,实际访问的是图中标红区域。

因此目前的问题是如何控制 fake sk_filter 字段,作者的思路是首先分配 amp_mgr ,然后再 kmalloc-128 中分配两块内存,从而控制 fake sk_filter 字段.

上述思路的关键是需要找到可以远程从 kmalloc-128 中分配内存的原语,常见思路是在协议栈代码中搜索内存申请函数(比如 kmalloc, kzalloc等),不过作者没有找到上述的原语,最终是利用 kmemdup 操作实现的内存申请,代码如下

// https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/net/bluetooth/a2mp.c
static int a2mp_getampassoc_rsp(struct amp_mgr *mgr, struct sk_buff *skb,
				struct a2mp_cmd *hdr)
{
	...
	u16 len = le16_to_cpu(hdr->len);
	...
	assoc_len = len - sizeof(*rsp);
	...
	ctrl = amp_ctrl_lookup(mgr, rsp->id);
	if (ctrl) {
		u8 *assoc;

		assoc = kmemdup(rsp->amp_assoc, assoc_len, GFP_KERNEL);
		if (!assoc) {
			amp_ctrl_put(ctrl);
			return -ENOMEM;
		}

		ctrl->assoc = assoc;
		ctrl->assoc_len = assoc_len;
		ctrl->assoc_rem_len = assoc_len;
		ctrl->assoc_len_so_far = 0;

		amp_ctrl_put(ctrl);
	}
	...
}

大概堆布局思路如下:

  1. 首先利用 kmemdup 的原语大量分配内存,清理之前 slab 中的碎片
  2. 然后分配 amp_mgr ,之后再分配一些 ctrl->assoc,最后触发漏洞就可以控制 fake sk_filter 的值。

不过看 exploit 里面的堆布局思路,貌似是直接先分配多个 ctrl->assoc,然后释放掉最后一个 assocamp_mgr,最后重连,就有一定概率会达到上面的布局,从而完成利用。

堆布局相关代码片段:

小结

  1. 搜索内存操作原语时,间接调用内存申请的函数也是需要关注的。
  2. 做漏洞利用时有一定概率也是可以接受的,比如堆布局,即使无法达到100%,能提升一点概率是一点,或者如果概率可以接受的话也可以直接随机着来。

Over The Air: Exploiting Broadcom’s Wi-Fi Stack (Part 1)

文章中利用的漏洞是固件在解除 TDLS 连接时 由于解析 FT IE 时没有检查长度导致的堆溢出漏洞

因此我们就有了一个堆溢出漏洞,vuln obj 的大小是 256 字节,下面就需要找被溢出的对象,以及寻找堆布局的方式。

作者首先逆向分析了固件中的堆分配器,然后通过 固件代码 patch、内存dump等手段实现了一个堆的可视化工具,可以跟踪固件的内存申请和释放、可视化某个时刻的堆内存布局:

图中红色表示正在使用的内存块,灰色表示处于 free 状态的内存块。

  • 图中第一行表示 初始的堆状态,可以看到这里有一块很大的空闲内存.
  • 第二行表示 创建 TDLS 连接后的堆状态,有很多内存被使用了,值得注意的是此时堆中还有两块特别标注的空闲内存块,即 0x1f03fc (大小为 0x11c )和 0x1f2108 (大小为 0x124).

然后我们如果尝试解除 TDLS 连接并发送恶意的FT IE触发漏洞,由于堆分配器的实现,此时 malloc(256) 会分配到 0x1f03fc 这块内存,我们也就可以溢出到 0x1f0520 后面的块。

此外经过作者的不断尝试发现,堆状态非常稳定,每次 创建/解除 TDLS 连接 时堆的状态基本是一样的,这样如果能在这个堆状态下完成利用,exploit的稳定性应该也是比较高的,不过如果程序中有一些可用于堆布局的原语(内存申请、释放),可能可以改变这个内存布局,不过作者没有尝试,而是在当前内存状态下完成了利用。

因此 vuln obj0x1f03fc 这块内存,target obj0x1f0520 后面的内存块。

对于堆溢出的利用常见的思路有两种:

  1. 修改堆块头的元数据,比如 size,或者处于释放状态的块中的 next 指针
  2. 修改堆中对象的数据,比如对象的长度字段,堆中的指针等,然后利用程序对对象的操作来进行后续的利用

vuln objtarget obj 的内存 dump 如下:

可以看到 target obj 的头不由两个看着像指针的值,不过经过修改尝试发现这两个指针好像没有人用,因此目前只能尝试修改内存块的 头部字段,由于 target obj 所在内存块是 inuse 状态,所以头部字段中只有 size 字段是有效的。

glibc 的堆溢出 中常用的方式是修改 size 字段来构造重叠的堆块,这里也是这样的思路,由于目标系统中堆的使用情况比较复杂, 作者采取的方式是,写脚本自动化地测试,即尝试把 target obj 的 size 字段修改成不同的值,然后检查操作完成后的堆状态。

经过测试,作者发现把 target objsize 字段覆盖为 72 并断开 TDLS 连接,堆中出现了重叠的堆块:

可以看到在断开 TDLS 连接后,0x1f072c 这个 0 字节的 chunk 位于一个大的空闲块中间。

创建重叠堆块后,还需要一个控制内存分配的原语,用于分配到重叠的块,然后修改它的 size 和 next 指针,从而实现任意地址写,固件处理 action 帧的代码中就存在这样的逻辑:

A 是一个全局变量,利用 A 的分配和申请逻辑,可以获取一个生命周期、大小、内容可控的内存控制原语

最后利用堆分配的 best-fit 特性,修改 chunknext 指针,实现把一个已分配的块链接到空闲块的链表里面,然后实现任意地址写,最后的利用手法如下:

首先利用任意地址写,往一个初始化阶段分配的堆块中布置 shellcode(由于没有地址随机化,系统启动过程中分配的一些堆内存的位置会固定),然后修改 堆中定时器的函数指针,最后等待定时器到期,执行 shellcode.

选择把处于使用中的堆块链入空闲块链表的原因是:处于使用的块的 next 指针为0,这样就不会导致内存分配器在分配内存时由于遍历链表导致崩溃。

小结

  1. 堆利用的时候,通过调试、打印、dump等手段查看 vuln obj 附件的内存状态,以及触发漏洞前后的内存使用情况,可以很好辅助漏洞利用、定位漏洞利用的方案。
  2. 对于比较复杂、不熟悉的场景可以尝试暴力枚举,来探测可利用的方案。

CVE-2021-3156

漏洞成因是 sudo 在解析命令行参数时存在堆溢出,vuln obj 的大小和内容可控。

目前问题就是寻找 target obj,作者的思路是fuzz,通过随机选择 vuln obj 的大小、溢出的大小、setlocale 涉及的相关环境变量(用于控制溢出前的内存块布局),最后通过对 crash 的上下文去重,发现了三种可用于利用的场景,本节将介绍通过覆盖 service_user 完成利用的过程。

首先通过调整 poc 和调试,能够发现在堆上确实是存在 service_user 结构,不过调试发现其和 vuln obj 的偏移过大,直接去尝试覆盖 service_user 程序会由于中间的一些数据被覆盖而崩溃,且两者的偏移也不固定。

为了解决这个问题,可以利用 setlocale 函数中的 内存分配/释放 操作进行堆布局,让 user_args 落在 service_user 的前面。

setlocale 函数在解析环境变量(比如LC_CTYPE, LC_MESSAGES, LC_TIME等)时,会申请内存并把环境变量的值拷贝到申请的内存中,申请的内存在函数退出前会被释放。

通过利用 setlocale 函数的逻辑和堆分配器的机制,我们提前在堆中创建一些 free 的堆块,让 service_user 分配到其中的一个空闲块,这样在漏洞触发时就能在 service_user 前面留一个 free 堆块,这时我们将 user_args 分配到 service_user 前面就可以稳定溢出service_user 了,大概流程图如下所示:

小结

  1. 通过 fuzz 来探测可能用于利用的内存状态值得学习。
  2. 通过提前在堆里面凿一些洞(空闲的堆块)进行堆布局的思路非常新奇。

Zoom RCE from Pwn2Own 2021

漏洞是 Zoom 在进行密钥协商时存在一个堆溢出,堆溢出的情况如下:

  1. vuln obj 的大小固定 1040 字节
  2. 溢出的大小和内容可控

漏洞利用的环境是 win10 + 32位的 Zoom 软件。

首先利用堆溢出实现信息泄露,常见的思路是分配一些带 length 字段的对象,然后覆盖length字段从而 leak 出一些数据,经过一番分析没有发现能够回显的这种对象。

最后发现,可以给目标 Zoom 客户端发送特定的请求,让对端 Zoom 向我们的服务器发起 https 请求,请求的 url 部分可控,有种 SSRF 的感觉。

接下来就是利用这个 bug 进行信息泄露,具体来说就是利用溢出把 URL 末尾的 \x00 覆盖掉,然后让目标客户端向我们的服务的发起请求,就可以泄露 URL 后面的内存数据了。

这里选择泄露的对象是 TLS1_PRF_PKEY_CTX,这个对象是 openssl 进行 TLS 协商时创建的,对象中存在指向 DLL 的指针,通过泄露这个指针可以可以拿到 DLL 的基地址。

如果堆布局按照预期就能在我们的服务器收到泄露的数据

不过由于 LFH 分配器的随机化和内存状态的不确定性,成功率还是比较低,作者采取了一些措施来提升成功率:

  1. 创建多个服务器的连接,用于不断的进行TLS协商,目的是在 LFH 的桶中分配大量 TLS1_PRF_PKEY_CTX,这个过程中即使对象被释放也没关系,因为释放之后内存中的指针不会被清理,也能进行leak,这样就可以增大布局的概率。
  2. 在覆盖的过程中,可能由于内存布局的不一致导致 vuln obj 和 url 之间存在一些带指针的对象(比如某些类的实例),作者的处理方式是首先通过堆喷在某个地址(比如 0x0d0d0d0d)放置数据,然后用 0x0d0d0d0d 覆盖对象,这样即使覆盖到了不正确的对象也不会导致进程崩溃,从而可以多次尝试。

完成信息泄露后,下一步就是找一个对象控制 PC, 这一步使用的是 FileWrapperImpl 该对象会在客户端被呼叫响铃的时候被不断的创建和删除,因此使用堆溢出覆盖其虚表即可劫持控制流。

为了劫持虚表还需要进行堆喷,攻击者可以发起请求让目标客户端加载 gif 图片,而且没有大小限制,利用这个机制就可以进行堆喷。

第二部的过程中也会存在很多不确定性,比如溢出 vuln obj 的过程中可能会覆盖到堆中的其他类的虚表,作者的解决方案如下:

  1. 不断尝试溢出、调试搜集在利用过程中可能会触发的场景,在虚表、rop 中进行适配,比如 A 类调用的时候可能用的是虚表偏移 0x20 的函数指针,我们就在 0x20 的函数指针布置适合 A 的利用 rop.
  2. 不断溢出,每次溢出的大小递增,避免出现一次溢出太多导致不稳定。

小结

  1. 利用客户端的 url 请求机制进行 leak 也是一个思路。
  2. 处理堆布局随机的情况可以多次尝试,为了能够多次尝试还需要合理控制溢出数据,避免溢出失败导致进程 Crash.
  3. 针对不同的溢出场景,在虚表中进行适配思路非常不错!

Instagram_RCE: Code Execution Vulnerability in Instagram App for Android and iOS

漏洞是在解析图片时存在整数溢出导致的堆溢出

首先根据 width, height, output_components 计算分配内存的大小,然后从文件中一段一段读入内存,如果 width * height * output_components 发生整数溢出,后面从文件中读取内容时就会溢出。

之前提到过利用整数溢出漏洞需要找到终止拷贝的条件,或者在访问到非法地址前劫持控制流,这里采用的是覆盖拷贝过程中会用到的函数指针,在循环访问到非法地址前劫持控制流。

作者通过分析循环中的一些函数调用,发现在 jpeg_read_scanlines 函数和子函数里面会使用 cinfo 结构体中的一些的函数指针

struct jpeg_decompress_struct { 
       ......
       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
       ......
};

target obj 找到了不过由于没有找到合适的内存控制原语,没法进行完美的堆布局,作者最终采取的策略是通过调试和观察漏洞触发前后的堆状态,来搜索可能用于利用的 vuln obj 大小。

首先通过分析知道 cinfo 分配在 0x5000run 中,然后查看 cinfo 前面的一些run中的使用情况,发现可以通过分配 0xe0 的内存,然后往后溢出即可覆盖 cinfo .

原文作者到这里就没继续了,这种方式有点撞运气的成分,如果进程在之前有些其他分配就可能会导致偏移不一致,不过原文中也提到了,我们可以溢出多一点总是能够溢出到函数指针的。

小结

  1. 通过修改循环中的函数指针来防止整数溢出后的Crash也是比较经典。
  2. 有时候实在没有堆控制原语时,也需要多调试、分析,查看 target obj 和 vuln obj 的附件是否有可以用于利用的内存块。

3D-Red-Pill-A-Guest-To-Host-Escape-On-QEMUKVM-Virtio-Device

漏洞利用链包含两个漏洞:

  1. malloc 未初始化导致的信息泄露。
  2. vrend_resource 堆溢出,vuln obj 的大小可控,溢出的大小内容可控。

利用未初始化漏洞的思路是堆喷带有函数指针的结构体,然后释放这些结构体,最后触发未初始化漏洞并获取未初始化的内存内容,从而获取 virglrenderer 库的地址。

获取 libc 的地址的思路是申请大块内存,内存中就会包含libc 的地址

堆溢出的利用思路是堆喷多个 vrend_resource 对象,然后利用溢出修改下一个 vrend_resource 对象的指针,从而实现任意地址写

溢出前,两个 vrend_resource 对象的布局

溢出后,第二个 vrend_resource 对象的指针修改为任意地址,然后利用程序的逻辑就可以实现任意地址写

小结

  1. 利用未初始化漏洞进行内存泄露的思路值得学习,比如堆喷带指针的结构,然后利用未初始化漏洞获取地址。
  2. 通过堆喷 vuln obj ,然后溢出下一个 vuln obj 的指针来进行任意地址写,得到启示是做利用的时候可以先看 vuln obj 是否就可以作为 target obj.

GROOMING THE IOS KERNEL HEAP

主要就是介绍如何在 ios 的内核里面进行占位式堆布局

具体思路是先清理堆中的内存碎片,然后用多个 victim object 包着一个 vuln object ,这样就能提升溢出到 victim object 的成功率,如下图所示

其中: V 表示 victim object, P 表示用于占位的对象,后面溢出的时候就会把 P 释放,然后分配 vuln object.

为了进一步增加漏洞利用的成功率,可以多创建几个用于漏洞利用的区域,多次触发漏洞来提升成功率,如下图所示:

小结

通过在 vuln obj 前后多包裹几个 victim object 和 创建多个漏洞利用区域来提升成功率的思路值得借鉴。

posted @ 2021-10-06 16:01  hac425  阅读(1052)  评论(0编辑  收藏  举报