0.前言
这题挺复杂的,为了设个题特地做了个有趣(?)的文字冒险游戏。
而且文字量挺大的。说实话我也是看了其他人的wp才做出来的,而且据说是pwn新手区难度很大的一题。主要是观察比较困难,而且程序代码整体阅读有一定的难度。但是它解题的逻辑倒挺简单的:就是通过格式化字符串漏洞修改变量的值使条件满足,向一段内存中写入shellcode并用函数指针强制转换类型使之可执行。
1.思路
一些无关紧要的部分省略。
先查看一下保护:
发现只有PIE保护没开,所以多半不是栈题。(其实题目string已经暗示这很有可能是利用格式化字符串漏洞了。)
拖入IDA中查看(这里我为了好理解改了一些变量或函数的名称。),之前试运行过,为了找程序主逻辑。
其中主函数的伪C代码如下:
结合之前试运行的结果,可以看出里面主要函数是play()。并且在这之前给了我们v4[0]和v4[1]的地址。
play传递了v4。
查看play(),伪C代码如下:
这个主体是让你取个长度<12的名字,并依次执行我改过名为choose(),target(),和getshell()的函数。
到这里还没有什么漏洞可以利用。
查看choose(),如下:
这里的文本比较长,前面的文本都没什么用(纯粹剧情需要,对解题没什么帮助),然后就来到为我们的巫师主角选择方向的时候了!撒,一库马修!
其实但凡能看懂都知道这个选择就是个寂寞。从代码逻辑上分析可以发现——这完全没得选啊!只能选east。
(也好,省去了多分支的麻烦……)
然后再查看target(),这里是选择了east后会执行的函数。如下图:
可以观察到有很明显的格式化字符串漏洞,然后之前有个让我们输入地址的操作。这里的利用目前还不太明确,先观察一下getshell():
可以看出,如果v4[0]==v4[1],那么则可以向v1内读入至多0x100的数据,并且这些数据可以被当成函数来执行。(函数指针强制类型转换,具体用法我也不太清楚)
那么现在我们要想的就是如何使v4[0]等于v4[1]。
(这里其实我改过变量名,最开始IDA里用的好像是a1,但通过参数传递的跟踪可以发现这里的a1就是主函数的v4,因此为了好理解就把a1改名为v4了。同样的情况在之前的一些函数中也有,这里不一一赘述。)
那么如何使条件满足呢?我们知道主函数中这两个的值并不相等,而且也没有明显的地方可以给我们直接更改其中某个的值。这时候就该利用刚才的格式化字符串漏洞实现任意地址写。
我们知道,格式化字符串%n可以将它前面的字符长度数赋值给一个整型变量等。
且在有格式化字符串漏洞的情况下可以用%k$n将其前面的字符长度数赋值给偏移该函数为k的参数。
现在我们需要做的就是确定其中一个(这里以v4[0]为例)的偏移。
回到刚才的target(),刚刚提到,之前有个输入地址的操作,运行进行测试看看如何利用。
运行程序,如下图:
运行至输入地址部分,输入65535(0xffff)。再在下一个输入处输入一连串%x。如下:
可以看出,偏移为7。
因此这个输入地址的作用就是将输入的地址处的偏移改为7。(虽然具体的原理我不清楚……有没有大佬能告诉我?)
因此,只要输入的地址为之前告诉我们的v4[0]或v4[1],再结合后面的格式化字符串漏洞就可达到改变其中一个的值使条件满足,之后再输入shellcode就可以获取shell权限了。
2.exp
根据思路,exp可以这样写:
1 | from pwn import * |
执行完后效果如下:
(flag已打码。)
3.总结
- 做这种题一定得有耐心。
- 像这种格式化字符串漏洞的题,如果涉及到要求某个整型数据为某一特定值或者要求满足两数据相等的情况一般都是涉及任意地址写。
- 还有就是偏移的获取,如果题目中有明显可以输入某个地址的地方,一般就要利用这里来获取我们要改变的变量等的偏移。(毕竟一般不会无缘无故给你一个看着就很奇怪的输入。)
- 另外就是程序告诉你的地址肯定是有用的。(不过可能不一定全部都要用到,因为有可能有多种方法。)
- 最后就是虽然开启了NX保护,但有函数指针,所以shellcode依然可以被执行。