diff --git a/src/phoenix.spec.ts b/src/phoenix.spec.ts new file mode 100644 index 0000000..d3cbc23 --- /dev/null +++ b/src/phoenix.spec.ts @@ -0,0 +1,27 @@ +import * as bios from "./phoenix"; + +describe("Test Phoenix BIOS", () => { + it("check correct passwords", () => { + expect(bios.phoenixSolver.calculateHash("abstoou")).toEqual(12345); + expect(bios.phoenixHPCompaqSolver.calculateHash("vnflm")).toEqual(12345); + expect(bios.phoenixFsiSolver.calculateHash("411113")).toEqual(12345); + expect(bios.phoenixFsiLSolver.calculateHash("362153")).toEqual(12345); + expect(bios.phoenixFsiPSolver.calculateHash("4465237")).toEqual(12345); + expect(bios.phoenixFsiSSolver.calculateHash("71669")).toEqual(12345); + expect(bios.phoenixFsiXSolver.calculateHash("739979")).toEqual(12345); + }); + + it("find password for hash 12345", () => { + function phoenixValidate(solver: bios.PhoenixSolver, hash: string): number { + return solver.calculateHash(solver(hash)[0]); + } + + expect(phoenixValidate(bios.phoenixSolver, "12345")).toEqual(12345); + expect(phoenixValidate(bios.phoenixHPCompaqSolver, "12345")).toEqual(12345); + expect(phoenixValidate(bios.phoenixFsiSolver, "12345")).toEqual(12345); + expect(phoenixValidate(bios.phoenixFsiLSolver, "12345")).toEqual(12345); + expect(phoenixValidate(bios.phoenixFsiPSolver, "12345")).toEqual(12345); + expect(phoenixValidate(bios.phoenixFsiSSolver, "12345")).toEqual(12345); + expect(phoenixValidate(bios.phoenixFsiXSolver, "12345")).toEqual(12345); + }); +}); diff --git a/src/phoenix.ts b/src/phoenix.ts new file mode 100644 index 0000000..af8a2f5 --- /dev/null +++ b/src/phoenix.ts @@ -0,0 +1,207 @@ +/* tslint:disable:no-bitwise */ +import { + asciiToKeyboardEnc, keyboardEncToAscii, makeSolver, 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; +} + +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[] = [0, 0, 0, 0, 0, 0, 0]; + + if (hash > 0x3FFF) { + return PhoenixErrors.BadHash; + } + + let kk = 0; + while (true) { + kk++; + if (kk > 50000) { + return PhoenixErrors.NotFound; + } + + generatePhoenixPassword(encodedPwd, characters); + for (let i = minLen; i <= maxLen; i++) { + if (badCRC16(encodedPwd.slice(0, i), salt) === hash) { + return keyboardEncToAscii(encodedPwd.slice(0, i)); + } + } + } +} + +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 +}); diff --git a/src/utils.ts b/src/utils.ts index c990d79..af55564 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -19,7 +19,7 @@ export interface Solver { keygen(code: string): string[]; } -const keyboardDict: {[key: number]: string} = { +export const keyboardDict: {[key: number]: string} = { 2: "1", 3: "2", 4: "3", 5: "4", 6: "5", 7: "6", 8: "7", 9: "8", 10: "9", 11: "0", 16: "q", 17: "w", 18: "e", 19: "r", 20: "t", 21: "y", 22: "u", 23: "i", 24: "o", 25: "p", 30: "a", 31: "s", 32: "d", 33: "f", @@ -27,6 +27,19 @@ const keyboardDict: {[key: number]: string} = { 47: "v", 48: "b", 49: "n", 50: "m" }; +// reverse scan code table +function generateReverseKeyDict(): {[key: string]: number} { + let result: {[key: string]: number} = {}; + for (let key in keyboardDict) { + if (keyboardDict.hasOwnProperty(key)) { + result[keyboardDict[key]] = parseInt(key, 10); + } + } + return result; +} + +export const reversedScanCodes = generateReverseKeyDict(); + /* Decode Keyboard code to Ascii symbol */ export function keyboardEncToAscii(inKey: number[]): string { let out = ""; @@ -44,6 +57,17 @@ export function keyboardEncToAscii(inKey: number[]): string { return out; } +export function asciiToKeyboardEnc(password: string): number[] { + return password.split("").map((c) => { + let code = reversedScanCodes[c]; + if (code === void 0) { + throw new Error("Undefined scan code"); + } else { + return code; + } + }); +} + function cleanSerial(serial: string): string { return serial.trim().replace(/-/gi, ""); }