Skip to content

Instantly share code, notes, and snippets.

@iacore
Created May 30, 2023 22:47
Show Gist options
  • Save iacore/5839839f1a9a9286ca25da744b05451e to your computer and use it in GitHub Desktop.
Save iacore/5839839f1a9a9286ca25da744b05451e to your computer and use it in GitHub Desktop.
# (c) 04/11/2016, Rolf Rolles, Mobius Strip Reverse Engineering
import z3
import struct
# XOR key for src.bin
KEY_SECTOR = 0x37
# Counter position, as two words
CNTLO = 0
CNTHI = 0
# Symbolic names for array positions
POS_CONST0 = 0
POS_KEY0 = 1
POS_KEY2 = 2
POS_KEY4 = 3
POS_KEY6 = 4
POS_CONST2 = 5
POS_NONCE0 = 6
POS_NONCE2 = 7
POS_CNTLO = 8
POS_CNTHI = 9
POS_CONST4 = 10
POS_KEY8 = 11
POS_KEY10 = 12
POS_KEY12 = 13
POS_KEY14 = 14
POS_CONST6 = 15
# Constant values and their positions
constvals = [ 0x7865, 0x646E, 0x2D32, 0x6574 ]
constpos = [ POS_CONST0, POS_CONST2, POS_CONST4, POS_CONST6 ]
# Key byte and word variables
key_bytes = [ z3.BitVec('kb%d' % i,8) for i in xrange(8) ]
key_words = [ None ]*len(key_bytes)
# Representation of key bytes as transformed words
for i in xrange(len(key_bytes)):
hi = z3.ZeroExt(8,key_bytes[i]) << 9
lo = z3.ZeroExt(8,key_bytes[i]) + ord('z')
key_words[i] = hi | lo
# Position of key words within array
keypos = [ POS_KEY0, POS_KEY2, POS_KEY4, POS_KEY6, POS_KEY8, POS_KEY10, POS_KEY12, POS_KEY14 ]
# Initialize the modified Salsa.16-10 array (word-level values)
def init_array(nonce0,nonce2,cntlo,cnthi):
initial_array = [None]*16
for i in xrange(len(constvals)):
initial_array[constpos[i]] = z3.BitVecVal(constvals[i],16)
for i in xrange(len(key_words)):
initial_array[ keypos[i]] = key_words[i]
initial_array[POS_NONCE0] = z3.BitVecVal(nonce0,16)
initial_array[POS_NONCE2] = z3.BitVecVal(nonce2,16)
initial_array[POS_CNTLO] = z3.BitVecVal(cntlo, 16)
initial_array[POS_CNTHI] = z3.BitVecVal(cnthi, 16)
return initial_array
# Input values to the Salsa round primitive
salsa_steps = [
(4,0,12,7),
(8,4,0,9),
(12,8,4,13),
(0,12,8,18),
(9,5,1,7),
(13,9,5,9),
(1,13,9,13),
(5,1,13,18),
(14,10,6,7),
(2,14,10,9),
(6,2,14,13),
(10,6,2,18),
(3,15,11,7),
(7,3,15,9),
(11,7,3,13),
(15,11,7,18),
(1,0,3,7),
(2,1,0,9),
(3,2,1,13),
(0,3,2,18),
(6,5,4,7),
(7,6,5,9),
(4,7,6,13),
(5,4,7,18),
(11,10,9,7),
(8,11,10,9),
(9,8,11,13),
(10,9,8,18),
(12,15,14,7),
(13,12,15,9),
(14,13,12,13),
(15,14,13,18),
]
# z3 representation of the Salsa.16 round primitive
def step(arr,out,inl,inr,rotamt):
sum = z3.ZeroExt(16,arr[inl] + arr[inr])
rot = z3.RotateLeft(sum,rotamt)
arr[out] ^= z3.Extract(15,0,rot)
# Perform one Salsa.16 round
def salsarnd(arr):
for rnd in salsa_steps:
step(arr,*rnd)
# Perform all 10 Salsa.16 rounds
def salsa10(arr):
for i in xrange(10):
salsarnd(arr)
# Create solver object
s = z3.SolverFor('QF_BV')
# Extract nonce words from the binary nonce data; create initial array
with open("salsa10-nonce.bin", "rb") as f_nonce:
(n0,n2,n4,n6) = struct.unpack("HHHH", f_nonce.read())
initial = init_array(n0,n4,CNTLO,CNTHI)
init_copy = [ x for x in initial ]
# Extract ciphertext words from the binary sector data
with open("salsa10-src.bin", "rb") as f_src:
srcbytes = bytearray(f_src.read())
z3srcwords = [ None ] * len(initial)
for i in xrange(len(initial)):
lo = (srcbytes[4*i] ^ KEY_SECTOR)
hi = (srcbytes[4*i+1] ^ KEY_SECTOR) << 8
z3srcwords[i] = z3.BitVecVal(hi | lo,16)
# Specify constraints on key bytes
for k in key_bytes:
s.add(k != ord('O'))
s.add(k != ord('I'))
s.add(k != ord('l'))
s.add(z3.Or(z3.And(k >= 0x31,k <= 0x39),z3.And(k >= 0x61,k <= 0x78),z3.And(k >= 0x41,k <= 0x58)))
# Perform the Salsa.16 operation
salsa10(initial)
# Specify constraints on outputs
for i in xrange(len(initial)):
s.add((init_copy[i] + initial[i]) == z3srcwords[i])
# Check SAT, print solution if so
if s.check() == z3.sat:
m = s.model()
keybytes = map(lambda k: chr(m[k].as_long()), key_bytes)
print 'x'.join(keybytes)
else:
print "UNSAT!"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment