-
-
[原创]HITCTF 2018 pwn writeup
-
发表于: 2018-3-27 08:23 5837
-
先按一般的流程来,首先分析一下程序功能:
通过买卖龙珠来集齐7颗龙珠,就可以许愿。
所以程序包含买、卖、显示龙珠数、许愿和退出五个功能
可以看到,buy函数有一个很明显的缺陷,就是在购买龙珠时,只判断钱数是否为零,所以只要钱数不为零,就可以无限购买,所以通过卖一颗来使钱数恒不等于零,从而买到七颗龙珠,可以许愿。
买到七颗龙珠之后就可以许愿,可以很明显的看到wish函数中的两次input有问题,第一次刚好没有覆盖到EBP,而第二次则刚好覆盖到返回地址,可以劫持程序流,我们就可以通过这两次写入进行程序控制。
可以看到几乎没有任何保护措施,因此我们可以有多种思路可选。
由于只能覆盖到返回地址,则需要跳转到栈中,或者可控制的内存区域,来布置空间,从而可以执行函数或者通过ROP链来直接构造汇编语句getshell,我们可以找一下相应的gadgets:
并没有直接跳转到栈里gadgets
也没有足够多的gadgets可以构造出ROP链
所以我们只能换一种思路:
由于未开启NX和PIE保护措施,所以栈中的指令可执行,且内存中的地址固定,因此我们只要泄露出栈的基址,就可以在栈中执行shellcode,从而getshell。
通过调试可以发现,栈的返回地址始终不变,而返回地址之前的就是栈基址,而wish函数中的第一个input,如果刚好占满整个空间,就会在其后的printf函数中将这两个地址都打印出来,我们就可以通过截取字符串获得栈基址。
获取栈基址后,就可以将shellcode写入栈中,截取程序流,getshell。
首先用IDA查看程序,看到这么多未命名的函数,我们可以通过修改名称将它们变成我们熟悉的写法,方便我们后续的阅读。
通过对程序的了解,为程序中的函数以及全局变量加上注释,如果有结构体也可以为结构体加上注释。
该程序通过看代码看不出什么问题,只是可以发现不少的全局变量,而程序也在全局变量中存储了不少内容,我们可以通过动态调试可以看一下。
可以看出蓝色笔圈的就是堆块的大小,而黄色的就是插入在全局变量取的一句话。
当我们插入十个堆块是,很显然,原本存放堆块大小的地方被之前的字符串给覆盖了,不过目前覆盖的是零,所以我们申请一百个堆块试试。
堆块的大小被覆盖为0x73,也就是说我们可以在0x73范围内修改堆块,从而造成堆溢出。
有库文件,所以我们选择第一个堆块,将紧接着的下一个堆块地址修改为puts函数的got表地址,从而泄漏puts函数的地址,从而计算出库基址和system函数的地址。
本题可以有两种思路,泄露方式相同,只是利用方式略有不同:
这两种都是在泄露出puts函数和system函数地址后,通过edit将puts函数的地址改写。
第一种,将puts函数地址改写为system函数。
可以看到,在SHOW函数中,存在一个puts的参数是堆中的数据,所以我们只要将这个数据改写为"/bin/sh",在执行到这里的时候,就相当于执行system("/bin/sh"),从而getshell。
这是出题人的思路,按照程序源码确实可解,但是:
无论是IDA还是GDB,在反编译的时候,都在可以利用的puts前加入了一个puts函数,而源代码中这里应该是printf函数,所以造成了该题在本地调试无法getshell。
第二种,将puts函数地址改成one_gadget地址,从而再次执行puts函数的时候,就可以直接getshell。
这些地址是知道达到相对应的条件,就可以直接getshell。
但是,可能是由于库文件不同,在本地并没有找到合适的one_gadget,也无法getshell。
用IDA打开,可以看到程序开启了PIE,所以很显然如果要getshell,必须要先泄露出关键地址。
[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!