前言:
PHP的mt_rand函数作为一个随机数生成工具在程序中被广泛使用,但是我们不难发现它生成的随机数不是真正的随机数而是伪随机。既然是伪随机就有可能利用。
PHP mt_rand函数
mt_rand函数有两个可选参数 min 和 max,如果没有提供可选参数,mt_rand函数将返回返回 0 到 RAND_MAX (随机数的最大可能值) 之间的伪随机数。例如想要 5 到 15(包括 5 和 15)之间的随机数,用 mt_rand(5, 15)。
使用mt_rand()生成随机数的第一个步骤是使用种子(无符号整数值)来生成包含624个值的状态数组。我们可以调用mt_srand($seed)。从 PHP 4.2.0 开始,随机数生成器自动播种,当然如果我们再播种的话会覆盖了系统自动播的种子。
利用思路
因为是伪随机,那么如果我们输入同样的参数那么肯定会出同样的结果。它的可控参数有2个种子 和 范围 。也就是说它可以简化为这样的函数y=seed + i*x
其中seed 是我们的种子,x是我们使用mt_rand时给出的范围,i表示第几次调用这个函数。
一般我们看到了源码,x 范围肯定是已知的,然后会给我们几个y值如果再知道 i 值我们就可以知道 seed 的值。那么就是知道任意 i 的随机数是多少。
对于这样一个简单的函数,我们当然可以直接计算出一个解来,但 mt_rand实际使用的函数可是相当复杂且无法逆运算的。有效的破解方法其实是穷举所有的种子并根据种子生成随机数序列再跟已知的随机数序列做比对来验证种子是否正确。php_mt_seed就是这么一个工具,它的速度非常快。它可以根据单次mt_rand()的输出结果直接爆破出可能的种子,当然也可以爆破类似mt_rand(1,100)这样限定了MIN MAX输出的种子。
php_mt_seed 的使用
php_mt_seed是一个破解mt_rand函数seed的工具,在最简单的调用模式下,它能通过mt_rand第一次输出的值寻找mt_rand的seed,在更高级的模式中它能匹配不是第一次输出的和不明确具体输出的情况。php_mt_seed 4.0 支持以下几个大的版本: PHP 3.0.7 to 5.2.0,PHP 5.2.1 to 7.0.x, and PHP 7.1.0+ 。
安装 php_mt_seed
- 【下载地址】
- 下载以后解压,然后到文件夹下 make 就会生成一个可执行文件
- 安装需要 gcc 环境
参数问题
php_mt_seed基于命令行运行,命令行可以使用1,2,4或者更多的参数。这些参数需要详细说明mt_rand()的输出。
- 一个参数 当只有一个参数的时候,这个参数代表mt_rand第一次输出的值。
- 两个参数 当有两个参数的时候,他们代表mt_rand第一次输出应该位于什么区间内。第一个参数为最小值,第二个参数为最大值。
- 四个参数(高级模式) 前两个参数表示mt_rand第一次输出的区间,后两个参数表示mt_rand输出的区间。
- 多于五个参数(高级模式) 每四个参数一组,但是最后一组可以是1,2或4个参数
实验测试
测试一
代码如下
1 | <?php |
假如现在我们知道第一个随机数 10 我们尝试找出 种子的值使用命令 ./php_mt_seed 10
发现找到的种子不对,为什么呢? 因为我们使用的是 mt_rand(0,10) 还有一个范围参数我们需要给定。那么我们使用./php_mt_seed 10 10 0 10
可是 php_mt_seed 跑出很多 seed 至少 100 以上 这不是我们想要的结果。那么我们肯定有加参数了,使用./php_mt_seed 10 10 0 10 5 5 0 10
可是还是有很多呀。测试发现当使用了 8 次的值才可以跑出相对可以看的 seed 种子 那么这个方法需要很多数据才可以猜到呀。换一种说话这是相对安全的? 当然我说的是我这种情况不排除其它情况只有几个 seed 对应。
虽然可以说实验是失败了但是我们也可以得到一些结论:
- 1 个参数和2个参数只适用于调用mt_rand()函数时没带范围参数的。
- 虽然说都可以得到种子,但是有时候因为给的参数不足就会出现很多种子,这样我们很难利用。那么我们应尽可能的多输入参数。
- 当我们知道多个 随机值 但mt_rand()没有给范围参数我们怎么书写范围呢? 好像是多虑了,因为这样出来的 随机数很大所以对应的 seed 很少不需要输入多个参数了。
测试二 (常见)
代码如下:
1 | <?php |
因为没有手动播种所以每一次的种子值都不一样从而导致我们每次运行所产生的结果也不同。我们这次产生的3个密码是:
1 | 2hz6uvW4jG |
现在我们根据第一个密码(应该是只有知道第一个密码才有效)。密码有10位,每一位都使用了一次mt_rand()函数这样一来我们就有10组参数应该可以跑出具体的几个 seed种子。
我现使用python 编写个脚本来使已知的数据转化为我们想要的形式,脚本如下:
1 | str1='abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23456789' ##这里对应源码中的字符串没有数字0、1 |
得到的结果是:
1 | 49 49 0 56 7 7 0 56 24 24 0 56 53 53 0 56 19 19 0 56 20 20 0 56 45 45 0 56 51 51 0 56 9 9 0 56 31 31 0 56 |
我们到kali 里使用命令如下:
1 | ./php_mt_seed 49 49 0 56 7 7 0 56 24 24 0 56 53 53 0 56 19 19 0 56 20 20 0 56 45 45 0 56 51 51 0 56 9 9 0 56 31 31 0 56 |
我们找到源代码运行时对应的那个php版本(由于版本不同产生的随机数可能不同)。也就是1584359324
。我们现在知道了种子随后我们就可以直接copy源码,在调用随mt_rand函数前加上mt_srand函数来手动播种。这样就是和产生密码时的环境一样了,当然php版本也要一样。
代码如下:
1 | <?php |