PHP伪随机数

zOHF0Cxp49 // GWCTF 2019 枯燥的抽奖
<?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");
// 钓鱼城 2020 easyseed
$lock = random(6, 'abcdefghigklmnopqrstuvwxyzABCDEFGHIGKLMNOPQRSTUVWXYZ');
$key = random(16, '1294567890abcdefghigklmnopqrstuvwxyzABCDEFGHIGKLMNOPQRSTUVWXYZ');

function random($length, $chars = '0123456789ABC') {
    $hash = '';
    $max = strlen($chars) - 1;
    for($i = 0; $i < $length; $i++) {
        $hash .= $chars[mt_rand(0, $max)];
    }
    return $hash;
}

先看两组代码,都是伪随机数的题目。不难发现,都是rand + mt_srand的生成随机码的方式。

mt_rand()工作原理

mt_rand( void) : int

mt_rand( int $min, int $max) : int

很多老的libc的随机数发生器具有一些不确定和未知的特性而且很慢。PHPrand()函数默认使用libc随机数发生器。mt_rand()函数是非正式用来替换它的。该函数用了» Mersenne Twister中已知的特性作为随机数发生器,它可以产生随机数值的平均速度比libc提供的rand()快四倍。

如果没有提供可选参数minmaxmt_rand()返回 0 到mt_getrandmax()之间的伪随机数。例如想要 5 到 15(包括 5 和 15)之间的随机数,用mt_rand(5, 15)

提到了mt_rand()就要说一下mt_srand()函数:

mt_srand([ int $seed] ) : void

seed来给随机数发生器播种。 没有设定seed参数时,会被设为随时数。使用者在进行一次mt_srand()操作后,seed数值将被固定下来,给接下来的mt_rand()函数使用。

mt_rand()存在的问题

由于mt_rand()的生成的随机数只跟seed和调用该函数的次数有关。举一个简单的例子来说明一下这个问题,假设使用mt_srand(1111111)进行了一次播种操作,接下来调用mt_rand()函数,第一次生成的数值为a,第二次生成的为b,第三次生成的为c。任何一个人拿到这样的一串代码,所执行的结果都是跟刚刚描述的一样。所以当你的seed数值被他人知道后,就可以预测出你接下来的数值是多少,这就是该函数的一个问题,他并不能起到一个真随机数的作用。

php_mt_seed爆破工具

其有两种爆破方法:一是根据一串数字,二是根据一串由许多4个一组的数字组成的数字组合。

方法二中前面两个数是随机数的结果区间,后两位是随机数的随机范围区间。

怎么获得这个数字组合呢?比方说上边两个题目,他们每个随机字符的生成都用到了seed来做mt_srand的参数,根据其生成规律写出逆运算,得到的结果就是该种子在mt_srand函数作用下得到的数值,然后就可以利用工具爆破出seed了。

GWCTF 2019 枯燥的抽奖

右键查看源码,发现调用check.php,访问得源码。

原理上边也说了,直接写反解爆破所需数值:(即第二种四位一组得数据)

str1 = 'abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'
str2 = 'rNr38qNhOU'
length = str(len(str1) - 1)
res = ''
for i in range(len(str2)):
    for j in range(len(str1)):
        if str2[i] == str1[j]:
            res += str(j) + ' ' + str(j) + ' ' + '0' + ' ' + length + ' '
            break
print(res)

反解就是将已知数据和随机列表得数据位置相互对应,形成符合格式的数据。

root@kali:~/php_mt_seed-4.0# ./php_mt_seed 17 17 0 61 49 49 0 61 17 17 0 61 29 29 0 61 34 34 0 61 16 16 0 61 49 49 0 61 7 7 0 61 50 50 0 61 56 56 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 820.9 Mseeds/s 
Version: 5.2.1+
Found 0, trying 0x2c000000 - 0x2dffffff, speed 43.4 Mseeds/s 
seed = 0x2ca4378b = 748959627 (PHP 7.1.0+)
Found 1, trying 0xfe000000 - 0xffffffff, speed 43.7 Mseeds/s
Found 1

将得到的seed反代入程序,得到字符串:

<?php
mt_srand(748959627);
$str_long1 = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
$str='';
$len1=20;
for ( $i = 0; $i < $len1; $i++ ){
    $str.=substr($str_long1, mt_rand(0, strlen($str_long1) - 1), 1);       
}
echo "<p id='p1'>".$str."</p>";
?>

需要注意php版本,seed在不同php版本下生成的字符串不同。

钓鱼城 2020 easyseed

$lock = random(6, 'abcdefghigklmnopqrstuvwxyzABCDEFGHIGKLMNOPQRSTUVWXYZ');
$key = random(16, '1294567890abcdefghigklmnopqrstuvwxyzABCDEFGHIGKLMNOPQRSTUVWXYZ');

function random($length, $chars = '0123456789ABC') {
    $hash = '';
    $max = strlen($chars) - 1;
    for($i = 0; $i < $length; $i++) {
        $hash .= $chars[mt_rand(0, $max)];
    }
    return $hash;
}

在得到bak文件后结合数据生成的脚本得到可执行数据,进行爆破。得到seed,反代入得到key:

<?php
//Y1ng
function getSeed()
{
    $chars = 'abcdefghigklmnopqrstuvwxyzABCDEFGHIGKLMNOPQRSTUVWXYZ';
    $max = strlen($chars) - 1;

    $hash_result = 'vEUHaY';
    $arr = [];
    $index = 0;
    for ($i=0; $i< strlen($hash_result); $i++)
    {
        for ($j=0; $j< strlen($chars); $j++)
        {
            if ( $hash_result[$i] === $chars[$j] )
            {
                $arr[$index] = $j;
                $index++;
                break;
            }
        }
    }
    echo "./php_mt_seed ";
    for ($i = 0; $i<count($arr); $i++)
    {
        echo "${arr[$i]} ${arr[$i]} 0 ${max} ";
    }
    echo "\n";
}

function getKey()
{
    function random($length, $chars = '0123456789ABC') {
        $hash = '';
        $max = strlen($chars) - 1;
        for($i = 0; $i < $length; $i++) {
            $hash .= $chars[mt_rand(0, $max)];
        }
        return $hash;
    }
    mt_srand(718225);
    $lock = random(6, 'abcdefghigklmnopqrstuvwxyzABCDEFGHIGKLMNOPQRSTUVWXYZ');
    $key = random(16, '1294567890abcdefghigklmnopqrstuvwxyzABCDEFGHIGKLMNOPQRSTUVWXYZ');
    echo $lock . ' ' . $key;
}
getSeed(); //./php_mt_seed 21 21 0 51 30 30 0 51 46 46 0 51 33 33 0 51 0 0 0 51 50 50 0 51
getKey(); //  vEUHaY nRtqGR8mtd9ZOPyI

借用颖奇师傅的脚本,原理很简单。

最后修改:2020 年 09 月 01 日 03 : 55 PM
请作者喝杯奶茶吧~