mirror of
https://github.com/gchq/CyberChef.git
synced 2025-05-07 06:57:12 -04:00
Add files via upload
This commit is contained in:
parent
429b6ee9de
commit
a098893595
6 changed files with 615 additions and 0 deletions
208
src/core/operations/gaijinatByteAnalyser.mjs
Normal file
208
src/core/operations/gaijinatByteAnalyser.mjs
Normal file
|
@ -0,0 +1,208 @@
|
|||
/**
|
||||
* @author gaijinat [web@gaijin.at]
|
||||
* @copyright Crown Copyright 2023
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation.mjs";
|
||||
|
||||
/**
|
||||
* ByteAnalyser operation
|
||||
*/
|
||||
class ByteAnalyser extends Operation {
|
||||
|
||||
/**
|
||||
* ByteAnalyser constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "ByteAnalyser";
|
||||
this.module = "Default";
|
||||
this.description = "Analyses the bytes in the input and displays statistics about them.<br><br>The histogram shows the distribution of the individual bytes.";
|
||||
this.infoURL = "";
|
||||
this.inputType = "ArrayBuffer";
|
||||
this.outputType = "JSON";
|
||||
this.presentType = "html";
|
||||
this.args = [
|
||||
{
|
||||
"name": "Show 0%s",
|
||||
"type": "boolean",
|
||||
"value": false
|
||||
},
|
||||
{
|
||||
"name": "Sort by count",
|
||||
"type": "boolean",
|
||||
"value": true
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {ArrayBuffer} input
|
||||
* @param {Object[]} args
|
||||
* @returns {JSON}
|
||||
*/
|
||||
run(input, args) {
|
||||
const data = new Uint8Array(input);
|
||||
const byteTable = [],
|
||||
byteCount = data.length;
|
||||
|
||||
let byteRepresented = 0,
|
||||
byteRepresentedPercent = 0.0,
|
||||
byteNotRepresented = 256,
|
||||
byteNotRepresentedPercent = 0.0,
|
||||
asciiCharsRepresented = "",
|
||||
maxCount = 0,
|
||||
i;
|
||||
|
||||
// Initialisation of byte information data
|
||||
for (i = 0; i < 256; i++) {
|
||||
byteTable[i] = {
|
||||
dec: i,
|
||||
char: this.printableChar(i),
|
||||
hex: i.toString(16).toUpperCase().padStart(2, "0"),
|
||||
bin: i.toString(2).padStart(8, "0"),
|
||||
count: 0,
|
||||
percent: 0.0,
|
||||
proportion: 0.0,
|
||||
};
|
||||
}
|
||||
|
||||
// Count bytes in data
|
||||
for (i = 0; i < byteCount; ++i) {
|
||||
byteTable[data[i]].count++;
|
||||
}
|
||||
|
||||
// Get the count of the most represented byte (100% in statistics)
|
||||
for (i = 0; i < byteTable.length; ++i) {
|
||||
if (byteTable[i].count > maxCount) maxCount = byteTable[i].count;
|
||||
}
|
||||
|
||||
// Calculate percentage occurrence of bytes
|
||||
const divPercent = (100 / byteCount);
|
||||
const divProportion = (100 / maxCount);
|
||||
for (i = 0; i < byteTable.length; ++i) {
|
||||
byteTable[i].percent = (byteTable[i].count * divPercent).toFixed(4);
|
||||
byteTable[i].proportion = (byteTable[i].count * divProportion).toFixed(2);
|
||||
|
||||
if (byteTable[i].count > 0) {
|
||||
// Collect number of represented bytes
|
||||
byteRepresented++;
|
||||
// Collect represented printable ASCII characters
|
||||
if ((i >= 32) && (i <= 127)) {
|
||||
asciiCharsRepresented += byteTable[i].char;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
byteRepresentedPercent = (byteRepresented * (100 / 256)).toFixed(2);
|
||||
byteNotRepresented = 256 - byteRepresented;
|
||||
byteNotRepresentedPercent = (byteNotRepresented * (100 / 256)).toFixed(2);
|
||||
|
||||
// console.log(byteTable);
|
||||
|
||||
return {
|
||||
"byteTable": byteTable,
|
||||
"byteCount": byteCount,
|
||||
"byteRepresented": byteRepresented,
|
||||
"byteRepresentedPercent": byteRepresentedPercent,
|
||||
"byteNotRepresented": byteNotRepresented,
|
||||
"byteNotRepresentedPercent": byteNotRepresentedPercent,
|
||||
"asciiCharsRepresented": asciiCharsRepresented,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays the statistics for web apps.
|
||||
*
|
||||
* @param {json} statistics
|
||||
* @returns {html}
|
||||
*/
|
||||
present(stat, args) {
|
||||
const [showZeros, sortCount] = args;
|
||||
|
||||
let output = "",
|
||||
i;
|
||||
|
||||
const clsc = ' class="text-center" style="background-color: var(--secondary-background-colour);"',
|
||||
clsr = ' class="text-right"';
|
||||
|
||||
// Histogram
|
||||
output += '<div style="border:1px solid var(--table-border-colour);">';
|
||||
output += '<table style="border:0; table-layout:fixed; border-collapse:collapse; border-spacing:0; padding:0; width:100%;">';
|
||||
for (i = 0; i < 256; i++) {
|
||||
output += '<td title="';
|
||||
output += `Character ${stat.byteTable[i].dec.toString()} (0x${stat.byteTable[i].hex}) "${this.htmlEntities(stat.byteTable[i].char)}"\nCount: ${stat.byteTable[i].count.toLocaleString("en")} (${stat.byteTable[i].percent.toLocaleString("en")}%)`;
|
||||
output += '" style="vertical-align:bottom; padding:0; background-color: var(--';
|
||||
if (i % 2) output += "primary"; else output += "secondary";
|
||||
output += `-background-colour);"><div style="height:${(stat.byteTable[i].proportion * 2).toString()}px;background-color:var(--primary-font-colour);"></div></td>`;
|
||||
}
|
||||
output += "</tr></table></div>\n";
|
||||
|
||||
// Overview
|
||||
output += '<table class="table table-hover table-sm table-bordered" style="table-layout:fixed; width:auto;">';
|
||||
output += `<tr><td${clsr}>Number of bytes total:</td><td colspan="2"><b>${stat.byteCount.toLocaleString("en")}</b></td></tr>`;
|
||||
output += `<tr><td${clsr}>Number of bytes represented:</td><td${clsr}><b>${stat.byteRepresented.toLocaleString("en")}</b></td><td${clsr}>(${stat.byteRepresentedPercent.toLocaleString("en")}%)</td></tr>`;
|
||||
output += `<tr><td${clsr}>Number of bytes not represented:</td><td${clsr}><b>${stat.byteNotRepresented.toLocaleString("en")}</b></td><td${clsr}>(${stat.byteNotRepresentedPercent.toLocaleString("en")}%)</td></tr>`;
|
||||
output += "</table>\n";
|
||||
|
||||
output += "<p><b>Represented printable ASCII characters:</b><br>" + this.htmlEntities(stat.asciiCharsRepresented) + "</p>\n";
|
||||
|
||||
// Details
|
||||
if (sortCount) {
|
||||
stat.byteTable.sort(this.compareByteTableItemsCount);
|
||||
}
|
||||
output += '<table class="table table-hover table-sm table-bordered" style="table-layout:fixed; width:auto;">';
|
||||
output += `<tr><th${clsc}>Binary</th><th${clsc}>Hex</th><th${clsc}>Code</th><th${clsc}>Char</th><th${clsc}>Count</th><th${clsc}>Percent</th></tr>`;
|
||||
for (i = 0; i < 256; i++) {
|
||||
if (!showZeros && (stat.byteTable[i].count === 0)) continue;
|
||||
output += `<tr><td${clsr}>${stat.byteTable[i].bin}</td><td${clsr}>${stat.byteTable[i].hex}</td>`;
|
||||
output += `<td${clsr}>${stat.byteTable[i].dec.toString()}</td><td${clsc}><b>${this.htmlEntities(stat.byteTable[i].char)}</b></td>`;
|
||||
output += `<td${clsr}>${stat.byteTable[i].count.toLocaleString("en")}</td><td${clsr}>${stat.byteTable[i].percent.toLocaleString("en")}</td></tr>`;
|
||||
}
|
||||
output += "</table>\n";
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Gets the printable character for a byte.
|
||||
*
|
||||
* @param {UInt8} charCode
|
||||
* @returns {html}
|
||||
*/
|
||||
printableChar(charCode) {
|
||||
if (charCode < 32) return " ";
|
||||
if (charCode === 127) return " ";
|
||||
|
||||
return String.fromCharCode(charCode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compare byteTable items by count.
|
||||
*/
|
||||
compareByteTableItemsCount(a, b) {
|
||||
if (a.count > b.count) {
|
||||
return -1;
|
||||
} else if (a.count < b.count) {
|
||||
return 1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts reserved characters to HTML entities.
|
||||
*
|
||||
* @param {string} string
|
||||
* @returns {string}
|
||||
*/
|
||||
htmlEntities(string) {
|
||||
return String(string).replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default ByteAnalyser;
|
53
src/core/operations/gaijinatOutput.mjs
Normal file
53
src/core/operations/gaijinatOutput.mjs
Normal file
|
@ -0,0 +1,53 @@
|
|||
/**
|
||||
* @author gaijinat [web@gaijin.at]
|
||||
* @copyright Crown Copyright 2023
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation.mjs";
|
||||
import Utils from "../Utils.mjs";
|
||||
|
||||
/**
|
||||
* Output operation
|
||||
*/
|
||||
class Output extends Operation {
|
||||
|
||||
/**
|
||||
* Output constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "Output";
|
||||
this.module = "Default";
|
||||
this.description = "Outputs the entered text.<br><br>This is useful to output text with stored registers.<br><br>Example:<br>Assuming <code>$R0</code> is <code>test</code>, the string:<br><code>Value=$R0</code><br>will output:<br><code>Value=test</code>";
|
||||
this.infoURL = "";
|
||||
this.inputType = "string";
|
||||
this.outputType = "string";
|
||||
this.args = [
|
||||
{
|
||||
name: "Output",
|
||||
type: "toggleString",
|
||||
value: "",
|
||||
toggleValues: ["Simple string", "Extended (\\n, \\t, \\x...)"]
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
run(input, args) {
|
||||
const typeOutput = args[0].option;
|
||||
let strOutput = args[0].string;
|
||||
|
||||
if (typeOutput.startsWith("Extended")) strOutput = Utils.parseEscapedChars(strOutput);
|
||||
|
||||
return strOutput;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default Output;
|
113
src/core/operations/gaijinatPad.mjs
Normal file
113
src/core/operations/gaijinatPad.mjs
Normal file
|
@ -0,0 +1,113 @@
|
|||
/**
|
||||
* @author gaijinat [web@gaijin.at]
|
||||
* @copyright Crown Copyright 2023
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation.mjs";
|
||||
import Utils from "../Utils.mjs";
|
||||
|
||||
/**
|
||||
* Pad operation
|
||||
*/
|
||||
class Pad extends Operation {
|
||||
|
||||
/**
|
||||
* Pad constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "Pad";
|
||||
this.module = "Default";
|
||||
this.description = "Fills the input with one or more characters until the specified length is reached.";
|
||||
this.infoURL = "";
|
||||
this.inputType = "string";
|
||||
this.outputType = "string";
|
||||
this.args = [
|
||||
{
|
||||
name: "Position",
|
||||
type: "option",
|
||||
value: ["Start", "End", "Both"]
|
||||
},
|
||||
{
|
||||
name: "Length",
|
||||
type: "number",
|
||||
value: 0
|
||||
},
|
||||
{
|
||||
name: "String",
|
||||
type: "toggleString",
|
||||
value: "",
|
||||
toggleValues: ["Simple string", "Extended (\\n, \\t, \\x...)"]
|
||||
},
|
||||
{
|
||||
name: "Apply to",
|
||||
type: "option",
|
||||
value: ["Input", "Lines"],
|
||||
defaultIndex: 1
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
run(input, args) {
|
||||
const [position, padLength, {option: type}, applyTo] = args;
|
||||
let padCharacters = args[2].string;
|
||||
let output = "";
|
||||
let joinCharLenght = 0;
|
||||
|
||||
if (type.startsWith("Extended")) padCharacters = Utils.parseEscapedChars(padCharacters);
|
||||
if (padCharacters === "") padCharacters = " ";
|
||||
|
||||
if (applyTo === "Lines") {
|
||||
input = input.split("\n");
|
||||
joinCharLenght = 1;
|
||||
for (let i = 0; i < input.length; i++) {
|
||||
output += this.padEx(input[i], position, padLength, padCharacters) + "\n";
|
||||
}
|
||||
} else {
|
||||
output = this.padEx(input, position, padLength, padCharacters);
|
||||
}
|
||||
|
||||
if (output === "") {
|
||||
return input;
|
||||
} else {
|
||||
if (joinCharLenght > 0) output = output.slice(0, output.length - joinCharLenght);
|
||||
return output;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} input
|
||||
* @param {integer} position
|
||||
* @param {integer} padLength
|
||||
* @param {string} padCharacters
|
||||
* @returns {string}
|
||||
*/
|
||||
padEx(input, position, padLength, padCharacters) {
|
||||
let spaceLength, padStartLength;
|
||||
let output = "";
|
||||
|
||||
if (position === "Start") {
|
||||
output = input.padStart(padLength, padCharacters);
|
||||
} else if (position === "End") {
|
||||
output = input.padEnd(padLength, padCharacters);
|
||||
} else if (position === "Both") {
|
||||
spaceLength = padLength - input.length;
|
||||
if (spaceLength > 0) {
|
||||
padStartLength = Math.floor(spaceLength / 2) + input.length;
|
||||
output = input.padStart(padStartLength, padCharacters).padEnd(padLength, padCharacters);
|
||||
}
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default Pad;
|
81
src/core/operations/gaijinatPrependAppend.mjs
Normal file
81
src/core/operations/gaijinatPrependAppend.mjs
Normal file
|
@ -0,0 +1,81 @@
|
|||
/**
|
||||
* @author gaijinat [web@gaijin.at]
|
||||
* @copyright Crown Copyright 2023
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation.mjs";
|
||||
import Utils from "../Utils.mjs";
|
||||
|
||||
/**
|
||||
* Prepend / Append operation
|
||||
*/
|
||||
class PrependAppend extends Operation {
|
||||
|
||||
/**
|
||||
* PrependAppend constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "Prepend / Append";
|
||||
this.module = "Default";
|
||||
this.description = "Adds the specified text to the beginning and/or end of each line, character or the entire input.<br><br>Includes support for simple strings and extended strings (which support \\n, \\r, \\t, \\b, \\f and escaped hex bytes using \\x notation, e.g. \\x00 for a null byte).";
|
||||
this.infoURL = "";
|
||||
this.inputType = "string";
|
||||
this.outputType = "string";
|
||||
this.args = [
|
||||
{
|
||||
name: "Prepend",
|
||||
type: "toggleString",
|
||||
value: "",
|
||||
toggleValues: ["Simple string", "Extended (\\n, \\t, \\x...)"]
|
||||
},
|
||||
{
|
||||
name: "Append",
|
||||
type: "toggleString",
|
||||
value: "",
|
||||
toggleValues: ["Simple string", "Extended (\\n, \\t, \\x...)"]
|
||||
},
|
||||
{
|
||||
name: "Apply to",
|
||||
type: "option",
|
||||
value: ["Input", "Lines", "Characters"],
|
||||
defaultIndex: 1
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
run(input, args) {
|
||||
const [{option: typePrepend}, {option: typeAppend}, applyTo] = args;
|
||||
let [{string: strPrepend}, {string: strAppend}] = args;
|
||||
let output = "";
|
||||
let joinChar = "";
|
||||
|
||||
if (typePrepend.startsWith("Extended")) strPrepend = Utils.parseEscapedChars(strPrepend);
|
||||
if (typeAppend.startsWith("Extended")) strAppend = Utils.parseEscapedChars(strAppend);
|
||||
|
||||
if (applyTo === "Input") {
|
||||
output = strPrepend + input + strAppend;
|
||||
} else {
|
||||
if (applyTo === "Lines") {
|
||||
input = input.split("\n");
|
||||
joinChar = "\n";
|
||||
}
|
||||
for (let i = 0; i < input.length; i++) {
|
||||
output += strPrepend + input[i] + strAppend + joinChar;
|
||||
}
|
||||
}
|
||||
|
||||
if (joinChar.length > 0) output = output.slice(0, output.length - joinChar.length);
|
||||
return output;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default PrependAppend;
|
99
src/core/operations/gaijinatStoreRestoreInput.mjs
Normal file
99
src/core/operations/gaijinatStoreRestoreInput.mjs
Normal file
|
@ -0,0 +1,99 @@
|
|||
/**
|
||||
* @author gaijinat [web@gaijin.at]
|
||||
* @copyright Crown Copyright 2023
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation.mjs";
|
||||
import Dish from "../Dish.mjs";
|
||||
|
||||
/**
|
||||
* StoreRestoreInput operation
|
||||
*/
|
||||
class StoreRestoreInput extends Operation {
|
||||
|
||||
static variables = {};
|
||||
|
||||
/**
|
||||
* StoreRestoreInput constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "Store / Restore Input";
|
||||
this.flowControl = true;
|
||||
this.module = "Default";
|
||||
this.description = "Stores the input value and restores it later as output.<br><br><code>Store</code> stores the input under the given name.<br><code>Restore</code> restores the input with the given name as output.<br><code>Clear</code> removes the stored input with the given name. Without a name, all stored inputs will be removed.<br><br>You should deactivate 'Auto Bake' for this operation.";
|
||||
this.infoURL = "";
|
||||
this.inputType = "string";
|
||||
this.outputType = "string";
|
||||
this.args = [
|
||||
{
|
||||
name: "Mode",
|
||||
type: "option",
|
||||
value: ["Clear", "Store", "Restore"]
|
||||
},
|
||||
{
|
||||
name: "Name",
|
||||
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;
|
||||
const [mode, varName] = ings;
|
||||
const input = await state.dish.get(Dish.STRING);
|
||||
|
||||
let firstOpIndex = -1;
|
||||
|
||||
// Find the first index that matches this operation and clear the variables
|
||||
for (let i = 0; i < state.opList.length; i++) {
|
||||
if (state.opList[i].name === this.name) {
|
||||
firstOpIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (state.progress === firstOpIndex) {
|
||||
StoreRestoreInput.variables = {};
|
||||
}
|
||||
|
||||
if (mode === "Clear") {
|
||||
|
||||
if (varName === "") {
|
||||
StoreRestoreInput.variables = {};
|
||||
} else {
|
||||
if (StoreRestoreInput.variables[varName] !== undefined) {
|
||||
delete StoreRestoreInput.variables[varName];
|
||||
}
|
||||
}
|
||||
|
||||
} else if (varName && (mode === "Store")) {
|
||||
|
||||
StoreRestoreInput.variables[varName] = input;
|
||||
|
||||
} else if (varName && (mode === "Restore")) {
|
||||
|
||||
if (StoreRestoreInput.variables[varName] !== undefined) {
|
||||
state.dish.set(StoreRestoreInput.variables[varName], Dish.STRING);
|
||||
return state;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// console.log(StoreRestoreInput.variables);
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default StoreRestoreInput;
|
61
src/core/operations/gaijinatTrim.mjs
Normal file
61
src/core/operations/gaijinatTrim.mjs
Normal file
|
@ -0,0 +1,61 @@
|
|||
/**
|
||||
* @author gaijinat [web@gaijin.at]
|
||||
* @copyright Crown Copyright 2023
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation.mjs";
|
||||
|
||||
/**
|
||||
* Trim operation
|
||||
*/
|
||||
class Trim extends Operation {
|
||||
|
||||
/**
|
||||
* Trim constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "Trim";
|
||||
this.module = "Default";
|
||||
this.description = "Removes all whitespaces and line breaks from the beginning, end or both of the input data.";
|
||||
this.infoURL = "";
|
||||
this.inputType = "string";
|
||||
this.outputType = "string";
|
||||
this.args = [
|
||||
{
|
||||
name: "Position",
|
||||
type: "option",
|
||||
value: ["Start", "End", "Both"],
|
||||
defaultIndex: 2
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
run(input, args) {
|
||||
const [position] = args;
|
||||
let output = "";
|
||||
|
||||
switch (position) {
|
||||
case "Start":
|
||||
output = input.trimStart();
|
||||
break;
|
||||
case "End":
|
||||
output = input.trimEnd();
|
||||
break;
|
||||
default:
|
||||
output = input.trim();
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default Trim;
|
Loading…
Add table
Add a link
Reference in a new issue