DefCamp CTF Finals 2016-SMS
1.常规checksec操作,开了PIE和NX,首先shellcode不能用。其次PIE表示地址随机化,也就是没办法覆盖返回地址来直接跳转到我们想要的函数处。IDA打开找漏洞,可以看到在doms函数中的v1的栈地址被传递给set_user和set_sms
之后set_user中会读取输入保存在S这个栈地址上,然后从s中读取前四十个字节到a1[140]-a1[180],这个a1就是doms函数中的v1。
再往后看,在set_sms函数中,同样读取1024个字节到S这个栈变量中,并且最后将S的特定长度strncpy给a1,这个特定长度就是a1[180]。所以这里我们可以通过set_user来控制a1[180],进而控制set_sms函数中strncpy给a1拷贝的长度,也就是doms函数中v1的长度,使其大于v1距离栈底的距离0xc0,从而在doms函数栈中执行栈溢出,而doms函数中的v1也就是a1,是在set_sms中由我们输入到S上的内容拷贝过去的,长度为0x400,完全可控。
另外程序存在后门frontdoor(),只要进入这个函数,再输入binsh字符串就能getshell。
2.所以现存在doms函数栈溢出,后门函数这两个漏洞,但是由于PIE,在程序运行过程中没办法确定frontdoor()的地址,无法直接覆盖doms函数返回地址到达后门函数
3.这里就需要用到内存页的一个知识点,由于一个内存页的大小为0x1000,而frontdoor()函数和dosms函数和main函数等等函数,都在同一个内存页上,所以在64位程序下他们的函数地址都是0x############x这种类型,前面12位#每次加载都不一样,而后面的三位不会发生改变,因为都在0x0000563cc913(x)000 - 0x0000563cc913(x+1)000这个内存页上。用IDA打开按ctrl+s可以看到
这些段都在0x0000563cc913(x)000 - 0x0000563cc913(x+1)000这个内存页上。而开启了PIE程序的,0000563cc913(x)这个数值每次都会变化,但是最后三位是不会改变的,就是固定相对于这个内存页起始位置的偏移。
4.所以覆盖返回地址时,可以想到,dosms函数的返回地址是call dosms下一条指令,也就是在main函数上,而frontdoor函数的地址与main函数的地址都在0x0000563cc913(x)这个内存页上。所以当程序被加载,0x0000563cc913(x)这个数值发生改变时,frontdoor函数和main函数地址中对应的数值也会相应改变,而且都是一样的。这种情况下,就可以通过修改dosms返回地址的后四位,也就是之前的(x)yyy来跳转到frontdoor。
5.如果直接爆破,按照数学期望需要尝试0xffff+1=65535+1这么多次,太巨大。这里又考虑到yyy时不会改变的,所以用IDA可以看到frontdoor函数的后三位地址为900,我们在写payload的时候直接写入即可,就是PIE也不会发生改变。现在唯一不确定的就是(x)yyy中的x。直接爆破就好,平均尝试的数学期望为f+1=16次,也不算太高。
6.所以尝试写payload:
(1)修改set_user中的a1[180]的值:
1 | #注释头 |
(2)执行栈溢出,覆盖返回地址的低两个字节为”\x(x)9”和”\x01”(大端序,注意顺序)
1 | #注释头 |
这里跳过push rbp的原因是因为strncpy的关系,如果发送的是\x00,\xa9,那么先复制\x00,则会由于strncpy的机制提前结束复制,造成a9没办法复制进去,从而程序出错。(发送的由于是fget函数,所以会全盘接受,\x00也会接受,不是读取函数的原因。)而跳过push rbp并不影响frontdoor里面的函数执行,所以不会影响getshell。
(3)由于每次地址随机,所以地址随机成a900的概率为1/16,那么就考虑用自动化来爆破实施:
1 | #注释头 |
▲如果直接process本地则没办法成功运行,需要用socat转发,用127.0.0.1本地连接才可以。
参考资料:
https://bbs.ichunqiu.com/forum.php?mod=collection&action=view&ctid=157