Nguyễn Trọng Nhân
    • Create new note
    • Create a note from template
      • Sharing URL Link copied
      • /edit
      • View mode
        • Edit mode
        • View mode
        • Book mode
        • Slide mode
        Edit mode View mode Book mode Slide mode
      • Customize slides
      • Note Permission
      • Read
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Write
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Engagement control Commenting, Suggest edit, Emoji Reply
    • Invite by email
      Invitee

      This note has no invitees

    • Publish Note

      Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

      Your note will be visible on your profile and discoverable by anyone.
      Your note is now live.
      This note is visible on your profile and discoverable online.
      Everyone on the web can find and read all notes of this public team.
      See published notes
      Unpublish note
      Please check the box to agree to the Community Guidelines.
      View profile
    • Commenting
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
      • Everyone
    • Suggest edit
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
    • Emoji Reply
    • Enable
    • Versions and GitHub Sync
    • Note settings
    • Note Insights New
    • Engagement control
    • Make a copy
    • Transfer ownership
    • Delete this note
    • Save as template
    • Insert from template
    • Import from
      • Dropbox
      • Google Drive
      • Gist
      • Clipboard
    • Export to
      • Dropbox
      • Google Drive
      • Gist
    • Download
      • Markdown
      • HTML
      • Raw HTML
Menu Note settings Note Insights Versions and GitHub Sync Sharing URL Create Help
Create Create new note Create a note from template
Menu
Options
Engagement control Make a copy Transfer ownership Delete this note
Import from
Dropbox Google Drive Gist Clipboard
Export to
Dropbox Google Drive Gist
Download
Markdown HTML Raw HTML
Back
Sharing URL Link copied
/edit
View mode
  • Edit mode
  • View mode
  • Book mode
  • Slide mode
Edit mode View mode Book mode Slide mode
Customize slides
Note Permission
Read
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Write
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Engagement control Commenting, Suggest edit, Emoji Reply
  • Invite by email
    Invitee

    This note has no invitees

  • Publish Note

    Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

    Your note will be visible on your profile and discoverable by anyone.
    Your note is now live.
    This note is visible on your profile and discoverable online.
    Everyone on the web can find and read all notes of this public team.
    See published notes
    Unpublish note
    Please check the box to agree to the Community Guidelines.
    View profile
    Engagement control
    Commenting
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    • Everyone
    Suggest edit
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    Emoji Reply
    Enable
    Import from Dropbox Google Drive Gist Clipboard
       Owned this note    Owned this note      
    Published Linked with GitHub
    • Any changes
      Be notified of any changes
    • Mention me
      Be notified of mention me
    • Unsubscribe
    # Breaking Random > Nhan_laptop | > -- > Có bao giờ bạn nghĩ mình sẽ đoán được số sẽ được xổ vào ngày mai không ???, hay đơn giản chỉ là đoán được số sẽ được random từ một thư viện ._. ???? > Blog này sẽ giới thiệu bạn một số cách để rack random trong một vài thư viện > Implement: https://github.com/Nhan-Laptop/Break-random > ## Mở Đầu Chúng ta sẽ bắt đầu với hàm `random` trong Python trước. Python ngẫu nhiên sử dụng [Mersenne Twister](https://en.wikipedia.org/wiki/Mersenne_Twister). Nếu như có đủ 624 output 32-bit ta sẽ giải quyết được vấn đề!!??? ### Mersenne Twister > main resource: > + https://rbtree.blog/posts/2021-05-18-breaking-python-random-module/ Mersenne Twister có 624 số nguyên 32 bit dưới dạng trạng thái và mỗi lần nó xuất ra một số nguyên 32 bit dưới dạng đầu ra và thay đổi trạng thái. [Here](https://github.com/python/cpython/blob/23362f8c301f72bbf261b56e1af93e8c52f5b6cf/Modules/_randommodule.c) miêu tả chi tiết cách hoạt động của MT. Nếu có 624 đầu ra 32‑bit đầy đủ, bạn có thể khôi phục toàn bộ trạng thái → dự đoán hoàn hảo các số tiếp theo. Nếu chỉ có đầu ra bị cắt bớt bit (ví dụ getrandbits(30)), vẫn có thể khôi phục gần như toàn bộ bằng cách mô hình hóa tuyến tính trên GF(2); đôi khi sẽ còn một vài bit “tự do” không thể suy ra từ các quan sát đó. ```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; } ``` remarks: - `self->index` được tăng thêm 1 mỗi khi nó được gọi. Nếu nó lớn hơn, nó sẽ thay đổi trạng thái hiện tại sang trạng thái tiếp theo: `self->index >= N` - `mt[self->index++]` để xuất ra nó. - Quá trình thay đổi trạng thái: Mỗi `kk` tương ứng lấy `y = (mt[kk] & UPPER_MASK) | (mt[(kk + 1) % N] & LOWER_MASK)mt[kk] = mt[(kk + M) % N] ^ (y >> 1) ^ mag01[y & 0x1U]` - Ta thấy y bị biến đổi khá là nhiều. ```c! Temper: y = mt[self->index++]; y ^= (y >> 11); y ^= (y << 7) & 0x9d2c5680U; y ^= (y << 15) & 0xefc60000U; y ^= (y >> 18); ``` Nhưng bản chất vẫn chỉ là các phép biến đổi `Temper`, nếu ta recover được state từ bước này thì hiển nhiên ta sẽ đoán được phía sau mà thôi. ### Khôi phục từ đầu ra đầy đủ 32 bit ```c! // https://rbtree.blog/posts/2021-05-18-breaking-python-random-module/ TemperingMaskB = 0x9d2c5680 TemperingMaskC = 0xefc60000 def untemper(y): y = undoTemperShiftL(y) y = undoTemperShiftT(y) y = undoTemperShiftS(y) y = undoTemperShiftU(y) return y def undoTemperShiftL(y): last14 = y >> 18 final = y ^ last14 return final def undoTemperShiftT(y): first17 = y << 15 final = y ^ (first17 & TemperingMaskC) return final def undoTemperShiftS(y): a = y << 7 b = y ^ (a & TemperingMaskB) c = b << 7 d = y ^ (c & TemperingMaskB) e = d << 7 f = y ^ (e & TemperingMaskB) g = f << 7 h = y ^ (g & TemperingMaskB) i = h << 7 final = y ^ (i & TemperingMaskB) return final def undoTemperShiftU(y): a = y >> 11 b = y ^ a c = b >> 11 final = y ^ c return final ``` Temper là chuỗi phép dịch bit và XOR (có thêm AND với mặt nạ ở bước trái). Các phép này có thể đảo (un‑temper) từng bước nếu làm theo đúng thứ tự và hướng bit phù hợp. Khi đã “un‑temper” đủ 624 giá trị 32‑bit liên tiếp, ta thu lại đúng 624 phần tử trạng thái (với index=0), rồi nạp lại vào Python qua random.setstate(...) bạn có thể kiểm chứng: ```py! import random # Lấy nhiều đầu ra 32-bit đầy đủ outs = [random.getrandbits(32) for _ in range(1000)] # Dựng lại state từ 624 đầu ra đầu tiên state_words = [untemper(v) for v in outs[:624]] restored = (3, tuple(state_words + [0]), None) random.setstate(restored) for i in range(1000): assert outs[i] == random.getrandbits(32) ``` Vấn đề về thu thập đủ 32 bit gần như đã được hoàn thành Vậy nếu các bit đầu ra không đầu đủ như điều kiện thì sao???? ### Khôi phục từ đầu ra một phần. Như đã đề cập ở phần cuối về bit, ví dụ: `python.getrandbits(30)` Với`k<=32`, Python chỉ trả về k bit cao của giá trị sau temper. Vì vậy, nếu gọi `getrandbits(30)`, 2 bit thấp nhất của mỗi giá trị 32‑bit không được quan sát. ```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; } ``` Dù thu thập rất nhiều đầu ra (hàng nghìn lần gọi), sẽ có trường hợp một (vài) bit của trạng thái không ảnh hưởng đến bất kỳ bit quan sát được nào ⇒ bit “tự do” không thể suy ra. ```c! #define MATRIX_A 0x9908b0dfU /* constant vector a */ #define UPPER_MASK 0x80000000U /* most significant w-r bits */ #define LOWER_MASK 0x7fffffffU /* least significant r bits */ ``` MT19937 và temper là tuyến tính theo XOR (trên GF(2)). Ta có thể theo dõi lan truyền của từng bit trạng thái bằng cách thay vì giữ số nguyên, ta giữ một vector cơ sở 19968‑bit (biểu diễn bằng bitmask Python int), sau đó áp dụng đúng các phép twist và temper nhưng ở dạng tuyến tính trên bitmask. Mỗi bit đầu ra quan sát được tạo thành một phương trình tuyến tính với 19968 ẩn (các bit trạng thái). Gom đủ phương trình ⇒ giải bằng khử Gauss (bitset) trên GF(2). ### Implementation Twisted.py: ```py! ITW = 32 N = 624 M = 397 A = 0x9908B0DF UPPER = 0x80000000 LOWER = 0x7FFFFFFF class Twister: """Theo dõi ảnh hưởng của từng bit trạng thái dưới dạng bitmask.""" def __init__(self): self.state = [ [(1 << (32*i + (31-b))) for b in range(32)] for i in range(N) ] self.idx = 0 @staticmethod def _xor(a, b): return [x ^ y for x, y in zip(a, b)] @staticmethod def _and_mask(bits, mask): return [bm if ((mask >> (31 - i)) & 1) else 0 for i, bm in enumerate(bits)] def _twist_once(self): mt = self.state if self.idx >= N: for k in range(N): k1 = (k + 1) % N km = (k + M) % N y = [ (mt[k][0] & UPPER) | (mt[k1][0] & LOWER) ] + mt[k][1:] y_sh = [0] + y[:-1] mag = [0]*32 if (y[-1] & 1): mag = self._and_mask([y_sh[0]] + y_sh[1:], A) mt[k] = self._xor(mt[km], self._xor(y_sh, mag)) self.idx = 0 def _temper_bits(self, bits): y = bits[:] y = [ y[i] ^ (y[i-11] if i>=11 else 0) for i in range(32) ] t = [ (y[i+7] if i<=24 else 0) for i in range(32) ] t = self._and_mask(t, MASK_B) y = [ y[i] ^ t[i] for i in range(32) ] t = [ (y[i+15] if i<=16 else 0) for i in range(32) ] t = self._and_mask(t, MASK_C) y = [ y[i] ^ t[i] for i in range(32) ] y = [ y[i] ^ (y[i-18] if i>=18 else 0) for i in range(32) ] return y def gen_word_masks(self): self._twist_once() masks = self._temper_bits(self.state[self.idx]) self.idx += 1 return masks def getrandbits_masks(self, k): if k <= 32: masks = self.gen_word_masks() return masks[:k] bits = [] while k > 0: masks = self.gen_word_masks() take = min(32, k) if take < 32: masks = masks[:take] bits = masks[:take] + bits k -= take return bits ``` Solver(): ```py! class Solver: def __init__(self): self.eq = [] self.bs = [] def add(self, mask: int, bit: int): m, b = mask, bit & 1 i = 0 while i < len(self.eq) and m: lsb = self.eq[i] & -self.eq[i] if m & lsb: m ^= self.eq[i] b ^= self.bs[i] i += 1 if m: self.eq.append(m) self.bs.append(b) def solve_any(self) -> int: x = 0 for m, b in zip(self.eq, self.bs): if b: x ^= (m & -m) return x ``` Kết quả: với khoảng 1247 lần gọi `getrandbits(30)`, hệ phương trình đủ để phục hồi 19967/19968 bit trạng thái — tức thiếu đúng 1 bit (một biến tự do). Việc đặt biến tự do đó là 0 (hay 1) không ảnh hưởng đến chuỗi bit đã quan sát (và một đoạn đáng kể sau đó), nên bạn vẫn đồng bộ được với bộ sinh của đối phương và dự đoán tiếp. Việc này đã được tác giả trong bài viết chỉnh sửa bằng: ```! Chúng tôi đã dành rất nhiều thời gian để xác minh phần này, nhưng cuối cùng, chúng tôi có thể kết luận rằng phần được đề cập thực sự không thể được tìm thấy. Điều này có nghĩa là một bit cụ thể ở trạng thái ban đầu chỉ ảnh hưởng đến một vị trí cụ thể trong đầu ra 32 bit. Do đó, tôi phải bỏ qua biến tự do và viết lại nó bằng mã giải quyết nó. ``` ```py! 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: 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 import random num = 1247 bit = 30 twister = Twister() outputs = [ random.getrandbits(bit) for _ in range(num) ] equations = [ twister.getrandbits(bit) for _ in range(num) ] solver = Solver() for i in range(num): for j in range(bit): print(i, j) solver.insert(equations[i][j], (outputs[i] >> (bit - 1 - j)) & 1) state = solver.solve() recovered_state = (3, tuple(state + [0]), None) random.setstate(recovered_state) for i in range(num): assert outputs[i] == random.getrandbits(bit) ``` Vậy nếu với trường hợp `bit truncated` thì phải làm sao, đây là một `câu hỏi nhỏ trong đầu`: https://github.com/icemonster/symbolic_mersenne_cracker/blob/main/README.md với repo này thì ta có thể solve được trường hợp ở trên. !!! Ngoài ra còn 1 số cách recover lại state thông qua [Z3 (instructions)](https://hackmd.io/tA80GD-7R4GcmJTPmMPCag), [gf2bv](https://hackmd.io/@ADP/SJ4ceI3pkx?stext=7722%3A2686%3A0%3A1755526394%3AYPXZJJ): đây là Z3: ```py! from z3 import * class Break_rand: def __init__(self): self.output = [] def untemper(self,output): y1 = BitVec('y1', 32) y2 = BitVec('y2', 32) y3 = BitVec('y3', 32) y4 = BitVec('y4', 32) y = BitVecVal(output, 32) s = Solver() equations = [ y2 == y1 ^ (LShR(y1, 11)), y3 == y2 ^ ((y2 << 7) & 0x9D2C5680), y4 == y3 ^ ((y3 << 15) & 0xEFC60000), y == y4 ^ (LShR(y4, 18)) ] s.add(equations) s.check() return s.model()[y1].as_long() def recover_state_mt(self): assert len(self.output)>= 624, "Can not recover state!" state = tuple(self.untemper(n)& 0xffffffff for n in self.output[:624]) return (3, state + (624,), None) def submit(self,n): self.output.append(n) """ Test function """ from random import * rng = Random() tmp = [] breakrand = Break_rand() for i in range(624): breakrand.submit(rng.getrandbits(32)) Ran = Random() Ran.setstate(breakrand.recover_state_mt()) assert Ran.getrandbits(32)==rng.getrandbits(32) ``` ## js `Math.random()` ```js! // https://soon.haari.me/import-random/ uint64_t state0, state1; // the 128-bit state // update state and produce a double double MathRandom(void) { uint64_t s0 = state1; // notice the swap uint64_t s1 = state0; s1 = s1 ^ (s1 << 23); s1 = s1 ^ (s1 >> 17) ^ (s0 >> 26) ^ s0; state0 = s0; state1 = s1; if (Firefox || Webkit) // Firefox v47.0, Webkit 2019-07 thru 2020-02 return (double)( (s0+s1) & (((uint64_t)1<<53)-1) ) / ((uint64_t)1<<53); else // ChromX V8, 2019-01 thru 2020-02 return (double)( s0 >> 12 ) / ((uint64_t)1<<52); } ``` Nhìn chung thì với `Math.random()` các biểu thức tạo nên số giả ngẫu nhiên - PRNG có đôi phần đơn giản hơn MT19937, mình có thể hoàn toàn dễ dàng đảo ngược được dựa trên: [Xorshift128](https://en.wikipedia.org/wiki/Xorshift). Nhưng với trình duyệt `(Firefox || Webkit)`, có thêm phần cộng `s0+s1` trên kiểu `uint64_t`, điều này không dựa trên bit, nên khó đảo ngược. Nhưng với các trường hợp còn lại - `v8` thì ta có thể đảo ngược lại quá trình này: `(double) s0 >> 12` - làm mất đi 12 bits LSB. Không hiểu sao mình lại stuck 2 tiếng :))) ## c/c++ `rand()` > main resource: > + https://www.mscs.dal.ca/~selinger/random/ ![image](https://hackmd.io/_uploads/H1CO-_bFxx.png) ```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); } } ``` remarks: - Cách phép toán biến đổi tuyến tính trên `214783647 = 2^31` - `r[0]` chính là seed, vì seed chỉ có `32-bit`, nên ta có thể hoàn toàn bruteforce được. - Phép Nhân với `16897LL` có dấu được thực hiện nhầm mục đính cho kết quả đủ lớn để không xảy ra tràn số trước khi module. Hoàn thành xong các bước khởi tạo: ```c! for (i=34; i<344; i++) { r[i] = r[i-31] + r[i-3]; } ``` chạy `Addive large Fibonacci`: $$ r[i] ≡ r[i-1] + r[i-31] \mod 2^{32} $$ - Giá trị trả về là 31 bit-dương: $out_i = (r[i]>>1)$ - lấy `31 bit` cao bỏ đi `LSB` ### Phá vỡ tuyến tính: Ta cần ít nhất 63 output liên tiếp để recover lại trạng thái ban đầu: `r[i-3] --- r[i-31]` phương trình 2 ẩn ( ý mình là 2 ẩn này hông liên quan, nhưng thật chất thì có thể 32 phương trình là được ). Nhưng ta có được hẳn: `999 - 344` trạng thái `31 bit MSB` ~ dễ dàng cho việc recover. Chiến thuật là: - Ta cần recover lại được các bit `LSB`. - Sau đó là lùi về trạng thái ban đầu. ### Phase - Khôi phục LSB: Đầu tiên ta nên liệt kê ra các trạng thái - ở dạng đơn giản với 2 bit mỗi loại: ```py! i-3 i-31 i 00 00 00 00 01 01 <------- 00 10 10 00 11 11 <------- 01 00 01 <------- 01 01 10 01 10 11 <------- 01 11 00 10 00 10 10 01 11 <------- 10 10 00 10 11 01 <------- 11 00 11 <------- 11 01 00 11 10 01 <------- 11 11 00 ``` Mình đánh dấu lại trạng thái `LSB` bằng 1 của i. Tiếp theo là tìm điểm khác biệt: ```python! i-3 i-31 i 00 00 00 == 00 01 01 00 01 01 == 00 00 00 <------- 00 10 10 == 00 11 11 00 11 11 == 00 10 10 <------- 01 00 01 == 00 00 00 <------- 01 01 10 01 10 11 == 00 10 10 <------- 01 11 00 10 00 10 == 10 01 11 10 01 11 == 10 00 10 <------- 10 10 00 == 10 11 01 10 11 01 == 10 10 00 <------- 11 00 11 == 10 00 10 <------- 11 01 00 11 10 01 == 10 10 00 <------- 11 11 00 == 11 10 01 ``` :))) nó hum ra gì để có thể xác minh `LSB = 1 ` Nhưng đổi lại ta có được vài trạng thái `LSB = 0`: ```py! i-3 i-31 i 01 01 10 01 11 00 11 01 00 ``` Quy tắc loại của mình là: ta tập trung vào MSB của các số ví dụ, nếu có loại trùng thì ta loại ra khỏi danh sách, và cuối cùng được 3 mẫu. Sau khi mình tìm ra được 1 khoảng các số thỏa mản rồi thì bây giờ: ```py! """ i-3 i-31 i 01 01 10 01 11 00 11 01 00 """ while index -31 >= 344 : if fre[index] == 0: x = output[ index - 3 ] y = output[ index - 31 ] z = output[ index ] xbit = bin(x)[-1] ybit = bin(y)[-1] zbit = bin(z)[-1] if ( xbit == '0' and ybit == '0' and zbit =='1' and fre[index]==0 and fre[index-3]==0 and fre[index - 31]==0 ): if fre[index]==0: output[index] = (output[index] * 2) % 2**32 fre[index]=1 if fre[index-3] ==0 : output[index-3] = (output[index - 3]* 2 + 1) %2**32 fre[index-3] = 1 if fre[index-31] ==0 : output[index-31]= (output[index - 31]* 2 + 1) % 2**32 fre[index-31]= 1 fre[index] = 1 assert output[index ] == tmp[index] assert output[index-3 ] == tmp[index-3] assert output[index-31] == tmp[index-31] if ( xbit == '0' and ybit == '1' and zbit =='0' and fre[index]==0 and fre[index-3]==0 and fre[index - 31]==0 ): if fre[index]==0: output[index] = (output[index] * 2) % 2**32 fre[index]=1 if fre[index-3] ==0 : output[index-3] = (output[index - 3]* 2 + 1) %2**32 fre[index-3] = 1 if fre[index-31] ==0 : output[index-31]= (output[index - 31]* 2 + 1) % 2**32 fre[index-31]= 1 fre[index] = 1 assert output[index ] == tmp[index] assert output[index-3 ] == tmp[index-3] assert output[index-31] == tmp[index-31] if ( xbit == '1' and ybit == '0' and zbit =='0' and fre[index]==0 and fre[index-3]==0 and fre[index - 31]==0 ): if fre[index] == 0: output[index] = (output[index] * 2) % 2**32 fre[index]=1 if fre[index-3] ==0 : output[index-3] = (output[index - 3]* 2 + 1) %2**32 fre[index-3] = 1 if fre[index-31] ==0 : output[index-31]= (output[index - 31]* 2 + 1) % 2**32 fre[index-31]= 1 fre[index] = 1 assert output[index ] == tmp[index] assert output[index-3 ] == tmp[index-3] assert output[index-31] == tmp[index-31] index -= 1 check = 1 while check: check = 0 for i in range(344,MAX): if fre[ i - 31 ] and fre[ i - 3 ]==0 and fre[i] : fre[i - 3]= 1 check = 1 output[i-3] = (output[i]-output[i - 31]) % 2**32 assert output[i-3]==tmp[i-3] if fre[ i - 31 ]==0 and fre[ i - 3 ] and fre[i] : fre[i - 31]= 1 check = 1 output[i-31] = (output[i]-output[i - 3]) % 2**32 assert output[i - 31]==tmp[i-31] if fre[ i - 31 ] and fre[ i - 3 ] and fre[i] == 0 : fre[i] = 1 check = 1 output[i] = (output[i - 3] + output[i - 31]) % 2**32 assert output[i] == tmp[i] ``` Hmmmmm.... tới đây thì nó đủ rồi.... ### Phase 2: khôi phục seed Hmmmm, phần này thì bạn có thể làm nhiều cách: - Khử Gau - Sagemath.... mình không làm ra phần này - Z3 - mình làm. ```py! from z3 import * x = [BitVec(f'x_{i}',32) for i in range(31)] u = x for i in range(31,34): x.append(x[i-31]) for i in range(34,344): x.append(x[i-31] + x[i-3]) s = Solver() for i in range(344,344+80): x.append( x[i-31] + x[i-3]) s.add(x[i] == int(output[i]) ) assert s.check() == sat m = s.model() print(m[u[0]].as_long()) ``` - Remarks: Các bạn sẽ khó hiểu làm sao mà mình liệt kê các trạng thái `bit-sample` rồi recover được `LSB`, mình làm trên phép toán cộng bình thường -> nên chỗ này sẽ khó hiểu. Nhưng bản chất thì $\mod 2^{32}$: là $-$ hoặc $+$ $k*2^{32}$. Mà $2^{32}$ = `100000000000000000000000000000000` -> nên suy ra các phép LSB nó được bảo toàn -> từ đây ta chỉ viết làm như phân tích trên. ## golang `math/rand` `Rand.go`: `https://cs.opensource.google/go/go/+/refs/tags/go1.24.2:src/math/rand/rand.go` Hmmm golang opensource code - Mình đọc `rand.go - API` một hồi thì thấy là: Đầu tiên ta nên đọc từ hàm rand trong rand.go: ```go! // Int31n returns, as an int32, a non-negative pseudo-random number in the half-open interval [0,n). // It panics if n <= 0. func (r *Rand) Int31n(n int32) int32 { if n <= 0 { panic("invalid argument to Int31n") } if n&(n-1) == 0 { // n is power of two, can mask return r.Int31() & (n - 1) } max := int32((1 << 31) - 1 - (1<<31)%uint32(n)) v := r.Int31() for v > max { v = r.Int31() } return v % n } ``` Sau đó ta truy về hàm `Int31()`: ```go! // Int31 returns a non-negative pseudo-random 31-bit integer as an int32. func (r *Rand) Int31() int32 { return int32(r.Int63() >> 32) } ``` Thấy nó lấy `r.Int63()>>32` tức là nó sinh ra số `64 bit` rồi nó dịch còn` 32 bit ` ```go! // Int63 returns a non-negative pseudo-random 63-bit integer as an int64. func (r *Rand) Int63() int64 { return r.src.Int63() } ``` ```go! // A Rand is a source of random numbers. type Rand struct { src Source s64 Source64 // non-nil if src is source64 // readVal contains remainder of 63-bit integer used for bytes // generation during most recent Read call. // It is saved so next Read call can start where the previous // one finished. readVal int64 // readPos indicates the number of low-order bytes of readVal // that are still valid. readPos int8 } ``` Thấy nó gọi Source ```go! // A Source represents a source of uniformly-distributed // pseudo-random int64 values in the range [0, 1<<63). // // A Source is not safe for concurrent use by multiple goroutines. type Source interface { Int63() int64 Seed(seed int64) } ``` Truy một hồi ta thấy được cái `Int63()`. ```go! // NewSource returns a new pseudo-random [Source] seeded with the given value. // Unlike the default [Source] used by top-level functions, this source is not // safe for concurrent use by multiple goroutines. // The returned [Source] implements [Source64]. func NewSource(seed int64) Source { return newSource(seed) } func newSource(seed int64) *rngSource { var rng rngSource rng.Seed(seed) return &rng } ``` Nhìn vào khúc này tiếp theo ta hiểu `Source` implement bằng cách gọi `rng.go` để lấy seed và hiểu thêm được toàn bộ `Source` gọi hàm từ `rng.go` để tạo ra thuật tạo `PRNG`. rng.go: `https://cs.opensource.google/go/go/+/refs/tags/go1.24.2:src/math/rand/rng.go` ```go! type rngSource struct { tap int // index into vec feed int // index into vec vec [rngLen]int64 // current feedback register } // seed rng x[n+1] = 48271 * x[n] mod (2**31 - 1) func seedrand(x int32) int32 { const ( A = 48271 Q = 44488 R = 3399 ) hi := x / Q lo := x % Q x = A*lo - R*hi if x < 0 { x += int32max } return x } // Seed uses the provided seed value to initialize the generator to a deterministic state. 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 } } } // Int63 returns a non-negative pseudo-random 63-bit integer as an int64. func (rng *rngSource) Int63() int64 { return int64(rng.Uint64() & rngMask) } // Uint64 returns a non-negative pseudo-random 64-bit integer as a uint64. func (rng *rngSource) Uint64() uint64 { rng.tap-- if rng.tap < 0 { rng.tap += rngLen } rng.feed-- if rng.feed < 0 { rng.feed += rngLen } x := rng.vec[rng.feed] + rng.vec[rng.tap] rng.vec[rng.feed] = x return uint64(x) } ``` Rồi đây là các hàm chính của `rng.go` Bây giờ ta phân tích 2 hàm tạo seed: ```go! func seedrand(x int32) int32 { const ( A = 48271 Q = 44488 R = 3399 ) hi := x / Q lo := x % Q x = A*lo - R*hi if x < 0 { x += int32max } return x } // Seed uses the provided seed value to initialize the generator to a deterministic state. 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 } } } ``` seed: `seed = seed % int32max` là số `32 - bits`, ta có thể bruteforce. Nhưng các phép biến đổi seed tuyến tính nên ta có thể truy ngược về được. Ta sẽ sympolic bằng z3 rồi giải. Như flow nãy giờ ta phân tích ta sẽ symbolic toàn bộ những thứ tạo nên seed để in ra, rồi giải = Z3. ## Bash `$RANDOM` > main resource: > + https://github.com/bminor/bash/blob/master/lib/sh/random.c ```c! /* Returns a 32-bit pseudo-random number. */ static u_bits32_t intrand32 (u_bits32_t last) { /* Minimal Standard generator from "Random number generators: good ones are hard to find", Park and Miller, Communications of the ACM, vol. 31, no. 10, October 1988, p. 1195. Filtered through FreeBSD. x(n+1) = 16807 * x(n) mod (m) where 16807 == 7^5. We split up the calculations to avoid overflow. h = last / q; l = last % q; t = a * l - h * r m = 2147483647, a = 16807, q = 127773, r = 2836 There are lots of other combinations of constants to use; look at https://www.gnu.org/software/gsl/manual/html_node/Other-random-number-generators.html#Other-random-number-generators */ bits32_t h, l, t; u_bits32_t ret; /* Can't seed with 0. */ ret = (last == 0) ? 123459876 : last; h = ret / 127773; l = ret % 127773; t = 16807 * l - 2836 * h; ret = (t < 0) ? t + 0x7fffffff : t; return (ret); } ``` Hmmm cái phần này :)))) quen quen, hơi giống bước khởi tạo của Crand. ```c! genseed (void) { struct timeval tv; u_bits32_t iv; gettimeofday (&tv, NULL); iv = (uintptr_t)seedrand; /* let the compiler truncate */ iv = tv.tv_sec ^ tv.tv_usec ^ getpid () ^ getppid () ^ current_user.uid ^ iv; return (iv); } ``` Seed được dựa trên thời gian để tạo. ```c! #define BASH_RAND_MAX 32767 /* 0x7fff - 16 bits */ /* Returns a pseudo-random number between 0 and 32767. */ int brand (void) { unsigned int ret; rseed = intrand32 (rseed); if (shell_compatibility_level > 50) ret = (rseed >> 16) ^ (rseed & 65535); else ret = rseed; return (ret & BASH_RAND_MAX); } ``` Hàm chính để cho ra giá trị random khi gọi `$RANDOM`. Vẫn tuyến tính không gì. ```c! /* Set the random number generator seed to SEED. */ void sbrand (unsigned long seed) { rseed = seed; last_random_value = 0; } void seedrand (void) { u_bits32_t iv; iv = genseed (); sbrand (iv); } ``` Nó chỉ là tạo lại seed thôi. ```c! #define BASH_RAND32_MAX 0x7fffffff /* 32 bits */ /* Returns a 32-bit pseudo-random number between 0 and 4294967295. */ static u_bits32_t brand32 (void) { u_bits32_t ret; rseed32 = intrand32 (rseed32); return (rseed32 & BASH_RAND32_MAX); } ``` Tóm lại là ta chỉ thấy nó chỉ là các phép biến đổi tuyển tính nếu ta có từ 2-3 output thì sẽ khôi phục được. ## References: [0] https://github.com/tvdat20004?tab=repositories [1] https://hackmd.io/@ADP/SJ4ceI3pkx?stext=7722%3A2686%3A0%3A1755526394%3AYPXZJJ [2] https://github.com/TACIXAT/XorShift128Plus/blob/master/README [3] https://github.com/PwnFunction/v8-randomness-predictor/blob/main/main.py [4] https://github.com/maple3142/gf2bv [5] https://github.com/anneouyang/MT19937/blob/master/code/implement_mt19937.py [6] https://giapppp.notion.site/Crack-random-23e6a3e68e0a80a4ba35dc2e4f7f3bb9 [7] https://cs.opensource.google/go/go/+/refs/tags/go1.24.2:src/math/rand/rand.go [8] https://github.com/bminor/bash/blob/master/lib/sh/random.c#L150 Trong lúc mình làm thì mình có đọc khá nhiều tài liệu nhưng mà quên mất đi link:>>>>

    Import from clipboard

    Paste your markdown or webpage here...

    Advanced permission required

    Your current role can only read. Ask the system administrator to acquire write and comment permission.

    This team is disabled

    Sorry, this team is disabled. You can't edit this note.

    This note is locked

    Sorry, only owner can edit this note.

    Reach the limit

    Sorry, you've reached the max length this note can be.
    Please reduce the content or divide it to more notes, thank you!

    Import from Gist

    Import from Snippet

    or

    Export to Snippet

    Are you sure?

    Do you really want to delete this note?
    All users will lose their connection.

    Create a note from template

    Create a note from template

    Oops...
    This template has been removed or transferred.
    Upgrade
    All
    • All
    • Team
    No template.

    Create a template

    Upgrade

    Delete template

    Do you really want to delete this template?
    Turn this template into a regular note and keep its content, versions, and comments.

    This page need refresh

    You have an incompatible client version.
    Refresh to update.
    New version available!
    See releases notes here
    Refresh to enjoy new features.
    Your user state has changed.
    Refresh to load new user state.

    Sign in

    Forgot password

    or

    By clicking below, you agree to our terms of service.

    Sign in via Facebook Sign in via Twitter Sign in via GitHub Sign in via Dropbox Sign in with Wallet
    Wallet ( )
    Connect another wallet

    New to HackMD? Sign up

    Help

    • English
    • 中文
    • Français
    • Deutsch
    • 日本語
    • Español
    • Català
    • Ελληνικά
    • Português
    • italiano
    • Türkçe
    • Русский
    • Nederlands
    • hrvatski jezik
    • język polski
    • Українська
    • हिन्दी
    • svenska
    • Esperanto
    • dansk

    Documents

    Help & Tutorial

    How to use Book mode

    Slide Example

    API Docs

    Edit in VSCode

    Install browser extension

    Contacts

    Feedback

    Discord

    Send us email

    Resources

    Releases

    Pricing

    Blog

    Policy

    Terms

    Privacy

    Cheatsheet

    Syntax Example Reference
    # Header Header 基本排版
    - Unordered List
    • Unordered List
    1. Ordered List
    1. Ordered List
    - [ ] Todo List
    • Todo List
    > Blockquote
    Blockquote
    **Bold font** Bold font
    *Italics font* Italics font
    ~~Strikethrough~~ Strikethrough
    19^th^ 19th
    H~2~O H2O
    ++Inserted text++ Inserted text
    ==Marked text== Marked text
    [link text](https:// "title") Link
    ![image alt](https:// "title") Image
    `Code` Code 在筆記中貼入程式碼
    ```javascript
    var i = 0;
    ```
    var i = 0;
    :smile: :smile: Emoji list
    {%youtube youtube_id %} Externals
    $L^aT_eX$ LaTeX
    :::info
    This is a alert area.
    :::

    This is a alert area.

    Versions and GitHub Sync
    Get Full History Access

    • Edit version name
    • Delete

    revision author avatar     named on  

    More Less

    Note content is identical to the latest version.
    Compare
      Choose a version
      No search result
      Version not found
    Sign in to link this note to GitHub
    Learn more
    This note is not linked with GitHub
     

    Feedback

    Submission failed, please try again

    Thanks for your support.

    On a scale of 0-10, how likely is it that you would recommend HackMD to your friends, family or business associates?

    Please give us some advice and help us improve HackMD.

     

    Thanks for your feedback

    Remove version name

    Do you want to remove this version name and description?

    Transfer ownership

    Transfer to
      Warning: is a public team. If you transfer note to this team, everyone on the web can find and read this note.

        Link with GitHub

        Please authorize HackMD on GitHub
        • Please sign in to GitHub and install the HackMD app on your GitHub repo.
        • HackMD links with GitHub through a GitHub App. You can choose which repo to install our App.
        Learn more  Sign in to GitHub

        Push the note to GitHub Push to GitHub Pull a file from GitHub

          Authorize again
         

        Choose which file to push to

        Select repo
        Refresh Authorize more repos
        Select branch
        Select file
        Select branch
        Choose version(s) to push
        • Save a new version and push
        • Choose from existing versions
        Include title and tags
        Available push count

        Pull from GitHub

         
        File from GitHub
        File from HackMD

        GitHub Link Settings

        File linked

        Linked by
        File path
        Last synced branch
        Available push count

        Danger Zone

        Unlink
        You will no longer receive notification when GitHub file changes after unlink.

        Syncing

        Push failed

        Push successfully