Merge branch 'port-flowcontrol' of https://github.com/d98762625/CyberChef into d98762625-port-flowcontrol

This commit is contained in:
n1474335 2018-05-21 11:27:00 +00:00
commit 1472f82205
18 changed files with 4300 additions and 4361 deletions

6905
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,22 @@
/**
* Flow control functions
*
* @author d98762625 [d98762625@gmail.com]
* @copyright Crown Copyright 2018
* @license Apache-2.0
*
*/
/**
* Returns the index of a label.
*
* @private
* @param {Object} state - The current state of the recipe.
* @param {string} name - The label name to look for.
* @returns {number}
*/
export function getLabelIndex(name, state) {
return state.opList.findIndex((operation) => {
return (operation.name === "Label") && (name === operation.ingValues[0]);
});
}

View File

@ -0,0 +1,51 @@
/**
* @author n1474335 [n1474335@gmail.com]
* @copyright Crown Copyright 2018
* @license Apache-2.0
*/
import Operation from "../Operation";
/**
* Comment operation
*/
class Comment extends Operation {
/**
* Comment constructor
*/
constructor() {
super();
this.name = "Comment";
this.flowControl = true;
this.module = "Default";
this.description = "Provides a place to write comments within the flow of the recipe. This operation has no computational effect.";
this.inputType = "string";
this.outputType = "string";
this.args = [
{
"name": "",
"type": "text",
"value": ""
}
];
}
/**
* Comment operation.
*
* @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.
*/
run(state) {
return state;
}
}
export default Comment;

View File

@ -0,0 +1,89 @@
/**
* @author n1474335 [n1474335@gmail.com]
* @copyright Crown Copyright 2018
* @license Apache-2.0
*/
import Operation from "../Operation";
import Dish from "../Dish";
import { getLabelIndex } from "../lib/FlowControl";
/**
* Conditional Jump operation
*/
class ConditionalJump extends Operation {
/**
* ConditionalJump constructor
*/
constructor() {
super();
this.name = "Conditional Jump";
this.flowControl = true;
this.module = "Default";
this.description = "Conditionally jump forwards or backwards to the specified Label based on whether the data matches the specified regular expression.";
this.inputType = "string";
this.outputType = "string";
this.args = [
{
"name": "Match (regex)",
"type": "string",
"value": ""
},
{
"name": "Invert match",
"type": "boolean",
"value": false
},
{
"name": "Label name",
"type": "shortString",
"value": ""
},
{
"name": "Maximum jumps (if jumping backwards)",
"type": "number",
"value": 10
}
];
}
/**
* Conditional Jump operation.
*
* @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.
* @param {number} state.numJumps - The number of jumps taken so far.
* @returns {Object} The updated state of the recipe.
*/
async run(state) {
const ings = state.opList[state.progress].ingValues,
dish = state.dish,
regexStr = ings[0],
invert = ings[1],
label = ings[2],
maxJumps = ings[3],
jmpIndex = getLabelIndex(label, state);
if (state.numJumps >= maxJumps || jmpIndex === -1) {
return state;
}
if (regexStr !== "") {
const str = await dish.get(Dish.STRING);
const strMatch = str.search(regexStr) > -1;
if (!invert && strMatch || invert && !strMatch) {
state.progress = jmpIndex;
state.numJumps++;
}
}
return state;
}
}
export default ConditionalJump;

View File

