伪随机数的预测--3

伪随机数的预测–3

1 简介

接上篇,这一部分实现用GPU暴力猜解MT19937的种子。参考php里的随机数这篇文章,用cpu来跑,需要比较长的时间,实验下用gpu跑的速度。

2 具体实现

同上篇一样,由于要用clojure的gpu相关的库,要添加依赖到deps.edn:

{:deps {uncomplicate/neanderthal {:mvn/version "0.22.0"}}}

然后是gpu执行的设备代码, php7_mt.cu:

//参考地址
// https://5alt.me/2017/06/php%E9%87%8C%E7%9A%84%E9%9A%8F%E6%9C%BA%E6%95%B0/ 
// c的多线程MT19937爆破代码,基于此代码修改

/**
   The code below is mostly stripped from the PHP sources.
 **/

#define MAX_SEED 0xffffffff

// Mersenne Twister macros and parameters
#define hiBit(u)      ((u) & 0x80000000U)  /* mask all but highest   bit of u */
#define loBit(u)      ((u) & 0x00000001U)  /* mask all but lowest    bit of u */
#define loBits(u)     ((u) & 0x7FFFFFFFU)  /* mask     the highest   bit of u */
#define mixBits(u, v) (hiBit(u)|loBits(v)) /* move hi bit of u to hi bit of v */

#define N             (624)                /* length of state vector */
#define M             (397)                /* a period parameter */

#define RAND_RANGE(__n, __min, __max, __tmax)       \
  (__n) = (__min) + (long) ((double) ( (double) (__max) - (__min) + 1.0) * ((__n) / ((__tmax) + 1.0)))

#define PHP_MT_RAND_MAX ((long) (0x7FFFFFFF)) /* (1<<31) - 1 */

typedef unsigned int uint32_t;

typedef struct {
  uint32_t state[N];
  uint32_t left;
  uint32_t *next;
} MTState;

__device__
static inline uint32_t
php_twist(uint32_t m, uint32_t u, uint32_t v)
{
  return (m ^ (mixBits(u,v)>>1) ^ ((uint32_t)(-(uint32_t)(loBit(u))) & 0x9908b0dfU));
}

__device__
static inline uint32_t
mt_twist(uint32_t m, uint32_t u, uint32_t v)
{
  return (m ^ (mixBits(u,v)>>1) ^ ((uint32_t)(-(uint32_t)(loBit(v))) & 0x9908b0dfU));
}

__device__
void
mtInitialize(uint32_t seed, MTState *mtInfo)
{
  /* Initialize generator state with seed
     See Knuth TAOCP Vol 2, 3rd Ed, p.106 for multiplier.
     In previous versions, most significant bits (MSBs) of the seed affect
     only MSBs of the state array.  Modified 9 Jan 2002 by Makoto Matsumoto. */

  register uint32_t *s = mtInfo->state;
  register uint32_t *r = mtInfo->state;
  register int i = 1;

  *s++ = seed & 0xffffffffU;
  for( ; i < N; ++i ) {
    *s++ = ( 1812433253U * ( *r ^ (*r >> 30) ) + i ) & 0xffffffffU;
    r++;
  }
}

__device__
void
mtReload(MTState *mtInfo)
{
  /* Generate N new values in state
     Made clearer and faster by Matthew Bellew ([email protected]) */

  register uint32_t *p = mtInfo->state;
  register int i;
  register uint32_t (*twist)(uint32_t, uint32_t, uint32_t) = mt_twist;


  for (i = N - M; i--; ++p)
    *p = twist(p[M], p[0], p[1]);
  for (i = M; --i; ++p)
    *p = twist(p[M-N], p[0], p[1]);
  *p = twist(p[M-N], p[0], mtInfo->state[0]);
  mtInfo->left = N;
  mtInfo->next = mtInfo->state;
}

__device__
void
mt_srand(uint32_t seed, MTState *mtInfo) {
  mtInitialize(seed, mtInfo);
  mtInfo->left = 0;

}

__device__
uint32_t
mt_rand(MTState *mtInfo)
{
  /* Pull a 32-bit integer from the generator state
     Every other access function simply transforms the numbers extracted here */

  register uint32_t s1;

  if (mtInfo->left == 0)
    mtReload(mtInfo);

  -- (mtInfo->left);
  s1 = *mtInfo->next ++;

  s1 ^= (s1 >> 11);
  s1 ^= (s1 <<  7) & 0x9d2c5680U;
  s1 ^= (s1 << 15) & 0xefc60000U;
  s1 ^= (s1 >> 18) ;
  return s1;
}

__device__
uint32_t
php_mt_rand(MTState *mtInfo)
{
  return mt_rand(mtInfo);
}

__device__
uint32_t
php_mt_rand_range(MTState *mtInfo, uint32_t min, uint32_t max)
{
  uint32_t num = php_mt_rand(mtInfo);
  return num%(max-min+1)+min;
}

__device__
int compare(MTState *mtInfo, uint32_t value, uint32_t min, uint32_t max){
  if(php_mt_rand_range(mtInfo, min, max) == value) return 1;
  else return 0;
}

// 以上代码来自 https://5alt.me/downloads/mt_rand.c


// 函数基本和mt_rand_me.cu相同,不再注释
extern "C"
__global__
void mt_rand_find(uint32_t sseed,
                  uint32_t *value,
                  uint32_t length,
                  uint32_t min,
                  uint32_t max,
                  bool *found,
                  unsigned int* sols){
  unsigned int gid = blockDim.x * blockIdx.x + threadIdx.x;
  uint32_t seed = sseed + gid;

  sols[gid] = 0;

  MTState info;
  mt_srand(seed, &info);

  for(int i = 0; i < length; i++){
    if(!compare(&info, value[i], min, max)) return;
  }

  *found = true;
  sols[gid] = seed;
}

//只用一个线程,用于测试rand
extern "C"
__global__
void mt_rand( unsigned int seed, int step, int min, int max, unsigned int* ret)
{
  MTState info;
  unsigned int x = 0;

  mt_srand(seed, &info);

  for(int i = 0; i <= step; i++){
    x = php_mt_rand_range(&info, min, max);
  }

  *ret = x;
}

最后是clojure的host代码:

