Hack.lu CTF 2017 部分Pwn小结
本次Hack.lu CTF到了比赛前一天才确定时间并开放注册,看来主办方也是很紧张啊,这次的题目也不多,主办方甚至拿了一道啤酒题来凑数。题目质量倒是差强人意,至少并不坑。做了几道Pwn,记录一下。
Heaps of Print
位于Heap上的Format String, 实际上跟heap没有特别大的关系,就是我们不能将地址输入到栈上了,在这种情况下需要利用栈上已有的指针来进行修改。一般来说,这种程序都会给出一个loop来多次进行printf,不过这个题偏偏没有,只有一次。那么就只能自己想办法去进行loop了。
在printf的时候栈上的布局大概是
1 | 0000| 0x7fffffffdfd0 --> 0x7fffffffdff0 --> 0x7fffffffe020 --> 0x555555554990 (<__libc_csu_init>: push r15) |
可以利用的一个就是顶部的rbp链,通过改掉第2个rbp的低位字节,让main
函数返回到_start
重新执行,由于一开始程序给出了一个字节的地址,我们可以计算出需要修改的低位字节。在这之前我们就可以完成leak libc和程序基址的过程。接下来每次都可以利用rbp让程序回到_start
,这样就形成了一个loop。这时候进一步的修改将使用另一条链就是栈上的环境变量,因为每次跳回_start
之后栈上都会有指向环境变量的指针,故可以稳定利用2个指针向栈上输入数据。比较麻烦的是每次回来栈会向下0x90个bytes,这样printf对应的参数要动态调整。
House of Scepticism
名字起的很剽悍,起初还以为是某种新的利用姿势,后来发现并不是。程序拥有5次调用malloc
的能力,但是不能free
自己创建的块,除此之外,程序会将用户的输入用8字节的key进行异或加密。在处理Read
输出数据的时候,指定了printf
的一个新的控制符%B
,实际上是检查了精度info->prec
是否为64,并且是否带有alt flag
也就是#
。最终的结果就是如果输入#.64
的format则会用一个类似base64的函数处理之后输出,如果直接输入回车则就是用%s
来输出。
关键在于这个#.64
的处理中会先malloc
一块空间之后再free
掉,这实际上就给了一个能够造出free chunk的能力。程序的漏洞是在创建时候输入sz
为0造成的整数下溢,进一步导致在堆上的任意长度溢出,而且这个溢出的数据不会被异或加密。
这样一来,我们可以使用Read
功能,事先造出fastbin的free chunk,然后在下面分配出其他块之后再占位回去,就可以使用任意长度的溢出来控制下一个块中的sz
数据,修改成-1,这样在edit时就可以在任意偏移输入任意长度的数据了,如果知道了heap和libc的地址,就可以做libc中的任意地址写了。
至于leak的方法,基本还是先靠heap上任意地址写的能力,先leak出key,之后通过malloc_conslidate
得到libc和heap的地址,在利用Read
功能去读就可以了。
Mult-o-flow
很有意思的一个题,利用字符串解析的不同来完成payload的构造,看flag应该是从一个IRC bot改的。
程序的功能是从一堆输入中解析出相应的字段并拼在一起,漏洞很明显,在一开始输入时候就有的strcat
,以及之后执行extract_table_entry
和sprintf
都有溢出的问题。但是程序中有一个自己实现的固定值的stack cookie,而且其中带有0字节,由于输入使用的strcat
会被0字节截断,导致不能直接溢出构造rop。具体来说,整个函数的stack大概是这个样子:
其中s是存放最终解析的buffer,而e则是extract_table_entry
的目标buffer,buf是一开始payload输入的位置,程序在开始时输入了3次0x7ff字节的数据并用strcat
连接,意味着这个时候就已经溢出了。为了完成利用我们至少要将程序中给的system
地址和binsh
的地址放到栈上,并且将cookie修复。这个程序虽然是32位的程序但是特意将代码高位地址设成了0字节,这样需要修复的0字节就有3处。由于/bin/sh
的地址是最远的,可以在第一次strcat
时就布置好。
接下来的过程用到了extract_table_entry
函数的一个性质就是遇到0字节不会停止,遇到<
才会停止,这样我们可以控制一下其解析的src和dst让它们相等,这样得到的结果就是从e这个buffer开始,遇到的第一个’<’将会被变成0.那么如果第一次解析ISP:
时的buffer布局是这样
最后cookie的最高位就可以被改成0,而由于第一次使用的是sprintf(s, "%s; ", e);
,在sprintf之后一个0字节会被放到e这个buffer靠前的位置
同时控制一下让s的尾部恰好为City:
这样下次解析时还会出现同样的效果,这次在返回地址的最高位上<
将会被变成0,而e前部的0有效地防止了之后sprintf对后面数据的破坏,最终完成利用。
参考代码
1 | #!/usr/bin/env python |