php rand相关
in 各种姿势 with 0 comment

php rand相关

in 各种姿势 with 0 comment

这篇文章更多的是记录的性质,毕竟关于php的随机数的文章已经很多了,每次比赛都现去查,去了解,搞得效率很低,所以这次记录到这里,以后遇到相关的题目直接使用就可以了,毕竟php随机数相关的函数都是使用php_rand()生成的,只需判断php_rand()就可以判断相关函数了,通用性还是很强的。

首先看到英文版的php手册里大大的警告:
php_rand

这个警告很久很久很久很久之前就已经放到了英文网页上了,现在还没有被翻译。。。也是很醉了

随机数预测

首先,php的rand函数使用的是libc的随机数发生器,也就是C语言中的rand函数,使用方法如下:

#include<stdlib.h>
#include<stdio.h>

int main(){
    srand(1234567890);
    printf("%d",rand());
}

$ gcc rand.c
$ ./a.out
1727406014
$ php -r "srand(1234567890); echo rand();"
1727406014

可以看到,种子一样,生成的随机数完全一样,也就是说,我们只需要获取了使用rand函数时的播种,就可以判断它生成的随机数了。

不过从php4.2开始,rand函数就是随机播种的了,不需要再用srand()或mt_srand()给随机数发生器播种。

所以就要使用另一种预测方法,也就是libc的rand函数中本身的缺陷导致的可以预测:

rand[i] = (rand[i-3] + rand[i-31]) % RAND_MAX

这里的RAND_MAX是指生成的随机数的最大值,就是rand()函数中的参数,如果没有规定最大值,那么在Linux下就是2147483647,在Windows下不能使用这个方法进行预测,关于Windows下的预测详情可以查看这个链接

需要注意的是,这里生成的数中,大概%50的概率和实际情况差1。

其衍生函数的预测

这里要举的例子是php_string_shuffle()这个函数。

这里说一下php函数与其源码中的函数的命名方法,比如string_shuffle()函数,在php的源码中定义是static void php_string_shuffle(char *str, long len TSRMLS_DC),会加上一个php_的前缀。

那么我们来看php_string_shuffle()的定义:

demo:
<?php
$str = 'abcdef';
$shuffled = str_shuffle($str);

// 输出类似于: bfdaec
echo $shuffled;
?>

源码:
static void php_string_shuffle(char *str, long len TSRMLS_DC) /* {{{ */
{
    long n_elems, rnd_idx, n_left;
    char temp;
    /* The implementation is stolen from array_data_shuffle       */
    /* Thus the characteristics of the randomization are the same */
    n_elems = len;
 
    if (n_elems <= 1) {
        return;
    }
 
    n_left = n_elems;
 
    while (--n_left) {
        rnd_idx = php_rand(TSRMLS_C);
        RAND_RANGE(rnd_idx, 0, n_left, PHP_RAND_MAX);
        if (rnd_idx != n_left) {
            temp = str[n_left];
            str[n_left] = str[rnd_idx];
            str[rnd_idx] = temp;
        }
    }
}

可以看到,在倒数第9行:

rnd_idx = php_rand(TSRMLS_C);

我们之前说的源码中的源码的命名就可以知道,这里php的C语言源码中的php_rand()函数其实就是我们在php中调用的rand()函数。

同理,rand()函数可以预测,那么string_shuffle()这个函数就可以预测。

转化为python代码如下:

def rand_range(n,min,max,RAND_MAX):
    value=min+((max-min+1.0)*(n/(RAND_MAX+1.0)))
    return value


/*
ori_str为string_shuffle()函数的参数,rand_num是一个有着ori_str的长度个连续的预测出的rand()产生的随机数的数组(也就是说用32个连续的rand()预测出ori_str的长度个随机数,然后存成rand_num数组)
*/
def get_rand_str(ori_str,rand_num):
    RAND_MAX = 2147483647
    rand_str=ori_str
    i=len(ori_str)
    while i>1:
        i-=1
        rnd_idx=int(rand_range(rand_num[len(ori_str)-1-i],0,i,RAND_MAX))
        #print(len(ori_str)-1-i,rand_num[len(ori_str)-1-i],rnd_idx)
        if rnd_idx != i:
            temp = rand_str[i]
            rand_str= rand_str[:i]+rand_str[rnd_idx]+rand_str[i+1:]
            rand_str=rand_str[:rnd_idx]+temp+rand_str[rnd_idx+1:]
 
             
    return rand_str

有的读者可能有些疑问了,之前不是说%50的几率和实际情况差1嘛,php_string_shuffle()函数根据传入的参数多次调用了php_rand(),怎么保证一次都不出错呢?

这里最主要就是RAND_RANGE()函数了,在python脚本中这里我把它改写成了rand_range(),这个函数中,会用生成的随机数除以RAND_MAX,由于这里的RAND_MAX非常大,是Linux下默认的2147483647,那么之前生成的时候的1的误差基本就没有了。

PS:php 7.1.0版本之后str_shuffle中内置的随机算法从 libc rand 函数改成了梅森旋转演伪随机数发生算法,本文方法不再适用.

参考资料:
https://www.sjoerdlangkemper.nl/2016/02/11/cracking-php-rand/

Responses