2021 XCTF-final,就看了三道题。house-of-pig帮助我回忆了largbin-attack和Tcache-stashing-unlink攻击。
House of pig
程序分析
程序总体实现了一个菜单题,可以增加,删除,修改,输出堆块内容。但是总共分为三种类型,第一种只能修改堆块的前 0x10个字节,第二种只能修改堆块的0x10-0x20个字节,第三种只能修改堆块的0x20-0x30个字节。申请的堆块范围为 0x8f - 0x440。
1 | unsigned __int64 __fastcall add_3(__int64 a1) |
这里在 创建第三类堆块时,如果创建了5次,则会触发一个后门函数 calloc(0xe8)
。
还有一个切换角色的功能,可以切换三种角色。这里就存在漏洞点:
1 | unsigned __int64 __fastcall change_role(__int64 a1) |
这里用 mmap_addr
作为一个全局变量来存储 角色的属性,当要从一个角色切换到另一个角色的时候,会先将当前角色的属性拷贝到 mmap_addr
,然后再从 mmap_addr
中将要切换的角色属性拷贝到 全局结构体中。这里存在的问题是,在切换角色保存当前角色属性时,并没有将所有属性保存成功。从下可以看到,当执行 delete
函数时,会将全局属性中该堆块 idx+288 和 idx+312 的字节写为 1,以表示该堆块被删除。但是 在保存属性时却没有保存这些属性。
1 | unsigned __int64 __fastcall del1(__int64 a1) |
如果我们现在角色1,将堆块删除,然后切换到角色2 申请堆块,将 全局变量中该堆块的 标识位改为0。然后再切换为 角色1,就可以实现对已释放的堆块 UAF
漏洞。
利用分析
这道题的利用比较负责,主要涉及的 largebin attack
来对一个任意地址写上 堆地址,利用 tcache stashing unlink
来分配一个任意地堆块地址,利用 IO_FILE
来 触发一个 malloc
流程。
地址泄漏
这里地址泄漏比较简单,直接利用 UAF
即可泄漏。这里可以选择 一个 largebin
来泄漏,即可同时泄漏 libc
地址和 heap
地址。
1 | #calloc申请5个0xa0堆块,并放入 tcache |
Largebin attack
随后,这里需要利用两次 largebin attack
来向两个关键地方写上堆地址。
第一处需要修改 free_hook-0x8
处 写上 堆地址
1 | print("---> 1 largbin attack to change __free_hook-8") |
这里由于是用到 2.31
的 largebin attack
,所以只剩下一条路径可以实现向任意地址写入一个堆地址。
这里需要两个堆块,chunk1
位于largebin
中,chunk2
位于unsortedbin
中,chunk1
的size
比chunk2
大。修改 chunk1->bk_nextsize = target_addr-0x28
,然后申请一个比 chunk1
和chunk2
的size
都小的堆块,此时会先将 chunk2
放入 largebin
中,达到将 target_addr-0x28+0x20
修改为 chunk1
堆地址的效果。
这里最终实现了将 free_hook-8
的地方留下了 chunk1
的堆地址。
第二处需要修改_IO_list_all
1 | print("---> 2 largebin attack to change _IO_list_all") |
再次利用之前的 chunk8
来实现 largebin attack
,修改_IO_list_all
为 chunk8
的地址。
tcache stashing unlink
接着就是需要利用 tcache stashing unlink
来将一个伪造的堆地址写到 tcache
链中,实现任意分配一个堆块。
这里首先需要明确,选择伪造堆地址是 free_hook-8
的地址
1 | print("==== tcache stashing unlink attack and FILE attack") |
接着就是触发 tcache stashing unlink
攻击。这里需要修改 smallbin
中 chunk8->chunk7
中 chunk8->bk
指针指向一个伪造地址free_hook-0x20
。然后调用一个 calloc(0x90)
触发,会将 fake_chunk->chunk8
放入 tcache
中。这里需要保证伪造的 chunk
的 bk
位置能够可写,而这里我们之前已经通过一个 largebin attack
将 free_hook-8
的地方写上了 一个堆地址,所以这里能够成功通过检查。
IO_FILE 攻击
1 | IO_str_vtable = libc_base + 0x1ED560 |
最后就是去触发这个 后门,使用 calloc
分配一个 0xa0
的堆块,写上伪造的 IO_FILE
数据。
这里的 fake_IO_FILE
数据需要保证 _IO_write_base=1
,_IO_write_ptr = 0xffffffff
, 将 vtable
改为 _IO_str_vtable
。
最后去触发一个 exit
函数。
getshell流程
这里讲一下调试时的 IO_FILE
攻击流程。这里最后触发了 exit
流程,最后会调用 _IO_flush_all_lockp
函数来遍历 FILE
结构体,来决定是否要刷新输出 FILE
结构。
1 | int |
这里会从IO_list_all
进入,并且调用 overflow
指针。这里第一个 fake_IO
并不会进入该流程,因为该流程就是一个 chunk8
。随后会顺着该 FILE
的 chain
找到下一个 FILE
。
这里的第二个 FILE
刚好就是 我们用后门 calloc
分配出来的chunk
。这里使得其 mode=0
,write_ptr>write_base
,且数据为 /bin/sh\x00
。并且修改其 vtable
指向了IO_str_jumps
。所以最终 能够去执行 __GI__IO_str_overflow
1 | int |
可以看到这个函数中,有一个 malloc
函数,该函数的 size
,是由 (IO_buf_end - IO_buf_base)*2 + 100
。这里由于 IO_buf_end
和IO_buf_base
都可以由我们伪造,所以可以确定 malloc
的 size
为 0xa0
。此时就会刚好分配我们 之前通过 tcache stashing unlink
伪造到 0xa0的 tcache,这个堆块刚好是 free_hook-0x20
。然后,可以看到最后还有一个 memcpy
函数,其会将old_buf
即fp->_IO_buf_base
的数据拷贝到 new_buf
中。而这里我们之前在 fp->_IO_buf_base
处写上了 /bin/sh\x00+p64(system)
的数据。
所以,最终会将 free_hook
修改为system
。而紧接着可以看到有一个 free
函数,参数为之前申请的 chunk
。所以这里最后会调用 free_hook
来 getshell
EXP
1 | from pwn import * |
- 本文作者: A1ex
- 本文链接: http://yoursite.com/2021/06/30/2021-XCTF-final题解/
- 版权声明: 本博客所有文章除特别声明外,均采用 MIT 许可协议。转载请注明出处!