15年的一道题,尝试解题无果之后去搜了搜writeup,结果并没有搜到。。只有一篇非正常getshell的文章。。作者打比赛时候靠着本机环境和比赛机一致,靠着设定好system地址getshell。看的我一脸懵,还是靠自己解吧。。肝了一天多终于肝出来了。。本菜鸡还是菜啊。。
这道题虽然看似漏洞很多,但是要利用起来还真是有点困难。
checksec:
该程序是个书店,订书的功能,最多订购两本书,订购每本书的时候可以填写订单内容,这里面可以无限制任意写,然后是删除订单,删除订单中只free
掉了指针,没有把指针置为NULL,存在UAF漏洞,且指针的位置处于栈地址上,最后是一个提交功能,提交功能是将两本书的内容合在一起打印出来,但是提交后存在一个格式化字符串的漏洞。
那么问题来了,漏洞看起来这么的多,怎么去利用?首先我们得注意三点:
但是我们会遇到一些问题,比如说我们想用溢出控制第三块堆中的内容再利用格式化字符串的时候会遇到填写订单内容后被strcpy
函数给重新覆盖掉第三堆块的内容。
这里我们应当使用到Overlapping,先delete
第二堆块,再用堆块一溢出堆块二的size
字段为0x151
,这样当利用submit
功能的时候所申请的0x140
堆块就能出现在堆块二的位置上,随后便能利用submit
合并两个堆块内容的作用覆盖到第三堆块,如果构造好覆盖内容,我们便能在程序最后利用到格式化字符串的内容。
显然,一次利用并不能达到我们getshell的目的,这里用到这么一个知识点 :
所以我们可以利用第一次格式化字符串将.fini_array
地址处的函数修改成main
函数的地址,使程序重新回到main
函数。当然,我们还需要泄漏libc
地址。
delete
掉堆块二:
利用堆块一覆盖重写的这里我们需要注意,我们所构造的payload
需要和后面利用的格式化字符串所匹配。
堆块位置如下:
我们需要让printf
堆块处执行格式化的漏洞,就需要让submit
功能去帮助我们覆盖,submit
功能会加上order1:
等这些字符串,不能漏掉,总结后可以得知新申请的堆块内容为:
Order 1:
+ chunk1
+ \n
+ Order 2:
+ chunk2
+ \n
因为chunk2
已经被delete
掉了,所以当复制chunk2
中的内容的时候复制的其实是order 1: + chunk1
。所以上述可以变为:
Order 1:
+ chunk1
+ \n
+ Order 2:
+ Order 1:
+ chunk1
+ \n
所以我们可以构造第二次的chunk1
内容恰好覆盖到dest
堆块处。也就是:
size(Order 1:
+ chunk1
+ \n
+ Order 2:
+ Order 1:
) == 0x90
size(chunk1
) == 0x90 - 28
== 0x74
所以我们构造chunk1
中的内容的时候只要使其中非0
字符串的个数达到0x74
就行了。
因为输入选项的时候可输入128
个数字符串:
所以我们可以提前在栈中构造好我们所需要用格式化字符串修改的任意地址。
输入点在格式化偏移为12处,我们第一轮利用需要修改的是.fini_array
处的内容,所以我们在栈中可以这么构造:
而chunk1
中的内容可以这么构造:
这里的'.%31$p'
目的是泄漏__libc_start_main
函数的地址,从而leak libc
的地址。而这里的',%28$p'
目的我后面会说到。
这里我们能怎么接下去利用呢?ctf-wiki
上的解法是拿到system
函数的地址后去覆盖free_got
。我试了这解法后才知道这解法还需要再来一轮去触发free
函数。。所以这个思路行不通。。wiki
上贴的exp
好像只执行了第一阶段,第二阶段的利用没有。。
搜索到的唯一一个exp
是通过设定system
地址佛系getshell
的。。所以还是得自己来着手。。我想这题目肯定是有一个常规解的。所以我自己来肝。。
我的思路是通过第二阶段修改主函数返回地址getshell:
返回地址在栈上的位置是随机的,所以我们需要找一个与返回地址有固定偏移的栈地址。我们从栈上去找一找,我发现了这么一个地址:
这个栈地址始终指向比自己低0x10
字节的栈地址,而且指向的栈地址和返回地址也有固定的0x28
的偏移,所以我选择用格式化字符串泄漏这个栈地址,但是这里还有一个问题:
上面所提到的payload
中',%28$p'
的目的就是泄漏这个栈地址。
我们直接把gdb attach
上去计算固定的偏移:
泄漏得到的第一阶段的返回地址:0x7ffd88f7d6f8
gdb
中得到的第二阶段的返回地址:0x7ffd88f7d4e8
所以固定偏移为:0x7ffd88f7d6f8 - 0x7ffd88f7d4e8 = 0x210
我们得到了第二阶段的返回地址的栈地址之后就好办事了,重复第一阶段的构造利用即可,这里面为了方便,可以直接利用one_gadget
工具中的execve
函数地址来覆盖返回地址。这里经过观察可以发现,execve
的地址和返回地址只有最后三个字节不同,所以构造格式化漏洞的时候只需要覆盖返回地址最后的三位即可。
可能机子环境不一样这个固定偏移也不一样,我的环境是:
Ubuntu16.04,glibc2.23
Hack.lu 2015 bookstore writeup
[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!
最后于 2018-9-12 08:18
被V1NKe编辑
,原因: