看了这篇帖子 https://bbs.pediy.com/thread-223659.htm
前人之述备矣,然在下仍觉细节尚缺,故有此文。
看了题目名字就知道这是32c3那道readme翻版。
32c3那道题是一个栈溢出,通过stack_chk_fail去调用fortify_fail。
当时的利用方法是控制栈上的参数,也就是argv和环境变量。特别注意要控制LIBC_FATAL_STDERR_=1
让fortify_fail能够把输出作为stderr给我们。否则fortify_fail
下层接的是libc_message
,而libc_message
调用的是getenv
。

getenv就是从environ指针那里去找环境变量指针数组。
getenv如果发现这环境变量没设置,libc_message会syscall调用open('/dev/tty')
,输出你是看不到的。

而34c3这道题的想法应该是源自于0ctf 2017里面的easiest printf(个人猜测)。
当时在0ctf结束后的irc里面,rpisec的人说到了一种非预期解法,就是改printf相关的两个函数指针,用自定义扩展%k调用shell。连dragon sector的人都很吃惊。
这种精巧的利用来源于printf的一个功能:0a9K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6%4N6%4N6Q4x3X3g2Y4L8Y4g2Q4x3X3g2G2M7X3N6Q4x3V1k6K6L8$3k6@1N6$3q4J5k6g2)9J5c8X3I4A6j5X3y4Q4x3V1k6E0j5h3&6#2j5h3I4Q4x3V1k6Z5N6r3#2D9i4K6g2X3L8X3!0V1k6g2)9J5c8V1y4#2M7%4c8G2L8h3W2*7K9h3&6Y4i4K6u0V1f1s2u0A6L8Y4c8X3i4K6u0W2K9s2c8E0L8q4)9J5x3@1y4#2M7%4c8G2L8h3W2*7K9h3&6Y4i4K6u0V1f1s2u0A6L8Y4c8X3
我不知道原帖作者是否仔细看了GNU的文档或者printf的源代码。
这里实际有两次RIP控制的机会,对应与printf的逻辑:首先是printf_arginfo_table
,调用函数指针完成自定义spec的参数数目和类型的指定,后来才是对应的printf_function_table
做真正的输出。
这里还有一点是,看完printf的代码你会知道,检查自定义spec是优先于printf自带的spec的。
于是ESPR他们应该是造了readme-revenge来结合上述两道题。
由于这道题是静态连接,又有bss
段上的溢出,于是可以控制printf相关的函数指针和libc_argv
。那么你就可以去构造%s对应的函数指针去控制RIP。
但是需要注意到environ
指针是你覆盖不到的,他的位置在输入的上面。
所以说这种原帖作者所说的利用是存在问题的,你控制不了envivon啊?我不知道原帖作者是忘了、不知道还是故意没提(我希望是前两者)。在下第一时间就是因为这一点所以觉得这种思路不可用,去漫天找其他gadget。
实际上,217的同学也表示队友找了5个小时RCE的gadget找不到。
04:46 < david942j> _2can: yap.. it took my teammate 5hrs to try to do RCE (but fails as well)
<del> 是的,想要做这道题,需要撞大运,撞一个主办方帮你设置了环境变量LIBC_FATAL_STDERR_=1
。 </del>
更新:2018年1月16日 20:41:28
发现并不是如此。似乎xinetd有一些奇怪的行为,LIBC_FATAL_STDERR并不需要设置,readme那题我自己用docker+xinetd起了之后发现不设置这个环境变量也有flag回来。
于是strace上去看了一波,发现如下操作
还是回到__libc_message
这个函数:
即是说在docker xinetd的环境下,open('/dev/tty')
会失败?
对比一下ncat的结果,
open成功,write也就自然喷给了/dev/tty。
于是自己也实际跑了一次xinetd,发现仍然open失败。
在网络上搜了一会儿,找不到xinetd和/dev/tty相关的资料。希望有了解的朋友可以分享告诉我,不胜感激。
目前的猜测是:xinetd有一些奇怪的操作,导致open失败,然后还是会走stderr喷flag出来。所以之前32c3那道题,设置环境变量是多余的。但是ncat是需要同时设置环境变量和2>&1的。
更新:2018年1月19日 23:02:11
今天抽空看了下xinetd的源代码,通过/dev/tty很快定位到了util.c
然后再在init.c中找到了这个函数的调用。
搜索了一番后,网络上的说法是这是一种做daemon的常规套路。
此贴至此终结 (应该没后续了吧?)
后续:实际上还不对,早上起来想起我们平时都是dontfork的,不应该走daemon,再研究了一下,发现是docker本身的问题。
5bdK9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1k6E0L8$3u0&6i4K6u0r3L8h3!0T1P5g2)9J5c8X3W2K6M7%4g2W2M7#2)9J5c8U0p5@1
看起来不-it就不会分配tty。
发现删除线不好使了,用引用代替吧。
一个队友去撞了,于是成功get flag……真的是……哎。
如果你想要复现这个题目,要注意服务端把stderr导给stdout,并且把环境变量设好。
最后感谢原帖作者的分享。在下写这篇文章不是想批判原帖作者,只是认为有必要做一点细节的补充。因为个人认为,这些质量上乘的题目,好就好在引导我们去深入更加底层的程序运作和代码实现的细节。
希望能在pwn板块上看到更多的,经过自己辛勤探索、苦读代码、精心分析所得的干货。
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课