偽隨機數的爆破–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應該沒那么容易爆破了。
Created: 2019-03-17 周日 09:01