[GWCTF 2019]枯燥的抽奖
1
知识点:
1.substr(substring子字符串)
->意味提取一部分字符串
举例: substr($str, 0, 10);表示在str字符串中,从0处开始截取,一次截取10位
2.伪随机
- 算法的本质是“确定性函数”
PHP 的 mt_rand() 使用的是 Mersenne Twister(梅森旋转算法)。
- 数学本质:它是一个复杂的递归公式。每一个新产生的数字 $X_{n}$ 都是基于前一个状态 $X_{n-1}$ 通过位运算(移位、与、异或)计算出来的。
- 输入决定输出:该算法接受一个初始值,即 Seed(种子)。一旦 Seed 确定,算法生成的整个数字序列就完全固定了。
- 状态机的内部机理
计算机没有“灵魂”去随机想一个数字,它运行的是一个状态机:
- 种子 (Seed):初始化状态机的内部状态向量(Mersenne Twister 拥有一个 624 个 32 位整数的状态阵列)。
- 序列生成:每次你调用
mt_rand(),算法会对状态阵列执行一次“旋转”(Twist)变换,并提取出一个数字输出。
- 不可逆性 vs 可预测性:虽然通过单个输出很难直接反推状态阵列,但如果你拥有足够多的连续输出(样本),就能通过数学方法联立方程组,反解出状态阵列,从而预测后续所有输出。
- 为什么能“作弊”
由于 seed 的范围通常是 32 位整数($2^{32}$,约 42.9 亿),这在现代计算能力面前非常小。
- 暴力枚举:工具如
php_mt_seed 会利用 CPU 的并行指令集,在几秒钟内模拟 42.9 亿次初始化的过程。
- 验证逻辑:工具把每一个可能的
seed 都跑一遍,看它生成的第 1 到第 10 个随机数是否与你页面上显示的字符索引一致。
- 结果唯一性:因为算法是确定的,只要前 10 个数对上了,这个
seed 就是唯一的,那么第 11 到第 20 个数也必然完全一致。
抽奖猜数,直接看源码没有啥思路
抓包发现有个check界面,他说不让我们看那我们就去看看

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| 6zgZ4qvPR0
<?php #这不是抽奖程序的源代码!不许看! header("Content-Type: text/html;charset=utf-8"); session_start(); if(!isset($_SESSION['seed'])){ $_SESSION['seed']=rand(0,999999999); }
mt_srand($_SESSION['seed']); $str_long1 = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"; $str=''; $len1=20; for ( $i = 0; $i < $len1; $i++ ){ $str.=substr($str_long1, mt_rand(0, strlen($str_long1) - 1), 1); } $str_show = substr($str, 0, 10); echo "<p id='p1'>".$str_show."</p>";
if(isset($_POST['num'])){ if($_POST['num']===$str){x echo "<p id=flag>抽奖,就是那么枯燥且无味,给你flag{xxxxxxxxx}</p>"; } else{ echo "<p id=flag>没抽中哦,再试试吧</p>"; } } show_source("check.php");
|
原本的思路是是不抽奖找其他漏洞的,但是我想学一下寻找种子的方法
我以前一直以为种子是随机生成的,怎么会找到呢,现在觉得当时想的太简单了
已知:有一个长度为20的字符串,但是他只显示给我们10个
而我们竟然可以通过这10个字符串去寻找剩下的兄弟姐妹:
逻辑就是:
1.收集已有样本(10个字符)
2.拿样本去让工具php_mt_seed找到能生成包括了这十个的种子
3.拿到种子,也就拿到了伪随机计算公式,跑脚本去
首先你需要用到一个工具,我是在kali里使用的
下载指南
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| # 1. 下载工具压缩包 wget https://www.openwall.com/php_mt_seed/php_mt_seed-4.0.tar.gz
# 2. 解压 tar -zxvf php_mt_seed-4.0.tar.gz
# 3. 进入目录 cd php_mt_seed-4.0
# 4. 编译(Kali 自带 gcc) make
# 5. 现在运行命令(就在这个目录下跑) ./php_mt_seed 32 32 0 61 25 25 0 61 6 6 0 61 61 61 0 61 30 30 0 61 16 16 0 61 21 21 0 61 51 51 0 61 53 53 0 61 26 26 0 61
|
已经把使用的命令放在上面了,但是我们还不懂这是咋使用的:
已得:十个字符为6zgZ4qvPR0
abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ
我们先看到字母表的第32个刚好是6,说明就是去表里面找到一一对应的位置序号
6->32
32 32 0 61 的意思是:第一个生成的随机数确实是 32,且当时 mt_rand 的范围设定是 0 到 61。
使用方法:
1
| ./php_mt_seed "四个为一组的数字集合,用空格隔开"
|
得到
1 2 3 4 5 6 7 8 9 10 11
| ┌──(kali㉿kali)-[~/Desktop/php_mt_seed-4.0] └─$ ./php_mt_seed 32 32 0 61 25 25 0 61 6 6 0 61 61 61 0 61 30 30 0 61 16 16 0 61 21 21 0 61 51 51 0 61 53 53 0 61 26 26 0 61 Pattern: EXACT-FROM-62 EXACT-FROM-62 EXACT-FROM-62 EXACT-FROM-62 EXACT-FROM-62 EXACT-FROM-62 EXACT-FROM-62 EXACT-FROM-62 EXACT-FROM-62 EXACT-FROM-62 Version: 3.0.7 to 5.2.0 Found 0, trying 0xfc000000 - 0xffffffff, speed 685.2 Mseeds/s Version: 5.2.1+ Found 0, trying 0x02000000 - 0x03ffffff, speed 47.3 Mseeds/s seed = 0x027f2981 = 41888129 (PHP 7.1.0+) ->得到咱们的php版本号以及种子41888129 Found 1, trying 0xfe000000 - 0xffffffff, speed 45.2 Mseeds/s Found 1
|
拿到种子写脚本
源码
1 2 3 4 5 6 7 8
| mt_srand($_SESSION['seed']); $str_long1 = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"; $str=''; $len1=20; for ( $i = 0; $i < $len1; $i++ ){ $str.=substr($str_long1, mt_rand(0, strlen($str_long1) - 1), 1); } $str_show = substr($str, 0, 10);
|
所以我们要按照源码的逻辑跟他生成一样的字符串
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| <?php // 使用你跑出来的种子 mt_srand(41888129);
$str_long1 = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"; //复刻字母表 $str = ''; //准备好放num的容器
// 循环 20 次生成完整字符串 for ($i = 0; $i < 20; $i++){ $str .= substr($str_long1, mt_rand(0, 61), 1); }
echo "完整的 num 值为: " . $str; ?>
输出: 完整的 num 值为: 6zgZ4qvPR0b636lsfQ73
|
抓包传入即可
