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() |
This comment has been minimized.
This comment has been minimized.
You made my weekend! 8-) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
This comment has been minimized.
Very cool.