@ -0,0 +1,121 @@
/**
* @author n1474335 [n1474335@gmail.com]
* @copyright Crown Copyright 2018
* @license Apache-2.0
*/
import Operation from "../Operation";
import Recipe from "../Recipe";
import Dish from "../Dish";
/**
* Fork operation
*/
class Fork extends Operation {
/**
* Fork constructor
*/
constructor() {
super();
this.name = "Fork";
this.flowControl = true;
this.module = "Default";
this.description = "Split the input data up based on the specified delimiter and run all subsequent operations on each branch separately.<br><br>For example, to decode multiple Base64 strings, enter them all on separate lines then add the 'Fork' and 'From Base64' operations to the recipe. Each string will be decoded separately.";
this.inputType = "string";
this.outputType = "string";
this.args = [
{
"name": "Split delimiter",
"type": "binaryShortString",
"value": "\\n"
},
{
"name": "Merge delimiter",
"type": "binaryShortString",
"value": "\\n"
},
{
"name": "Ignore errors",
"type": "boolean",
"value": false
}
];
}
/**
* Fork operation.
*
* @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 opList = state.opList,
inputType = opList[state.progress].inputType,
outputType = opList[state.progress].outputType,
input = await state.dish.get(inputType),
ings = opList[state.progress].ingValues,
splitDelim = ings[0],
mergeDelim = ings[1],
ignoreErrors = ings[2],
subOpList = [];
let inputs = [],
i;
if (input)
inputs = input.split(splitDelim);
// Create subOpList for each tranche to operate on
// (all remaining operations unless we encounter a Merge)
for (i = state.progress + 1; i < opList.length; i++) {
if (opList[i].name === "Merge" && !opList[i].disabled) {
break;
} else {
subOpList.push(opList[i]);
}
}
const recipe = new Recipe();
let output = "",
progress = 0;
state.forkOffset += state.progress + 1;
recipe.addOperations(subOpList);
// Take a deep(ish) copy of the ingredient values
const ingValues = subOpList.map(op => JSON.parse(JSON.stringify(op.ingValues)));
// Run recipe over each tranche
for (i = 0; i < inputs.length; i++) {
// Baseline ing values for each tranche so that registers are reset
subOpList.forEach((op, i) => {
op.ingValues = JSON.parse(JSON.stringify(ingValues[i]));
});
const dish = new Dish();
dish.set(inputs[i], inputType);
try {
progress = await recipe.execute(dish, 0, state);
} catch (err) {
if (!ignoreErrors) {
throw err;
}
progress = err.progress + 1;
}
output += await dish.get(outputType) + mergeDelim;
}
state.dish.set(output, outputType);
state.progress += progress;
return state;
}
}
export default Fork;

View File

@ -0,0 +1,69 @@
/**
* @author n1474335 [n1474335@gmail.com]
* @copyright Crown Copyright 2018
* @license Apache-2.0
*/
import Operation from "../Operation";
import { getLabelIndex } from "../lib/FlowControl";
/**
* Jump operation
*/
class Jump extends Operation {
/**
* Jump constructor
*/
constructor() {
super();
this.name = "Jump";
this.flowControl = true;
this.module = "Default";
this.description = "Jump forwards or backwards to the specified Label";
this.inputType = "string";
this.outputType = "string";
this.args = [
{
"name": "Label name",
"type": "string",
"value": ""
},
{
"name": "Maximum jumps (if jumping backwards)",
"type": "number",
"value": 10
}
];
}
/**
* Jump operation.
*
* @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.
* @param {number} state.numJumps - The number of jumps taken so far.
* @returns {Object} The updated state of the recipe.
*/
run(state) {
const ings = state.opList[state.progress].ingValues;
const label = ings[0];
const maxJumps = ings[1];
const jmpIndex = getLabelIndex(label, state);
if (state.numJumps >= maxJumps || jmpIndex === -1) {
return state;
}
state.progress = jmpIndex;
state.numJumps++;
return state;
}
}
export default Jump;

View File

@ -0,0 +1,50 @@
/**
* @author n1474335 [n1474335@gmail.com]
* @copyright Crown Copyright 2016
* @license Apache-2.0
*/
import Operation from "../Operation";
/**
* Label operation
*/
class Label extends Operation {
/**
* Label constructor
*/
constructor() {
super();
this.name = "Label";
this.flowControl = true;
this.module = "Default";
this.description = "Provides a location for conditional and fixed jumps to redirect execution to.";
this.inputType = "string";
this.outputType = "string";
this.args = [
{
"name": "Name",
"type": "shortString",
"value": ""
}
];
}
/**
* Label operation. For use with Jump and Conditional Jump
*
* @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.
*/
run(state) {
return state;
}
}
export default Label;

View File

@ -0,0 +1,46 @@
/**
* @author n1474335 [n1474335@gmail.com]
* @copyright Crown Copyright 2018
* @license Apache-2.0
*/
import Operation from "../Operation";
/**
* Merge operation
*/
class Merge extends Operation {
/**
* Merge constructor
*/
constructor() {
super();
this.name = "Merge";
this.flowControl = true;
this.module = "Default";
this.description = "Consolidate all branches back into a single trunk. The opposite of Fork.";
this.inputType = "string";
this.outputType = "string";
this.args = [];
}
/**
* Merge operation.
*
* @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.
*/
run(state) {
// No need to actually do anything here. The fork operation will
// merge when it sees this operation.
return state;
}
}
export default Merge;

View File

@ -0,0 +1,115 @@
/**
* @author n1474335 [n1474335@gmail.com]
* @copyright Crown Copyright 2018
* @license Apache-2.0
*/
import Operation from "../Operation";
import Dish from "../Dish";
/**
* Register operation
*/
class Register extends Operation {
/**
* Register constructor
*/
constructor() {
super();
this.name = "Register";
this.flowControl = true;
this.module = "Default";
this.description = "Extract data from the input and store it in registers which can then be passed into subsequent operations as arguments. Regular expression capture groups are used to select the data to extract.<br><br>To use registers in arguments, refer to them using the notation <code>$Rn</code> where n is the register number, starting at 0.<br><br>For example:<br>Input: <code>Test</code><br>Extractor: <code>(.*)</code><br>Argument: <code>$R0</code> becomes <code>Test</code><br><br>Registers can be escaped in arguments using a backslash. e.g. <code>\\$R0</code> would become <code>$R0</code> rather than <code>Test</code>.";
this.inputType = "string";
this.outputType = "string";
this.args = [
{
"name": "Extractor",
"type": "binaryString",
"value": "([\\s\\S]*)"
},
{
"name": "Case insensitive",
"type": "boolean",
"value": true
},
{
"name": "Multiline matching",
"type": "boolean",
"value": false
}
];
}
/**
* Register operation.
*
* @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;
const extractorStr = ings[0];
const i = ings[1];
const m = ings[2];
let modifiers = "";
if (i) modifiers += "i";
if (m) modifiers += "m";
const extractor = new RegExp(extractorStr, modifiers),
input = await state.dish.get(Dish.STRING),
registers = input.match(extractor);
if (!registers) return state;
if (ENVIRONMENT_IS_WORKER()) {
self.setRegisters(state.forkOffset + state.progress, state.numRegisters, registers.slice(1));
}
/**
* Replaces references to registers (e.g. $R0) with the contents of those registers.
*
* @param {string} str
* @returns {string}
*/
function replaceRegister(str) {
// Replace references to registers ($Rn) with contents of registers
return str.replace(/(\\*)\$R(\d{1,2})/g, (match, slashes, regNum) => {
const index = parseInt(regNum, 10) + 1;
if (index <= state.numRegisters || index >= state.numRegisters + registers.length)
return match;
if (slashes.length % 2 !== 0) return match.slice(1); // Remove escape
return slashes + registers[index - state.numRegisters];
});
}
// Step through all subsequent ops and replace registers in args with extracted content
for (let i = state.progress + 1; i < state.opList.length; i++) {
if (state.opList[i].disabled) continue;
let args = state.opList[i].ingValues;
args = args.map(arg => {
if (typeof arg !== "string" && typeof arg !== "object") return arg;
if (typeof arg === "object" && arg.hasOwnProperty("string")) {
arg.string = replaceRegister(arg.string);
return arg;
}
return replaceRegister(arg);
});
state.opList[i].ingValues = args;
}
state.numRegisters += registers.length - 1;
return state;
}
}
export default Register;

