偽隨機數的爆破--3


偽隨機數的爆破–3

1 簡介

接上篇,這一部分實現用GPU暴力猜解MT19937的種子。上一部分已經說過,php7.1.0之后的實現是標准的MT19937算法,這里就是針對標准MT19937算法進行爆破種子。

主要是因為php中mt_rand函數的種子空間是32位的(即seed最大取值232-1),長度不是很長,參考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 following functions are based on a C++ class MTRand by
  Richard J. Wagner. For more information see the web page at
  http://www.math.sci.hiroshima-u.ac.jp/~m-mat/MT/VERSIONS/C-LANG/MersenneTwister.h

  Mersenne Twister random number generator -- a C++ class MTRand
  Based on code by Makoto Matsumoto, Takuji Nishimura, and Shawn Cokus
  Richard J. Wagner  v1.0  15 May 2003  rjwagner@writeme.com

  The Mersenne Twister is an algorithm for generating random numbers.  It
  was designed with consideration of the flaws in various other generators.
  The period, 2^19937-1, and the order of equidistribution, 623 dimensions,
  are far greater.  The generator is also fast; it avoids multiplication and
  division, and it benefits from caches and pipelines.  For more information
  see the inventors' web page at http://www.math.sci.hiroshima-u.ac.jp/~m-mat/MT/emt.html

  Reference
  M. Matsumoto and T. Nishimura, "Mersenne Twister: A 623-Dimensionally
  Equidistributed Uniform Pseudo-Random Number Generator", ACM Transactions on
  Modeling and Computer Simulation, Vol. 8, No. 1, January 1998, pp 3-30.

  Copyright (C) 1997 - 2002, Makoto Matsumoto and Takuji Nishimura,
  Copyright (C) 2000 - 2003, Richard J. Wagner
  All rights reserved.

  Redistribution and use in source and binary forms, with or without
  modification, are permitted provided that the following conditions
  are met:

  1. Redistributions of source code must retain the above copyright
     notice, this list of conditions and the following disclaimer.

  2. Redistributions in binary form must reproduce the above copyright
     notice, this list of conditions and the following disclaimer in the
     documentation and/or other materials provided with the distribution.

  3. The names of its contributors may not be used to endorse or promote
     products derived from this software without specific prior written
     permission.

  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR
  CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
  EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
  PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
  PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
  LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
  NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

#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 (matthew.bellew@home.com) */

  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()返回的數字右移了一位
;; 參考php7 mt_rand.c中的注釋,為了和以前兼容,mt_rand()返回的最大值位2^31
;; /*
;; * Melo: it could be 2^^32 but we only use 2^^31 to maintain
;; * compatibility with the previous php_rand
;; */
;; RETURN_LONG(PHP_MT_RAND_MAX); /* 2^^31 */
;;
;; 這里只是示例,全部采用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字符串。

當然種子空間增加的話,難度也會直線上升,以現在的硬件水平,64位的mt19937應該沒那么容易爆破了。

作者: ntestoc

Created: 2019-03-17 周日 09:01


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM