1. 从“爆个🔨”到“原来如此”:Web25的解题心路历程
我记得第一次做CTFshow的Web25这道题时,整个人都是懵的。题目代码看起来不长,但里面那个mt_rand()函数就像个黑盒子,不知道它到底会吐出什么数字。最要命的是,这个随机数的种子居然来自flag的MD5值前8位转十进制——这flag我要是知道,还用做题吗?这不就成了“先有鸡还是先有蛋”的问题吗?
当时我犯了个很多新手都会犯的错误:看代码不仔细。我一眼扫过去,看到hexdec(substr(md5($flag), 0,8)),脑子里自动脑补成了“flag的前8个字符的MD5”。结果折腾了半天,怎么算都不对。后来静下心来一行行读代码,才发现人家说的是“整个flag的MD5值,取前8位十六进制,再转成十进制”。这一字之差,思路就完全跑偏了。
真正让我开窍的是想明白了$rand这个变量。代码里写着if((!$rand)),意思是只有当$rand为0的时候,才会进入检查token的逻辑。而$rand = intval($r)-intval(mt_rand()),所以要让$rand为0,就必须让intval($r)等于intval(mt_rand())。这时候我突然想到:如果我传r=0会怎么样?那$rand不就等于-mt_rand()了吗?服务器会把这个负的随机数回显给我,我不就知道mt_rand()的值了吗?
这个“灵光一现”让我兴奋了半天。实测下来,传?r=0果然返回了一个负数,比如-646081337。取个绝对值,我就得到了服务器第一次调用mt_rand()生成的值:646081337。但问题还没完——后面还有个token==mt_rand()+mt_rand()的判断。这里又有个坑:很多人会以为mt_rand()+mt_rand()就是2*mt_rand(),但实际上每次调用mt_rand()都会生成不同的数,哪怕种子相同。
我写了个简单的测试代码验证了一下:
<?php
mt_srand(123456);
echo mt_rand(), "\n"; // 第一次调用
echo mt_rand(), "\n"; // 第二次调用
echo mt_rand(), "\n"; // 第三次调用
?>
运行后发现三次输出的数字都不一样。这意味着我需要知道种子,然后生成第二次和第三次的随机数,把它们加起来作为token。但种子是未知的,怎么破?这时候就该请出我们今天的主角——php_mt_seed工具了。
2. 深入理解PHP伪随机数的“伪”在哪里
在讲具体工具之前,我觉得有必要先搞清楚PHP的mt_rand()到底是怎么回事。很多人听到“随机数”就觉得是完全不可预测的,但在计算机领域,除了硬件随机数生成器,大部分所谓的随机数都是“伪随机数”。
你可以把mt_srand(种子)想象成


66

被折叠的 条评论
为什么被折叠?