View File

@ -0,0 +1,45 @@
/**
* @author n1474335 [n1474335@gmail.com]
* @copyright Crown Copyright 2018
* @license Apache-2.0
*/
import Operation from "../Operation";
/**
* Return operation
*/
class Return extends Operation {
/**
* Return constructor
*/
constructor() {
super();
this.name = "Return";
this.flowControl = true;
this.module = "Default";
this.description = "End execution of operations at this point in the recipe.";
this.inputType = "string";
this.outputType = "string";
this.args = [];
}
/**
* Return operation.
*
* @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.
*/
run(state) {
state.progress = state.opList.length;
return state;
}
}
export default Return;

View File

@ -1,386 +0,0 @@
import Recipe from "./Recipe.js";
import Dish from "./Dish.js";
import Magic from "./lib/Magic.js";
import Utils from "./Utils.js";
/**
* Flow Control operations.
*
* @author n1474335 [n1474335@gmail.com]
* @copyright Crown Copyright 2016
* @license Apache-2.0
*
* @namespace
*/
const FlowControl = {
/**
* Fork operation.
*
* @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.
*/
runFork: async function(state) {
const opList = state.opList,
inputType = opList[state.progress].inputType,
outputType = opList[state.progress].outputType,
input = await state.dish.get(inputType),
ings = opList[state.progress].ingValues,
splitDelim = ings[0],
mergeDelim = ings[1],
ignoreErrors = ings[2],
subOpList = [];
let inputs = [],
i;
if (input)
inputs = input.split(splitDelim);
// Create subOpList for each tranche to operate on
// (all remaining operations unless we encounter a Merge)
for (i = state.progress + 1; i < opList.length; i++) {
if (opList[i].name === "Merge" && !opList[i].disabled) {
break;
} else {
subOpList.push(opList[i]);
}
}
const recipe = new Recipe();
let output = "",
progress = 0;
state.forkOffset += state.progress + 1;
recipe.addOperations(subOpList);
// Take a deep(ish) copy of the ingredient values
const ingValues = subOpList.map(op => JSON.parse(JSON.stringify(op.ingValues)));
// Run recipe over each tranche
for (i = 0; i < inputs.length; i++) {
log.debug(`Entering tranche ${i + 1} of ${inputs.length}`);
// Baseline ing values for each tranche so that registers are reset
subOpList.forEach((op, i) => {
op.ingValues = JSON.parse(JSON.stringify(ingValues[i]));
});
const dish = new Dish();
dish.set(inputs[i], inputType);
try {
progress = await recipe.execute(dish, 0, state);
} catch (err) {
if (!ignoreErrors) {
throw err;
}
progress = err.progress + 1;
}
output += await dish.get(outputType) + mergeDelim;
}
state.dish.set(output, outputType);
state.progress += progress;
return state;
},
/**
* Merge operation.
*
* @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.
*/
runMerge: function(state) {
// No need to actually do anything here. The fork operation will
// merge when it sees this operation.
return state;
},
/**
* Register operation.
*
* @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.
*/
runRegister: async function(state) {
const ings = state.opList[state.progress].ingValues,
extractorStr = ings[0],
i = ings[1],
m = ings[2];
let modifiers = "";
if (i) modifiers += "i";
if (m) modifiers += "m";
const extractor = new RegExp(extractorStr, modifiers),
input = await state.dish.get(Dish.STRING),
registers = input.match(extractor);
if (!registers) return state;
if (ENVIRONMENT_IS_WORKER()) {
self.setRegisters(state.forkOffset + state.progress, state.numRegisters, registers.slice(1));
}
/**
* Replaces references to registers (e.g. $R0) with the contents of those registers.
*
* @param {string} str
* @returns {string}
*/
function replaceRegister(str) {
// Replace references to registers ($Rn) with contents of registers
return str.replace(/(\\*)\$R(\d{1,2})/g, (match, slashes, regNum) => {
const index = parseInt(regNum, 10) + 1;
if (index <= state.numRegisters || index >= state.numRegisters + registers.length)
return match;
if (slashes.length % 2 !== 0) return match.slice(1); // Remove escape
return slashes + registers[index - state.numRegisters];
});
}
// Step through all subsequent ops and replace registers in args with extracted content
for (let i = state.progress + 1; i < state.opList.length; i++) {
if (state.opList[i].disabled) continue;
let args = state.opList[i].ingValues;
args = args.map(arg => {
if (typeof arg !== "string" && typeof arg !== "object") return arg;
if (typeof arg === "object" && arg.hasOwnProperty("string")) {
arg.string = replaceRegister(arg.string);
return arg;
}
return replaceRegister(arg);
});
state.opList[i].setIngValues(args);
}
state.numRegisters += registers.length - 1;
return state;
},
/**
* Jump operation.
*
* @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.
* @param {number} state.numJumps - The number of jumps taken so far.
* @returns {Object} The updated state of the recipe.
*/
runJump: function(state) {
const ings = state.opList[state.progress].ingValues,
label = ings[0],
maxJumps = ings[1],
jmpIndex = FlowControl._getLabelIndex(label, state);
if (state.numJumps >= maxJumps || jmpIndex === -1) {
log.debug("Maximum jumps reached or label cannot be found");
return state;
}
state.progress = jmpIndex;
state.numJumps++;
log.debug(`Jumping to label '${label}' at position ${jmpIndex} (jumps = ${state.numJumps})`);
return state;
},
/**
* Conditional Jump operation.
*
* @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.
* @param {number} state.numJumps - The number of jumps taken so far.
* @returns {Object} The updated state of the recipe.
*/
runCondJump: async function(state) {
const ings = state.opList[state.progress].ingValues,
dish = state.dish,
regexStr = ings[0],
invert = ings[1],
label = ings[2],
maxJumps = ings[3],
jmpIndex = FlowControl._getLabelIndex(label, state);
if (state.numJumps >= maxJumps || jmpIndex === -1) {
log.debug("Maximum jumps reached or label cannot be found");
return state;
}
if (regexStr !== "") {
const strMatch = await dish.get(Dish.STRING).search(regexStr) > -1;
if (!invert && strMatch || invert && !strMatch) {
state.progress = jmpIndex;
state.numJumps++;
log.debug(`Jumping to label '${label}' at position ${jmpIndex} (jumps = ${state.numJumps})`);
}
}
return state;
},
/**
* Return operation.
*
* @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.
*/
runReturn: function(state) {
state.progress = state.opList.length;
return state;
},
/**
* Comment operation.
*
* @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.
*/
runComment: function(state) {
return state;
},
/**
* Magic operation.
*
* @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.
*/
runMagic: async function(state) {
const ings = state.opList[state.progress].ingValues,
depth = ings[0],
intensive = ings[1],
extLang = ings[2],
dish = state.dish,
currentRecipeConfig = state.opList.map(op => op.getConfig()),
magic = new Magic(dish.get(Dish.ARRAY_BUFFER)),
options = await magic.speculativeExecution(depth, extLang, intensive);
let output = `<table
class='table table-hover table-condensed table-bordered'
style='table-layout: fixed;'>
<tr>
<th>Recipe (click to load)</th>
<th>Result snippet</th>
<th>Properties</th>
</tr>`;
/**
* 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, state.progress)
.concat(option.recipe)
.concat(currentRecipeConfig.slice(state.progress + 1)),
recipeURL = "recipe=" + Utils.encodeURIFragment(Utils.generatePrettyRecipe(recipeConfig));
let language = "",
fileType = "",
matchingOps = "",
useful = "",
entropy = `<span data-toggle="tooltip" data-container="body" title="Shannon Entropy is measured from 0 to 8. High entropy suggests encrypted or compressed data. Normal text is usually around 3.5 to 5.">Entropy: <span style="color: ${chooseColour(option.entropy)}">${option.entropy.toFixed(2)}</span></span>`,
validUTF8 = option.isUTF8 ? "<span data-toggle='tooltip' data-container='body' title='The data could be a valid UTF8 string based on its encoding.'>Valid UTF8</span>\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 = "<span data-toggle='tooltip' data-container='body' title='Based on a statistical comparison of the frequency of bytes in various languages. Ordered by likelihood.'>" +
"Possible languages:\n " + likelyLangs.map(lang => {
return Magic.codeToLanguage(lang.lang);
}).join("\n ") + "</span>\n";
}
if (option.fileType) {
fileType = `<span data-toggle="tooltip" data-container="body" title="Based on the presence of magic bytes.">File type: ${option.fileType.mime} (${option.fileType.ext})</span>\n`;
}
if (option.matchingOps.length) {
matchingOps = `Matching ops: ${[...new Set(option.matchingOps.map(op => op.op))].join(", ")}\n`;
}
if (option.useful) {
useful = "<span data-toggle='tooltip' data-container='body' title='This could be an operation that displays data in a useful way, such as rendering an image.'>Useful op detected</span>\n";
}
output += `<tr>
<td><a href="#${recipeURL}">${Utils.generatePrettyRecipe(option.recipe, true)}</a></td>
<td>${Utils.escapeHtml(Utils.printable(Utils.truncate(option.data, 99)))}</td>
<td>${language}${fileType}${matchingOps}${useful}${validUTF8}${entropy}</td>
</tr>`;
});
output += "</table><script type='application/javascript'>$('[data-toggle=\"tooltip\"]').tooltip()</script>";
if (!options.length) {
output = "Nothing of interest could be detected about the input data.\nHave you tried modifying the operation arguments?";
}
dish.set(output, Dish.HTML);
return state;
},
/**
* Returns the index of a label.
*
* @private
* @param {Object} state
* @param {string} name
* @returns {number}
*/
_getLabelIndex: function(name, state) {
for (let o = 0; o < state.opList.length; o++) {
const operation = state.opList[o];
if (operation.name === "Label"){
const ings = operation.ingValues;
if (name === ings[0]) {
return o;
}
}
}
return -1;
},
};
export default FlowControl;

View File

@ -38,7 +38,12 @@ import "./tests/operations/Checksum";
// import "./tests/operations/Compress";
// import "./tests/operations/Crypt";
// import "./tests/operations/DateTime";
// import "./tests/operations/FlowControl";
import "./tests/operations/Fork";
import "./tests/operations/Jump";
import "./tests/operations/ConditionalJump";
import "./tests/operations/Register";
import "./tests/operations/Comment";
import "./tests/operations/Hash";
import "./tests/operations/Hexdump";
// import "./tests/operations/Image";

View File

@ -0,0 +1,76 @@
/**
* Flow Control tests.
*
* @author tlwr [toby@toby.codes]
*
* @copyright Crown Copyright 2018
* @license Apache-2.0
*/
import TestRegister from "../../TestRegister";
const ALL_BYTES = [
"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f",
"\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f",
"\x20\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f",
"\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3d\x3e\x3f",
"\x40\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x4b\x4c\x4d\x4e\x4f",
"\x50\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a\x5b\x5c\x5d\x5e\x5f",
"\x60\x61\x62\x63\x64\x65\x66\x67\x68\x69\x6a\x6b\x6c\x6d\x6e\x6f",
"\x70\x71\x72\x73\x74\x75\x76\x77\x78\x79\x7a\x7b\x7c\x7d\x7e\x7f",
"\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f",
"\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f",
"\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf",
"\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf",
"\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf",
"\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf",
"\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef",
"\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff",
].join("");
TestRegister.addTests([
{
name: "Comment: nothing",
input: "",
expectedOutput: "",
recipeConfig: [
{
"op": "Comment",
"args": [""]
}
]
},
{
name: "Fork, Comment, Base64",
input: "cat\nsat\nmat",
expectedOutput: "Y2F0\nc2F0\nbWF0\n",
recipeConfig: [
{
"op": "Fork",
"args": ["\\n", "\\n", false]
},
{
"op": "Comment",
"args": ["Testing 123"]
},
{
"op": "To Base64",
"args": ["A-Za-z0-9+/="]
}
]
},
{
name: "Label, Comment: Complex content",
input: ALL_BYTES,
expectedOutput: ALL_BYTES,
recipeConfig: [
{
op: "Label",
args: [""]
},
{
op: "Comment",
args: [""]
}
]
}
]);

View File

@ -0,0 +1,93 @@
/**
* Conditional Jump tests
*
* @author tlwr [toby@toby.codes]
*
* @copyright Crown Copyright 2018
* @license Apache-2.0
*/
import TestRegister from "../../TestRegister";
TestRegister.addTests([
{
name: "Conditional Jump: Skips 0",
input: [
"should be changed",
].join("\n"),
expectedOutput: [
"YzJodmRXeGtJR0psSUdOb1lXNW5aV1E9"
].join("\n"),
recipeConfig: [
{
op: "Conditional Jump",
args: ["match", false, "", 0],
},
{
op: "To Base64",
args: ["A-Za-z0-9+/="],
},
{
op: "To Base64",
args: ["A-Za-z0-9+/="],
},
],
},
{
name: "Conditional Jump: Skips 1",
input: [
"should be changed",
].join("\n"),
// Expecting base32, not base64 output
expectedOutput: [
"ONUG65LMMQQGEZJAMNUGC3THMVSA====",
].join("\n"),
recipeConfig: [
{
op: "Conditional Jump",
args: ["should", false, "skip match", 10],
},
{
op: "To Base64",
args: ["A-Za-z0-9+/="],
},
{
op: "Label", args: ["skip match"],
},
{
op: "To Base32",
args: ["A-Z2-7="],
},
],
},
{
name: "Conditional Jump: Skips backwards",
input: [
"match",
].join("\n"),
expectedOutput: [
"f7cf556f7f4fc6635db8c314f7a81f2a",
].join("\n"),
recipeConfig: [
{
op: "Label",
args: ["back to the beginning"],
},
{
op: "Jump",
args: ["skip replace"],
},
{
op: "MD2",
args: [],
},
{
op: "Label",
args: ["skip replace"],
},
{
op: "Conditional Jump",
args: ["match", false, "back to the beginning", 10],
},
],
}
]);

View File

@ -1,391 +0,0 @@
/**
* Flow Control tests.
*
* @author tlwr [toby@toby.codes]
*
* @copyright Crown Copyright 2017
* @license Apache-2.0
*/
import TestRegister from "../../TestRegister";
const ALL_BYTES = [
"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f",
"\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f",
"\x20\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f",
"\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3d\x3e\x3f",
"\x40\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x4b\x4c\x4d\x4e\x4f",
"\x50\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a\x5b\x5c\x5d\x5e\x5f",
"\x60\x61\x62\x63\x64\x65\x66\x67\x68\x69\x6a\x6b\x6c\x6d\x6e\x6f",
"\x70\x71\x72\x73\x74\x75\x76\x77\x78\x79\x7a\x7b\x7c\x7d\x7e\x7f",
"\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f",
"\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f",
"\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf",
"\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf",
"\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf",
"\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf",
"\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef",
"\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff",
].join("");
TestRegister.addTests([
{
name: "Fork: nothing",
input: "",
expectedOutput: "",
recipeConfig: [
{
op: "Fork",
args: ["\n", "\n", false],
},
],
},
{
name: "Fork, Merge: nothing",
input: "",
expectedOutput: "",
recipeConfig: [
{
op: "Fork",
args: ["\n", "\n", false],
},
{
op: "Merge",
args: [],
},
],
},
{
name: "Fork, (expect) Error, Merge",
input: "1.1\n2.5\na\n3.4",
expectedError: true,
recipeConfig: [
{
op: "Fork",
args: ["\n", "\n", false],
},
{
op: "Object Identifier to Hex",
args: [],
},
{
op: "Merge",
args: [],
},
],
},
{
name: "Fork, Conditional Jump, Encodings",
input: "Some data with a 1 in it\nSome data with a 2 in it",
expectedOutput: "U29tZSBkYXRhIHdpdGggYSAxIGluIGl0\n53 6f 6d 65 20 64 61 74 61 20 77 69 74 68 20 61 20 32 20 69 6e 20 69 74\n",
recipeConfig: [
{"op": "Fork", "args": ["\\n", "\\n", false]},
{"op": "Conditional Jump", "args": ["1", false, "skipReturn", "10"]},
{"op": "To Hex", "args": ["Space"]},
{"op": "Return", "args": []},
{"op": "Label", "args": ["skipReturn"]},
{"op": "To Base64", "args": ["A-Za-z0-9+/="]}
]
},
{
name: "Jump: Empty Label",
input: [
"should be changed",
].join("\n"),
expectedOutput: [
"should be changed was changed",
].join("\n"),
recipeConfig: [
{
op: "Jump",
args: ["", 10],
},
{
op: "Find / Replace",
args: [
{
"option": "Regex",
"string": "should be changed"
},
"should be changed was changed",
true,
true,
true,
],
},
],
},
{
name: "Jump: skips 1",
input: [
"shouldnt be changed",
].join("\n"),
expectedOutput: [
"shouldnt be changed",
].join("\n"),
recipeConfig: [
{
op: "Jump",
args: ["skipReplace", 10],
},
{
op: "Find / Replace",
args: [
{
"option": "Regex",
"string": "shouldnt be changed"
},
"shouldnt be changed was changed",
true,
true,
true,
],
},
{
op: "Label",
args: ["skipReplace"]
},
],
},
{
name: "Conditional Jump: Skips 0",
input: [
"match",
"should be changed 1",
"should be changed 2",
].join("\n"),
expectedOutput: [
"match",
"should be changed 1 was changed",
"should be changed 2 was changed"
].join("\n"),
recipeConfig: [
{
op: "Conditional Jump",
args: ["match", false, "", 0],
},
{
op: "Find / Replace",
args: [
{
"option": "Regex",
"string": "should be changed 1"
},
"should be changed 1 was changed",
true,
true,
true,
],
},
{
op: "Find / Replace",
args: [
{
"option": "Regex",
"string": "should be changed 2"
},
"should be changed 2 was changed",
true,
true,
true,
],
},
],
},
{
name: "Comment: nothing",
input: "",
expectedOutput: "",
recipeConfig: [
{
"op": "Comment",
"args": [""]
}
]
},
{
name: "Fork, Comment, Base64",
input: "cat\nsat\nmat",
expectedOutput: "Y2F0\nc2F0\nbWF0\n",
recipeConfig: [
{
"op": "Fork",
"args": ["\\n", "\\n", false]
},
{
"op": "Comment",
"args": ["Testing 123"]
},
{
"op": "To Base64",
"args": ["A-Za-z0-9+/="]
}
]
},
{
name: "Conditional Jump: Skips 1",
input: [
"match",
"should not be changed",
"should be changed",
].join("\n"),
expectedOutput: [
"match",
"should not be changed",
"should be changed was changed"
].join("\n"),
recipeConfig: [
{
op: "Conditional Jump",
args: ["match", false, "skip match", 10],
},
{
op: "Find / Replace",
args: [
{
"option": "Regex",
"string": "should not be changed"
},
"should not be changed was changed",
true,
true,
true,
],
},
{
op: "Label", args: ["skip match"],
},
{
op: "Find / Replace",
args: [
{
"option": "Regex",
"string": "should be changed"
},
"should be changed was changed",
true,
true,
true,
],
},
],
},
{
name: "Conditional Jump: Skips backwards",
input: [
"match",
].join("\n"),
expectedOutput: [
"replaced",
].join("\n"),
recipeConfig: [
{
op: "Label",
args: ["back to the beginning"],
},
{
op: "Jump",
args: ["skip replace"],
},
{
op: "Find / Replace",
args: [
{
"option": "Regex",
"string": "match"
},
"replaced",
true,
true,
true,
],
},
{
op: "Label",
args: ["skip replace"],
},
{
op: "Conditional Jump",
args: ["match", false, "back to the beginning", 10],
},
],
},
{
name: "Register: RC4 key",
input: "http://malwarez.biz/beacon.php?key=0e932a5c&data=8db7d5ebe38663a54ecbb334e3db11",
expectedOutput: "All the secrets",
recipeConfig: [
{
op: "Register",
args: ["key=([\\da-f]*)", true, false]
},
{
op: "Find / Replace",
args: [
{
"option": "Regex",
"string": ".*data=(.*)"
}, "$1", true, false, true
]
},
{
op: "RC4",
args: [
{
"option": "Hex",
"string": "$R0"
}, "Hex", "Latin1"
]
}
]
},
{
name: "Register: AES key",
input: "51e201d463698ef5f717f71f5b4712af20be674b3bff53d38546396ee61daac4908e319ca3fcf7089bfb6b38ea99e781d26e577ba9dd6f311a39420b8978e93014b042d44726caedf5436eaf652429c0df94b521676c7c2ce812097c277273c7c72cd89aec8d9fb4a27586ccf6aa0aee224c34ba3bfdf7aeb1ddd477622b91e72c9e709ab60f8daf731ec0cc85ce0f746ff1554a5a3ec291ca40f9e629a872592d988fdd834534aba79c1ad1676769a7c010bf04739ecdb65d95302371d629d9e37e7b4a361da468f1ed5358922d2ea752dd11c366f3017b14aa011d2af03c44f95579098a15e3cf9b4486f8ffe9c239f34de7151f6ca6500fe4b850c3f1c02e801caf3a24464614e42801615b8ffaa07ac8251493ffda7de5ddf3368880c2b95b030f41f8f15066add071a66cf60e5f46f3a230d397b652963a21a53f",
expectedOutput: `"You know," said Arthur, "it's at times like this, when I'm trapped in a Vogon airlock with a man from Betelgeuse, and about to die of asphyxiation in deep space that I really wish I'd listened to what my mother told me when I was young."
"Why, what did she tell you?"
"I don't know, I didn't listen."`,
recipeConfig: [
{
op: "Register",
args: ["(.{32})", true, false]
},
{
op: "Drop bytes",
args: [0, 32, false]
},
{
op: "AES Decrypt",
args: [
{
"option": "Hex",
"string": "1748e7179bd56570d51fa4ba287cc3e5"
},
{
"option": "Hex",
"string": "$R0"
},
"CTR", "Hex", "Raw",
{
"option": "Hex",
"string": ""
}
]
}
]
},
{
name: "Label, Comment: Complex content",
input: ALL_BYTES,
expectedOutput: ALL_BYTES,
recipeConfig: [
{
op: "Label",
args: [""]
},
{
op: "Comment",
args: [""]
}
]
},
]);

View File

@ -0,0 +1,70 @@
/**
* Fork tests
*
* @author tlwr [toby@toby.codes]
*
* @copyright Crown Copyright 2018
* @license Apache-2.0
*/
import TestRegister from "../../TestRegister";
TestRegister.addTests([
{
name: "Fork: nothing",
input: "",
expectedOutput: "",
recipeConfig: [
{
op: "Fork",
args: ["\n", "\n", false],
},
],
},
{
name: "Fork, Merge: nothing",
input: "",
expectedOutput: "",
recipeConfig: [
{
op: "Fork",
args: ["\n", "\n", false],
},
{
op: "Merge",
args: [],
},
],
},
{
name: "Fork, (expect) Error, Merge",
input: "1,2,3,4\n\n3,4,5,6",
expectedOutput: "Incorrect number of sets, perhaps you need to modify the sample delimiter or add more samples?",
recipeConfig: [
{
op: "Fork",
args: ["\n\n", "\n\n", false],
},
{
op: "Set Union",
args: ["\n\n", ","],
},
{
op: "Merge",
args: [],
},
],
},
{
name: "Fork, Conditional Jump, Encodings",
input: "Some data with a 1 in it\nSome data with a 2 in it",
expectedOutput: "U29tZSBkYXRhIHdpdGggYSAxIGluIGl0\n53 6f 6d 65 20 64 61 74 61 20 77 69 74 68 20 61 20 32 20 69 6e 20 69 74\n",
recipeConfig: [
{"op": "Fork", "args": ["\\n", "\\n", false]},
{"op": "Conditional Jump", "args": ["1", false, "skipReturn", "10"]},
{"op": "To Hex", "args": ["Space"]},
{"op": "Return", "args": []},
{"op": "Label", "args": ["skipReturn"]},
{"op": "To Base64", "args": ["A-Za-z0-9+/="]}
]
}
]);

View File

@ -0,0 +1,54 @@
/**
* Jump tests
*
* @author tlwr [toby@toby.codes]
*
* @copyright Crown Copyright 2018
* @license Apache-2.0
*/
import TestRegister from "../../TestRegister";
TestRegister.addTests([
{
name: "Jump: Empty Label",
input: [
"should be changed",
].join("\n"),
expectedOutput: [
"c2hvdWxkIGJlIGNoYW5nZWQ=",
].join("\n"),
recipeConfig: [
{
op: "Jump",
args: ["", 10],
},
{
op: "To Base64",
args: ["A-Za-z0-9+/="],
},
],
},
{
name: "Jump: skips 1",
input: [
"shouldnt be changed",
].join("\n"),
expectedOutput: [
"shouldnt be changed",
].join("\n"),
recipeConfig: [
{
op: "Jump",
args: ["skipReplace", 10],
},
{
op: "To Base64",
args: ["A-Za-z0-9+/="],
},
{
op: "Label",
args: ["skipReplace"]
},
],
}
]);

View File

@ -0,0 +1,71 @@
/**
* Register tests
*
* @author tlwr [toby@toby.codes]
*
* @copyright Crown Copyright 2018
* @license Apache-2.0
*/
import TestRegister from "../../TestRegister";
TestRegister.addTests([
{
name: "Register: RC4 key",
input: "http://malwarez.biz/beacon.php?key=0e932a5c&data=8db7d5ebe38663a54ecbb334e3db11",
expectedOutput: "zNu5y53uBoU2rm7qhq9ijjnVHSlJ9PJ/zpp+xL/to8qIBzkDwKzUNQ==",
recipeConfig: [
{
op: "Register",
args: ["key=([\\da-f]*)", true, false]
},
{
op: "RC4",
args: [
{
"option": "Hex",
"string": "$R0"
}, "Hex", "Latin1"
]
},
{
op: "To Base64",
args: ["A-Za-z0-9+/="]
}
]
},
{
name: "Register: AES key",
input: "51e201d463698ef5f717f71f5b4712af20be674b3bff53d38546396ee61daac4908e319ca3fcf7089bfb6b38ea99e781d26e577ba9dd6f311a39420b8978e93014b042d44726caedf5436eaf652429c0df94b521676c7c2ce812097c277273c7c72cd89aec8d9fb4a27586ccf6aa0aee224c34ba3bfdf7aeb1ddd477622b91e72c9e709ab60f8daf731ec0cc85ce0f746ff1554a5a3ec291ca40f9e629a872592d988fdd834534aba79c1ad1676769a7c010bf04739ecdb65d95302371d629d9e37e7b4a361da468f1ed5358922d2ea752dd11c366f3017b14aa011d2af03c44f95579098a15e3cf9b4486f8ffe9c239f34de7151f6ca6500fe4b850c3f1c02e801caf3a24464614e42801615b8ffaa07ac8251493ffda7de5ddf3368880c2b95b030f41f8f15066add071a66cf60e5f46f3a230d397b652963a21a53f",
expectedOutput: `"You know," said Arthur, "it's at times like this, when I'm trapped in a Vogon airlock with a man from Betelgeuse, and about to die of asphyxiation in deep space that I really wish I'd listened to what my mother told me when I was young."
"Why, what did she tell you?"
"I don't know, I didn't listen."`,
recipeConfig: [
{
op: "Register",
args: ["(.{32})", true, false]
},
{
op: "Drop bytes",
args: [0, 32, false]
},
{
op: "AES Decrypt",
args: [
{
"option": "Hex",
"string": "1748e7179bd56570d51fa4ba287cc3e5"
},
{
"option": "Hex",
"string": "$R0"
},
"CTR", "Hex", "Raw",
{
"option": "Hex",
"string": ""
}
]
}
]
}
]);