diff --git a/fallout.py b/fallout.py index 70f44fc..942b98a 100755 --- a/fallout.py +++ b/fallout.py @@ -2,6 +2,7 @@ import fallout_login as login import fallout_boot as boot import fallout_locked as locked +import fallout_hack as hack import sys hard = False @@ -9,8 +10,11 @@ if len(sys.argv) == 2 and sys.argv[1].lower() == 'hard': hard = True if boot.beginBoot(hard): - if login.beginLogin(): + pwd = hack.beginLogin() + if pwd != None: + login.beginLogin(hard, 'ADMIN', pwd) print 'Login successful' else: locked.beginLocked() print 'Login failed' + diff --git a/fallout_boot.py b/fallout_boot.py index b64166b..2f393f2 100644 --- a/fallout_boot.py +++ b/fallout_boot.py @@ -1,4 +1,7 @@ from fallout_functions import slowWrite +from fallout_functions import INPUT_PAUSE +from fallout_functions import TYPE_DELAY +from fallout_functions import upperInput import curses ######################## global 'constants' ############## @@ -11,9 +14,6 @@ ENTRY_3 = 'SET HALT RESTART/MAINT' ENTRY_4 = 'RUN DEBUG/ACCOUNTS.F' -INPUT_PAUSE = 500 # ms - -TYPE_DELAY = 25 ######################## text strings #################### @@ -35,12 +35,16 @@ MESSAGE_3 = 'Initializing Robco Industries(TM) MF Boot Agent v2.3.0\n' \ def runBoot(scr, hardMode): """ Start the boot portion of the terminal + + hardMode - boolean indicating whether the user has to enter the ENTRY + constants, or if they are entered automatically """ curses.use_default_colors() scr.erase() scr.move(0, 0) - curses.echo() + curses.noecho() + scr.scrollok(True) slowWrite(scr, MESSAGE_1 + '\n\n') @@ -49,7 +53,7 @@ def runBoot(scr, hardMode): entry = '' while entry.upper() != ENTRY_1.upper(): slowWrite(scr, '>') - entry = scr.getstr() + entry = upperInput(scr) else: # input is entered for them slowWrite(scr, '>') @@ -62,10 +66,10 @@ def runBoot(scr, hardMode): entry = '' while entry.upper() != ENTRY_2.upper(): slowWrite(scr, '>') - entry = scr.getstr() + entry = upperInput(scr) while entry.upper() != ENTRY_3.upper(): slowWrite(scr, '>') - entry = scr.getstr() + entry = upperInput(scr) else: slowWrite(scr, '>') curses.napms(INPUT_PAUSE) @@ -80,7 +84,7 @@ def runBoot(scr, hardMode): entry = '' while entry.upper() != ENTRY_4.upper(): slowWrite(scr, '>') - entry = scr.getstr() + entry = upperInput(scr) else: slowWrite(scr, '>') curses.napms(INPUT_PAUSE) diff --git a/fallout_functions.py b/fallout_functions.py index 8ece4bb..88149e6 100644 --- a/fallout_functions.py +++ b/fallout_functions.py @@ -4,6 +4,16 @@ import sys LETTER_PAUSE = 5 +INPUT_PAUSE = 500 # ms + +TYPE_DELAY = 30 + +HIDDEN_MASK = '*' + +NEWLINE = 10 + +DELETE = 127 + def slowWrite(window, text, pause = LETTER_PAUSE): """ wrapper for curses.addstr() which writes the text slowely @@ -12,3 +22,40 @@ def slowWrite(window, text, pause = LETTER_PAUSE): window.addstr(text[i]) window.refresh() curses.napms(pause) + +def upperInput(window, hidden = False, can_newline = True): + """ + Reads user input until enter key is pressed. Echoes the input in upper case + + hidden - if true the output will be masked + can_newline - if true the input is followed by a newline and the screen is + scrolled if necessary + """ + inchar = 0 + instr = '' + while inchar != NEWLINE: + inchar = window.getch() + # convert lower case to upper + if inchar > 96 and inchar < 123: + inchar -= 32 + # deal with backspace + if inchar == DELETE: + if len(instr) > 0: + instr = instr[:-1] + cur = window.getyx() + window.move(cur[0], cur[1] - 1) + window.clrtobot() + else: + continue + elif inchar > 255: + continue + # output the character + elif inchar != NEWLINE: + instr += chr(inchar) + if hidden: + window.addch(HIDDEN_MASK) + else: + window.addch(inchar) + elif can_newline: + window.addch(NEWLINE) + return instr diff --git a/fallout_hack.py b/fallout_hack.py new file mode 100644 index 0000000..4b32d52 --- /dev/null +++ b/fallout_hack.py @@ -0,0 +1,272 @@ +import curses +import random +import os +from fallout_functions import slowWrite +from fallout_functions import upperInput + +################## text strings ###################### + +HEADER_TEXT = 'ROBCO INDUSTRIES (TM) TERMLINK PROTOCOL' + +################## global "constants" ################ + +# number of characters for hex digits and spaces +CONST_CHARS = 16 + +# position of the attempt squares +SQUARE_X = 19 +SQUARE_Y = 3 + +LOGIN_ATTEMPTS = 4 + +HEADER_LINES = 5 + +# amount of time to pause after correct password input +LOGIN_PAUSE = 3000 + +# starting number for hex generation +START_HEX = 0xf650 + +# list of possible symbols for password hiding +SYMBOLS = '!@#$%^*()_-+={}[]|\\:;\'",<>./?' + +################## functions ######################### + +def generateHex(n): + """ + generates n numbers starting at START_HEX and increasing by 12 each time + """ + num = START_HEX + list = [] + for i in xrange(n): + list.append(num) + num += 12 + return list + + +def getSymbols(n): + """ + return n random symbols + """ + count = len(SYMBOLS) + result = "" + for i in xrange(n): + result += SYMBOLS[random.randint(0, count - 1)] + return result + + +def getPasswords(): + """ + Returns an array of strings to be used as the password and the decoys + """ + groups = [] + + # script file / password file location + __location__ = os.path.realpath(os.path.join(os.getcwd(), + os.path.dirname(__file__))) + + # read from passwords.txt + with open(os.path.join(__location__, "passwords.txt")) as pwfile: + for line in pwfile: + if not line.strip(): + groups.append([]) + elif len(groups) > 0: + groups[len(groups) - 1].append(line[:-1]) + + passwords = groups[random.randint(0, len(groups) - 1)] + + random.shuffle(passwords) + return passwords + + +def getFiller(length, passwords): + """ + Return a string of symbols with potential passwords mixed in + + length - the length of the string to create + passwords - an array of passwords to hide in the symbols + """ + filler = getSymbols(length) + + # add the passwords to the symbols + pwdLen = len(passwords[0]) + pwdCount = len(passwords) + i = 0 + for pwd in passwords: + # skip a distance based on total size to cover then place a password + maxSkip = length / pwdCount - pwdLen + i += random.randint(maxSkip - 2, maxSkip) + filler = filler[:i] + pwd + filler[i + pwdLen:] + i += pwdLen + return filler + + +def initScreen(scr): + """ + Fill the screen to prepare for password entry + + scr - curses window returned from curses.initscr() + """ + size = scr.getmaxyx() + height = size[0] + width = size[1] + fillerHeight = height - HEADER_LINES + + hexes = generateHex(fillerHeight * 2) + + hexCol1 = hexes[:fillerHeight] + hexCol2 = hexes[fillerHeight:] + + # generate the symbols and passwords + fillerLength = width / 2 * fillerHeight + passwords = getPasswords() + filler = getFiller(fillerLength, passwords) + fillerCol1 = filler[:len(filler) / 2] + fillerCol2 = filler[len(filler) / 2:] + + # each column of symbols and passwords should be 1/4 of the screen + fillerWidth = width / 4 + + # print the header stuff + slowWrite(scr, HEADER_TEXT) + slowWrite(scr, '\nENTER PASSWORD NOW\n\n') + slowWrite(scr, str(LOGIN_ATTEMPTS) + ' ATTEMPT(S) LEFT: ') + for i in xrange(LOGIN_ATTEMPTS): + scr.addch(curses.ACS_BLOCK) + slowWrite(scr, ' ') + slowWrite(scr, '\n\n') + + # print the hex and filler + for i in xrange(fillerHeight): + slowWrite(scr, "0x%X %s" % (hexCol1[i], fillerCol1[i * fillerWidth: (i + 1) * fillerWidth]), 1) + if i < fillerHeight - 1: + scr.addstr('\n') + + for i in xrange(fillerHeight): + scr.move(HEADER_LINES + i, CONST_CHARS / 2 + fillerWidth) + slowWrite(scr, '0x%X %s' % (hexCol2[i], fillerCol2[i * fillerWidth: (i + 1) * fillerWidth]), 1) + + scr.refresh() + + return passwords + + +def moveInput(scr, inputPad): + """ + moves the input pad to display all text then a blank line then the cursor + """ + size = scr.getmaxyx() + height = size[0] + width = size[1] + + inputPad.addstr('\n>') + + # cursor position relative to inputPad + cursorPos = inputPad.getyx() + + inputPad.refresh(0, 0, + height - cursorPos[0] - 1, + width / 2 + CONST_CHARS, + height - 1, + width - 1) + + +def userInput(scr, passwords): + """ + let the user attempt to crack the password + + scr - curses window returned from curses.initscr() + passwords - array of passwords hidden in the symbols + """ + size = scr.getmaxyx() + height = size[0] + width = size[1] + + # set up a pad for user input + inputPad = curses.newpad(height, width / 2 + CONST_CHARS) + + attempts = LOGIN_ATTEMPTS + + # randomly pick a password from the list + pwd = passwords[random.randint(0, len(passwords) - 1)] + curses.noecho() + + while attempts > 0: + # move the curser to the correct spot for typing + scr.move(height - 1, width / 2 + CONST_CHARS + 1) + + # scroll user input up as the user tries passwords + moveInput(scr, inputPad) + + guess = upperInput(scr, False, False) + cursorPos = inputPad.getyx() + + # write under the last line of text + inputPad.move(cursorPos[0] - 1, cursorPos[1] - 1) + inputPad.addstr('>' + guess.upper() + '\n') + + # user got password right + if guess.upper() == pwd.upper(): + inputPad.addstr('>Exact match!\n') + inputPad.addstr('>Please wait\n') + inputPad.addstr('>while system\n') + inputPad.addstr('>is accessed.\n') + + moveInput(scr, inputPad) + + curses.napms(LOGIN_PAUSE) + return pwd + + # wrong password + else: + pwdLen = len(pwd) + matched = 0 + try: + for i in xrange(pwdLen): + if pwd[i].upper() == guess[i].upper(): + matched += 1 + except IndexError: + pass # user did not enter enough letters + + inputPad.addstr('>Entry denied\n') + inputPad.addstr('>' + str(matched) + '/' + str(pwdLen) + + ' correct.\n') + + attempts -= 1 + # show remaining attempts + scr.move(SQUARE_Y, 0) + scr.addstr(str(attempts)) + scr.move(SQUARE_Y, SQUARE_X) + for i in xrange(LOGIN_ATTEMPTS): + if i < attempts: + scr.addch(curses.ACS_BLOCK) + else: + scr.addstr(' ') + scr.addstr(' ') + + # Out of attempts + return None + +def runLogin(scr): + """ + Start the login portion of the terminal + Returns the password if the user correctly guesses it + """ + curses.use_default_colors() + size = scr.getmaxyx() + width = size[1] + height = size[0] + random.seed() + # set screen to initial position + scr.erase() + scr.move(0, 0) + passwords = initScreen(scr) + return userInput(scr, passwords) + + +def beginLogin(): + """ + Initialize curses and start the login process + Returns the password if the user correctly guesses it + """ + return curses.wrapper(runLogin) diff --git a/fallout_login.py b/fallout_login.py index 4284ebc..363d807 100644 --- a/fallout_login.py +++ b/fallout_login.py @@ -1,270 +1,84 @@ import curses -import random -import os from fallout_functions import slowWrite - +from fallout_functions import INPUT_PAUSE +from fallout_functions import TYPE_DELAY +from fallout_functions import upperInput +from fallout_functions import HIDDEN_MASK ################## text strings ###################### -HEADER_TEXT = 'ROBCO INDUSTRIES (TM) TERMLINK PROTOCOL' +HEADER_TEXT = 'WELCOME TO ROBCO INDUSTRIES (TM) TERMLINK' + +PASSWORD_PROMPT = 'ENTER PASSWORD NOW' + +PASSWORD_ERROR = 'INCORRECT PASSWORD, PLEASE TRY AGAIN' ################## global "constants" ################ -# number of characters for hex digits and spaces -CONST_CHARS = 16 - -# position of the attempt squares -SQUARE_X = 19 -SQUARE_Y = 3 - -LOGIN_ATTEMPTS = 4 - -HEADER_LINES = 5 - -# amount of time to pause after correct password input -LOGIN_PAUSE = 3000 - -# starting number for hex generation -START_HEX = 0xf650 - -# list of possible symbols for password hiding -SYMBOLS = '!@#$%^*()_-+={}[]|\\:;\'",<>./?' +ENTRY = 'LOGON ' ################## functions ######################### -def generateHex(n): +def runLogin(scr, hardMode, username, password): """ - generates n numbers starting at START_HEX and increasing by 12 each time - """ - num = START_HEX - list = [] - for i in xrange(n): - list.append(num) - num += 12 - return list + Start the login process - -def getSymbols(n): - """ - return n random symbols - """ - count = len(SYMBOLS) - result = "" - for i in xrange(n): - result += SYMBOLS[random.randint(0, count - 1)] - return result - - -def getPasswords(): - """ - Returns an array of strings to be used as the password and the decoys - """ - groups = [] - - # script file / password file location - __location__ = os.path.realpath(os.path.join(os.getcwd(), - os.path.dirname(__file__))) - - # read from passwords.txt - with open(os.path.join(__location__, "passwords.txt")) as pwfile: - for line in pwfile: - if not line.strip(): - groups.append([]) - elif len(groups) > 0: - groups[len(groups) - 1].append(line[:-1]) - - passwords = groups[random.randint(0, len(groups) - 1)] - - random.shuffle(passwords) - return passwords - - -def getFiller(length, passwords): - """ - Return a string of symbols with potential passwords mixed in - - length - the length of the string to create - passwords - an array of passwords to hide in the symbols - """ - filler = getSymbols(length) - - # add the passwords to the symbols - pwdLen = len(passwords[0]) - pwdCount = len(passwords) - i = 0 - for pwd in passwords: - # skip a distance based on total size to cover then place a password - maxSkip = length / pwdCount - pwdLen - i += random.randint(maxSkip - 2, maxSkip) - filler = filler[:i] + pwd + filler[i + pwdLen:] - i += pwdLen - return filler - - -def initScreen(scr): - """ - Fill the screen to prepare for password entry - - scr - curses window returned from curses.initscr() - """ - size = scr.getmaxyx() - height = size[0] - width = size[1] - fillerHeight = height - HEADER_LINES - - hexes = generateHex(fillerHeight * 2) - - hexCol1 = hexes[:fillerHeight] - hexCol2 = hexes[fillerHeight:] - - # generate the symbols and passwords - fillerLength = width / 2 * fillerHeight - passwords = getPasswords() - filler = getFiller(fillerLength, passwords) - fillerCol1 = filler[:len(filler) / 2] - fillerCol2 = filler[len(filler) / 2:] - - # each column of symbols and passwords should be 1/4 of the screen - fillerWidth = width / 4 - - # print the header stuff - slowWrite(scr, HEADER_TEXT) - slowWrite(scr, '\nENTER PASSWORD NOW\n\n') - slowWrite(scr, str(LOGIN_ATTEMPTS) + ' ATTEMPT(S) LEFT: ') - for i in xrange(LOGIN_ATTEMPTS): - scr.addch(curses.ACS_BLOCK) - slowWrite(scr, ' ') - slowWrite(scr, '\n\n') - - # print the hex and filler - for i in xrange(fillerHeight): - slowWrite(scr, "0x%X %s" % (hexCol1[i], fillerCol1[i * fillerWidth: (i + 1) * fillerWidth]), 1) - if i < fillerHeight - 1: - scr.addstr('\n') - - for i in xrange(fillerHeight): - scr.move(HEADER_LINES + i, CONST_CHARS / 2 + fillerWidth) - slowWrite(scr, '0x%X %s' % (hexCol2[i], fillerCol2[i * fillerWidth: (i + 1) * fillerWidth]), 1) - - scr.refresh() - - return passwords - - -def moveInput(scr, inputPad): - """ - moves the input pad to display all text then a blank line then the cursor - """ - size = scr.getmaxyx() - height = size[0] - width = size[1] - - inputPad.addstr('\n>') - - # cursor position relative to inputPad - cursorPos = inputPad.getyx() - - inputPad.refresh(0, 0, - height - cursorPos[0] - 1, - width / 2 + CONST_CHARS, - height - 1, - width - 1) - - -def userInput(scr, passwords): - """ - let the user attempt to crack the password - - scr - curses window returned from curses.initscr() - passwords - array of passwords hidden in the symbols - """ - size = scr.getmaxyx() - height = size[0] - width = size[1] - - # set up a pad for user input - inputPad = curses.newpad(height, width / 2 + CONST_CHARS) - - attempts = LOGIN_ATTEMPTS - - # randomly pick a password from the list - pwd = passwords[random.randint(0, len(passwords) - 1)] - curses.echo() - - while attempts > 0: - # move the curser to the correct spot for typing - scr.move(height - 1, width / 2 + CONST_CHARS + 1) - - # scroll user input up as the user tries passwords - moveInput(scr, inputPad) - - guess = scr.getstr() - cursorPos = inputPad.getyx() - - # write under the last line of text - inputPad.move(cursorPos[0] - 1, cursorPos[1] - 1) - inputPad.addstr('>' + guess.upper() + '\n') - - # user got password right - if guess.upper() == pwd.upper(): - inputPad.addstr('>Exact match!\n') - inputPad.addstr('>Please wait\n') - inputPad.addstr('>while system\n') - inputPad.addstr('>is accessed.\n') - - moveInput(scr, inputPad) - - curses.napms(LOGIN_PAUSE) - return True - - # wrong password - else: - pwdLen = len(pwd) - matched = 0 - try: - for i in xrange(pwdLen): - if pwd[i].upper() == guess[i].upper(): - matched += 1 - except IndexError: - pass # user did not enter enough letters - - inputPad.addstr('>Entry denied\n') - inputPad.addstr('>' + str(matched) + '/' + str(pwdLen) + - ' correct.\n') - - attempts -= 1 - # show remaining attempts - scr.move(SQUARE_Y, 0) - scr.addstr(str(attempts)) - scr.move(SQUARE_Y, SQUARE_X) - for i in xrange(LOGIN_ATTEMPTS): - if i < attempts: - scr.addch(curses.ACS_BLOCK) - else: - scr.addstr(' ') - scr.addstr(' ') - - # Out of attempts - return False - -def runLogin(scr): - """ - Start the login portion of the terminal + hardMode - boolean indicating whether the user has to enter the username + and password or if they are entered automatically + username - the username to log in + password - the password to log in + Returns true if hardMode == false or if the user entered the correct string """ curses.use_default_colors() - size = scr.getmaxyx() - width = size[1] - height = size[0] - random.seed() - # set screen to initial position scr.erase() scr.move(0, 0) - passwords = initScreen(scr) - return userInput(scr, passwords) + + curses.noecho() + scr.scrollok(True) + + slowWrite(scr, HEADER_TEXT + '\n\n') + + if hardMode: + # use must enter the correct text to proceed + entry = '' + while entry.upper() != ENTRY.upper() + username.upper(): + slowWrite(scr, '> ') + entry = upperInput(scr) + else: + # input is entered for them + slowWrite(scr, '> ') + curses.napms(INPUT_PAUSE) + slowWrite(scr, ENTRY + username.upper() + '\n', TYPE_DELAY) + + slowWrite(scr, '\n' + PASSWORD_PROMPT + '\n\n') + + if hardMode: + # use must enter the correct text to proceed + entry = '' + while entry.upper() != password.upper(): + if entry: + slowWrite(scr, PASSWORD_ERROR + '\n\n') + + slowWrite(scr, '> ') + entry = upperInput(scr, True) + else: + # input is entered for them + slowWrite(scr, '> ') + curses.napms(INPUT_PAUSE) + password_stars = HIDDEN_MASK * len(password) + slowWrite(scr, password_stars + '\n', TYPE_DELAY) + + curses.napms(500) -def beginLogin(): +def beginLogin(hardMode, username, password): """ Initialize curses and start the login process - Returns true if the user correctly guesses the password + + hardMode - boolean indicating whether the user has to enter the username + and password or if they are entered automatically + username - the username to log in + password - the password to log in + Returns true if hardMode == false or if the user entered the correct string """ - return curses.wrapper(runLogin) + res = curses.wrapper(runLogin, hardMode, username, password) + return res