2020网鼎杯部分题目WriteUp。
orwpwn
程序分析
去掉了符号表,不过问题不大,唯一有点注意的是堆分配函数,最开始以为是malloc,后面调试是才知道是 calloc。
1 | __int64 __usercall Delete@<rax>(__int64 a1@<rbp>) |
漏洞点很明显,delete函数没有将堆指针清空。但是这里只限制删除2次。Create函数中,对创建堆块的大小没有限制,但只能申请19次。
利用分析
这题,我做的十分复杂。主要是受到Create函数没有限制堆块大小,所以就一下想到 largebin attack去了。所以后面就越做越复杂。不过还是先说说自己的方法,然后再说说BlueSheep学长的方法。
- 修改
delete_num
由于delete_num为2,只能删除2次。所以想到把这个值改大,并且程序没有开启PIE。而且对申请堆块没有限制。所以就先用largebin attack将delete_num改为一个堆块地址。由于2.31对 largebin attack加了保护,导致我们只能修改 largebin->bk_nextsize这一个地方,所以2.31下的 largebin attack一次只能修改一个地址。这里就先修改 delete_num。
- 修改 global_max_fast
现在,我们能够无限次delete,也能够UAF。但是这里是用 calloc分配,所以 tcache相关的攻击都无效了,需要考虑 fastbin attack。
这里,我主要参考了这篇文章和BlueSheep学长的这篇文章。
大概思路就是先修改global_max_fast,来使得我们能够将大块放到fastbin中,然后劫持 main_arena。
这里,我们执行第二次 largebin attack来修改 global_max_fast的值,将其改为 一个堆块值。
- 劫持 main_arena
这里劫持 main_arena的方法,是我想重点介绍的。我们通过上一步将 global_max_fast改为一个堆块值后,我们释放 8个 0x100的堆块。
此时,会填满 tcache,然后最后一个放入 fastbin中。此时 main_arena开头处 会被填入一个 1。我们通过错位便可构造一个 chunk_size 为 0x100.
然后,我们使用 fastbin attack,修改 fd指向 fake_chunk。最后分配出来,就可以劫持 main_arena数组。
- 修改
free_hook
这里劫持了 main_arena方法后,其实就有很多做法了。比较传统的是修改 top chunk的地址为 free_hook之上的地址。然后后续就可以不断分配 top chunk分配到 free_hook了。但是这里我不推荐,因为我们前面通过 largebin attack留下了很多 largebin。这里分配堆块时,会先去切割 largebin,这里就会报错。
我采取另一种做法:我们先申请一个 0x70的堆块,在chunk_size_list中留下 0x70。然后将 main_arena中 0x70 fastbin 链表指向 chunk_size_list中0x70的位置。最后,我们申请 0x70的堆块,就能够再次劫持 chunk_list了。
getshell
劫持了chunk_list,我们后面就可以直接修改 free_hook为 magic_addr。这个magic_addr我在之前 2.31攻击中讲过,这次不赘述。但是这里还是要说明一下我自己写得那个构造 magic_frame的函数(因为,我自己都忘记这是啥了:)
1 | def magic_frame(rdx_rdi, secontext_addr, rdi, rsi, rdx, rsp, rip): |
这个函数,可以直接构造 对应2.31magic_gadget所用的frame数据。7个参数的说明分别如下:
1 | rdx_rdi: magic_frame地址 |
当然,这里我直接构造ORW的rop链,所以我exp中的rip就是ret地址。还有一个要点,我们自己后续构造的rsp前面要加上p64(0),用来让rip占位。
正常思路:
上面我的思路,我踩了很多坑才达到了劫持chunk_list的目的。但是,如果我们一开始就想着如何劫持chunk_list。那么我们可以使用 unlink attack。
这里 BlueSheep学长的思路就是通过构造 unlink attack 来劫持 chunk_list,劫持完后的思路大同小异。exp我就不放了。
EXP
1 | from pwn import * |
message_system
程序分析
程序有很多函数,但是只要头铁不断逆,就能分清楚整个逻辑和找到漏洞函数。毕竟外表上也是一个常见的菜单题。
1 | unsigned __int64 __fastcall insert(__int64 *list_begin, __int64 list_ptr, __int64 addr) |
漏洞点在 Create函数,创建新堆块后将堆块插入 chunk_list时。如果原来的chunk_list满了,就会拓展chunk_list,其逻辑如上所示。拓展chunk_list,他会把原来的chunk_list的值填入新的chunk_list,然后删掉原来的堆块和原来的chunk_list。这里就造成了 UAF,原来的堆块被释放了,可是新的 chunk_list中仍然有他们的地址。
利用分析
这道题的难点,还在于 每次创建一个堆块写入数据后,程序都会对我们写入的数据和一个随机数数组异或加密。而这个随机数数组是通过如下逻辑生成:
1 | unsigned __int64 init_0() |
通过时间种子mod 8后,通过rand生成的伪随机数。那么这里我们就可以将这个随机数数组爆破出来。毕竟只有8种情况。
然后我们再写个解密函数,将我们每次输入的数据使用解密函数解密后再输入。这样后面加密后,就变成了正常的数据。
Tips:这里由于加密函数的逻辑不是太难,所以可以直接逆。如果太难了,xmzyshpnc告诉我了一种取巧的方法,以后如果太难了可以使用。
EXP
1 | from pwn import * |
heatmap
程序分析
程序有一个system函数:
1 | sprintf(&s, "%s.png", templatea); |
执行system的参数 command是受到 aGnuplotEsetTer影响。而这个数组在data段中紧邻于 point_list之下。
1 | __int64 __fastcall get_list(double chunk1, double chunk2) |
而 ponit_list数组有一个 自加功能。这里就存在一个数组越界。
利用分析
数组越界了,且可以自加。那么我们就将 aGnuplotEsetTer的前几位字符串改为 /bin/sh\x00,即可完成 getshell。
EXP
1 | from pwn import * |
- 本文作者: A1ex
- 本文链接: http://yoursite.com/2020/12/02/2020网鼎杯/
- 版权声明: 本博客所有文章除特别声明外,均采用 MIT 许可协议。转载请注明出处!