├── README.md
├── main.py
└── requirements.txt
/README.md:
--------------------------------------------------------------------------------
1 | # v8-randomness-predictor
2 |
3 | Using [z3](https://github.com/Z3Prover/z3) to predict `Math.random` in [v8](https://v8.dev)
4 |
5 | ## YouTube video
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 | Watch the [✨ YouTube Video](https://www.youtube.com/watch?v=-h_rj2-HP2E)
14 |
15 | ## Run Instructions
16 |
17 | Get a few Random numbers from v8, run to following code in [d8](https://v8.dev/docs/d8), [nodejs](https://nodejs.org/) or [chrome](https://www.google.com/chrome/).
18 |
19 | ```js
20 | Array.from(Array(5), Math.random)
21 | ```
22 |
23 | Optionally you can set the random seed in nodejs so you'd get the same numbers as shown below.
24 | ```js
25 | /*
26 | * Run nodejs with `--random_seed` flag like
27 | * node --random_seed=1337
28 | */
29 | Array.from(Array(5), Math.random)
30 | // [0.9311600617849973, 0.3551442693830502, 0.7923158995678377, 0.787777942408997, 0.376372264303491]
31 | ```
32 |
33 | Next we feed these random numbers into the python script (line 23).
34 |
35 | ```py
36 | sequence = [
37 | 0.9311600617849973,
38 | 0.3551442693830502,
39 | 0.7923158995678377,
40 | 0.787777942408997,
41 | 0.376372264303491,
42 | ][::-1]
43 | ```
44 |
45 | Run the script.
46 |
47 | ```sh
48 | $ python3 main.py
49 |
50 | # Outputs
51 | # {'se_state1': 6942842836049070467, 'se_state0': 4268050313212552111}
52 | # 0.23137147109312428
53 | ```
54 |
55 | ## Resources
56 | - [Learn z3 by solving simple challenges](https://github.com/PwnFunction/learn-z3)
57 | - [There’s Math.random(), and then there’s Math.random()](https://v8.dev/blog/math-random)
58 | - [Further scramblings of Marsaglia’s xorshift generators](https://vigna.di.unimi.it/ftp/papers/xorshiftplus.pdf)
59 | - [(V8 Deep Dives) Random Thoughts on Math.random()](https://apechkurov.medium.com/v8-deep-dives-random-thoughts-on-math-random-fb155075e9e5)
60 | - [Hacking the JavaScript Lottery](https://blog.securityevaluators.com/hacking-the-javascript-lottery-80cc437e3b7f)
61 |
--------------------------------------------------------------------------------
/main.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python3
2 | import z3
3 | import struct
4 | import sys
5 |
6 |
7 | """
8 | Solving for seed states in XorShift128+ used in V8
9 | > https://v8.dev/blog/math-random
10 | > https://apechkurov.medium.com/v8-deep-dives-random-thoughts-on-math-random-fb155075e9e5
11 | > https://blog.securityevaluators.com/hacking-the-javascript-lottery-80cc437e3b7f
12 |
13 | > Tested on Chrome(102.0.5005.61) or Nodejs(v18.2.0.)
14 | """
15 |
16 | """
17 | Plug in a handful random number sequences from node/chrome
18 | > Array.from(Array(5), Math.random)
19 |
20 | (Optional) In node, we can specify the seed
21 | > node --random_seed=1337
22 | """
23 | sequence = [
24 | 0.9311600617849973,
25 | 0.3551442693830502,
26 | 0.7923158995678377,
27 | 0.787777942408997,
28 | 0.376372264303491,
29 | # 0.23137147109312428
30 | ]
31 |
32 | """
33 | Random numbers generated from xorshift128+ is used to fill an internal entropy pool of size 64
34 | > https://github.com/v8/v8/blob/7a4a6cc6a85650ee91344d0dbd2c53a8fa8dce04/src/numbers/math-random.cc#L35
35 |
36 | Numbers are popped out in LIFO(Last-In First-Out) manner, hence the numbers presented from the entropy pool are reveresed.
37 | """
38 | sequence = sequence[::-1]
39 |
40 | solver = z3.Solver()
41 |
42 | """
43 | Create 64 bit states, BitVec (uint64_t)
44 | > static inline void XorShift128(uint64_t* state0, uint64_t* state1);
45 | > https://github.com/v8/v8/blob/a9f802859bc31e57037b7c293ce8008542ca03d8/src/base/utils/random-number-generator.h#L119
46 | """
47 | se_state0, se_state1 = z3.BitVecs("se_state0 se_state1", 64)
48 |
49 | for i in range(len(sequence)):
50 | """
51 | XorShift128+
52 | > https://vigna.di.unimi.it/ftp/papers/xorshiftplus.pdf
53 | > https://github.com/v8/v8/blob/a9f802859bc31e57037b7c293ce8008542ca03d8/src/base/utils/random-number-generator.h#L119
54 |
55 | class V8_BASE_EXPORT RandomNumberGenerator final {
56 | ...
57 | static inline void XorShift128(uint64_t* state0, uint64_t* state1) {
58 | uint64_t s1 = *state0;
59 | uint64_t s0 = *state1;
60 | *state0 = s0;
61 | s1 ^= s1 << 23;
62 | s1 ^= s1 >> 17;
63 | s1 ^= s0;
64 | s1 ^= s0 >> 26;
65 | *state1 = s1;
66 | }
67 | ...
68 | }
69 | """
70 | se_s1 = se_state0
71 | se_s0 = se_state1
72 | se_state0 = se_s0
73 | se_s1 ^= se_s1 << 23
74 | se_s1 ^= z3.LShR(se_s1, 17) # Logical shift instead of Arthmetric shift
75 | se_s1 ^= se_s0
76 | se_s1 ^= z3.LShR(se_s0, 26)
77 | se_state1 = se_s1
78 |
79 | """
80 | IEEE 754 double-precision binary floating-point format
81 | > https://en.wikipedia.org/wiki/Double-precision_floating-point_format
82 | > https://www.youtube.com/watch?v=p8u_k2LIZyo&t=257s
83 |
84 | Sign (1) Exponent (11) Mantissa (52)
85 | [#] [###########] [####################################################]
86 | """
87 |
88 | """
89 | Pack as `double` and re-interpret as unsigned `long long` (little endian)
90 | > https://stackoverflow.com/a/65377273
91 | """
92 | float_64 = struct.pack("d", sequence[i] + 1)
93 | u_long_long_64 = struct.unpack(" https://github.com/v8/v8/blob/a9f802859bc31e57037b7c293ce8008542ca03d8/src/base/utils/random-number-generator.h#L111
126 |
127 | static inline double ToDouble(uint64_t state0) {
128 | // Exponent for double values for [1.0 .. 2.0)
129 | static const uint64_t kExponentBits = uint64_t{0x3FF0000000000000};
130 | uint64_t random = (state0 >> 12) | kExponentBits;
131 | return base::bit_cast(random) - 1;
132 | }
133 | """
134 | u_long_long_64 = (state0 >> 12) | 0x3FF0000000000000
135 | float_64 = struct.pack("