# Crack coding language’s default random
https://github.com/DPhuoc/Crack-coding-language-s-default-random
## python random
### Mersenne Twister Algorithm
Đầu tiên ta cần nhìn qua hàm `genrand_uint32` dưới đây:
```c++
/* Period parameters -- These are all magic. Don't change. */
#define N 624
#define M 397
#define MATRIX_A 0x9908b0dfU /* constant vector a */
#define UPPER_MASK 0x80000000U /* most significant w-r bits */
#define LOWER_MASK 0x7fffffffU /* least significant r bits */
static uint32_t genrand_uint32(RandomObject *self)
{
uint32_t y;
static const uint32_t mag01[2] = {0x0U, MATRIX_A};
/* mag01[x] = x * MATRIX_A for x=0,1 */
uint32_t *mt;
mt = self->state;
if (self->index >= N) { /* generate N words at one time */
int kk;
for (kk=0;kk<N-M;kk++) {
y = (mt[kk]&UPPER_MASK)|(mt[kk+1]&LOWER_MASK);
mt[kk] = mt[kk+M] ^ (y >> 1) ^ mag01[y & 0x1U];
}
for (;kk<N-1;kk++) {
y = (mt[kk]&UPPER_MASK)|(mt[kk+1]&LOWER_MASK);
mt[kk] = mt[kk+(M-N)] ^ (y >> 1) ^ mag01[y & 0x1U];
}
y = (mt[N-1]&UPPER_MASK)|(mt[0]&LOWER_MASK);
mt[N-1] = mt[M-1] ^ (y >> 1) ^ mag01[y & 0x1U];
self->index = 0;
}
y = mt[self->index++];
y ^= (y >> 11);
y ^= (y << 7) & 0x9d2c5680U;
y ^= (y << 15) & 0xefc60000U;
y ^= (y >> 18);
return y;
}
```
Khi chú ý vào đoạn code sau:
```c++
y = mt[self->index++];
y ^= (y >> 11);
y ^= (y << 7) & 0x9d2c5680U;
y ^= (y << 15) & 0xefc60000U;
y ^= (y >> 18);
```
Ta nhận ra rằng đây là một đoạn có thể được reverse lại và khi có đầy đủ 624 states 32 bits thì ta có thể tạo lại được nguyên mảng `mt` và predict được random
```python
def unshiftRight(x, shift):
res = x
for i in range(32):
res = x ^ res >> shift
return res
def unshiftLeft(x, shift, mask):
res = x
for i in range(32):
res = x ^ (res << shift & mask)
return res
def untemper(v):
v = unshiftRight(v, 18)
v = unshiftLeft(v, 15, 0xefc60000)
v = unshiftLeft(v, 7, 0x9d2c5680)
v = unshiftRight(v, 11)
return v
import random
outputs = [ random.getrandbits(32) for _ in range(1000) ]
recovered_state = (3, tuple([ untemper(v) for v in outputs[:624] ] + [0]), None)
random.setstate(recovered_state)
for i in range(1000):
assert outputs[i] == random.getrandbits(32)
```
### Recovering from partial outputs
Tuy nhiên trong một số trường hợp ta lại không có đủ 32 bit của 1 state, vậy thì nên xử lí như thế nào?
Ta thấy hàm `getrandbits` được implement như sau:
```c++
static PyObject * _random_Random_getrandbits_impl(RandomObject *self, int k)
/*[clinic end generated code: output=b402f82a2158887f input=8c0e6396dd176fc0]*/
{
int i, words;
uint32_t r;
uint32_t *wordarray;
PyObject *result;
if (k < 0) {
PyErr_SetString(PyExc_ValueError,
"number of bits must be non-negative");
return NULL;
}
if (k == 0)
return PyLong_FromLong(0);
if (k <= 32) /* Fast path */
return PyLong_FromUnsignedLong(genrand_uint32(self) >> (32 - k));
words = (k - 1) / 32 + 1;
wordarray = (uint32_t *)PyMem_Malloc(words * 4);
if (wordarray == NULL) {
PyErr_NoMemory();
return NULL;
}
/* Fill-out bits of long integer, by 32-bit words, from least significant
to most significant. */
#if PY_LITTLE_ENDIAN
for (i = 0; i < words; i++, k -= 32)
#else
for (i = words - 1; i >= 0; i--, k -= 32)
#endif
{
r = genrand_uint32(self);
if (k < 32)
r >>= (32 - k); /* Drop least significant bits */
wordarray[i] = r;
}
result = _PyLong_FromByteArray((unsigned char *)wordarray, words * 4,
PY_LITTLE_ENDIAN, 0 /* unsigned */);
PyMem_Free(wordarray);
return result;
}
```
Khi ta lấy `getrandbits(k)` $(k < 32)$ thì kết quả ta nhận được sẽ là `genrand_uint32() >> (32 - k)` hay nói cách khác là ta có được $k$ bit đầu.
Nhìn lại đoạn code của hàm `genrand_uint32()` ta có thể rút gọn lại như sau:
```python
N = 624
M = 397
MATRIX_A = 0x9908b0df; mag01 = [0, MATRIX_A]
UPPER_MASK = 0x80000000
LOWER_MASK = 0x7fffffff
if index >= N:
for kk in range(N):
y = (mt[kk] & UPPER_MASK) | (mt[kk + 1] & LOWER_MASK);
mt[kk] = mt[(kk + M) % N] ^ (y >> 1) ^ mag01[y & 1];
index = 0
y = mt[index];
y ^= (y >> 11)
y ^= (y << 7) & 0x9d2c5680
y ^= (y << 15) & 0xefc60000
y ^= (y >> 18)
index += 1
```
Ta thấy toàn bộ các biểu thức tính toán đều được sử dụng phép `and`, `xor` và `shift` nên ta có thể biểu diễn output của y dưới dạng các biến trong GF(2).
Điều này có nghĩa là, khi ta thu thập đủ phương trình ta có thể giải được state ban đầu mà không cần đầy đủ bit của hàm `genrand_uint32()`. Tuy nhiên trong quá trình biến đổi của Mersenne Twister có vài bit nếu chúng ta không có được sẽ không thể tạo ra đủ phương trình để giải.
Sau đây là cách implement của `rbtree`:
https://rbtree.blog/posts/2021-05-18-breaking-python-random-module/
`class Twister` được sử dụng để gen bit mask:
```python
class Twister:
N = 624
M = 397
A = 0x9908b0df
def __init__(self):
self.state = [ [ (1 << (32 * i + (31 - j))) for j in range(32) ] for i in range(624)]
self.index = 0
@staticmethod
def _xor(a, b):
return [x ^ y for x, y in zip(a, b)]
@staticmethod
def _and(a, x):
return [ v if (x >> (31 - i)) & 1 else 0 for i, v in enumerate(a) ]
@staticmethod
def _shiftr(a, x):
return [0] * x + a[:-x]
@staticmethod
def _shiftl(a, x):
return a[x:] + [0] * x
def get32bits(self):
if self.index >= self.N:
for kk in range(self.N):
y = self.state[kk][:1] + self.state[(kk + 1) % self.N][1:]
z = [ y[-1] if (self.A >> (31 - i)) & 1 else 0 for i in range(32) ]
self.state[kk] = self._xor(self.state[(kk + self.M) % self.N], self._shiftr(y, 1))
self.state[kk] = self._xor(self.state[kk], z)
self.index = 0
y = self.state[self.index]
y = self._xor(y, self._shiftr(y, 11))
y = self._xor(y, self._and(self._shiftl(y, 7), 0x9d2c5680))
y = self._xor(y, self._and(self._shiftl(y, 15), 0xefc60000))
y = self._xor(y, self._shiftr(y, 18))
self.index += 1
return y
def getrandbits(self, bit):
return self.get32bits()[:bit]
```
`class Solver` dùng để khử gauss mỗi khi 1 equation mới được thêm vào và tìm ra `state`
```python
class Solver:
def __init__(self):
self.equations = []
self.outputs = []
def insert(self, equation, output):
for eq, o in zip(self.equations, self.outputs):
lsb = eq & -eq
if equation & lsb:
equation ^= eq
output ^= o
if equation == 0:
return
lsb = equation & -equation
for i in range(len(self.equations)):
if self.equations[i] & lsb:
self.equations[i] ^= equation
self.outputs[i] ^= output
self.equations.append(equation)
self.outputs.append(output)
def solve(self):
num = 0
for i, eq in enumerate(self.equations):
if self.outputs[i]:
# Assume every free variable is 0
num |= eq & -eq
state = [ (num >> (32 * i)) & 0xFFFFFFFF for i in range(624) ]
return state
```
Ngoài ra còn có thể sử dụng `z3` hoặc là `gf2bv` để giải phương trình trong GF(2)
```python!
import random
from gf2bv import LinearSystem, BitVec
class MersenneTwister:
def __init__(self, mt, w, n, m, r, a, u, d, s, b, t, c, l):
w1 = (1 << w) - 1
if len(mt) != n or min(r, u, s, t, l) > w and max(a, b, c, d) > w1:
raise ValueError("invalid parameters")
self.mt = list(mt)
self.w = w
self.n = n
self.m = m
self.r = r
self.a = a
self.u = u
self.d = d
self.s = s
self.b = b
self.t = t
self.c = c
self.l = l
self.w1 = w1
self.lmsk = w1 & ((1 << r) - 1)
self.umsk = w1 ^ self.lmsk
self.mti = 624
def twist(self):
for i in range(self.n):
y = (self.mt[i] & self.umsk) ^ (self.mt[(i + 1) % self.n] & self.lmsk)
sel = (
y.broadcast(0, 32) & self.a
if isinstance(y, BitVec)
else (y & 1) * self.a
)
self.mt[i] = self.mt[(i + self.m) % self.n] ^ (y >> 1) ^ sel
def temper(self, y):
y ^= (y >> self.u) & self.d
y ^= (y << self.s) & self.w1 & self.b
y ^= (y << self.t) & self.w1 & self.c
y ^= y >> self.l
return y
def __call__(self):
if self.mti >= self.n:
self.twist()
self.mti = 0
y = self.mt[self.mti]
self.mti += 1
return self.temper(y)
def _getrandbits_word(self, k):
r = self()
if isinstance(r, BitVec):
return r[self.w - k :]
return r >> (self.w - k)
def getrandbits(self, k=None):
"""Uses the CPython's implementation of random.getrandbits()"""
if k is None:
k = self.w
if k < 0:
raise ValueError("number of bits cannot be negative")
if k <= self.w:
return self._getrandbits_word(k)
words = (k - 1) // self.w + 1
x = 0
for i in range(words):
r = self._getrandbits_word(min(k, self.w))
if isinstance(r, BitVec):
x |= r.lshift_ext(self.w * i)
else:
x |= r << (self.w * i)
k -= self.w
return x
bs = 30
samples = 1500
rand = random.Random(1503)
st = tuple(rand.getstate()[1][:-1])
effective_bs = ((bs - 1) & bs) or bs
out = [rand.getrandbits(bs) for _ in range(samples)]
lin = LinearSystem([32] * 624)
mt = lin.gens()
rng = MersenneTwister(mt, 32, 624, 397, 31, 0x9908B0DF, 11, 0xFFFFFFFF, 7, 0x9D2C5680, 15, 0xEFC60000, 18)
zeros = [rng.getrandbits(bs) ^ o for o in out] + [mt[0] ^ 0x80000000]
print("solving...")
sol = lin.solve_one(zeros)
assert sol == st
```
## js `Math.random()`
Đầu tiên ta nhìn vào các hàm sau:
```javascript!
static inline void XorShift128(uint64_t* state0, uint64_t* state1) {
uint64_t s1 = *state0;
uint64_t s0 = *state1;
*state0 = s0;
s1 ^= s1 << 23;
s1 ^= s1 >> 17;
s1 ^= s0;
s1 ^= s0 >> 26;
*state1 = s1;
}
static inline double ToDouble(uint64_t state0) {
// Exponent for double values for [0.0 .. 1.0)
static const uint64_t kExponentBits = uint64_t{0x3FF0000000000000};
uint64_t random = (state0 >> 12) | kExponentBits;
return base::bit_cast<double>(random) - 1;
}
Address MathRandom::RefillCache(Isolate* isolate, Address raw_native_context) {
Tagged<FixedDoubleArray> cache =
Cast<FixedDoubleArray>(native_context->math_random_cache());
// Create random numbers.
for (int i = 0; i < kCacheSize; i++) {
// Generate random numbers using xorshift128+.
base::RandomNumberGenerator::XorShift128(&state.s0, &state.s1);
cache->set(i, base::RandomNumberGenerator::ToDouble(state.s0));
}
pod->set(0, state);
Tagged<Smi> new_index = Smi::FromInt(kCacheSize);
native_context->set_math_random_index(new_index);
return new_index.ptr();
}
```
Khi gọi hàm `Random` chương trình sẽ tạo một mảng `cache` lưu lại `ToDouble(state.s0)` và từ đó lấy ra giá trị theo thứ tự cuối đến đầu mảng mỗi khi người dùng gọi hàm.
Ngoài ra với mỗi `state` có được ta sẽ bị mất 12 LSB do quá trình chuyển sang `double`.
Tương tự như `python random`, `xorshift128` cũng được thực hiện chỉ bằng phép `xor`, `shift` nên cũng có thể biểu diễn output trong GF(2)
## c\c++ `rand()`
```c
#include <stdio.h>
#define MAX 1000
#define seed 1
main() {
int r[MAX];
int i;
r[0] = seed;
for (i = 1; i < 31; i++) {
r[i] = (16807LL * r[i - 1]) % 2147483647;
if (r[i] < 0) {
r[i] += 2147483647;
}
}
for (i = 31; i < 34; i++) {
r[i] = r[i - 31];
}
for (i = 34; i < 344; i++) {
r[i] = r[i - 31] + r[i - 3];
}
for (i = 344; i < MAX; i++) {
r[i] = r[i - 31] + r[i - 3];
printf("%d\n", ((unsigned int)r[i]) >> 1);
}
}
```
Thật sự thì `seed` có thể được bruteforce do chỉ có 32 bits. Nhưng vẫn còn cách khác.
Đầu tiên ta sẽ `recover` lại `r[344]` --> `r[MAX]`. Tuy ta chỉ có `r[i] >> 1` nhưng ra biết rằng `r[i] = r[i - 31] + r[i - 3]` nên nếu `(r[i] >> 1) != (r[i - 31] >> 1) + (r[i - 3] >> 1)` thì có nghĩa là bit cuối của `r[i - 31]` và `r[i - 3]` = 1 và bit cuối của `r[i]` = 0
```python!
def crack(outputs):
states = []
for i in range(len(outputs)):
if (i >= 31 and
outputs[i] != None and
outputs[i - 31] != None and
outputs[i - 3] != None and
outputs[i] != (outputs[i - 31] + outputs[i - 3]) & 0x7fffffff
):
states[i - 31] = (outputs[i - 31] << 1) + 1
states[i - 3] = (outputs[i - 3] << 1) + 1
states.append((outputs[i] << 1) + 0)
else:
states.append(None)
return states
```
Với `r[i] = r[i - 31] + r[i - 3]` ta có thể duyệt lại mảng lần nữa để kiểm tra xem còn điền thêm được `r` nào không
```python!
def self_recover(states):
MOD = 2 ** 32
length = len(states)
while True:
updated = False
for i in range(34, length):
s_i = states[i]
s_31 = states[i - 31]
s_3 = states[i - 3]
if s_i is not None and s_3 is not None and s_31 is None:
states[i - 31] = (s_i - s_3) % MOD
updated = True
elif s_i is not None and s_31 is not None and s_3 is None:
states[i - 3] = (s_i - s_31) % MOD
updated = True
elif s_i is None and s_3 is not None and s_31 is not None:
states[i] = (s_3 + s_31) % MOD
updated = True
if not updated:
break
```
Sau đó ta chỉ cần lần lượt recover lại các state cũ (sẽ có thể có vài state là `None`)
```python!
def recover_seed(outputs):
states = crack(outputs)
init = [None] * 344
for i in range(343, 2, -1):
s31 = states[i + 31 - 344] if i + 31 >= 344 else init[i + 31]
s28 = states[i + 28 - 344] if i + 28 >= 344 else init[i + 28]
if s31 is not None and s28 is not None:
init[i] = (s31 - s28) % 2**32
for i in range(3):
init[i] = init[i + 31]
base_idx = next((i for i in range(31) if init[i] is not None), None)
if base_idx is not None:
for i in range(31):
if init[i] is None:
init[i] = (
pow(16807, i - base_idx, 2147483647) *
init[base_idx] % 2147483647
)
all_states = init + states
self_recover(all_states)
print(all_states[:20])
assert all(s is None or s < 2147483647 for s in all_states[1:31]), "Invalid states"
return all_states[0] if all_states[0] is not None else None
```
Đến đây có thể xài Z3 để tìm lại seed ban đầu
## golang `math/rand`
Để ý hàm seed của `rng.go`
```go!
func (rng *rngSource) Seed(seed int64) {
rng.tap = 0
rng.feed = rngLen - rngTap
seed = seed % int32max
if seed < 0 {
seed += int32max
}
if seed == 0 {
seed = 89482311
}
x := int32(seed)
for i := -20; i < rngLen; i++ {
x = seedrand(x)
if i >= 0 {
var u int64
u = int64(x) << 40
x = seedrand(x)
u ^= int64(x) << 20
x = seedrand(x)
u ^= int64(x)
u ^= rngCooked[i]
rng.vec[i] = u
}
}
}
```
Ta thấy seed chỉ là **int32** nên có thể bruteforce dễ dàng.
Ngoài ra sau đây là cách implement sử dụng z3 mà không cần bruteforce seed.
```python!
from rand import new_source, new
from rng import RngSource, RNG_LEN
from tqdm import trange
from z3 import *
s = Solver()
NUM_TEST = 700
vec = [BitVec(f'vec_{i}', 64) for i in range(RNG_LEN)]
rng = RngSource(vec)
psudo_rand = new(rng)
test = [psudo_rand.uint64() for _ in range(NUM_TEST)]
print(vec[0])
src = new_source(123843)
hehe = list(src.vec)
rand = new(src)
real = [rand.uint64() for _ in range(NUM_TEST)]
for i in trange(NUM_TEST):
s.add(real[i] == test[i])
print("solve......")
if s.check() == sat:
model = s.model()
array_values = [model.evaluate(vec[i]).as_long() for i in range(RNG_LEN)]
rng = RngSource(array_values)
print(f"{hehe[:10] = }")
print(f"{rng.vec[:10] = }")
test_rand = new(rng)
test = [test_rand.uint64() for _ in range(NUM_TEST)]
for i in range(20):
print(test_rand.uint64(), rand.uint64())
```
Các file cần thiết sẽ ở trong github của mình
## bash `$RANDOM`
```python!
def next_seed(self) -> int:
if self.seed == 0:
self.seed = 123459876
h = self.seed // 127773
l = self.seed - (127773 * h)
t = 16807 * l - 2836 * h
self.seed = (t + 0x7fffffff) if t < 0 else t
return self.seed
```
Tiếp tục là bruteforce seed :v
## Tài liệu tham khảo
1. https://soon.haari.me/import-random/
2. https://rbtree.blog/posts/2021-05-18-breaking-python-random-module/
3. https://github.com/maple3142/gf2bv
4. https://www.mscs.dal.ca/~selinger/random/
5. https://cs.opensource.google/go/go/+/refs/tags/go1.24.2:src/math/rand/rng.go