BCTF 2017 - Poisonous Milk & Overwatch
2017年的BCTF,又匆匆出了2个heap的问题,都是些之前出现过的套路了。结果出了unintended有点遗憾,不过还好问题不大。
Poisonous Milk
去年Alictf中Starcraft一题的加强版,不过这次的漏洞仅仅是vector对象的UAF而不是上次的任意Double Free了。所以当drink操作完成后,可以取回之前的vector对象并任意伪造。
Leak的方法比较明显,输入color的时候错误就能够造成一个未初始化的pointer,那么我们构造一个fastbin chunk的链就可以leak出heap的地址了;关于libc地址的话由于程序的size限制不能直接得到non-fastbin chunk,可以通过不断加大vector的大小使其重分配
以得到一个指向main_arena
中bin的指针,而那边的内容又是一个地址,即可完成leak libc。
接下来就是利用了,可以通过vector的特性完成任意地址写,在drink操作之后,覆盖vector对象中的3个指针start
、end
和capacity
,每次vector做push_back
操作时,会将新分配出来的元素写到end
的位置,同时将end
加8。利用这个特性,我们可以将自己分配出的地址写到任意位置。
攻击的目标非常多样,自从上次house of orange
之后,大家普遍喜欢去_IO_2_1_stdout_
搞事情。
1 | 0x7ffff7a50620 <_IO_2_1_stdout_>: 0x00000000fbad2887 0x00007ffff7a506a3 |
在这个结构尾部是一个类似虚表的jumptable(0x00007ffff7a4e6e0
),在调用其中函数时会将结构自身作为参数传入。紧接着的就是程序要用到的stdin
、stdout
和stderr
指针。我们可以先控制这个虚表指针,让其输出的时候调用gets
,这样就能够再一次输入控制整个结构,此时就能将/bin/sh
写到结构头部,再一次控制虚表,让其调用system
就可以了。
Overwatch
程序逻辑真的非常简单,重点就是在对2个指针的操作了。在输入函数中有着一个十分明显的null-byte off-by-one,然后就是程序没有显式的输出chunk内容的函数,需要另外寻找方法leak。
程序中唯一能够动态提供信息的地方就是通过随机数生成队友信息以及Round
这个变量,所以通过一个unsorted_bin attack
将unsorted_bin
地址写到这2个地方就可以了。本来我没想通过Round
来进行leak,而是希望通过队友的信息来分别计算出这4个bytes的值,所以没将Round
设成unsigned
导致简化了leak的过程,不过这也问题不大。
程序仅仅使用了realloc
函数,不过其实它可以完成很多的功能;例如,输入空行则相当于free
操作,而输入和之前达到同样size
的payload则相当于edit
操作,不会触发heap分配的代码。
现在问题就变成了如何利用这个null-byte off-by-one来构造一个unsorted_bin attack
,一般来说,heap中通过对size
的破坏来完成利用,多是需要构造overlapped chunk,这次也不例外。其实整个过程也并不复杂,核心思想即是在破坏下一块prev_inuse
的标志位的同时,同样伪造一个prev_size
给这个chunk,使其在找上一块的时候能够找到一个合法的chunk进行合并,就能够通过合并操作吃掉一块,从而产生overlapped chunk.具体来说,大概长这个样子
1 | +----------------+ |
chunk B触发了null-byte off-by-one,使得chunk C的prev_inuse
被置为0,同时其prev_size
被置为0xb0,此时free这个chunk C,其会找到上面合法的chunk A进行合并。而此时我们还留存着一个指向chunk B的指针,再次malloc一个big chunk就可以形成overlapped chunk了。
接下来就是在chunk B的位置构造一个non-fastbin chunk,并在下面放上2个0x20的inuse chunk,接着free chunk B。编辑我们的big chunk,覆盖unsorted chunk
的bk完成任意内存写,就可以做到leak了。最后,通过不断地edit构造一个fastbin attack,改写__realloc_hook
到system
就可以了。
下面的脚本供参考
1 | #!/usr/bin/env python |