/** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2016 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import Utils from "../Utils.mjs"; import Dish from "../Dish.mjs"; import MagicLib from "../lib/Magic.mjs"; /** * Magic operation */ class Magic extends Operation { /** * Magic constructor */ constructor() { super(); this.name = "Magic"; this.flowControl = true; this.module = "Default"; this.description = "The Magic operation attempts to detect various properties of the input data and suggests which operations could help to make more sense of it.

Options
Depth: If an operation appears to match the data, it will be run and the result will be analysed further. This argument controls the maximum number of levels of recursion.

Intensive mode: When this is turned on, various operations like XOR, bit rotates, and character encodings are brute-forced to attempt to detect valid data underneath. To improve performance, only the first 100 bytes of the data is brute-forced.

Extensive language support: At each stage, the relative byte frequencies of the data will be compared to average frequencies for a number of languages. The default set consists of ~40 of the most commonly used languages on the Internet. The extensive list consists of 284 languages and can result in many languages matching the data if their byte frequencies are similar.

Optionally enter a regular expression to match a string you expect to find to filter results (crib)."; this.infoURL = "https://github.com/gchq/CyberChef/wiki/Automatic-detection-of-encoded-data-using-CyberChef-Magic"; this.inputType = "ArrayBuffer"; this.outputType = "JSON"; this.presentType = "html"; this.args = [ { "name": "Depth", "type": "number", "value": 3 }, { "name": "Intensive mode", "type": "boolean", "value": false }, { "name": "Extensive language support", "type": "boolean", "value": false }, { "name": "Crib (known plaintext string or regex)", "type": "string", "value": "" } ]; } /** * @param {Object} state - The current state of the recipe. * @param {number} state.progress - The current position in the recipe. * @param {Dish} state.dish - The Dish being operated on. * @param {Operation[]} state.opList - The list of operations in the recipe. * @returns {Object} The updated state of the recipe. */ async run(state) { const ings = state.opList[state.progress].ingValues, [depth, intensive, extLang, crib] = ings, dish = state.dish, magic = new MagicLib(await dish.get(Dish.ARRAY_BUFFER)), cribRegex = (crib && crib.length) ? new RegExp(crib, "i") : null; let options = await magic.speculativeExecution(depth, extLang, intensive, [], false, cribRegex); // Filter down to results which matched the crib if (cribRegex) { options = options.filter(option => option.matchesCrib); } // Record the current state for use when presenting this.state = state; dish.set(options, Dish.JSON); return state; } /** * Displays Magic results in HTML for web apps. * * @param {JSON} options * @returns {html} */ present(options) { const currentRecipeConfig = this.state.opList.map(op => op.config); let output = ``; /** * Returns a CSS colour value based on an integer input. * * @param {number} val * @returns {string} */ function chooseColour(val) { if (val < 3) return "green"; if (val < 5) return "goldenrod"; return "red"; } options.forEach(option => { // Construct recipe URL // Replace this Magic op with the generated recipe const recipeConfig = currentRecipeConfig.slice(0, this.state.progress) .concat(option.recipe) .concat(currentRecipeConfig.slice(this.state.progress + 1)), recipeURL = "recipe=" + Utils.encodeURIFragment(Utils.generatePrettyRecipe(recipeConfig)); let language = "", fileType = "", matchingOps = "", useful = ""; const entropy = `Entropy: ${option.entropy.toFixed(2)}`, validUTF8 = option.isUTF8 ? "Valid UTF8\n" : ""; if (option.languageScores[0].probability > 0) { let likelyLangs = option.languageScores.filter(l => l.probability > 0); if (likelyLangs.length < 1) likelyLangs = [option.languageScores[0]]; language = "" + "Possible languages:\n " + likelyLangs.map(lang => { return MagicLib.codeToLanguage(lang.lang); }).join("\n ") + "\n"; } if (option.fileType) { fileType = `File type: ${option.fileType.mime} (${option.fileType.ext})\n`; } if (option.matchingOps.length) { matchingOps = `Matching ops: ${[...new Set(option.matchingOps.map(op => op.op))].join(", ")}\n`; } if (option.useful) { useful = "Useful op detected\n"; } output += ``; }); output += "
Recipe (click to load) Result snippet Properties
${Utils.generatePrettyRecipe(option.recipe, true)} ${Utils.escapeHtml(Utils.escapeWhitespace(Utils.truncate(option.data, 99)))} ${language}${fileType}${matchingOps}${useful}${validUTF8}${entropy}
"; if (!options.length) { output = "Nothing of interest could be detected about the input data.\nHave you tried modifying the operation arguments?"; } return output; } } export default Magic;