(require '[uncomplicate.clojurecuda.core :refer :all])
(require '[uncomplicate.commons.core :refer :all])

(init)
(device-count)

(def my-gpu (device 0))
(def ctx (context my-gpu))

(current-context! ctx)
(def php-rand (slurp "php7_mt.cu"))

(def rand-program (compile! (program php-rand)))
(def rand-module (module rand-program))
(def mt-rand-find(function rand-module "mt_rand_find"))
(def mt-rand-one(function rand-module "mt_rand"))

;; 注意,php7.1.0以上mt_rand()和mt_rand(min,max)返回结果不同
;; mt_rand()返回的数字要右移一位
;; 这里只是示例,全部采用mt_rand(min,max)进行测试

(def threads 2000000) ;; GPU线程数量
(def size threads) ;; 保存GPU计算结果的数组大小,等同于GPU线程数量

(def bool-size 1)
(def uint-size 4)
(def max-rand (int (dec (Math/pow 2 31))))

(defn find-rand-range-one-block
  [n values & [opts]]
  (let [found (mem-alloc bool-size)
        _ (memcpy-host! (byte-array [0]) found)

        values-len (count values)
        values-match (mem-alloc (* uint-size values-len))
        _ (memcpy-host! (int-array values) values-match)

        min (get opts :min 0)
        max (get opts :max max-rand)
        sols-len (* size uint-size)
        sols (mem-alloc sols-len)
        _ (launch! mt-rand-find (grid-1d size)
                   (parameters n
                               values-match
                               values-len
                               min
                               max
                               found
                               sols))
        ret-found (-> (memcpy-host! found (byte-array 1))
                      first)
        ret-sols (memcpy-host! sols (int-array size))]
    (release sols)
    (release values-match)
    (release found)
    (when-not (zero? ret-found)
      (println "block:" n "ret found:" ret-found)
      (filter (comp not zero?) ret-sols))))

(def max-blocks (/ 0xffffffff (+ size 1)))
(defn find-all-seed
  [vals & [opts]]
  (doseq [n (range (int (Math/ceil max-blocks)))]
    (let [rs (find-rand-range-one-block (* size n) vals opts)]
      (when rs
        (doseq [r rs]
          (println "found:" (Integer/toUnsignedString r)))))))

;; 跟第二部分不同,随机序列的长度对速度影响不大了
;; 因为算法的实现,取624个值后才进行reload,现在计算同一个seed的下一个随机数时mt_rand的计算量较小。
;; 这里仅为了测试,指定了一个比较大的max,这样就算随机序列短一点,seed也不会那么多

;; php7.3执行,获取两个元素的随机数序列
;; php -r 'mt_srand(88663332); echo mt_rand(0,12345689)."---".mt_rand(0,12345689). "\n";'
;; 5079804---2835549

(time (find-all-seed [5079804 2835549] {:max 12345689}))
;; block: 88000000 ret found: 1
;; found: 88663332
;; "Elapsed time: 465653.628795 msecs"
;; 跑完整个32位地址空间,不到8分钟,可以看到比cpu还是快很多的。

(defn rand-range-one
  [seed & [opts]]
  (let [step (get opts :step 0)
        ret (mem-alloc 50)
        min (get opts :min 0)
        max (get opts :max max-rand)
        _ (launch! mt-rand-one (grid-1d 1)
                   (parameters seed step min max ret))
        ret-sols (memcpy-host! ret (int-array 1))]
    (release ret)
    (first ret-sols)))

(comment

  ;; 应该和php结果相同
  (rand-range-one 1234 {:max 62 :step 0})
  ;; => 6

  (rand-range-one 1234 {:max 62 :step 1})
  ;; => 39

  ;; php7.3 执行结果如下
  ;; $ php -r 'mt_srand(1234); echo mt_rand(0,62)."---".mt_rand(0,62). "\n";'
  ;; 6---39

;;; php中不指定min max,需要把结果右移一位
  (rand-range-one 1234)
  ;; => 822569775
  (bit-shift-right *1 1)
  ;; => 411284887 

  (-> (rand-range-one 1234 {:step 1})
      (bit-shift-right 1))
  ;; => 1068724585

  ;; php7.3 执行结果如下
  ;;  $ php -r 'mt_srand(1234); echo mt_rand()."---".mt_rand(). "\n";'
  ;;  411284887---1068724585

  )

;; clean env
(release rand-module)
(release rand-program)
(release ctx)

同第二部分一样,测试随机字符串序列,如下php代码:

<?php
    function randStr($l=4){
            $ret="";
            $chars="qwertyuiopasdfghjklzxcvbnm1234567890QWERTYUIOPASDFGHJKLZXCVBNM";
            for($i=0;$i<$l;$i++)    $ret.=$chars[mt_rand(0,strlen($chars))];
            return $ret;
    }

// 注意mt_srand的参数与第二部分的不同
    mt_srand( 6688991);
    echo randStr(5);
?>

randStr结果是:P3ThR 现在根据randStr的结果猜解出seed:

;; 首先要获得随机后的转换表,才能把字符串转换回随机数序列。
;; 这里假定已经有了这个表
(def chars "qwertyuiopasdfghjklzxcvbnm1234567890QWERTYUIOPASDFGHJKLZXCVBNM")

(defn rand-strs->rand-seq
  "把随机结果字符串转换回随机数字序列"
  [strs]
  (mapv #(->> (str %1)
              (.indexOf chars))
        strs))

;; randStr结果
(def result "P3ThR")

(def rand-seq (rand-strs->rand-seq result))
rand-seq
;; => [45 28 40 15 39]

(def max-r (count chars))
max-r
;; => 62

(time (find-all-seed rand-seq {:max max-r}))
;; block: 6000000 ret found: 1
;; found: 6688991
;; block: 32000000 ret found: 1
;; found: 32333633
;; block: 322000000 ret found: 1
;; found: 322799803
;; block: 828000000 ret found: 1
;; found: 829594924
;; block: 1930000000 ret found: 1
;; found: 1930582992
;; block: 2016000000 ret found: 1
;; found: 2016439798
;; block: 2310000000 ret found: 1
;; found: 2311825734
;; block: 3632000000 ret found: 1
;; found: 3633831162
;; block: 3776000000 ret found: 1
;; found: 3776934990
;; "Elapsed time: 466266.530259 msecs"

可以看到,对于比较小的随机范围和随机序列,找到的seed比较多。

3 总结

GPU计算确实快,对于爆破,值得拥有。如果为了加快速度,可以多块显卡并行计算,并不难实现。 可以说分分钟就能预测出原始seed。难度在于得到mt_rand生成随机序列之后的变换过程,只有知道这个过程才能获得原始的随机序列,进行seed爆破。比如上面示例的php代码中的chars字符串。

作者: ntestoc

Created: 2019-03-16 周六 20:53

扫描二维码关注公众号,回复: 5553868 查看本文章

猜你喜欢

转载自www.cnblogs.com/ntestoc/p/10544291.html