CTFshow Web25:深入解析php_mt_seed工具在伪随机数爆破中的实战应用
1. 从“爆个锤子”到“伪随机数”的认知升级
做CTF题目最怕什么?不是题目难,而是思路一开始就错了。我刚开始做CTFshow Web25这道题时,就犯了个低级错误——看到代码里有个md5($flag),然后截取前8位转十进制作为种子,我下意识以为种子就是ctfshow{ 这八个字符的MD5值。结果折腾了半天,发现完全不对路。
后来仔细看代码才明白,人家是先对整个flag进行MD5加密,然后取前8位十六进制,再转换成十进制作为种子。这个区别可大了去了,就像你以为密码是“123456”,结果人家用的是“123456”的SHA256值,完全是两码事。
这道题的核心逻辑其实挺有意思的。代码里有个关键判断:if((!$rand)),意思是只有当$rand为0时,才会执行后面的token验证逻辑。而$rand的计算方式是intval($r)-intval(mt_rand())。所以最简单的思路就是让$r等于mt_rand()的值,这样两者相减就是0。
但问题来了,我们不知道mt_rand()会生成什么值啊。这时候有个小技巧:我们可以先传?r=0,这样$rand就等于-mt_rand(),服务器会把这个负的随机数回显给我们。比如我测试时得到了-646081337,那么mt_rand()就是646081337。
你以为这就完了?更麻烦的还在后面。代码里验证token的逻辑是:$_COOKIE['token']==(mt_rand()+mt_rand())。注意这里的mt_rand()+mt_rand()可不是同一个随机数加两次,而是连续调用两次mt_rand()得到两个不同的值再相加。
我刚开始也犯糊涂,以为token就是2*mt_rand(),写了个小测试就发现不对:
<?php mt_srand(123456); $rand1 = mt_rand(); $rand2 = mt_rand(); echo $rand1,"\n",$rand2; ?> 同样的种子,第一次和第二次生成的随机数完全不同。所以我们需要的是第二次和第三次mt_rand()的和,因为第一次已经被我们用?r=0的方式“消耗”掉了。
2. php_mt_seed:伪随机数的“时光倒流”机器
知道了随机数值,怎么反推种子呢?理论上可以写脚本爆破,但32位的种子空间有40多亿种可能,纯暴力破解太慢了。这时候就该php_mt_seed这个神器出场了。
我在实际