Fix Chrome.

This commit is contained in:
Douglas Goddard 2020-03-24 20:01:44 -07:00
parent a9c9d6a786
commit f2ae4a3f9e
2 changed files with 63 additions and 38 deletions

6
README
View File

@ -1,10 +1,14 @@
For usage on the LA Time's powerball simulator. Careful about clicking the page. For usage on the LA Time's powerball simulator. Careful about clicking the page.
Blog post: https://blog.securityevaluators.com/hacking-the-javascript-lottery-80cc437e3b7f
Run the following snippet in your browser's console. Run the following snippet in your browser's console.
_ = []; for(var i=0; i<5; ++i) { _.push(Math.random()) } ; console.log(_) _ = []; for(var i=0; i<5; ++i) { _.push(Math.random()) } ; console.log(_)
Paste at least 3 of those values into the dubs array in main(). Paste at least 3 of those (5 for Chrome) values into the dubs array in main().
It will warn you if the model is too "loose" and has multiple solutions.
Set the browser in main() to Chrome or Firefox. (Safari hasn't updated yet.) Set the browser in main() to Chrome or Firefox. (Safari hasn't updated yet.)

View File

@ -2,20 +2,24 @@ import sys
import math import math
import struct import struct
import random import random
sys.path.append('/home/dgoddard/tools/z3/build')
from z3 import * from z3 import *
MASK = 0xFFFFFFFFFFFFFFFF
# xor_shift_128_plus algorithm # xor_shift_128_plus algorithm
def xs128p(state0, state1): def xs128p(state0, state1, browser):
s1 = state0 & 0xFFFFFFFFFFFFFFFF s1 = state0 & MASK
s0 = state1 & 0xFFFFFFFFFFFFFFFF s0 = state1 & MASK
s1 ^= (s1 << 23) & 0xFFFFFFFFFFFFFFFF s1 ^= (s1 << 23) & MASK
s1 ^= (s1 >> 17) & 0xFFFFFFFFFFFFFFFF s1 ^= (s1 >> 17) & MASK
s1 ^= s0 & 0xFFFFFFFFFFFFFFFF s1 ^= s0 & MASK
s1 ^= (s0 >> 26) & 0xFFFFFFFFFFFFFFFF s1 ^= (s0 >> 26) & MASK
state0 = state1 & 0xFFFFFFFFFFFFFFFF state0 = state1 & MASK
state1 = s1 & 0xFFFFFFFFFFFFFFFF state1 = s1 & MASK
generated = (state0 + state1) & 0xFFFFFFFFFFFFFFFF if browser == 'chrome':
generated = state0 & MASK
else:
generated = (state0 + state1) & MASK
return state0, state1, generated return state0, state1, generated
@ -29,11 +33,14 @@ def sym_xs128p(slvr, sym_state0, sym_state1, generated, browser):
s1 ^= LShR(s0, 26) s1 ^= LShR(s0, 26)
sym_state0 = sym_state1 sym_state0 = sym_state1
sym_state1 = s1 sym_state1 = s1
calc = (sym_state0 + sym_state1) if browser == 'chrome':
calc = sym_state0
else:
calc = (sym_state0 + sym_state1)
condition = Bool('c%d' % int(generated * random.random())) condition = Bool('c%d' % int(generated * random.random()))
if browser == 'chrome': if browser == 'chrome':
impl = Implies(condition, (calc & 0xFFFFFFFFFFFFF) == int(generated)) impl = Implies(condition, LShR(calc, 12) == int(generated))
elif browser == 'firefox' or browser == 'safari': elif browser == 'firefox' or browser == 'safari':
# Firefox and Safari save an extra bit # Firefox and Safari save an extra bit
impl = Implies(condition, (calc & 0x1FFFFFFFFFFFFF) == int(generated)) impl = Implies(condition, (calc & 0x1FFFFFFFFFFFFF) == int(generated))
@ -45,15 +52,20 @@ def reverse17(val):
return val ^ (val >> 17) ^ (val >> 34) ^ (val >> 51) return val ^ (val >> 17) ^ (val >> 34) ^ (val >> 51)
def reverse23(val): def reverse23(val):
return (val ^ (val << 23) ^ (val << 46)) & 0xFFFFFFFFFFFFFFFF return (val ^ (val << 23) ^ (val << 46)) & MASK
def xs128p_backward(state0, state1): def xs128p_backward(state0, state1, browser):
prev_state1 = state0 prev_state1 = state0
prev_state0 = state1 ^ (state0 >> 26) prev_state0 = state1 ^ (state0 >> 26)
prev_state0 = prev_state0 ^ state0 prev_state0 = prev_state0 ^ state0
prev_state0 = reverse17(prev_state0) prev_state0 = reverse17(prev_state0)
prev_state0 = reverse23(prev_state0) prev_state0 = reverse23(prev_state0)
generated = (prev_state0 + prev_state1) & 0xFFFFFFFFFFFFFFFF # this is only called from an if chrome
# but let's be safe in case someone copies it out
if browser == 'chrome':
generated = prev_state0
else:
generated = (prev_state0 + prev_state1) & MASK
return prev_state0, prev_state1, generated return prev_state0, prev_state1, generated
# Print 'last seen' random number # Print 'last seen' random number
@ -66,9 +78,9 @@ def xs128p_backward(state0, state1):
# with an arrow. # with an arrow.
def power_ball(generated, browser): def power_ball(generated, browser):
# for each random number (skip 4 of 5 that we generated) # for each random number (skip 4 of 5 that we generated)
for idx in xrange(len(generated[4:])): for idx in range(len(generated[4:])):
# powerball range is 1 to 69 # powerball range is 1 to 69
poss = range(1, 70) poss = list(range(1, 70))
# base index 4 to skip # base index 4 to skip
gen = generated[4+idx:] gen = generated[4+idx:]
# get 'last seen' number # get 'last seen' number
@ -77,11 +89,11 @@ def power_ball(generated, browser):
# make sure we have enough numbers # make sure we have enough numbers
if len(gen) < 6: if len(gen) < 6:
break break
print g0 print(g0)
# generate 5 winning numbers # generate 5 winning numbers
nums = [] nums = []
for jdx in xrange(5): for jdx in range(5):
index = int(gen[jdx] * len(poss)) index = int(gen[jdx] * len(poss))
val = poss[index] val = poss[index]
poss = poss[:index] + poss[index+1:] poss = poss[:index] + poss[index+1:]
@ -89,28 +101,28 @@ def power_ball(generated, browser):
# print indicator # print indicator
if idx == 0 and browser == 'chrome': if idx == 0 and browser == 'chrome':
print '--->', print('--->', end='')
elif idx == 2 and browser == 'firefox': elif idx == 2 and browser == 'firefox':
print '--->', print('--->', end='')
else: else:
print ' ', print(' ', end='')
# print winning numbers # print winning numbers
print sorted(nums), print(sorted(nums), end='')
# generate / print power number or w/e it's called # generate / print power number or w/e it's called
double = gen[5] double = gen[5]
val = int(math.floor(double * 26) + 1) val = int(math.floor(double * 26) + 1)
print val print(val)
# Firefox nextDouble(): # Firefox nextDouble():
# (rand_uint64 & ((1 << 53) - 1)) / (1 << 53) # (rand_uint64 & ((1 << 53) - 1)) / (1 << 53)
# Chrome nextDouble(): # Chrome nextDouble():
# ((rand_uint64 & ((1 << 52) - 1)) | 0x3FF0000000000000) - 1.0 # (state0 | 0x3FF0000000000000) - 1.0
# Safari weakRandom.get(): # Safari weakRandom.get():
# (rand_uint64 & ((1 << 53) - 1) * (1.0 / (1 << 53))) # (rand_uint64 & ((1 << 53) - 1) * (1.0 / (1 << 53)))
def to_double(browser, out): def to_double(browser, out):
if browser == 'chrome': if browser == 'chrome':
double_bits = (out & 0xFFFFFFFFFFFFF) | 0x3FF0000000000000 double_bits = (out >> 12) | 0x3FF0000000000000
double = struct.unpack('d', struct.pack('<Q', double_bits))[0] - 1 double = struct.unpack('d', struct.pack('<Q', double_bits))[0] - 1
elif browser == 'firefox': elif browser == 'firefox':
double = float(out & 0x1FFFFFFFFFFFFF) / (0x1 << 53) double = float(out & 0x1FFFFFFFFFFFFF) / (0x1 << 53)
@ -126,22 +138,25 @@ def main():
# browser = 'safari' # browser = 'safari'
browser = 'chrome' browser = 'chrome'
# browser = 'firefox' # browser = 'firefox'
print 'BROWSER: %s' % browser print('BROWSER: %s' % browser)
# In your browser's JavaScript console: # In your browser's JavaScript console:
# _ = []; for(var i=0; i<5; ++i) { _.push(Math.random()) } ; console.log(_) # _ = []; for(var i=0; i<5; ++i) { _.push(Math.random()) } ; console.log(_)
# Enter at least the 3 first random numbers you observed here: # Enter at least the 3 first random numbers you observed here:
dubs = [0.4752549301773037, 0.08162196013326506, 0.8333085432653353] # Observations show Chrome needs ~5
dubs = [
0.5368584449767335, 0.883588766746984, 0.7895949638905317,
0.5106241305628436, 0.49965622623126693]
if browser == 'chrome': if browser == 'chrome':
dubs = dubs[::-1] dubs = dubs[::-1]
print dubs print(dubs)
# from the doubles, generate known piece of the original uint64 # from the doubles, generate known piece of the original uint64
generated = [] generated = []
for idx in xrange(3): for idx in range(len(dubs)):
if browser == 'chrome': if browser == 'chrome':
recovered = struct.unpack('<Q', struct.pack('d', dubs[idx] + 1))[0] & 0xFFFFFFFFFFFFF recovered = struct.unpack('<Q', struct.pack('d', dubs[idx] + 1))[0] & (MASK >> 12)
elif browser == 'firefox': elif browser == 'firefox':
recovered = dubs[idx] * (0x1 << 53) recovered = dubs[idx] * (0x1 << 53)
elif browser == 'safari': elif browser == 'safari':
@ -157,7 +172,7 @@ def main():
# run symbolic xorshift128+ algorithm for three iterations # run symbolic xorshift128+ algorithm for three iterations
# using the recovered numbers as constraints # using the recovered numbers as constraints
for ea in xrange(3): for ea in range(len(dubs)):
sym_state0, sym_state1, ret_conditions = sym_xs128p(slvr, sym_state0, sym_state1, generated[ea], browser) sym_state0, sym_state1, ret_conditions = sym_xs128p(slvr, sym_state0, sym_state1, generated[ea], browser)
conditions += ret_conditions conditions += ret_conditions
@ -166,21 +181,27 @@ def main():
m = slvr.model() m = slvr.model()
state0 = m[ostate0].as_long() state0 = m[ostate0].as_long()
state1 = m[ostate1].as_long() state1 = m[ostate1].as_long()
slvr.add(Or(ostate0 != m[ostate0], ostate1 != m[ostate1]))
if slvr.check(conditions) == sat:
print('WARNING: multiple solutions found! use more dubs!')
print('state', state0, state1)
generated = [] generated = []
# generate random numbers from recovered state # generate random numbers from recovered state
for idx in xrange(15): for idx in range(15):
if browser == 'chrome': if browser == 'chrome':
state0, state1, out = xs128p_backward(state0, state1) state0, state1, out = xs128p_backward(state0, state1, browser)
out = state0 & MASK
else: else:
state0, state1, out = xs128p(state0, state1) state0, state1, out = xs128p(state0, state1, browser)
double = to_double(browser, out) double = to_double(browser, out)
print('gen', double)
generated.append(double) generated.append(double)
# use generated numbers to predict powerball numbers # use generated numbers to predict powerball numbers
power_ball(generated, browser) power_ball(generated, browser)
else: else:
print 'UNSAT' print('UNSAT')
main() main()