/** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2016 * @license Apache-2.0 */ import Operation from "../Operation"; import Utils from "../Utils"; import Dish from "../Dish"; import MagicLib from "../lib/Magic"; /** * 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."; 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 } ]; } /** * @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] = ings, dish = state.dish, magic = new MagicLib(await dish.get(Dish.ARRAY_BUFFER)), options = await magic.speculativeExecution(depth, extLang, intensive); // 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.printable(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;