├── Makefile ├── php_mt_seed.c └── README /Makefile: -------------------------------------------------------------------------------- 1 | CC = gcc 2 | RM = rm -f 3 | CFLAGS = -Wall -march=native -mtune=generic -O2 -fomit-frame-pointer -funroll-loops -fopenmp 4 | PROJ = php_mt_seed 5 | 6 | php_mt_seed: php_mt_seed.c 7 | $(CC) $(CFLAGS) $< -o $@ 8 | 9 | mic: 10 | $(MAKE) CC=icc CFLAGS='-mmic -O3 -openmp' 11 | 12 | clean: 13 | $(RM) $(PROJ) 14 | -------------------------------------------------------------------------------- /php_mt_seed.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2012-2014,2017 Solar Designer 3 | * 4 | * Redistribution and use in source and binary forms, with or without 5 | * modification, are permitted. 6 | * 7 | * There's ABSOLUTELY NO WARRANTY, express or implied. 8 | */ 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include /* sysconf() */ 15 | #include 16 | #include 17 | 18 | #if defined(__MIC__) || defined(__AVX512F__) 19 | #include 20 | typedef __m512i vtype; 21 | /* hack */ 22 | #define _mm_set1_epi32(x) _mm512_set1_epi32(x) 23 | #define _mm_add_epi32(x, y) _mm512_add_epi32(x, y) 24 | #define _mm_mullo_epi32(x, y) _mm512_mullo_epi32(x, y) 25 | #ifdef __MIC__ 26 | #define _mm_macc_epi32(x, y, z) _mm512_fmadd_epi32(x, y, z) 27 | #endif 28 | #define _mm_slli_epi32(x, i) _mm512_slli_epi32(x, i) 29 | #define _mm_srli_epi32(x, i) _mm512_srli_epi32(x, i) 30 | #define _mm_and_si128(x, y) _mm512_and_epi32(x, y) 31 | #define _mm_or_si128(x, y) _mm512_or_epi32(x, y) 32 | #define _mm_xor_si128(x, y) _mm512_xor_epi32(x, y) 33 | #elif defined(__AVX2__) 34 | #include 35 | typedef __m256i vtype; 36 | /* hack */ 37 | #define _mm_set1_epi32(x) _mm256_set1_epi32(x) 38 | #define _mm_add_epi32(x, y) _mm256_add_epi32(x, y) 39 | #define _mm_mullo_epi32(x, y) _mm256_mullo_epi32(x, y) 40 | #define _mm_slli_epi32(x, i) _mm256_slli_epi32(x, i) 41 | #define _mm_srli_epi32(x, i) _mm256_srli_epi32(x, i) 42 | #define _mm_and_si128(x, y) _mm256_and_si256(x, y) 43 | #define _mm_or_si128(x, y) _mm256_or_si256(x, y) 44 | #define _mm_xor_si128(x, y) _mm256_xor_si256(x, y) 45 | #define _mm_cmpeq_epi32(x, y) _mm256_cmpeq_epi32(x, y) 46 | #define _mm_testz_si128(x, y) _mm256_testz_si256(x, y) 47 | #warning AVX-512 not enabled. Try gcc -mavx512f (on Intel Knights Landing, Skylake-X, or some newer). 48 | #elif defined(__SSE2__) 49 | #include 50 | typedef __m128i vtype; 51 | #ifdef __XOP__ 52 | #include 53 | #else 54 | #ifdef __SSE4_1__ 55 | #include 56 | #else 57 | #ifdef __GNUC__ 58 | #define _mm_mullo_epi32(a, b) ({ \ 59 | __m128i _a = (a), _b = (b); \ 60 | _mm_unpacklo_epi32( \ 61 | _mm_shuffle_epi32(_mm_mul_epu32(_a, _b), 0x08), \ 62 | _mm_shuffle_epi32(_mm_mul_epu32(_mm_srli_epi64(_a, 32), \ 63 | _mm_srli_epi64(_b, 32)), 0x08)); \ 64 | }) 65 | #else 66 | #define _mm_mullo_epi32(a, b) \ 67 | _mm_unpacklo_epi32( \ 68 | _mm_shuffle_epi32(_mm_mul_epu32((a), (b)), 0x08), \ 69 | _mm_shuffle_epi32(_mm_mul_epu32(_mm_srli_epi64((a), 32), \ 70 | _mm_srli_epi64((b), 32)), 0x08)) 71 | #endif 72 | #warning SSE4.1 not enabled, will use only SSE2 doing only 2 multiplies per 4-element vector. Try gcc -msse4 (on capable CPUs). 73 | #endif 74 | #ifdef __AVX__ 75 | #warning XOP and AVX2 are not enabled. Try gcc -mxop (on AMD Bulldozer or some newer) or -mavx2 (on Intel Haswell, AMD Zen, or newer). 76 | #elif defined(__SSE4_1__) 77 | #warning AVX* and XOP are not enabled. Try gcc -mxop (on AMD Bulldozer or some newer), -mavx (on Intel Sandy Bridge or newer), or -mavx2 (on Intel Haswell, AMD Zen, or newer). 78 | #endif 79 | #endif 80 | #else 81 | #warning SSE2 not enabled, will use non-vectorized code. Try gcc -msse2 (on non-ancient x86 CPUs). 82 | #endif 83 | 84 | #if !defined(__XOP__) && !defined(_mm_macc_epi32) 85 | #define _mm_macc_epi32(x, y, z) \ 86 | _mm_add_epi32(_mm_mullo_epi32(x, y), z) 87 | #endif 88 | 89 | #ifndef _OPENMP 90 | #warning OpenMP not enabled, will only use one CPU core. Try gcc -fopenmp. 91 | #endif 92 | 93 | #define M 397 94 | #define N 624 95 | 96 | #define MATCH_PURE 1 97 | #define MATCH_FULL 2 98 | #define MATCH_SKIP 4 99 | #define MATCH_LAST 8 100 | 101 | typedef struct { 102 | uint32_t flags; 103 | int32_t mmin, mmax; 104 | int32_t rmin; 105 | uint32_t rspan; 106 | } match_t; 107 | 108 | typedef enum { 109 | PHP_LEGACY = 0, 110 | PHP_MODERN = 1, 111 | PHP_521 = 1, 112 | PHP_710 = 2 113 | } version_t; 114 | 115 | static const char *flavors[] = { 116 | "3.0.7 to 5.2.0", 117 | "5.2.1+" 118 | }; 119 | 120 | static const char *versions[] = { 121 | "3.0.7 to 5.2.0", 122 | "5.2.1 to 7.0.x; HHVM", 123 | "7.1.0+" 124 | }; 125 | 126 | #define NEXT_STATE(x, i) \ 127 | (x) = 1812433253U * ((x) ^ ((x) >> 30)) + (i); 128 | 129 | #if defined(__SSE2__) || defined(__MIC__) 130 | static inline int diff(uint32_t x, uint32_t xs, uint32_t seed, 131 | const match_t *match, version_t version) 132 | #else 133 | static inline int diff(uint32_t x, uint32_t x1, uint32_t xs, 134 | const match_t *match, version_t version) 135 | #endif 136 | { 137 | #if defined(__SSE2__) || defined(__MIC__) 138 | uint32_t xsi = seed; 139 | #else 140 | uint32_t xsi = x1; 141 | #endif 142 | unsigned int i = 1; 143 | 144 | while (1) { 145 | if (match->flags & MATCH_SKIP) { 146 | /* nothing */ 147 | } else 148 | if (match->flags & MATCH_PURE) { 149 | if (x != match->mmin) 150 | break; 151 | } else { 152 | int32_t xr; 153 | if (match->flags & MATCH_FULL) 154 | xr = x; 155 | else if (version != PHP_710) 156 | xr = match->rmin + 157 | (int32_t)((double)match->rspan * (x >> 1) / 158 | (0x7fffffff + 1.0)); 159 | else 160 | xr = match->rmin + x % match->rspan; 161 | if (xr < match->mmin || xr > match->mmax) 162 | break; 163 | } 164 | 165 | if (match->flags & MATCH_LAST) 166 | return 0; 167 | 168 | if (version == PHP_LEGACY) { 169 | #if defined(__SSE2__) || defined(__MIC__) 170 | if (i == 1) { 171 | xsi = (69069 * 2) * xsi + 69069; 172 | i = 0; 173 | } 174 | #endif 175 | do { 176 | x = xsi; 177 | xsi *= 69069; 178 | xs *= 69069; 179 | } while ((++match)->flags & MATCH_SKIP); 180 | } else { 181 | #if defined(__SSE2__) || defined(__MIC__) 182 | if (i == 1) 183 | NEXT_STATE(xsi, 1) 184 | #endif 185 | do { 186 | x = xsi; 187 | NEXT_STATE(xsi, i + 1) 188 | NEXT_STATE(xs, M + i) 189 | i++; 190 | } while ((++match)->flags & MATCH_SKIP); 191 | } 192 | 193 | x = (((x & 0x80000000) | (xsi & 0x7fffffff)) >> 1) ^ xs ^ 194 | ((((version == PHP_521) ? x : xsi) & 1) * 0x9908b0df); 195 | x ^= x >> 11; 196 | x ^= (x << 7) & 0x9d2c5680; 197 | x ^= (x << 15) & 0xefc60000; 198 | x ^= x >> 18; 199 | 200 | if (match->flags & MATCH_FULL) 201 | x >>= 1; 202 | } 203 | 204 | return -1; 205 | } 206 | 207 | static void print_guess(uint32_t seed, uint64_t *found, version_t version) 208 | { 209 | if (version == PHP_LEGACY) 210 | seed <<= 1; 211 | 212 | #ifdef _OPENMP 213 | #pragma omp critical 214 | #endif 215 | do { 216 | if (!*found) 217 | putc('\n', stderr); 218 | printf("seed = 0x%08x = %u (PHP %s)\n", 219 | seed, seed, versions[version]); 220 | (*found)++; 221 | } while (version == PHP_LEGACY && !(seed++ & 1)); 222 | } 223 | 224 | #if defined(__SSE2__) || defined(__MIC__) 225 | #define COMPARE(x, xM, seed) \ 226 | if (!diff((x), (xM), (seed), match, version)) \ 227 | print_guess((seed), &found, version); 228 | #else 229 | #define COMPARE(x, x1, xM, seed) \ 230 | if (!diff((x), (x1), (xM), match, version)) \ 231 | print_guess((seed), &found, version); 232 | #endif 233 | 234 | #if defined(__MIC__) || defined(__AVX512F__) 235 | #define P 7 236 | #elif defined(__AVX2__) 237 | #define P 6 238 | #else 239 | #define P 5 240 | #endif 241 | 242 | static uint64_t crack_range(int32_t start, int32_t end, 243 | const match_t *match, version_t flavor) 244 | { 245 | uint64_t found = 0; 246 | int32_t base; /* signed type for OpenMP 2.5 compatibility */ 247 | #if defined(__SSE4_1__) || defined(__MIC__) 248 | vtype vvalue = _mm_set1_epi32(match->mmin); 249 | #endif 250 | #if defined(__SSE2__) || defined(__MIC__) 251 | vtype seed_and_0x80000000, seed_shr_30; 252 | #else 253 | uint32_t seed_and_0x80000000, seed_shr_30; 254 | #endif 255 | 256 | assert((start >> (30 - P)) == ((end - 1) >> (30 - P))); 257 | 258 | { 259 | uint32_t seed = (uint32_t)start << (P + (flavor == PHP_LEGACY)); 260 | #if defined(__SSE2__) || defined(__MIC__) 261 | vtype vseed = _mm_set1_epi32(seed); 262 | const vtype c0x80000000 = _mm_set1_epi32(0x80000000); 263 | seed_and_0x80000000 = _mm_and_si128(vseed, c0x80000000); 264 | seed_shr_30 = _mm_srli_epi32(vseed, 30); 265 | #else 266 | seed_and_0x80000000 = seed & 0x80000000; 267 | seed_shr_30 = seed >> 30; 268 | #endif 269 | } 270 | 271 | #ifdef _OPENMP 272 | #if defined(__SSE4_1__) || defined(__MIC__) 273 | #pragma omp parallel for default(none) private(base) shared(match, flavor, start, end, found, seed_and_0x80000000, seed_shr_30, vvalue) 274 | #elif defined(__SSE2__) 275 | #pragma omp parallel for default(none) private(base) shared(match, flavor, start, end, found, seed_and_0x80000000, seed_shr_30) 276 | #else 277 | #pragma omp parallel for default(none) private(base) shared(match, flavor, start, end, found, seed_and_0x80000000, seed_shr_30) 278 | #endif 279 | #endif 280 | for (base = start; base < end; base++) { 281 | uint32_t seed = (uint32_t)base << P; 282 | #if defined(__SSE2__) || defined(__MIC__) 283 | typedef struct { 284 | vtype a, b, c, d, e, f, g, h; 285 | } atype; 286 | atype xM, x = {}, x710 = {}; 287 | /* Hint to compiler not to waste registers */ 288 | volatile atype x1; 289 | const vtype cone = _mm_set1_epi32(1); 290 | vtype vseed = _mm_set1_epi32(seed); 291 | version_t version; 292 | 293 | #define DO(which, add) \ 294 | xM.which = _mm_add_epi32(xM.a, _mm_set1_epi32(add)); 295 | #if defined(__MIC__) || defined(__AVX512F__) 296 | xM.a = _mm512_add_epi32(vseed, _mm512_set_epi32( 297 | 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30)); 298 | DO(b, 1) DO(c, 32) DO(d, 33) 299 | DO(e, 64) DO(f, 65) DO(g, 96) DO(h, 97) 300 | #elif defined(__AVX2__) 301 | xM.a = _mm256_add_epi32(vseed, _mm256_set_epi32( 302 | 0, 2, 4, 6, 8, 10, 12, 14)); 303 | DO(b, 1) DO(c, 16) DO(d, 17) 304 | DO(e, 32) DO(f, 33) DO(g, 48) DO(h, 49) 305 | #else 306 | xM.a = _mm_add_epi32(vseed, _mm_set_epi32(0, 2, 4, 6)); 307 | DO(b, 1) DO(c, 8) DO(d, 9) 308 | DO(e, 16) DO(f, 17) DO(g, 24) DO(h, 25) 309 | #endif 310 | #undef DO 311 | 312 | #define DO_ALL \ 313 | DO(x.a, x1.a, xM.a) \ 314 | DO(x.b, x1.b, xM.b) \ 315 | DO(x.c, x1.c, xM.c) \ 316 | DO(x.d, x1.d, xM.d) \ 317 | DO(x.e, x1.e, xM.e) \ 318 | DO(x.f, x1.f, xM.f) \ 319 | DO(x.g, x1.g, xM.g) \ 320 | DO(x.h, x1.h, xM.h) 321 | 322 | if (flavor == PHP_LEGACY) { 323 | const vtype c69069 = _mm_set1_epi32(69069); 324 | const vtype c69069to396 = _mm_set1_epi32(0x4396a0b1); 325 | 326 | #define DO(x, x1, xM) \ 327 | xM = _mm_add_epi32(_mm_add_epi32(xM, xM), cone); \ 328 | x1 = xM = _mm_mullo_epi32(c69069, xM); \ 329 | xM = _mm_mullo_epi32(c69069to396, xM); 330 | DO_ALL 331 | #undef DO 332 | } else { 333 | const vtype cmul = _mm_set1_epi32(1812433253U); 334 | vtype vi = _mm_add_epi32(cone, cone); 335 | unsigned int n = (M - 1) / 22; 336 | 337 | #define DO(x, x1, xM) \ 338 | x1 = xM = _mm_macc_epi32(cmul, _mm_xor_si128(xM, seed_shr_30), cone); 339 | DO_ALL 340 | #undef DO 341 | 342 | do { 343 | #define DO(x, x1, xM) \ 344 | xM = _mm_macc_epi32(cmul, _mm_xor_si128(xM, _mm_srli_epi32(xM, 30)), vi); 345 | #define DO_ALLI \ 346 | DO_ALL \ 347 | vi = _mm_add_epi32(vi, cone); 348 | DO_ALLI DO_ALLI DO_ALLI DO_ALLI DO_ALLI DO_ALLI 349 | DO_ALLI DO_ALLI DO_ALLI DO_ALLI DO_ALLI DO_ALLI 350 | DO_ALLI DO_ALLI DO_ALLI DO_ALLI DO_ALLI DO_ALLI 351 | DO_ALLI DO_ALLI DO_ALLI DO_ALLI 352 | #undef DO_ALLI 353 | #undef DO 354 | } while (--n); 355 | } 356 | 357 | version = flavor; 358 | 359 | if (!(match->flags & MATCH_SKIP)) { 360 | const vtype c0x7fffffff = _mm_set1_epi32(0x7fffffff); 361 | const vtype c0x9908b0df = _mm_set1_epi32(0x9908b0df); 362 | 363 | #define DO(x, x1, xM) \ 364 | x = _mm_xor_si128(xM, _mm_srli_epi32(_mm_or_si128(seed_and_0x80000000, \ 365 | _mm_and_si128(x1, c0x7fffffff)), 1)); 366 | DO_ALL 367 | #undef DO 368 | 369 | #define DO(xout, xin, x1) \ 370 | xout = _mm_xor_si128(xin, _mm_mullo_epi32(c0x9908b0df, \ 371 | _mm_and_si128(x1, cone))); 372 | DO(x710.a, x.a, x1.a) 373 | DO(x710.b, x.b, x1.b) 374 | DO(x710.c, x.c, x1.c) 375 | DO(x710.d, x.d, x1.d) 376 | DO(x710.e, x.e, x1.e) 377 | DO(x710.f, x.f, x1.f) 378 | DO(x710.g, x.g, x1.g) 379 | DO(x710.h, x.h, x1.h) 380 | #undef DO 381 | 382 | if (version == PHP_521) { 383 | #define DO(x) \ 384 | x = _mm_xor_si128(x, c0x9908b0df); 385 | DO(x.b) 386 | DO(x.d) 387 | DO(x.f) 388 | DO(x.h) 389 | #undef DO 390 | } else 391 | x = x710; 392 | } 393 | 394 | do { 395 | uint32_t maybe = 1; 396 | 397 | if (!(match->flags & MATCH_SKIP)) { 398 | const vtype c0x9d2c5680 = _mm_set1_epi32(0x9d2c5680); 399 | const vtype c0xefc60000 = _mm_set1_epi32(0xefc60000); 400 | 401 | #define DO(x, x1, xM) \ 402 | x = _mm_xor_si128(x, _mm_srli_epi32(x, 11)); 403 | DO_ALL 404 | #undef DO 405 | 406 | #define DO_SC(x, s, c) \ 407 | x = _mm_xor_si128(x, _mm_and_si128(_mm_slli_epi32(x, s), c)); 408 | #define DO(x, x1, xM) \ 409 | DO_SC(x, 7, c0x9d2c5680) \ 410 | DO_SC(x, 15, c0xefc60000) 411 | DO_ALL 412 | #undef DO 413 | #undef DO_SC 414 | 415 | #define DO(x, x1, xM) \ 416 | x = _mm_xor_si128(x, _mm_srli_epi32(x, 18)); 417 | DO_ALL 418 | #undef DO 419 | 420 | if (match->flags & MATCH_FULL) { 421 | #define DO(x, x1, xM) \ 422 | x = _mm_srli_epi32(x, 1); 423 | DO_ALL 424 | #undef DO 425 | } 426 | } 427 | 428 | #if defined(__SSE4_1__) || defined(__MIC__) 429 | if (match->flags & MATCH_PURE) { 430 | #if defined(__MIC__) || defined(__AVX512F__) 431 | maybe = _mm512_cmpeq_epi32_mask(x.a, vvalue) | 432 | _mm512_cmpeq_epi32_mask(x.b, vvalue) | 433 | _mm512_cmpeq_epi32_mask(x.c, vvalue) | 434 | _mm512_cmpeq_epi32_mask(x.d, vvalue) | 435 | _mm512_cmpeq_epi32_mask(x.e, vvalue) | 436 | _mm512_cmpeq_epi32_mask(x.f, vvalue) | 437 | _mm512_cmpeq_epi32_mask(x.g, vvalue) | 438 | _mm512_cmpeq_epi32_mask(x.h, vvalue); 439 | #else 440 | vtype amask = _mm_cmpeq_epi32(x.a, vvalue); 441 | vtype bmask = _mm_cmpeq_epi32(x.b, vvalue); 442 | vtype cmask = _mm_cmpeq_epi32(x.c, vvalue); 443 | vtype dmask = _mm_cmpeq_epi32(x.d, vvalue); 444 | vtype emask = _mm_cmpeq_epi32(x.e, vvalue); 445 | vtype fmask = _mm_cmpeq_epi32(x.f, vvalue); 446 | vtype gmask = _mm_cmpeq_epi32(x.g, vvalue); 447 | vtype hmask = _mm_cmpeq_epi32(x.h, vvalue); 448 | maybe = !(_mm_testz_si128(amask, amask) && 449 | _mm_testz_si128(bmask, bmask) && 450 | _mm_testz_si128(cmask, cmask) && 451 | _mm_testz_si128(dmask, dmask) && 452 | _mm_testz_si128(emask, emask) && 453 | _mm_testz_si128(fmask, fmask) && 454 | _mm_testz_si128(gmask, gmask) && 455 | _mm_testz_si128(hmask, hmask)); 456 | #endif 457 | } 458 | #endif 459 | 460 | if (maybe) { 461 | unsigned int i; 462 | uint32_t iseed; 463 | typedef union { 464 | atype v; 465 | uint32_t s[8][sizeof(vtype) / 4]; 466 | } utype; 467 | utype u; 468 | /* Hint to compiler not to waste registers */ 469 | volatile utype uM; 470 | u.v = x; 471 | uM.v = xM; 472 | #if defined(__MIC__) || defined(__AVX512F__) 473 | for (i = 0, iseed = seed; i < 8; i++, iseed += 32) { 474 | unsigned int j, k; 475 | for (j = 0, k = 30; j < 16; j++, k -= 2) { 476 | COMPARE(u.s[i][j], uM.s[i][j], 477 | iseed + k) 478 | } 479 | i++; 480 | for (j = 0, k = 31; j < 16; j++, k -= 2) { 481 | COMPARE(u.s[i][j], uM.s[i][j], 482 | iseed + k) 483 | } 484 | } 485 | #elif defined(__AVX2__) 486 | for (i = 0, iseed = seed; i < 8; i++, iseed += 16) { 487 | unsigned int j, k; 488 | for (j = 0, k = 14; j < 8; j++, k -= 2) { 489 | COMPARE(u.s[i][j], uM.s[i][j], 490 | iseed + k) 491 | } 492 | i++; 493 | for (j = 0, k = 15; j < 8; j++, k -= 2) { 494 | COMPARE(u.s[i][j], uM.s[i][j], 495 | iseed + k) 496 | } 497 | } 498 | #else 499 | for (i = 0, iseed = seed; i < 8; i++, iseed += 8) { 500 | COMPARE(u.s[i][0], uM.s[i][0], iseed + 6) 501 | COMPARE(u.s[i][1], uM.s[i][1], iseed + 4) 502 | COMPARE(u.s[i][2], uM.s[i][2], iseed + 2) 503 | COMPARE(u.s[i][3], uM.s[i][3], iseed) 504 | i++; 505 | COMPARE(u.s[i][0], uM.s[i][0], iseed + 7) 506 | COMPARE(u.s[i][1], uM.s[i][1], iseed + 5) 507 | COMPARE(u.s[i][2], uM.s[i][2], iseed + 3) 508 | COMPARE(u.s[i][3], uM.s[i][3], iseed + 1) 509 | } 510 | #endif 511 | /* Hint to compiler not to spill xM above */ 512 | xM = uM.v; 513 | } 514 | 515 | if (version != PHP_521) 516 | break; 517 | version = PHP_710; 518 | x = x710; 519 | } while (1); 520 | #else 521 | typedef struct { 522 | uint32_t a, b, c, d; 523 | } atype; 524 | atype x = {}, x710 = {}; 525 | do { 526 | atype x1, xM; 527 | version_t version; 528 | unsigned int i; 529 | 530 | xM.a = seed; 531 | xM.b = seed + 1; 532 | xM.c = seed + 2; 533 | xM.d = seed + 3; 534 | 535 | #define DO_ALL \ 536 | DO(x.a, x1.a, xM.a) \ 537 | DO(x.b, x1.b, xM.b) \ 538 | DO(x.c, x1.c, xM.c) \ 539 | DO(x.d, x1.d, xM.d) 540 | 541 | if (flavor == PHP_LEGACY) { 542 | #define DO(x, x1, xM) \ 543 | xM += xM + 1; \ 544 | x1 = xM *= 69069; \ 545 | xM *= 0x4396a0b1; 546 | DO_ALL 547 | #undef DO 548 | } else { 549 | #define DO(x, x1, xM) \ 550 | x1 = xM = 1812433253U * (xM ^ seed_shr_30) + 1; 551 | DO_ALL 552 | #undef DO 553 | 554 | for (i = 2; i <= M; i++) { 555 | #define DO(x, x1, xM) \ 556 | NEXT_STATE(xM, i) 557 | DO_ALL 558 | #undef DO 559 | } 560 | } 561 | 562 | version = flavor; 563 | 564 | if (!(match->flags & MATCH_SKIP)) { 565 | #define DO(x, x1, xM) \ 566 | x = ((seed_and_0x80000000 | (x1 & 0x7fffffff)) >> 1) ^ xM; 567 | DO_ALL 568 | #undef DO 569 | 570 | #define DO(xout, xin, x1) \ 571 | xout = xin ^ ((x1 & 1) * 0x9908b0df); 572 | DO(x710.a, x.a, x1.a) 573 | DO(x710.b, x.b, x1.b) 574 | DO(x710.c, x.c, x1.c) 575 | DO(x710.d, x.d, x1.d) 576 | #undef DO 577 | 578 | if (version == PHP_521) { 579 | x.b ^= 0x9908b0df; 580 | x.d ^= 0x9908b0df; 581 | } else 582 | x = x710; 583 | } 584 | 585 | do { 586 | if (!(match->flags & MATCH_SKIP)) { 587 | #define DO(x, x1, xM) \ 588 | x ^= x >> 11; \ 589 | x ^= (x << 7) & 0x9d2c5680; \ 590 | x ^= (x << 15) & 0xefc60000; \ 591 | x ^= x >> 18; 592 | DO_ALL 593 | #undef DO 594 | 595 | if (match->flags & MATCH_FULL) { 596 | #define DO(x, x1, xM) \ 597 | x >>= 1; 598 | DO_ALL 599 | #undef DO 600 | } 601 | } 602 | 603 | COMPARE(x.a, x1.a, xM.a, seed) 604 | COMPARE(x.b, x1.b, xM.b, seed + 1) 605 | COMPARE(x.c, x1.c, xM.c, seed + 2) 606 | COMPARE(x.d, x1.d, xM.d, seed + 3) 607 | 608 | if (version != PHP_521) 609 | break; 610 | version = PHP_710; 611 | x = x710; 612 | } while (1); 613 | 614 | seed += 4; 615 | } while (seed & ((1 << P) - 1)); 616 | #endif 617 | } 618 | 619 | return found; 620 | } 621 | 622 | static uint64_t crack(const match_t *match) 623 | { 624 | uint64_t found = 0, recent = 0; 625 | uint32_t base, top; 626 | #if defined(__MIC__) || defined(__AVX512F__) 627 | const uint32_t step = 0x10000000 >> P; 628 | #else 629 | const uint32_t step = 0x2000000 >> P; 630 | #endif 631 | version_t flavor; 632 | long clk_tck; 633 | clock_t start_time; 634 | struct tms tms; 635 | 636 | flavor = PHP_LEGACY; 637 | do { 638 | unsigned int shift = (flavor == PHP_LEGACY); 639 | 640 | fprintf(stderr, "Version: %s\n", flavors[flavor]); 641 | 642 | clk_tck = sysconf(_SC_CLK_TCK); 643 | start_time = times(&tms); 644 | 645 | top = 0x40000000 >> (P - 2 + shift); 646 | for (base = 0; base < top; base += step) { 647 | uint32_t start = base << (P + shift); 648 | uint32_t next = (base + step) << (P + shift); 649 | clock_t running_time = times(&tms) - start_time; 650 | fprintf(stderr, 651 | "\rFound %llu, trying 0x%08x - 0x%08x, " 652 | "speed %.1f Mseeds/s ", 653 | (unsigned long long)found, start, next - 1, 654 | (double)start * clk_tck / 655 | (running_time ? running_time * 1e6 : 1e6)); 656 | 657 | recent = crack_range(base, base + step, match, flavor); 658 | found += recent; 659 | } 660 | 661 | if (flavor == PHP_MODERN) 662 | break; 663 | flavor = PHP_MODERN; 664 | if (!recent) 665 | putc('\n', stderr); 666 | } while (1); 667 | 668 | return found; 669 | } 670 | 671 | #undef P 672 | 673 | static int32_t parse_int(const char *s) 674 | { 675 | unsigned long ulvalue; 676 | uint32_t uvalue; 677 | char *error; 678 | 679 | errno = 0; 680 | uvalue = ulvalue = strtoul(s, &error, 10); 681 | if (!errno && !*error && 682 | *s >= '0' && *s <= '9' && 683 | uvalue == ulvalue && uvalue <= 0x7fffffff) 684 | return uvalue; 685 | 686 | return -1; 687 | } 688 | 689 | static void parse(int argc, char **argv, match_t *match, unsigned int nmatch) 690 | { 691 | const char *prog = argv[0] ? argv[0] : "php_mt_seed"; 692 | int ok = 0; 693 | match_t *first = match, *last = match; 694 | 695 | argc--; 696 | argv++; 697 | 698 | while (nmatch && argc > 0) { 699 | int32_t value = parse_int(argv[0]); 700 | ok = value >= 0; 701 | 702 | match->flags = MATCH_PURE | MATCH_FULL; 703 | match->mmin = match->mmax = value; 704 | match->rmin = 0; match->rspan = 0x80000000U; 705 | 706 | if (argc >= 2) { 707 | value = parse_int(argv[1]); 708 | ok &= value >= match->mmin; 709 | if (value != match->mmin) 710 | match->flags &= ~MATCH_PURE; 711 | match->mmax = value; 712 | } 713 | 714 | if (argc == 3) { 715 | ok = 0; 716 | break; 717 | } 718 | 719 | if (argc >= 4) { 720 | value = parse_int(argv[2]); 721 | ok &= value >= 0 && value <= match->mmax; 722 | if (value != 0) 723 | match->flags &= ~(MATCH_PURE | MATCH_FULL); 724 | match->rmin = value; 725 | 726 | value = parse_int(argv[3]); 727 | ok &= value >= match->rmin && value >= match->mmin; 728 | if (value != 0x7fffffff) 729 | match->flags &= ~(MATCH_PURE | MATCH_FULL); 730 | if (match->mmin == match->rmin && 731 | match->mmax == value) 732 | match->flags |= MATCH_SKIP; 733 | match->rspan = (uint32_t)value - match->rmin + 1; 734 | } 735 | 736 | if (!(match->flags & MATCH_SKIP)) 737 | last = match; 738 | 739 | nmatch--; 740 | match++; 741 | if (!ok) 742 | break; 743 | if (argc <= 4) { 744 | argc = 0; 745 | break; 746 | } 747 | argc -= 4; 748 | argv += 4; 749 | } 750 | 751 | if (!ok || (!nmatch && argc > 0) || (last->flags & MATCH_SKIP)) { 752 | printf("Usage: %s VALUE_OR_MATCH_MIN" 753 | " [MATCH_MAX [RANGE_MIN RANGE_MAX]] ...\n", prog); 754 | exit(1); 755 | } 756 | 757 | last->flags |= MATCH_LAST; 758 | 759 | printf("Pattern:"); 760 | match = first; 761 | do { 762 | if (match->flags & MATCH_SKIP) 763 | printf(" SKIP"); 764 | else if (match->flags & MATCH_PURE) 765 | printf(" EXACT"); 766 | else if (match->flags & MATCH_FULL) 767 | printf(" RANGE"); 768 | else if (match->mmin == match->mmax) 769 | printf(" EXACT-FROM-%u", match->rspan); 770 | else 771 | printf(" RANGE-FROM-%u", match->rspan); 772 | } while (match++ != last); 773 | putchar('\n'); 774 | } 775 | 776 | int main(int argc, char **argv) 777 | { 778 | match_t match[N - M]; 779 | 780 | parse(argc, argv, match, sizeof(match) / sizeof(match[0])); 781 | 782 | printf("\nFound %llu\n", (unsigned long long)crack(match)); 783 | 784 | return 0; 785 | } 786 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | What is php_mt_seed? 2 | 3 | php_mt_seed is a PHP mt_rand() seed cracker. In the most trivial 4 | invocation mode, it finds possible seeds given the very first mt_rand() 5 | output after possible seeding with mt_srand(). With advanced invocation 6 | modes, it is also able to match multiple, non-first, and/or inexact 7 | mt_rand() outputs to possible seed values. 8 | 9 | PHP's mt_rand() algorithm changed over the years since its introduction 10 | in PHP 3.0.6. php_mt_seed 4.0 supports 3 major revisions of the 11 | algorithm: PHP 3.0.7 to 5.2.0, PHP 5.2.1 to 7.0.x, and PHP 7.1.0+ (at 12 | least up to the latest as of this writing, which is PHP 7.2.0beta3). 13 | 14 | php_mt_seed uses attack-optimized reimplementations of PHP's mt_rand() 15 | algorithms. It is written in C with optional SIMD intrinsics (SSE2, 16 | SSE4.1/AVX, XOP, AVX2, AVX-512, as well as MIC) and OpenMP. On a modern 17 | quad-core CPU, it is able to search the full 32-bit seed space in under 18 | a minute. On second generation Xeon Phi, it does the same in 3 seconds. 19 | 20 | 21 | Why crack mt_rand() seeds? 22 | 23 | It is well-known that mt_rand() is a non-cryptographic PRNG and that its 24 | 32-bit seed space would be too small for cryptographic applications. 25 | Yet many PHP applications misuse mt_rand() for purposes where a CSPRNG 26 | would be needed. Thus, a use case of php_mt_seed is to demonstrate to 27 | developers and users of those applications just how very practical it is 28 | to attack mt_rand() and how vulnerable those applications are, so that 29 | the misuses of mt_rand() would decline. Specific opportunities for such 30 | demonstration include source code audits and network/system penetration 31 | tests. In the latter, the cracked seeds may allow the penetration test 32 | to proceed further into the network or system, potentially exposing 33 | other vulnerabilities there may be. Other opportunities to practice 34 | with php_mt_seed include CTFs (capture the flag competitions). 35 | 36 | Common misuses of mt_rand() include generation of anti-CSRF tokens, 37 | custom session tokens (not relying on PHP's builtin sessions support, 38 | which uses a different PRNG yet was also vulnerable until recently), 39 | password reset tokens, passwords, database backup filenames, etc. If 40 | one of these items is exposed and another is generated later without the 41 | web application or server reseeding the PRNG, then an attack is possible 42 | where the seed is cracked from the item generated earlier and is then 43 | used to infer the unknown item generated later. For example, if an 44 | application generates (and necessarily exposes) an anti-CSRF token or a 45 | custom session token and then generates (and sends to the target user's 46 | registered e-mail address) a password reset token, then the latter may 47 | be inferred from the former, resulting in compromise of the target 48 | account (such as an admin account). On web servers that do not 49 | reinitialize PHP (and thus do not reseed the PRNG) across PHP script 50 | invocations, inferring of another user's token or generated password 51 | from the attacker's own token or generated password may be possible as 52 | well, without needing more uses of mt_rand() in the application itself. 53 | 54 | As a curiosity, certain website encrypting ransomware misused mt_rand() 55 | as well, and seed cracking enabled quick recovery of the encryption key. 56 | 57 | 58 | How to build php_mt_seed. 59 | 60 | To build php_mt_seed from source on a system that has GCC and (GNU) make 61 | installed, simply type "make" in its directory. For example, here's 62 | what this looks like on CentOS 7 running on a i7-4770K CPU (supporting 63 | SIMD instruction sets up to AVX2, so that's what php_mt_seed will use): 64 | 65 | $ make 66 | gcc -Wall -march=native -mtune=generic -O2 -fomit-frame-pointer -funroll-loops -fopenmp php_mt_seed.c -o php_mt_seed 67 | php_mt_seed.c:47:2: warning: #warning AVX-512 not enabled. Try gcc -mavx512f (on Intel Knights Landing, Skylake-X, or some newer). [-Wcpp] 68 | #warning AVX-512 not enabled. Try gcc -mavx512f (on Intel Knights Landing, Skylake-X, or some newer). 69 | ^ 70 | 71 | (The warning tells us that a more advanced SIMD instruction set is not 72 | enabled in the build, in this case because the CPU actually lacks it. 73 | These warnings are safe to ignore, but they're sometimes useful in case 74 | non-default compiler flags are used and a SIMD instruction set would be 75 | left disabled inadvertently.) 76 | 77 | 78 | How to use php_mt_seed. 79 | 80 | php_mt_seed should be run from the command line, with command-line 81 | arguments given to it according to the syntax described below. 82 | 83 | Usage of php_mt_seed can be trivial or complex, depending on use case 84 | details. Here's a trivial usage example: 85 | 86 | First generate a "random" number using PHP, e.g. with: 87 | 88 | $ php5 -r 'mt_srand(1234567890); echo mt_rand(), "\n";' 89 | 1328851649 90 | 91 | Then run the cracker (in this example, on the same system as we used for 92 | the build above): 93 | 94 | $ time ./php_mt_seed 1328851649 95 | Pattern: EXACT 96 | Version: 3.0.7 to 5.2.0 97 | Found 0, trying 0xfc000000 - 0xffffffff, speed 16261.0 Mseeds/s 98 | Version: 5.2.1+ 99 | Found 0, trying 0x1e000000 - 0x1fffffff, speed 91.8 Mseeds/s 100 | seed = 0x1fd65f9a = 534142874 (PHP 7.1.0+) 101 | Found 1, trying 0x26000000 - 0x27ffffff, speed 91.9 Mseeds/s 102 | seed = 0x273a3517 = 658126103 (PHP 5.2.1 to 7.0.x; HHVM) 103 | Found 2, trying 0x48000000 - 0x49ffffff, speed 91.9 Mseeds/s 104 | seed = 0x499602d2 = 1234567890 (PHP 5.2.1 to 7.0.x; HHVM) 105 | seed = 0x499602d2 = 1234567890 (PHP 7.1.0+) 106 | Found 4, trying 0xfe000000 - 0xffffffff, speed 91.9 Mseeds/s 107 | Found 4 108 | 109 | real 0m47.028s 110 | user 6m15.211s 111 | sys 0m0.015s 112 | 113 | php_mt_seed first searches for seeds for the legacy PHP 3.0.7 to 5.2.0, 114 | which it typically completes in a fraction of a second. Then it 115 | proceeds to search for seeds for PHP 5.2.1 to 7.0.x and for PHP 7.1.0+ 116 | simultaneously, which takes a while. 117 | 118 | In 47 seconds, it found the original seed (which in this specific case 119 | happens to produce this same mt_rand() output both with PHP 5.2.1 to 120 | 7.0.x and with PHP 7.1.0+ due to similarities in their algorithms) and 121 | two other seeds that also produce the same mt_rand output (albeit one 122 | only with PHP 5.2.1 to 7.0.x and the other only with PHP 7.1.0+), and it 123 | searched the rest of the 32-bit seed space (not finding other matches). 124 | 125 | For reference, on a 16-core server with two E5-2670 v1 CPUs (supporting 126 | AVX, but not yet AVX2) the same trivial attack completes in 18 seconds, 127 | on Xeon Phi 5110P in 8 seconds, and on Xeon Phi 7290 in 3 seconds. 128 | 129 | You'll find a complex usage example further below. 130 | 131 | 132 | Command-line syntax. 133 | 134 | php_mt_seed expects 1, 2, 4, or more numbers on its command line. The 135 | numbers specify constraints on mt_rand() outputs. 136 | 137 | When invoked with only 1 number, that's the first mt_rand() output to 138 | find seeds for. 139 | 140 | When invoked with 2 numbers, those are the bounds (minimum and maximum, 141 | in that order) that the first mt_rand() output should fall within. 142 | 143 | When invoked with 4 numbers, the first 2 give the bounds for the first 144 | mt_rand() output and the second 2 give the range passed into mt_rand(). 145 | 146 | When invoked with 5 or more numbers, each group of 4 and then the last 147 | group of 1, 2, or (usually) 4 are processed as above, where each group 148 | refers to a corresponding mt_rand() output. 149 | 150 | Although the syntax above technically requires specification of ranges 151 | when matching multiple mt_rand() outputs, it is also possible to match 152 | exact outputs and/or outputs from mt_rand() without a range specified by 153 | listing the value to match twice (same minimum and maximum) and/or by 154 | listing the range "passed into" mt_rand() as "0 2147483647". The latter 155 | is assumed to be equivalent to mt_rand() called without a range. For 156 | example, this matches first mt_rand() output of 1328851649 followed by 157 | second mt_rand() output of 1423851145: 158 | 159 | $ time ./php_mt_seed 1328851649 1328851649 0 2147483647 1423851145 160 | Pattern: EXACT EXACT 161 | Version: 3.0.7 to 5.2.0 162 | Found 0, trying 0xfc000000 - 0xffffffff, speed 15658.7 Mseeds/s 163 | Version: 5.2.1+ 164 | Found 0, trying 0x48000000 - 0x49ffffff, speed 91.9 Mseeds/s 165 | seed = 0x499602d2 = 1234567890 (PHP 5.2.1 to 7.0.x; HHVM) 166 | Found 1, trying 0xfe000000 - 0xffffffff, speed 91.9 Mseeds/s 167 | Found 1 168 | 169 | real 0m47.035s 170 | user 6m15.273s 171 | sys 0m0.004s 172 | 173 | This is on the same machine as above. The additional constraint (on the 174 | second mt_rand() output) caused no slowdown, but removed extra seeds 175 | from the output. 176 | 177 | It is possible to have php_mt_seed skip (ignore) some mt_rand() outputs 178 | by listing for them 4 numbers that would match any output value. By 179 | convention, this is typically done by listing "0 0 0 0", which literally 180 | means "the output must be from 0 to 0 as returned by mt_rand(0, 0)", a 181 | condition that is always true. This is illustrated further below. 182 | 183 | 184 | Complex usage example. 185 | 186 | Here's a script embedding the vulnerable password generation function 187 | from old versions of Drupal: 188 | 189 | 222 | 223 | Given a password generated by that function, let's try to predict what 224 | password it'd generate next assuming no PRNG seed reset inbetween. 225 | First generate a bunch of passwords for an unknown seed (we let PHP seed 226 | the PRNG automatically, as non-ancient versions do): 227 | 228 | $ php drupal.php 229 | pAiwtk6Yed 230 | HW9UPrqKWC 231 | 57CN74bkzL 232 | 233 | Let's pretend to know only the first password. We need to convert it to 234 | inputs to php_mt_seed, which we may do with this script: 235 | 236 | 246 | 247 | We don't actually need to look at its output ourselves, but for the sake 248 | of illustration here it is: 249 | 250 | $ php pw2args.php pAiwtk6Yed 251 | 14 14 0 56 25 25 0 56 8 8 0 56 21 21 0 56 18 18 0 56 10 10 0 56 53 53 0 56 47 47 0 56 4 4 0 56 3 3 0 56 252 | 253 | What we actually need is to pass that output to php_mt_seed: 254 | 255 | $ time ./php_mt_seed `php pw2args.php pAiwtk6Yed` 256 | Pattern: EXACT-FROM-57 EXACT-FROM-57 EXACT-FROM-57 EXACT-FROM-57 EXACT-FROM-57 EXACT-FROM-57 EXACT-FROM-57 EXACT-FROM-57 EXACT-FROM-57 EXACT-FROM-57 257 | Version: 3.0.7 to 5.2.0 258 | Found 0, trying 0xfc000000 - 0xffffffff, speed 4404.0 Mseeds/s 259 | Version: 5.2.1+ 260 | Found 0, trying 0xa0000000 - 0xa1ffffff, speed 210.0 Mseeds/s 261 | seed = 0xa1872e34 = 2709990964 (PHP 5.2.1 to 7.0.x; HHVM) 262 | Found 1, trying 0xfe000000 - 0xffffffff, speed 210.1 Mseeds/s 263 | Found 1 264 | 265 | real 0m21.441s 266 | user 11m21.957s 267 | sys 0m0.034s 268 | 269 | This is 21 seconds on the 2x E5-2670 v1 server mentioned above, on 270 | which the trivial attack would run in 18 seconds. There's some 271 | performance cost from the more advanced constraints on mt_rand() outputs 272 | (and especially from not rejecting unsuitable outputs as quickly as we 273 | would when searching for an exact output value), but in this case it's 274 | not very high. 275 | 276 | Now let's recompute this and further passwords from the cracked seed: 277 | 278 | $ php drupal.php 2709990964 279 | pAiwtk6Yed 280 | HW9UPrqKWC 281 | 57CN74bkzL 282 | 283 | Here we are: it's the same three passwords we had above, out of which we 284 | used only the first one to infer the remaining two. 285 | 286 | For even more real-world complexity, what if someone else had already 287 | generated a password using the same instance of PHP (e.g., same mod_php 288 | child process), and the password we know is the second one after 289 | (automatic) seeding? Let's convert that one to php_mt_seed outputs as 290 | well, and let's also tell php_mt_seed to skip 10 mt_rand() outputs (as 291 | the application would have spent them on the first generated password, 292 | presumably unknown to us) before attempting a match: 293 | 294 | $ time ./php_mt_seed 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 `php pw2args.php HW9UPrqKWC` 295 | Pattern: SKIP SKIP SKIP SKIP SKIP SKIP SKIP SKIP SKIP SKIP EXACT-FROM-57 EXACT-FROM-57 EXACT-FROM-57 EXACT-FROM-57 EXACT-FROM-57 EXACT-FROM-57 EXACT-FROM-57 EXACT-FROM-57 EXACT-FROM-57 EXACT-FROM-57 296 | Version: 3.0.7 to 5.2.0 297 | Found 0, trying 0xfc000000 - 0xffffffff, speed 1457.9 Mseeds/s 298 | Version: 5.2.1+ 299 | Found 0, trying 0xa0000000 - 0xa1ffffff, speed 128.9 Mseeds/s 300 | seed = 0xa1872e34 = 2709990964 (PHP 5.2.1 to 7.0.x; HHVM) 301 | Found 1, trying 0xfe000000 - 0xffffffff, speed 128.9 Mseeds/s 302 | Found 1 303 | 304 | real 0m36.288s 305 | user 19m14.502s 306 | sys 0m0.031s 307 | 308 | We found the same seed as above, and would be able to infer the same 309 | passwords from it just like we did above (both the preceding and the 310 | next password relative to the only one we pretended to know), but now it 311 | took 36 seconds, which is double the time the trivial attack used to 312 | take. There's much room for optimization here for future versions of 313 | php_mt_seed (and in fact the older php_mt_seed 3.4 would process this 314 | much faster due to it lacking support for PHP 7.1.0+). Meanwhile, high 315 | and especially unknown SKIP counts are best avoided, which typically can 316 | be done through forcing the web server to allocate a fresh instance of 317 | PHP by setting up many connections (so that more instances of PHP would 318 | be created than the server had running previously) or by crashing an 319 | instance (so that it'd be restarted) via one of many non-security bugs 320 | or resource exhaustion in PHP (but first make sure you're authorized to 321 | do things like that). 322 | 323 | The above attack might not have worked against old versions of Drupal 324 | as-is. There could be reseeding and/or other uses of mt_rand() getting 325 | in the way. It is just an illustration of how to approach applying 326 | mt_rand() seed cracking in a real-world'ish scenario. 327 | 328 | 329 | When extra tools or php_mt_seed changes are needed. 330 | 331 | Sometimes applications post-process mt_rand() outputs in ways very 332 | different from what was illustrated above. It isn't always practical to 333 | write and use tiny scripts like we did above to reverse those tokens, 334 | generated passwords, etc. to mt_rand() output constraints that can be 335 | passed to php_mt_seed. 336 | 337 | In simpler ones of those other cases, a pre-existing extra tool can be 338 | used. For example, if a PHP application exposes md5(mt_rand()) as a 339 | token, then a password hash cracker such as John the Ripper -jumbo or 340 | Hashcat can be used to crack the MD5 hash, retrieving the mt_rand() 341 | output value that can be passed to php_mt_seed. For example: 342 | 343 | $ php -r 'echo md5(mt_rand()), "\n";' | tee hashfile 344 | a67d0e9f38d578eefb1720d611211a26 345 | $ time ./john --format=raw-md5 --incremental=digits --max-length=10 --fork=32 hashfile 2>/dev/null 346 | Loaded 1 password hash (Raw-MD5 [MD5 128/128 AVX 4x3]) 347 | 1871584565 (?) 348 | 349 | real 0m40.922s 350 | user 6m41.117s 351 | sys 0m1.739s 352 | $ time ./php_mt_seed 1871584565 353 | Pattern: EXACT 354 | Version: 3.0.7 to 5.2.0 355 | Found 0, trying 0x48000000 - 0x4bffffff, speed 24159.2 Mseeds/s 356 | seed = 0x4be01ac0 = 1272978112 (PHP 3.0.7 to 5.2.0) 357 | seed = 0x4be01ac1 = 1272978113 (PHP 3.0.7 to 5.2.0) 358 | Found 2, trying 0x5c000000 - 0x5fffffff, speed 25725.1 Mseeds/s 359 | seed = 0x5fe49e4e = 1608818254 (PHP 3.0.7 to 5.2.0) 360 | seed = 0x5fe49e4f = 1608818255 (PHP 3.0.7 to 5.2.0) 361 | Found 4, trying 0xfc000000 - 0xffffffff, speed 28185.7 Mseeds/s 362 | Version: 5.2.1+ 363 | Found 4, trying 0x86000000 - 0x87ffffff, speed 234.4 Mseeds/s 364 | seed = 0x86d2e002 = 2261966850 (PHP 7.1.0+) 365 | Found 5, trying 0xc2000000 - 0xc3ffffff, speed 234.5 Mseeds/s 366 | seed = 0xc24768d7 = 3259459799 (PHP 5.2.1 to 7.0.x; HHVM) 367 | seed = 0xc24768d7 = 3259459799 (PHP 7.1.0+) 368 | Found 7, trying 0xc6000000 - 0xc7ffffff, speed 234.4 Mseeds/s 369 | seed = 0xc6d8b812 = 3336091666 (PHP 5.2.1 to 7.0.x; HHVM) 370 | seed = 0xc6d8b812 = 3336091666 (PHP 7.1.0+) 371 | Found 9, trying 0xfe000000 - 0xffffffff, speed 234.5 Mseeds/s 372 | Found 9 373 | 374 | real 0m18.478s 375 | user 9m48.751s 376 | sys 0m0.015s 377 | $ php -r 'mt_srand(3259459799); echo md5(mt_rand()), "\n";' 378 | a67d0e9f38d578eefb1720d611211a26 379 | $ php -r 'mt_srand(3336091666); echo md5(mt_rand()), "\n";' 380 | a67d0e9f38d578eefb1720d611211a26 381 | 382 | We found two seeds that generate our observed md5(mt_rand()) token (and 383 | some more that would do it with other versions of PHP). While both are 384 | correct given what we knew (assuming that we know the PHP version), in a 385 | real-world scenario only one of those would likely allow us to correctly 386 | infer prior and predict further mt_rand() outputs. That's good enough. 387 | 388 | The invocation of JtR is sub-optimal in that it'd search all strings of 389 | up to 10 digits rather than numbers that fit in 31 bits and do not start 390 | with a 0 (except for the number 0). This can be partially corrected by 391 | splitting the invocation in two: 392 | 393 | $ time ./john --format=raw-md5 --incremental=digits --max-length=9 --fork=32 hashfile 2>/dev/null 394 | Loaded 1 password hash (Raw-MD5 [MD5 128/128 AVX 4x3]) 395 | 396 | real 0m4.540s 397 | user 0m43.320s 398 | sys 0m1.762s 399 | $ time ./john --format=raw-md5 --mask='[12]?d?d?d?d?d?d?d?d?d' --fork=32 hashfile 2>/dev/null 400 | Loaded 1 password hash (Raw-MD5 [MD5 128/128 AVX 4x3]) 401 | 1871584565 (?) 402 | 403 | real 0m4.092s 404 | user 1m58.155s 405 | sys 0m1.609s 406 | 407 | As a slightly trickier example, old eZ Publish used: 408 | 409 | $time = time(); 410 | $userID = $user->id(); 411 | $hashKey = md5( $userID . ':' . $time . ':' . mt_rand() ); 412 | 413 | yet this can be cracked similarly, by obtaining the timestamp from the 414 | server itself (such as from the HTTP headers) or assuming synchronized 415 | time and by knowing or cracking the target user ID as well. The known 416 | portions of the information may be specified in a JtR or Hashcat mask 417 | as-is (e.g., as --mask='100:1503415769:[12]?d?d?d?d?d?d?d?d?d' in the 418 | second invocation of JtR above) and then mt_rand() output extracted from 419 | the cracked "password" and passed into php_mt_seed. 420 | 421 | As a harder to handle example, old MediaWiki used: 422 | 423 | function generateToken( $salt = '' ) { 424 | $token = dechex( mt_rand() ) . dechex( mt_rand() ); 425 | return md5( $token . $salt ); 426 | } 427 | 428 | Two mt_rand() outputs at once are unlikely to be quickly cracked by a 429 | program not aware of mt_rand() specifics. This is a case where we'd 430 | need to modify php_mt_seed internals - specifically, introduce recording 431 | of two mt_rand() outputs in php_mt_seed's diff() function and have it 432 | compute MD5 and compare the result against our token value. Then we'd 433 | invoke php_mt_seed with dummy command-line arguments, but not exactly 434 | arbitrary ones: e.g., "0 1" is non-trivial enough for php_mt_seed to 435 | always call diff() and thus let our added code take over the comparison. 436 | 437 | Cracking seeds from old MediaWiki tokens as above is readily supported 438 | as an example exploit in Snowflake, an alternative to php_mt_seed. 439 | However, in general either php_mt_seed or Snowflake would need custom 440 | code written for new cases like this. 441 | 442 | 443 | Xeon Phi specifics. 444 | 445 | To build php_mt_seed for first generation Xeon Phi (Knights Corner), 446 | install Intel's C compiler and run "make mic". To run php_mt_seed on 447 | Xeon Phi, copy Intel C compiler's OpenMP runtime library (such as 448 | libiomp5.so) to the Xeon Phi card, e.g. using scp. Then SSH in to the 449 | card and run a command like: 450 | 451 | $ LD_LIBRARY_PATH=. time ./php_mt_seed 1328851649 452 | Pattern: EXACT 453 | Version: 3.0.7 to 5.2.0 454 | Found 0, trying 0xe0000000 - 0xffffffff, speed 8947.8 Mseeds/s 455 | Version: 5.2.1+ 456 | Found 0, trying 0x10000000 - 0x1fffffff, speed 583.6 Mseeds/s 457 | seed = 0x1fd65f9a = 534142874 (PHP 7.1.0+) 458 | Found 1, trying 0x20000000 - 0x2fffffff, speed 583.6 Mseeds/s 459 | seed = 0x273a3517 = 658126103 (PHP 5.2.1 to 7.0.x; HHVM) 460 | Found 2, trying 0x40000000 - 0x4fffffff, speed 586.7 Mseeds/s 461 | seed = 0x499602d2 = 1234567890 (PHP 5.2.1 to 7.0.x; HHVM) 462 | seed = 0x499602d2 = 1234567890 (PHP 7.1.0+) 463 | Found 4, trying 0xf0000000 - 0xffffffff, speed 586.1 Mseeds/s 464 | Found 4 465 | real 0m 7.82s 466 | user 30m 17.88s 467 | sys 0m 1.78s 468 | 469 | This is on a Xeon Phi 5110P. 470 | 471 | Advanced invocation modes work too, but the performance impact of the 472 | non-vectorized portions of code is higher than it is on regular CPUs: 473 | 474 | $ LD_LIBRARY_PATH=. time ./php_mt_seed 14 14 0 56 25 25 0 56 8 8 0 56 21 21 0 56 18 18 0 56 10 10 0 56 53 53 0 56 47 47 0 56 4 4 0 56 3 3 0 56 475 | Pattern: EXACT-FROM-57 EXACT-FROM-57 EXACT-FROM-57 EXACT-FROM-57 EXACT-FROM-57 EXACT-FROM-57 EXACT-FROM-57 EXACT-FROM-57 EXACT-FROM-57 EXACT-FROM-57 476 | Version: 3.0.7 to 5.2.0 477 | Found 0, trying 0xe0000000 - 0xffffffff, speed 1824.3 Mseeds/s 478 | Version: 5.2.1+ 479 | Found 0, trying 0xa0000000 - 0xafffffff, speed 247.0 Mseeds/s 480 | seed = 0xa1872e34 = 2709990964 (PHP 5.2.1 to 7.0.x; HHVM) 481 | Found 1, trying 0xf0000000 - 0xffffffff, speed 246.9 Mseeds/s 482 | Found 1 483 | real 0m 19.78s 484 | user 1h 17m 56s 485 | sys 0m 9.54s 486 | 487 | Building and running on second generation Xeon Phi (Knights Landing) and 488 | later doesn't necessarily require a special make target since those 489 | support AVX-512 (which recent gcc supports too) and doesn't necessarily 490 | require any runtime library tricks (perhaps not when Xeon Phi is the 491 | host CPU rather than a coprocessor). Also, the performance impact of 492 | the non-vectorized portions of code is lower than on first generation. 493 | 494 | 495 | PHP version curiosities (mostly unimportant). 496 | 497 | While php_mt_seed supports 3 major revisions of PHP's mt_rand() 498 | algorithm and that sort of covers PHP 3.0.7 through 7.1.0+ (up to the 499 | latest as of this writing and probably beyond), the reality is somewhat 500 | trickier than that. From older versions to newer: 501 | 502 | As a mere historical curiosity, php_mt_seed is in fact able to crack 503 | seeds of PHP 3.0.6, which is the very first version that introduced 504 | mt_rand(), but only as long as no range was passed into mt_rand(). That 505 | version had broken support for ranges, and indeed there's no point in 506 | supporting that short-lived breakage in php_mt_seed now. With this 507 | detail, php_mt_seed has some support for all mt_rand() capable versions 508 | of PHP released so far. 509 | 510 | Then there's PHP 3.0.7 through 5.2.0, where Mersenne Twister's state 511 | initialization is with multiples of 69069. This enables our stateless 512 | implementation to quickly jump to the state array element needed to 513 | compute the first mt_rand() output by using a precomputed value for 514 | 69069 raised to the power 396 (mod 2**32), which is MT's M-1. Another 515 | curiosity of those versions, which we take advantage of too, is that 516 | they treat adjacent even and odd seeds the same, so the effective seed 517 | space is 31-bit. 518 | 519 | PHP 3.0.6 to 4.1.2 used a default seed of 4357 (and thus also 4356) if 520 | mt_srand() was not called. PHP 4.2.0 changed that to automatic seeding 521 | using system time and PHP process ID (still predictable and now also 522 | leaky, but no longer a constant), but there was "Bug #25007 rand & 523 | mt_rand seed RNG every call" until 4.3.3, which presumably affected how 524 | cracked seeds could (not) be used. 525 | 526 | PHP 5.2.1 changed MT state initialization to MT authors' new recommended 527 | algorithm, which is no longer linear so we have to compute the first 397 528 | state elements (out of 624) even though in the simplest case we only 529 | need (and only store) the first and last one of those (or we could use a 530 | time-memory trade-off, which we currently don't). 531 | 532 | PHP 5.2.1 also introduced a bug into its implementation of MT (use of a 533 | wrong variable, whereas pre-5.2.1 code was correct in that respect). 534 | This bug lets us skip a few operations for every other seed, which we 535 | do, although this optimization is so minor that we could as well not 536 | bother. PHP 7.1.0 fixed this bug (reverting to pre-5.2.1 code in that 537 | respect, so we use the same logic for pre-5.2.1 and 7.1.0+ there). 538 | 539 | In PHP versions from 3.0.7 to 7.0.x, if mt_rand() was called with its 540 | optional output range specified, a 31-bit (0 to 2147483647) MT PRNG 541 | output was scaled to that range using floating-point math. This meant 542 | that if a range wider than 31-bit was requested on a 64-bit build of 543 | PHP, some values would never occur. This also meant that even for most 544 | ranges smaller than 31-bit a bias was introduced (some output values 545 | became more likely than others), as compared to MT's raw output (which 546 | was relatively unbiased). 547 | 548 | PHP 7.1.0 tried to fix those biases by dropping the floating-point math 549 | and instead mapping the raw 32-bit MT PRNG outputs to the target range 550 | using integer modulo division. To avoid inherent bias when the target 551 | range isn't a whole power of 2 of possible integer values, a loop was 552 | introduced to skip raw 32-bit PRNG outputs (until a suitable one is 553 | seen) that would result in such bias. A bug in that code was found and 554 | reported due to work on php_mt_seed. As it turned out, the loop only 555 | works right in 32-bit builds of PHP, and is ineffective on 64-bit 556 | (except with 64-bit ranges, see below). Luckily, this actually makes 557 | things simpler for php_mt_seed, and currently php_mt_seed fully supports 558 | the behavior of 64-bit builds only (for ranges up to 0 to 2147483646). 559 | 560 | There's currently no intent to add to php_mt_seed the complication of 561 | bias-avoidance of 32-bit builds of PHP 7.1.0+, as well as of 64-bit 562 | builds of future versions where the bug will presumably get fixed. What 563 | this means in practice is that for 32-bit builds of PHP and future 564 | versions of PHP, php_mt_seed may occasionally find wrong and miss 565 | correct seeds for mt_rand() invoked with a range, but the probability of 566 | this happening is very low except for very wide ranges that are not a 567 | whole power of 2 of possible integer values. For example, mt_rand(0, 568 | 61) or mt_rand(111, 222) are very unlikely to trigger the problem, 569 | mt_rand(0, 255) can't trigger the problem, whereas mt_rand(1000000000, 570 | 2000000000) is somewhat likely to trigger it. Such likely problematic 571 | ranges are probably rarely used and are of little relevance to uses of 572 | php_mt_seed. Also, supporting this buggy vs. correct behavior would 573 | require treating 32- and 64-bit builds of PHP separately and reporting 574 | on them differently. 575 | 576 | PHP 7.1.0 also tried to introduce proper support for 64-bit ranges in 577 | 64-bit builds. It generates two raw 32-bit PRNG outputs to derive one 578 | mt_rand() output when the target range spans more than a 32-bit space. 579 | Unfortunately, the implementation is buggy in a way where it'd introduce 580 | biases into such mt_rand() outputs. The bug will presumably get fixed 581 | as well, but regardless there's currently no intent to support wider 582 | than 31-bit ranges in php_mt_seed. This is obscure functionality 583 | (arguably, originally an accidental misfeature, which the PHP developers 584 | didn't really have to make official) that is only available on 64-bit 585 | builds of PHP. Currently, php_mt_seed does not allow specifying larger 586 | than 31-bit integers on its command line (it will report an error when a 587 | larger value is specified). 588 | 589 | Prior to PHP 7.1.0, mt_rand(0, 2147483647) was equivalent to mt_rand() 590 | without a range, and php_mt_seed still assumes so. This assumption is 591 | no longer valid for PHP 7.1.0+, which means that when searching for 592 | seeds for PHP 7.1.0+ for mt_rand() called with a range specified, you 593 | can specify at most a range one smaller than that, thus "0 2147483646" 594 | being the maximum that php_mt_seed supports for those versions. This 595 | minor limitation shouldn't matter in practice, except that you might 596 | need to be aware you can continue to specify a range of "0 2147483647" 597 | to indicate that no range was passed into mt_rand(). 598 | 599 | PHP 7.1.0 also aliased rand() to mt_rand() and srand() to mt_srand(). 600 | This means that on one hand you can use php_mt_seed to crack rand() 601 | seeds for PHP 7.1.0+ (since those are also mt_rand() seeds), but on the 602 | other hand this cross-seeding and cross-consumption of random numbers 603 | can affect which attacks work or don't work, and exactly how, against 604 | specific applications that make use of both sets of PHP functions. 605 | 606 | PHP 7.1.0 also introduced MT_RAND_PHP as optional second parameter to 607 | mt_srand(). When specified, it correctly enables behavior identical to 608 | that of PHP versions 5.2.1 to 7.0.x. Thus, seeds that php_mt_seed 609 | reports as valid for 5.2.1 to 7.0.x are always also valid for 7.1.0+ 610 | with MT_RAND_PHP, and conversely seeds that php_mt_seed reports as valid 611 | for 7.1.0+ are often invalid for 7.1.0+ with MT_RAND_PHP (except when 612 | the same seeds are also valid for 5.2.1 to 7.0.x, which is common). 613 | 614 | 615 | Contact info. 616 | 617 | Please check the php_mt_seed homepage for new versions: 618 | 619 | http://www.openwall.com/php_mt_seed/ 620 | 621 | If you have anything valuable to add or a non-trivial question to ask, 622 | you may contact the author of php_mt_seed at: 623 | 624 | Solar Designer 625 | --------------------------------------------------------------------------------