Skip to content

Instantly share code, notes, and snippets.

@shirriff
Last active February 17, 2019 17:42
Show Gist options
  • Save shirriff/978cd9a345870f7aa96234a2884c6321 to your computer and use it in GitHub Desktop.
Save shirriff/978cd9a345870f7aa96234a2884c6321 to your computer and use it in GitHub Desktop.
Rapidly crack Xerox Alto disk passwords using a mathematical formula that reverses the password hash
# Crack Xerox Alto disk passwords using math.
import sys
def findPasswd(passvec):
# a and b are the salt values
a = (passvec[1] << 16) + passvec[2]
b = (passvec[3] << 16) + passvec[4]
if a == 0 or b == 0:
print 'No password'
return
if passvec[0] == 0:
print 'Password (disabled):'
else:
print 'Password:'
# c is the known hashed password
# The password characters are concatenated into x and y.
# Since we don't know the password, brute force the 65536 possibilities for x
c = (passvec[5] << 48) | (passvec[6] << 32) | (passvec[7] << 16) | passvec[8];
for x in range(0, 65536):
# Solve the password equation for y
t = (x * x) & 0xffffffff
t = (t * a) & 0xffffffffffffffff
t ^= 0xffffffff00000000
t += 1
btimesy = (c - t) & 0xffffffffffffffff
y = btimesy / b
# If quotient didn't work, try next x
if (y * b) & 0xffffffffffffffff != btimesy: continue
# Concat sequence is first char into x, next two into y, one into x, etc.
# Try different password lengths to find which one works
for i in range(1, 15):
password = ''
x1 = x
y1 = y
for j in range(i, 0, -1):
if (j % 3) == 1:
char = chr((x1 >> 9) & 0x7f)
x1 = (x1 << 7) & 0xffff
else:
char = chr((y1 >> 25) & 0x7f)
y1 = (y1 << 7) & 0xffffffff
password = char + password
if x1 == 0 and y1 == 0 and '\0' not in password:
print i, password
# Get word at the word offset in the disk file
def getWord(wordOffset):
return ord(diskContents[2 * wordOffset]) + ord(diskContents[2 * wordOffset + 1]) * 256
def getSector(addr):
# Convert address (sector/cylinder/head) into a linear address
sec = addr >> 12
cyl = (addr >> 3) & 0x1ff
h = 1 if (addr & 0x4) else 0
n = sec + (cyl*2 + h) * 12
offset = n*(256 + 10 + 1)
header = [getWord(offset + i) for i in range(1, 3)]
label = [getWord(offset + i) for i in range(3, 11)]
data = [getWord(offset + i) for i in range(11, 267)]
return header, label, data, offset
diskContents = None
# Crack passwords from three Alto disks
def main():
global diskContents
if len(sys.argv) != 2:
sys.exit('Usage: mathcrack filename.dsk')
with open(sys.argv[1], 'rb') as f:
diskContents = f.read()
header, label, data, offset = getSector(0)
nextAddr = label[0]
header, label, data, offset = getSector(nextAddr)
chars = ''.join(['%s%s' % (chr(word>>8), chr(word&0xff)) for word in data])
off = 0
length = data[off] >> 8
print 'Disk user:', chars[2*off+1:2*off+1+length]
off = (1+length+1) / 2 # Move to next string
length = data[off] >> 8
print 'Disk name:', chars[2*off+1:2*off+1+length]
passvec = data[128:128+9]
findPasswd(passvec)
def test():
print 'Disk 37'
# passvec is the 9-word password vector from sys.boot
# passvec must be big-endian
passvec = [0xffff, 0xe9d3, 0x0f9a, 0x0da5, 0x53b4, 0xc3f4, 0x382a, 0xd974, 0x5589]
findPasswd(passvec)
print '\nDisk 47'
passvec = [0xffff, 0xcfa7, 0x9f3d, 0x669a, 0xf2bb, 0xf193, 0x6d09, 0x4571, 0xe1d1]
findPasswd(passvec)
print '\nDisk 72'
passvec = [0xffff, 0x8341, 0x772c, 0x249b, 0x1be2, 0xf71f, 0x5b87, 0x9fce, 0x0581]
findPasswd(passvec)
if __name__ == "__main__":
main()
@Cabalist
Copy link

Cabalist commented Jan 5, 2018

Very cool.

@bittorf
Copy link

bittorf commented Jan 6, 2018

You made my weekend! 8-)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment