Last active
February 17, 2019 17:42
-
-
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Very cool.