/* tslint:disable:no-bitwise */ import { asciiToKeyboardEnc, keyboardEncToAscii, reversedScanCodes, Solver } from "./utils"; export const enum PhoenixErrors { BadHash, NotFound } export interface PhoenixInfo { shift: number; salt: number; dictionary: string[]; minLen: number; maxLen: number; } export interface PhoenixBios { name: string; description?: string; shift?: number; salt?: number; dictionary?: string[]; minLen?: number; maxLen?: number; } export interface PhoenixSolver extends Solver { readonly info: PhoenixInfo; calculateHash(password: string): number; } // Without zero const digitsOnly: string[] = "123456789".split(""); const lettersOnly: string[] = "abcdefghijklmnopqrstuvwxyz".split(""); // const alphaNumeric: string[] = lettersOnly.concat(digitsOnly); const defaultPhoenix: PhoenixInfo = { shift: 0, salt: 0, dictionary: lettersOnly, minLen: 3, maxLen: 7 }; /* The phoenix implementation of the CRC-16 contains a rather severe bug * quartering the image space of the function: both the first and second MSB * are always zero regardless of the input. * For a working implementation, you'd have to change the polynom from 0x2001 * to e.g. 0xA001. */ function badCRC16(pwd: number[], salt: number = 0): number { let hash = salt; // tslint:disable-next-line:prefer-for-of for (let c = 0; c < pwd.length; c++) { hash ^= pwd[c]; for (let i = 8; i--;) { if (hash & 1) { hash = (hash >> 1) ^ 0x2001; } else { hash = (hash >> 1); } } } return hash; } /* * Modified version of badCRC16 to speedup bruteForce. * Returns pasword length if it matches required hash * or -1 if it isn't matches requiredHash. */ function searchBadCRC16(pwd: number[], salt: number, requiredHash: number, minLen: number): number { minLen--; let hash = salt; // tslint:disable-next-line:prefer-for-of for (let c = 0; c < pwd.length; c++) { hash ^= pwd[c]; for (let i = 8; i--;) { if (hash & 1) { hash = (hash >> 1) ^ 0x2001; } else { hash = (hash >> 1); } } if (c >= minLen && hash === requiredHash) { return c + 1; } } return -1; // not found } function generatePhoenixPassword(encodedPwd: number[], characters: string[] = lettersOnly): void { let rnd = Math.random() * characters.length; for (let i = 0; i < encodedPwd.length; i++) { let index = Math.floor(rnd % characters.length); encodedPwd[i] = reversedScanCodes[characters[index]]; rnd *= encodedPwd.length; } } function bruteForce(hash: number, salt: number = 0, characters: string[] = lettersOnly, minLen: number = 3, maxLen: number = 7): string | PhoenixErrors { let encodedPwd: number[] = []; for (let i = 0; i < maxLen; i++) { encodedPwd.push(0); } if (hash > 0x3FFF) { return PhoenixErrors.BadHash; } let kk = 0; while (true) { kk++; if (kk > 7000000) { return PhoenixErrors.NotFound; } generatePhoenixPassword(encodedPwd, characters); let found = searchBadCRC16(encodedPwd, salt, hash, minLen); if (found !== -1) { return keyboardEncToAscii(encodedPwd.slice(0, found)); } } } function makePhoenixSolver(description?: PhoenixBios): PhoenixSolver { if (description === void 0) { description = {} as PhoenixBios; } for (let key in defaultPhoenix) { if ((description as any)[key] === void 0) { (description as any)[key] = (defaultPhoenix as any)[key]; } } const info: PhoenixInfo = { shift: description.shift as number, salt: description.salt as number, dictionary: description.dictionary as string[], minLen: description.minLen as number, maxLen: description.maxLen as number }; let keygen = (code: string) => { let password = bruteForce(parseInt(code, 10) + info.shift, info.salt, info.dictionary, info.minLen, info.maxLen); if (typeof password === "string") { return [password]; } else { return []; } }; let cleaner = (code: string): string => code.trim().replace(/[-\s]/gi, ""); let validator = (s: string): boolean => /^[0-9]{5}$/i.test(s); let calculateHash = (password: string): number => { let pwd = asciiToKeyboardEnc(password); return badCRC16(pwd, info.salt) - info.shift; }; let solver: any = (code: string): string[] => { let cleanCode = cleaner(code); if (validator(cleanCode)) { return keygen(cleanCode); } else { return []; } }; solver.biosName = description.name; solver.validator = validator; solver.cleaner = cleaner; solver.keygen = keygen; solver.examples = ["12345"]; solver.info = info; solver.calculateHash = calculateHash; if (description.description) { solver.description = description; } return solver as PhoenixSolver; } export let phoenixSolver = makePhoenixSolver({ name: "Generic Phoenix" }); export let phoenixHPCompaqSolver = makePhoenixSolver({ name: "HP/Compaq Phoenix BIOS", salt: 17232 }); export let phoenixFsiSolver = makePhoenixSolver({ name: "Fujitsu-Siemens Phoenix", salt: 65, dictionary: digitsOnly }); export let phoenixFsiLSolver = makePhoenixSolver({ name: "Fujitsu-Siemens (model L) Phoenix", shift: 1, salt: "L".charCodeAt(0), dictionary: digitsOnly }); export let phoenixFsiPSolver = makePhoenixSolver({ name: "Fujitsu-Siemens (model P) Phoenix", shift: 1, salt: "P".charCodeAt(0), dictionary: digitsOnly }); export let phoenixFsiSSolver = makePhoenixSolver({ name: "Fujitsu-Siemens (model S) Phoenix", shift: 1, salt: "S".charCodeAt(0), dictionary: digitsOnly }); export let phoenixFsiXSolver = makePhoenixSolver({ name: "Fujitsu-Siemens (model X) Phoenix", shift: 1, salt: "X".charCodeAt(0), dictionary: digitsOnly });