2
0
mirror of https://github.com/gchq/CyberChef synced 2026-01-10 12:33:16 +00:00

Compare commits

..

13 Commits

Author SHA1 Message Date
n1474335
a5a89efc06 9.38.2 2022-06-03 12:58:50 +01:00
n1474335
1078c37043 Merge branch 'master' of https://github.com/LukeSerne/CyberChef 2022-06-03 12:55:56 +01:00
n1474335
535c7188a8 9.38.1 2022-06-03 12:53:22 +01:00
n1474335
d6f9e216a6 Merge branch 'fix-to-base45' of https://github.com/mikecat/CyberChef 2022-06-03 12:50:45 +01:00
n1474335
7d6a879a67 Added script for updating the CHANGELOG 2022-05-31 00:20:19 +01:00
n1474335
668eac1f9e Fixed Split.js issues when resizing to around 1000px wide 2022-05-30 22:53:17 +01:00
n1474335
ff99436ce6 Fixed 'To Hex' highlighting 2022-05-30 19:43:59 +01:00
n1474335
ec577fc075 Fixed CSS for maximising output pane 2022-05-30 19:25:41 +01:00
n1474335
cc9d51b7be 9.38.0 2022-05-30 18:14:46 +01:00
n1474335
cf2b54e8c0 Update CHANGELOG 2022-05-30 18:14:41 +01:00
n1474335
a895d1d82a Added 'Parse TCP' operation 2022-05-30 18:06:15 +01:00
MikeCAT
11da4188ee fix "To Base45" ( #1351 ) 2022-05-20 11:59:04 +00:00
Luke Serné
5b68bad185 Support UTF8 encoded characters in Substitution operation
This adds support for UTF8-encoded characters in the input and the parameters.
2022-05-13 17:35:50 +02:00
28 changed files with 707 additions and 118 deletions

View File

@@ -13,6 +13,9 @@ All major and minor version changes will be documented in this file. Details of
## Details ## Details
### [9.38.0] - 2022-05-30
- Added 'Parse TCP' operation [@n1474335] | [a895d1d]
### [9.37.0] - 2022-03-29 ### [9.37.0] - 2022-03-29
- 'SM4 Encrypt' and 'SM4 Decrypt' operations added [@swesven] | [#1189] - 'SM4 Encrypt' and 'SM4 Decrypt' operations added [@swesven] | [#1189]
- NoPadding options added for CBC and ECB modes in AES, DES and Triple DES Decrypt operations [@swesven] | [#1189] - NoPadding options added for CBC and ECB modes in AES, DES and Triple DES Decrypt operations [@swesven] | [#1189]
@@ -135,7 +138,7 @@ All major and minor version changes will be documented in this file. Details of
<details> <details>
<summary>Click to expand v8 minor versions</summary> <summary>Click to expand v8 minor versions</summary>
### [8.38.0] - 2019-07-03 ### [8.38.0] - 2019-07-03
- 'Streebog' and 'GOST hash' operations added [@MShwed] [@n1474335] | [#530] - 'Streebog' and 'GOST hash' operations added [@MShwed] [@n1474335] | [#530]
@@ -288,6 +291,7 @@ All major and minor version changes will be documented in this file. Details of
[9.38.0]: https://github.com/gchq/CyberChef/releases/tag/v9.38.0
[9.37.0]: https://github.com/gchq/CyberChef/releases/tag/v9.37.0 [9.37.0]: https://github.com/gchq/CyberChef/releases/tag/v9.37.0
[9.36.0]: https://github.com/gchq/CyberChef/releases/tag/v9.36.0 [9.36.0]: https://github.com/gchq/CyberChef/releases/tag/v9.36.0
[9.35.0]: https://github.com/gchq/CyberChef/releases/tag/v9.35.0 [9.35.0]: https://github.com/gchq/CyberChef/releases/tag/v9.35.0
@@ -416,6 +420,7 @@ All major and minor version changes will be documented in this file. Details of
[289a417]: https://github.com/gchq/CyberChef/commit/289a417dfb5923de5e1694354ec42a08d9395bfe [289a417]: https://github.com/gchq/CyberChef/commit/289a417dfb5923de5e1694354ec42a08d9395bfe
[e9ca4dc]: https://github.com/gchq/CyberChef/commit/e9ca4dc9caf98f33fd986431cd400c88082a42b8 [e9ca4dc]: https://github.com/gchq/CyberChef/commit/e9ca4dc9caf98f33fd986431cd400c88082a42b8
[dd18e52]: https://github.com/gchq/CyberChef/commit/dd18e529939078b89867297b181a584e8b2cc7da [dd18e52]: https://github.com/gchq/CyberChef/commit/dd18e529939078b89867297b181a584e8b2cc7da
[a895d1d]: https://github.com/gchq/CyberChef/commit/a895d1d82a2f92d440a0c5eca2bc7c898107b737
[#95]: https://github.com/gchq/CyberChef/pull/299 [#95]: https://github.com/gchq/CyberChef/pull/299
[#173]: https://github.com/gchq/CyberChef/pull/173 [#173]: https://github.com/gchq/CyberChef/pull/173
@@ -502,4 +507,4 @@ All major and minor version changes will be documented in this file. Details of
[#1242]: https://github.com/gchq/CyberChef/pull/1242 [#1242]: https://github.com/gchq/CyberChef/pull/1242
[#1244]: https://github.com/gchq/CyberChef/pull/1244 [#1244]: https://github.com/gchq/CyberChef/pull/1244
[#1313]: https://github.com/gchq/CyberChef/pull/1313 [#1313]: https://github.com/gchq/CyberChef/pull/1313
[#1326]: https://github.com/gchq/CyberChef/pull/1326 [#1326]: https://github.com/gchq/CyberChef/pull/1326

View File

@@ -217,7 +217,8 @@ module.exports = function (grunt) {
client: { client: {
logging: "error", logging: "error",
overlay: true overlay: true
} },
hot: "only"
}, },
plugins: [ plugins: [
new webpack.DefinePlugin(BUILD_CONSTANTS), new webpack.DefinePlugin(BUILD_CONSTANTS),

73
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{ {
"name": "cyberchef", "name": "cyberchef",
"version": "9.37.3", "version": "9.38.2",
"lockfileVersion": 2, "lockfileVersion": 2,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "cyberchef", "name": "cyberchef",
"version": "9.37.3", "version": "9.38.2",
"hasInstallScript": true, "hasInstallScript": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
@@ -98,7 +98,7 @@
"autoprefixer": "^10.4.4", "autoprefixer": "^10.4.4",
"babel-loader": "^8.2.4", "babel-loader": "^8.2.4",
"babel-plugin-dynamic-import-node": "^2.3.3", "babel-plugin-dynamic-import-node": "^2.3.3",
"chromedriver": "^99.0.0", "chromedriver": "^101.0.0",
"cli-progress": "^3.10.0", "cli-progress": "^3.10.0",
"colors": "^1.4.0", "colors": "^1.4.0",
"copy-webpack-plugin": "^10.2.4", "copy-webpack-plugin": "^10.2.4",
@@ -119,6 +119,7 @@
"html-webpack-plugin": "^5.5.0", "html-webpack-plugin": "^5.5.0",
"imports-loader": "^3.1.1", "imports-loader": "^3.1.1",
"mini-css-extract-plugin": "2.6.0", "mini-css-extract-plugin": "2.6.0",
"modify-source-webpack-plugin": "^3.0.0",
"nightwatch": "^2.0.10", "nightwatch": "^2.0.10",
"postcss": "^8.4.12", "postcss": "^8.4.12",
"postcss-css-variables": "^0.18.0", "postcss-css-variables": "^0.18.0",
@@ -4467,9 +4468,9 @@
} }
}, },
"node_modules/chromedriver": { "node_modules/chromedriver": {
"version": "99.0.0", "version": "101.0.0",
"resolved": "https://registry.npmjs.org/chromedriver/-/chromedriver-99.0.0.tgz", "resolved": "https://registry.npmjs.org/chromedriver/-/chromedriver-101.0.0.tgz",
"integrity": "sha512-pyB+5LuyZdb7EBPL3i5D5yucZUD+SlkdiUtmpjaEnLd9zAXp+SvD/hP5xF4l/ZmWvUo/1ZLxAI1YBdhazGTpgA==", "integrity": "sha512-LkkWxy6KM/0YdJS8qBeg5vfkTZTRamhBfOttb4oic4echDgWvCU1E8QcBbUBOHqZpSrYMyi7WMKmKMhXFUaZ+w==",
"dev": true, "dev": true,
"hasInstallScript": true, "hasInstallScript": true,
"dependencies": { "dependencies": {
@@ -10740,6 +10741,37 @@
"url": "https://github.com/chalk/supports-color?sponsor=1" "url": "https://github.com/chalk/supports-color?sponsor=1"
} }
}, },
"node_modules/modify-source-webpack-plugin": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/modify-source-webpack-plugin/-/modify-source-webpack-plugin-3.0.0.tgz",
"integrity": "sha512-m2g6QEittzzcde0PhgQtOwRywjqo8Q+vMLHbNoH2JDEFE/AlTBWvtNQrp17xbqDUXIueLljm4bhE5OninatHYw==",
"dev": true,
"dependencies": {
"loader-utils": "^2.0.0",
"schema-utils": "^3.0.0"
},
"peerDependencies": {
"webpack": "^4.37.0 || ^5.0.0"
}
},
"node_modules/modify-source-webpack-plugin/node_modules/schema-utils": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz",
"integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==",
"dev": true,
"dependencies": {
"@types/json-schema": "^7.0.8",
"ajv": "^6.12.5",
"ajv-keywords": "^3.5.2"
},
"engines": {
"node": ">= 10.13.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/webpack"
}
},
"node_modules/moment": { "node_modules/moment": {
"version": "2.29.1", "version": "2.29.1",
"resolved": "https://registry.npmjs.org/moment/-/moment-2.29.1.tgz", "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.1.tgz",
@@ -19171,9 +19203,9 @@
"dev": true "dev": true
}, },
"chromedriver": { "chromedriver": {
"version": "99.0.0", "version": "101.0.0",
"resolved": "https://registry.npmjs.org/chromedriver/-/chromedriver-99.0.0.tgz", "resolved": "https://registry.npmjs.org/chromedriver/-/chromedriver-101.0.0.tgz",
"integrity": "sha512-pyB+5LuyZdb7EBPL3i5D5yucZUD+SlkdiUtmpjaEnLd9zAXp+SvD/hP5xF4l/ZmWvUo/1ZLxAI1YBdhazGTpgA==", "integrity": "sha512-LkkWxy6KM/0YdJS8qBeg5vfkTZTRamhBfOttb4oic4echDgWvCU1E8QcBbUBOHqZpSrYMyi7WMKmKMhXFUaZ+w==",
"dev": true, "dev": true,
"requires": { "requires": {
"@testim/chrome-version": "^1.1.2", "@testim/chrome-version": "^1.1.2",
@@ -24019,6 +24051,29 @@
} }
} }
}, },
"modify-source-webpack-plugin": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/modify-source-webpack-plugin/-/modify-source-webpack-plugin-3.0.0.tgz",
"integrity": "sha512-m2g6QEittzzcde0PhgQtOwRywjqo8Q+vMLHbNoH2JDEFE/AlTBWvtNQrp17xbqDUXIueLljm4bhE5OninatHYw==",
"dev": true,
"requires": {
"loader-utils": "^2.0.0",
"schema-utils": "^3.0.0"
},
"dependencies": {
"schema-utils": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz",
"integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==",
"dev": true,
"requires": {
"@types/json-schema": "^7.0.8",
"ajv": "^6.12.5",
"ajv-keywords": "^3.5.2"
}
}
}
},
"moment": { "moment": {
"version": "2.29.1", "version": "2.29.1",
"resolved": "https://registry.npmjs.org/moment/-/moment-2.29.1.tgz", "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.1.tgz",

View File

@@ -1,6 +1,6 @@
{ {
"name": "cyberchef", "name": "cyberchef",
"version": "9.37.3", "version": "9.38.2",
"description": "The Cyber Swiss Army Knife for encryption, encoding, compression and data analysis.", "description": "The Cyber Swiss Army Knife for encryption, encoding, compression and data analysis.",
"author": "n1474335 <n1474335@gmail.com>", "author": "n1474335 <n1474335@gmail.com>",
"homepage": "https://gchq.github.io/CyberChef", "homepage": "https://gchq.github.io/CyberChef",
@@ -48,7 +48,7 @@
"autoprefixer": "^10.4.4", "autoprefixer": "^10.4.4",
"babel-loader": "^8.2.4", "babel-loader": "^8.2.4",
"babel-plugin-dynamic-import-node": "^2.3.3", "babel-plugin-dynamic-import-node": "^2.3.3",
"chromedriver": "^99.0.0", "chromedriver": "^101.0.0",
"cli-progress": "^3.10.0", "cli-progress": "^3.10.0",
"colors": "^1.4.0", "colors": "^1.4.0",
"copy-webpack-plugin": "^10.2.4", "copy-webpack-plugin": "^10.2.4",
@@ -69,6 +69,7 @@
"html-webpack-plugin": "^5.5.0", "html-webpack-plugin": "^5.5.0",
"imports-loader": "^3.1.1", "imports-loader": "^3.1.1",
"mini-css-extract-plugin": "2.6.0", "mini-css-extract-plugin": "2.6.0",
"modify-source-webpack-plugin": "^3.0.0",
"nightwatch": "^2.0.10", "nightwatch": "^2.0.10",
"postcss": "^8.4.12", "postcss": "^8.4.12",
"postcss-css-variables": "^0.18.0", "postcss-css-variables": "^0.18.0",
@@ -174,6 +175,7 @@
"lint": "npx grunt lint", "lint": "npx grunt lint",
"postinstall": "npx grunt exec:fixCryptoApiImports", "postinstall": "npx grunt exec:fixCryptoApiImports",
"newop": "node --experimental-modules --experimental-json-modules src/core/config/scripts/newOperation.mjs", "newop": "node --experimental-modules --experimental-json-modules src/core/config/scripts/newOperation.mjs",
"minor": "node --experimental-modules --experimental-json-modules src/core/config/scripts/newMinorVersion.mjs",
"getheapsize": "node -e 'console.log(`node heap limit = ${require(\"v8\").getHeapStatistics().heap_size_limit / (1024 * 1024)} Mb`)'", "getheapsize": "node -e 'console.log(`node heap limit = ${require(\"v8\").getHeapStatistics().heap_size_limit / (1024 * 1024)} Mb`)'",
"setheapsize": "export NODE_OPTIONS=--max_old_space_size=2048" "setheapsize": "export NODE_OPTIONS=--max_old_space_size=2048"
} }

View File

@@ -190,6 +190,7 @@
"Parse IP range", "Parse IP range",
"Parse IPv6 address", "Parse IPv6 address",
"Parse IPv4 header", "Parse IPv4 header",
"Parse TCP",
"Parse UDP", "Parse UDP",
"Parse SSH Host Key", "Parse SSH Host Key",
"Parse URI", "Parse URI",

View File

@@ -0,0 +1,144 @@
/**
* This script updates the CHANGELOG when a new minor version is created.
*
* @author n1474335 [n1474335@gmail.com]
* @copyright Crown Copyright 2022
* @license Apache-2.0
*/
/* eslint no-console: ["off"] */
import prompt from "prompt";
import colors from "colors";
import path from "path";
import fs from "fs";
import process from "process";
const dir = path.join(process.cwd() + "/src/core/config/");
if (!fs.existsSync(dir)) {
console.log("\nCWD: " + process.cwd());
console.log("Error: newMinorVersion.mjs should be run from the project root");
console.log("Example> node --experimental-modules src/core/config/scripts/newMinorVersion.mjs");
process.exit(1);
}
let changelogData = fs.readFileSync(path.join(process.cwd(), "CHANGELOG.md"), "utf8");
const lastVersion = changelogData.match(/## Details\s+### \[(\d+)\.(\d+)\.(\d+)\]/);
const newVersion = [
parseInt(lastVersion[1], 10),
parseInt(lastVersion[2], 10) + 1,
0
];
let knownContributors = changelogData.match(/^\[@([^\]]+)\]/gm);
knownContributors = knownContributors.map(c => c.slice(2, -1));
const date = (new Date()).toISOString().split("T")[0];
const schema = {
properties: {
message: {
description: "A short but descriptive summary of a feature in this version",
example: "Added 'Op name' operation",
prompt: "Feature description",
type: "string",
required: true,
},
author: {
description: "The author of the feature (only one supported, edit manually to add more)",
example: "n1474335",
prompt: "Author",
type: "string",
default: "n1474335"
},
id: {
description: "The PR number or full commit hash for this feature.",
example: "1200",
prompt: "Pull request or commit ID",
type: "string"
},
another: {
description: "y/n",
example: "y",
prompt: "Add another feature?",
type: "string",
pattern: /^[yn]$/,
}
}
};
// Build schema
for (const prop in schema.properties) {
const p = schema.properties[prop];
p.description = "\n" + colors.white(p.description) + colors.cyan("\nExample: " + p.example) + "\n" + colors.green(p.prompt);
}
prompt.message = "";
prompt.delimiter = ":".green;
const features = [];
const authors = [];
const prIDs = [];
const commitIDs = [];
prompt.start();
const getFeature = function() {
prompt.get(schema, (err, result) => {
if (err) {
console.log("\nExiting script.");
process.exit(0);
}
features.push(result);
if (result.another === "y") {
getFeature();
} else {
let message = `### [${newVersion[0]}.${newVersion[1]}.${newVersion[2]}] - ${date}\n`;
features.forEach(feature => {
const id = feature.id.length > 10 ? feature.id.slice(0, 7) : "#" + feature.id;
message += `- ${feature.message} [@${feature.author}] | [${id}]\n`;
if (!knownContributors.includes(feature.author)) {
authors.push(`[@${feature.author}]: https://github.com/${feature.author}`);
}
if (feature.id.length > 10) {
commitIDs.push(`[${id}]: https://github.com/gchq/CyberChef/commit/${feature.id}`);
} else {
prIDs.push(`[#${feature.id}]: https://github.com/gchq/CyberChef/pull/${feature.id}`);
}
});
// Message
changelogData = changelogData.replace(/## Details\n\n/, "## Details\n\n" + message + "\n");
// Tag
const newTag = `[${newVersion[0]}.${newVersion[1]}.${newVersion[2]}]: https://github.com/gchq/CyberChef/releases/tag/v${newVersion[0]}.${newVersion[1]}.${newVersion[2]}\n`;
changelogData = changelogData.replace(/\n\n(\[\d+\.\d+\.\d+\]: https)/, "\n\n" + newTag + "$1");
// Author
authors.forEach(author => {
changelogData = changelogData.replace(/(\n\[@[^\]]+\]: https:\/\/github\.com\/[^\n]+\n)\n/, "$1" + author + "\n\n");
});
// Commit IDs
commitIDs.forEach(commitID => {
changelogData = changelogData.replace(/(\n\[[^\].]+\]: https:\/\/github.com\/gchq\/CyberChef\/commit\/[^\n]+\n)\n/, "$1" + commitID + "\n\n");
});
// PR IDs
prIDs.forEach(prID => {
changelogData = changelogData.replace(/(\n\[#[^\]]+\]: https:\/\/github.com\/gchq\/CyberChef\/pull\/[^\n]+\n)\n/, "$1" + prID + "\n\n");
});
fs.writeFileSync(path.join(process.cwd(), "CHANGELOG.md"), changelogData);
console.log("Written CHANGELOG.md");
}
});
};
getFeature();

View File

@@ -13,7 +13,7 @@ import OperationError from "../errors/OperationError.mjs";
/** /**
* Convert a byte array into a binary string. * Convert a byte array into a binary string.
* *
* @param {Uint8Array|byteArray} data * @param {Uint8Array|byteArray|number} data
* @param {string} [delim="Space"] * @param {string} [delim="Space"]
* @param {number} [padding=8] * @param {number} [padding=8]
* @returns {string} * @returns {string}
@@ -26,13 +26,17 @@ import OperationError from "../errors/OperationError.mjs";
* toBinary([10,20,30], ":"); * toBinary([10,20,30], ":");
*/ */
export function toBinary(data, delim="Space", padding=8) { export function toBinary(data, delim="Space", padding=8) {
if (!data) return "";
delim = Utils.charRep(delim); delim = Utils.charRep(delim);
let output = ""; let output = "";
for (let i = 0; i < data.length; i++) { if (data.length) { // array
output += data[i].toString(2).padStart(padding, "0") + delim; for (let i = 0; i < data.length; i++) {
output += data[i].toString(2).padStart(padding, "0") + delim;
}
} else if (typeof data === "number") { // Single value
return data.toString(2).padStart(padding, "0");
} else {
return "";
} }
if (delim.length) { if (delim.length) {

View File

@@ -3778,8 +3778,8 @@ function parseDEFLATE(stream) {
while (!finalBlock) { while (!finalBlock) {
// Read header // Read header
finalBlock = stream.readBits(1); finalBlock = stream.readBits(1, "le");
const blockType = stream.readBits(2); const blockType = stream.readBits(2, "le");
if (blockType === 0) { if (blockType === 0) {
/* No compression */ /* No compression */
@@ -3798,16 +3798,16 @@ function parseDEFLATE(stream) {
/* Dynamic Huffman */ /* Dynamic Huffman */
// Read the number of liternal and length codes // Read the number of liternal and length codes
const hlit = stream.readBits(5) + 257; const hlit = stream.readBits(5, "le") + 257;
// Read the number of distance codes // Read the number of distance codes
const hdist = stream.readBits(5) + 1; const hdist = stream.readBits(5, "le") + 1;
// Read the number of code lengths // Read the number of code lengths
const hclen = stream.readBits(4) + 4; const hclen = stream.readBits(4, "le") + 4;
// Parse code lengths // Parse code lengths
const codeLengths = new Uint8Array(huffmanOrder.length); const codeLengths = new Uint8Array(huffmanOrder.length);
for (let i = 0; i < hclen; i++) { for (let i = 0; i < hclen; i++) {
codeLengths[huffmanOrder[i]] = stream.readBits(3); codeLengths[huffmanOrder[i]] = stream.readBits(3, "le");
} }
// Parse length table // Parse length table
@@ -3819,16 +3819,16 @@ function parseDEFLATE(stream) {
code = readHuffmanCode(stream, codeLengthsTable); code = readHuffmanCode(stream, codeLengthsTable);
switch (code) { switch (code) {
case 16: case 16:
repeat = 3 + stream.readBits(2); repeat = 3 + stream.readBits(2, "le");
while (repeat--) lengthTable[i++] = prev; while (repeat--) lengthTable[i++] = prev;
break; break;
case 17: case 17:
repeat = 3 + stream.readBits(3); repeat = 3 + stream.readBits(3, "le");
while (repeat--) lengthTable[i++] = 0; while (repeat--) lengthTable[i++] = 0;
prev = 0; prev = 0;
break; break;
case 18: case 18:
repeat = 11 + stream.readBits(7); repeat = 11 + stream.readBits(7, "le");
while (repeat--) lengthTable[i++] = 0; while (repeat--) lengthTable[i++] = 0;
prev = 0; prev = 0;
break; break;
@@ -3886,11 +3886,11 @@ function parseHuffmanBlock(stream, litTab, distTab) {
if (code < 256) continue; if (code < 256) continue;
// Length code // Length code
stream.readBits(lengthExtraTable[code - 257]); stream.readBits(lengthExtraTable[code - 257], "le");
// Dist code // Dist code
code = readHuffmanCode(stream, distTab); code = readHuffmanCode(stream, distTab);
stream.readBits(distanceExtraTable[code]); stream.readBits(distanceExtraTable[code], "le");
} }
} }
@@ -3948,7 +3948,7 @@ function readHuffmanCode(stream, table) {
const [codeTable, maxCodeLength] = table; const [codeTable, maxCodeLength] = table;
// Read max length // Read max length
const bitsBuf = stream.readBits(maxCodeLength); const bitsBuf = stream.readBits(maxCodeLength, "le");
const codeWithLength = codeTable[bitsBuf & ((1 << maxCodeLength) - 1)]; const codeWithLength = codeTable[bitsBuf & ((1 << maxCodeLength) - 1)];
const codeLength = codeWithLength >>> 16; const codeLength = codeWithLength >>> 16;

47
src/core/lib/Protocol.mjs Normal file
View File

@@ -0,0 +1,47 @@
/**
* Protocol parsing functions.
*
* @author n1474335 [n1474335@gmail.com]
* @copyright Crown Copyright 2022
* @license Apache-2.0
*/
import BigNumber from "bignumber.js";
import {toHexFast} from "../lib/Hex.mjs";
/**
* Recursively displays a JSON object as an HTML table
*
* @param {Object} obj
* @returns string
*/
export function objToTable(obj, nested=false) {
let html = `<table
class='table table-sm table-nonfluid ${nested ? "mb-0 table-borderless" : "table-bordered"}'
style='table-layout: fixed; ${nested ? "margin: -1px !important;" : ""}'>`;
if (!nested)
html += `<tr>
<th>Field</th>
<th>Value</th>
</tr>`;
for (const key in obj) {
html += `<tr><td style='word-wrap: break-word'>${key}</td>`;
if (typeof obj[key] === "object")
html += `<td style='padding: 0'>${objToTable(obj[key], true)}</td>`;
else
html += `<td>${obj[key]}</td>`;
html += "</tr>";
}
html += "</table>";
return html;
}
/**
* Converts bytes into a BigNumber string
* @param {Uint8Array} bs
* @returns {string}
*/
export function bytesToLargeNumber(bs) {
return BigNumber(toHexFast(bs), 16).toString();
}

View File

@@ -27,15 +27,17 @@ export default class Stream {
} }
/** /**
* Get a number of bytes from the current position. * Get a number of bytes from the current position, or all remaining bytes.
* *
* @param {number} numBytes * @param {number} [numBytes=null]
* @returns {Uint8Array} * @returns {Uint8Array}
*/ */
getBytes(numBytes) { getBytes(numBytes=null) {
if (this.position > this.length) return undefined; if (this.position > this.length) return undefined;
const newPosition = this.position + numBytes; const newPosition = numBytes !== null ?
this.position + numBytes :
this.length;
const bytes = this.bytes.slice(this.position, newPosition); const bytes = this.bytes.slice(this.position, newPosition);
this.position = newPosition; this.position = newPosition;
this.bitPos = 0; this.bitPos = 0;
@@ -91,34 +93,40 @@ export default class Stream {
} }
/** /**
* Reads a number of bits from the buffer. * Reads a number of bits from the buffer in big or little endian.
*
* @TODO Add endianness
* *
* @param {number} numBits * @param {number} numBits
* @param {string} [endianness="be"]
* @returns {number} * @returns {number}
*/ */
readBits(numBits) { readBits(numBits, endianness="be") {
if (this.position > this.length) return undefined; if (this.position > this.length) return undefined;
let bitBuf = 0, let bitBuf = 0,
bitBufLen = 0; bitBufLen = 0;
// Add remaining bits from current byte // Add remaining bits from current byte
bitBuf = (this.bytes[this.position++] & bitMask(this.bitPos)) >>> this.bitPos; bitBuf = this.bytes[this.position++] & bitMask(this.bitPos);
if (endianness !== "be") bitBuf >>>= this.bitPos;
bitBufLen = 8 - this.bitPos; bitBufLen = 8 - this.bitPos;
this.bitPos = 0; this.bitPos = 0;
// Not enough bits yet // Not enough bits yet
while (bitBufLen < numBits) { while (bitBufLen < numBits) {
bitBuf |= this.bytes[this.position++] << bitBufLen; if (endianness === "be")
bitBuf = (bitBuf << bitBufLen) | this.bytes[this.position++];
else
bitBuf |= this.bytes[this.position++] << bitBufLen;
bitBufLen += 8; bitBufLen += 8;
} }
// Reverse back to numBits // Reverse back to numBits
if (bitBufLen > numBits) { if (bitBufLen > numBits) {
const excess = bitBufLen - numBits; const excess = bitBufLen - numBits;
bitBuf &= (1 << numBits) - 1; if (endianness === "be")
bitBuf >>>= excess;
else
bitBuf &= (1 << numBits) - 1;
bitBufLen -= excess; bitBufLen -= excess;
this.position--; this.position--;
this.bitPos = 8 - excess; this.bitPos = 8 - excess;
@@ -133,7 +141,9 @@ export default class Stream {
* @returns {number} The bit mask * @returns {number} The bit mask
*/ */
function bitMask(bitPos) { function bitMask(bitPos) {
return 256 - (1 << bitPos); return endianness === "be" ?
(1 << (8 - bitPos)) - 1 :
256 - (1 << bitPos);
} }
} }

View File

@@ -0,0 +1,245 @@
/**
* @author n1474335 [n1474335@gmail.com]
* @copyright Crown Copyright 2022
* @license Apache-2.0
*/
import Operation from "../Operation.mjs";
import Stream from "../lib/Stream.mjs";
import {toHexFast, fromHex} from "../lib/Hex.mjs";
import {toBinary} from "../lib/Binary.mjs";
import {objToTable, bytesToLargeNumber} from "../lib/Protocol.mjs";
import Utils from "../Utils.mjs";
import OperationError from "../errors/OperationError.mjs";
import BigNumber from "bignumber.js";
/**
* Parse TCP operation
*/
class ParseTCP extends Operation {
/**
* ParseTCP constructor
*/
constructor() {
super();
this.name = "Parse TCP";
this.module = "Default";
this.description = "Parses a TCP header and payload (if present).";
this.infoURL = "https://wikipedia.org/wiki/Transmission_Control_Protocol";
this.inputType = "string";
this.outputType = "json";
this.presentType = "html";
this.args = [
{
name: "Input format",
type: "option",
value: ["Hex", "Raw"]
}
];
}
/**
* @param {string} input
* @param {Object[]} args
* @returns {html}
*/
run(input, args) {
const format = args[0];
if (format === "Hex") {
input = fromHex(input);
} else if (format === "Raw") {
input = Utils.strToArrayBuffer(input);
} else {
throw new OperationError("Unrecognised input format.");
}
const s = new Stream(new Uint8Array(input));
if (s.length < 20) {
throw new OperationError("Need at least 20 bytes for a TCP Header");
}
// Parse Header
const TCPPacket = {
"Source port": s.readInt(2),
"Destination port": s.readInt(2),
"Sequence number": bytesToLargeNumber(s.getBytes(4)),
"Acknowledgement number": s.readInt(4),
"Data offset": s.readBits(4),
"Flags": {
"Reserved": toBinary(s.readBits(3), "", 3),
"NS": s.readBits(1),
"CWR": s.readBits(1),
"ECE": s.readBits(1),
"URG": s.readBits(1),
"ACK": s.readBits(1),
"PSH": s.readBits(1),
"RST": s.readBits(1),
"SYN": s.readBits(1),
"FIN": s.readBits(1),
},
"Window size": s.readInt(2),
"Checksum": "0x" + toHexFast(s.getBytes(2)),
"Urgent pointer": "0x" + toHexFast(s.getBytes(2))
};
// Parse options if present
let windowScaleShift = 0;
if (TCPPacket["Data offset"] > 5) {
let remainingLength = TCPPacket["Data offset"] * 4 - 20;
const options = {};
while (remainingLength > 0) {
const option = {
"Kind": s.readInt(1)
};
let opt = { name: "Reserved", length: true };
if (Object.prototype.hasOwnProperty.call(TCP_OPTION_KIND_LOOKUP, option.Kind)) {
opt = TCP_OPTION_KIND_LOOKUP[option.Kind];
}
// Add Length and Value fields
if (opt.length) {
option.Length = s.readInt(1);
if (option.Length > 2) {
if (Object.prototype.hasOwnProperty.call(opt, "parser")) {
option.Value = opt.parser(s.getBytes(option.Length - 2));
} else {
option.Value = option.Length <= 6 ?
s.readInt(option.Length - 2):
"0x" + toHexFast(s.getBytes(option.Length - 2));
}
// Store Window Scale shift for later
if (option.Kind === 3 && option.Value) {
windowScaleShift = option.Value["Shift count"];
}
}
}
options[opt.name] = option;
const length = option.Length || 1;
remainingLength -= length;
}
TCPPacket.Options = options;
}
if (s.hasMore()) {
TCPPacket.Data = "0x" + toHexFast(s.getBytes());
}
// Improve values
TCPPacket["Data offset"] = `${TCPPacket["Data offset"]} (${TCPPacket["Data offset"] * 4} bytes)`;
const trueWndSize = BigNumber(TCPPacket["Window size"]).multipliedBy(BigNumber(2).pow(BigNumber(windowScaleShift)));
TCPPacket["Window size"] = `${TCPPacket["Window size"]} (Scaled: ${trueWndSize})`;
return TCPPacket;
}
/**
* Displays the TCP Packet in a tabular style
* @param {Object} data
* @returns {html}
*/
present(data) {
return objToTable(data);
}
}
// Taken from https://www.iana.org/assignments/tcp-parameters/tcp-parameters.xhtml
// on 2022-05-30
const TCP_OPTION_KIND_LOOKUP = {
0: { name: "End of Option List", length: false },
1: { name: "No-Operation", length: false },
2: { name: "Maximum Segment Size", length: true },
3: { name: "Window Scale", length: true, parser: windowScaleParser },
4: { name: "SACK Permitted", length: true },
5: { name: "SACK", length: true },
6: { name: "Echo (obsoleted by option 8)", length: true },
7: { name: "Echo Reply (obsoleted by option 8)", length: true },
8: { name: "Timestamps", length: true, parser: tcpTimestampParser },
9: { name: "Partial Order Connection Permitted (obsolete)", length: true },
10: { name: "Partial Order Service Profile (obsolete)", length: true },
11: { name: "CC (obsolete)", length: true },
12: { name: "CC.NEW (obsolete)", length: true },
13: { name: "CC.ECHO (obsolete)", length: true },
14: { name: "TCP Alternate Checksum Request (obsolete)", length: true, parser: tcpAlternateChecksumParser },
15: { name: "TCP Alternate Checksum Data (obsolete)", length: true },
16: { name: "Skeeter", length: true },
17: { name: "Bubba", length: true },
18: { name: "Trailer Checksum Option", length: true },
19: { name: "MD5 Signature Option (obsoleted by option 29)", length: true },
20: { name: "SCPS Capabilities", length: true },
21: { name: "Selective Negative Acknowledgements", length: true },
22: { name: "Record Boundaries", length: true },
23: { name: "Corruption experienced", length: true },
24: { name: "SNAP", length: true },
25: { name: "Unassigned (released 2000-12-18)", length: true },
26: { name: "TCP Compression Filter", length: true },
27: { name: "Quick-Start Response", length: true },
28: { name: "User Timeout Option (also, other known unauthorized use)", length: true },
29: { name: "TCP Authentication Option (TCP-AO)", length: true },
30: { name: "Multipath TCP (MPTCP)", length: true },
69: { name: "Encryption Negotiation (TCP-ENO)", length: true },
70: { name: "Reserved (known unauthorized use without proper IANA assignment)", length: true },
76: { name: "Reserved (known unauthorized use without proper IANA assignment)", length: true },
77: { name: "Reserved (known unauthorized use without proper IANA assignment)", length: true },
78: { name: "Reserved (known unauthorized use without proper IANA assignment)", length: true },
253: { name: "RFC3692-style Experiment 1 (also improperly used for shipping products) ", length: true },
254: { name: "RFC3692-style Experiment 2 (also improperly used for shipping products) ", length: true }
};
/**
* Parses the TCP Alternate Checksum Request field
* @param {Uint8Array} data
*/
function tcpAlternateChecksumParser(data) {
const lookup = {
0: "TCP Checksum",
1: "8-bit Fletchers's algorithm",
2: "16-bit Fletchers's algorithm",
3: "Redundant Checksum Avoidance"
}[data[0]];
return `${lookup} (0x${toHexFast(data)})`;
}
/**
* Parses the TCP Timestamp field
* @param {Uint8Array} data
*/
function tcpTimestampParser(data) {
const s = new Stream(data);
if (s.length !== 8)
return `Error: Timestamp field should be 8 bytes long (received 0x${toHexFast(data)})`;
const tsval = bytesToLargeNumber(s.getBytes(4)),
tsecr = bytesToLargeNumber(s.getBytes(4));
return {
"Current Timestamp": tsval,
"Echo Reply": tsecr
};
}
/**
* Parses the Window Scale field
* @param {Uint8Array} data
*/
function windowScaleParser(data) {
if (data.length !== 1)
return `Error: Window Scale should be one byte long (received 0x${toHexFast(data)})`;
return {
"Shift count": data[0],
"Multiplier": 1 << data[0]
};
}
export default ParseTCP;

View File

@@ -6,7 +6,9 @@
import Operation from "../Operation.mjs"; import Operation from "../Operation.mjs";
import Stream from "../lib/Stream.mjs"; import Stream from "../lib/Stream.mjs";
import {toHex} from "../lib/Hex.mjs"; import {toHexFast, fromHex} from "../lib/Hex.mjs";
import {objToTable} from "../lib/Protocol.mjs";
import Utils from "../Utils.mjs";
import OperationError from "../errors/OperationError.mjs"; import OperationError from "../errors/OperationError.mjs";
/** /**
@@ -24,58 +26,61 @@ class ParseUDP extends Operation {
this.module = "Default"; this.module = "Default";
this.description = "Parses a UDP header and payload (if present)."; this.description = "Parses a UDP header and payload (if present).";
this.infoURL = "https://wikipedia.org/wiki/User_Datagram_Protocol"; this.infoURL = "https://wikipedia.org/wiki/User_Datagram_Protocol";
this.inputType = "ArrayBuffer"; this.inputType = "string";
this.outputType = "json"; this.outputType = "json";
this.presentType = "html"; this.presentType = "html";
this.args = []; this.args = [
{
name: "Input format",
type: "option",
value: ["Hex", "Raw"]
}
];
} }
/** /**
* @param {ArrayBuffer} input * @param {string} input
* @param {Object[]} args
* @returns {Object} * @returns {Object}
*/ */
run(input, args) { run(input, args) {
if (input.byteLength < 8) { const format = args[0];
throw new OperationError("Need 8 bytes for a UDP Header");
if (format === "Hex") {
input = fromHex(input);
} else if (format === "Raw") {
input = Utils.strToArrayBuffer(input);
} else {
throw new OperationError("Unrecognised input format.");
} }
const s = new Stream(new Uint8Array(input)); const s = new Stream(new Uint8Array(input));
if (s.length < 8) {
throw new OperationError("Need 8 bytes for a UDP Header");
}
// Parse Header // Parse Header
const UDPPacket = { const UDPPacket = {
"Source port": s.readInt(2), "Source port": s.readInt(2),
"Destination port": s.readInt(2), "Destination port": s.readInt(2),
"Length": s.readInt(2), "Length": s.readInt(2),
"Checksum": toHex(s.getBytes(2), "") "Checksum": "0x" + toHexFast(s.getBytes(2))
}; };
// Parse data if present // Parse data if present
if (s.hasMore()) { if (s.hasMore()) {
UDPPacket.Data = toHex(s.getBytes(UDPPacket.Length - 8), ""); UDPPacket.Data = "0x" + toHexFast(s.getBytes(UDPPacket.Length - 8));
} }
return UDPPacket; return UDPPacket;
} }
/** /**
* Displays the UDP Packet in a table style * Displays the UDP Packet in a tabular style
* @param {Object} data * @param {Object} data
* @returns {html} * @returns {html}
*/ */
present(data) { present(data) {
const html = []; return objToTable(data);
html.push("<table class='table table-hover table-sm table-bordered table-nonfluid' style='table-layout: fixed'>");
html.push("<tr>");
html.push("<th>Field</th>");
html.push("<th>Value</th>");
html.push("</tr>");
for (const key in data) {
html.push("<tr>");
html.push("<td style=\"word-wrap:break-word\">" + key + "</td>");
html.push("<td>" + data[key] + "</td>");
html.push("</tr>");
}
html.push("</table>");
return html.join("");
} }
} }

View File

@@ -44,8 +44,8 @@ class Substitute extends Operation {
* @returns {string} * @returns {string}
*/ */
run(input, args) { run(input, args) {
const plaintext = Utils.expandAlphRange(args[0]).join(""), const plaintext = Utils.expandAlphRange([...args[0]]),
ciphertext = Utils.expandAlphRange(args[1]).join(""); ciphertext = Utils.expandAlphRange([...args[1]]);
let output = "", let output = "",
index = -1; index = -1;
@@ -53,9 +53,9 @@ class Substitute extends Operation {
output = "Warning: Plaintext and Ciphertext lengths differ\n\n"; output = "Warning: Plaintext and Ciphertext lengths differ\n\n";
} }
for (let i = 0; i < input.length; i++) { for (const character of input) {
index = plaintext.indexOf(input[i]); index = plaintext.indexOf(character);
output += index > -1 && index < ciphertext.length ? ciphertext[index] : input[i]; output += index > -1 && index < ciphertext.length ? ciphertext[index] : character;
} }
return output; return output;

View File

@@ -65,6 +65,10 @@ class ToBase45 extends Operation {
if (chars < 2) { if (chars < 2) {
res.push("0"); res.push("0");
chars++;
}
if (pair.length > 1 && chars < 3) {
res.push("0");
} }
} }

View File

@@ -67,7 +67,7 @@ class ToHex extends Operation {
* @returns {Object[]} pos * @returns {Object[]} pos
*/ */
highlight(pos, args) { highlight(pos, args) {
let delim, commaLen; let delim, commaLen = 0;
if (args[0] === "0x with comma") { if (args[0] === "0x with comma") {
delim = "0x"; delim = "0x";
commaLen = 1; commaLen = 1;
@@ -86,7 +86,7 @@ class ToHex extends Operation {
pos[0].start = pos[0].start * (2 + len) + countLF(pos[0].start); pos[0].start = pos[0].start * (2 + len) + countLF(pos[0].start);
pos[0].end = pos[0].end * (2 + len) + countLF(pos[0].end); pos[0].end = pos[0].end * (2 + len) + countLF(pos[0].end);
// if the deliminators are not prepended, trim the trailing deliminator // if the delimiters are not prepended, trim the trailing delimiter
if (!(delim === "0x" || delim === "\\x")) { if (!(delim === "0x" || delim === "\\x")) {
pos[0].end -= delim.length; pos[0].end -= delim.length;
} }

View File

@@ -57,7 +57,7 @@ class App {
this.populateOperationsList(); this.populateOperationsList();
this.manager.setup(); this.manager.setup();
this.manager.output.saveBombe(); this.manager.output.saveBombe();
this.resetLayout(); this.adjustComponentSizes();
this.setCompileMessage(); this.setCompileMessage();
log.debug("App loaded"); log.debug("App loaded");
@@ -295,9 +295,7 @@ class App {
gutterSize: 4, gutterSize: 4,
expandToMin: true, expandToMin: true,
onDrag: debounce(function() { onDrag: debounce(function() {
this.manager.recipe.adjustWidth(); this.adjustComponentSizes();
this.manager.input.calcMaxTabs();
this.manager.output.calcMaxTabs();
}, 50, "dragSplitter", this, []) }, 50, "dragSplitter", this, [])
}); });
@@ -307,7 +305,7 @@ class App {
minSize: minimise ? [0, 0] : [100, 100] minSize: minimise ? [0, 0] : [100, 100]
}); });
this.resetLayout(); this.adjustComponentSizes();
} }
@@ -581,6 +579,13 @@ class App {
resetLayout() { resetLayout() {
this.columnSplitter.setSizes([20, 30, 50]); this.columnSplitter.setSizes([20, 30, 50]);
this.ioSplitter.setSizes([50, 50]); this.ioSplitter.setSizes([50, 50]);
this.adjustComponentSizes();
}
/**
* Adjust components to fit their containers.
*/
adjustComponentSizes() {
this.manager.recipe.adjustWidth(); this.manager.recipe.adjustWidth();
this.manager.input.calcMaxTabs(); this.manager.input.calcMaxTabs();
this.manager.output.calcMaxTabs(); this.manager.output.calcMaxTabs();

View File

@@ -176,7 +176,7 @@
<div id="recipe" class="split split-horizontal no-select"> <div id="recipe" class="split split-horizontal no-select">
<div class="title no-select"> <div class="title no-select">
Recipe Recipe
<span class="float-right"> <span class="pane-controls hide-on-maximised-output">
<button type="button" class="btn btn-primary bmd-btn-icon" id="save" data-toggle="tooltip" title="Save recipe"> <button type="button" class="btn btn-primary bmd-btn-icon" id="save" data-toggle="tooltip" title="Save recipe">
<i class="material-icons">save</i> <i class="material-icons">save</i>
</button> </button>
@@ -190,7 +190,7 @@
</div> </div>
<ul id="rec-list" class="list-area no-select"></ul> <ul id="rec-list" class="list-area no-select"></ul>
<div id="controls" class="no-select"> <div id="controls" class="no-select hide-on-maximised-output">
<div id="controls-content" class="d-flex align-items-center"> <div id="controls-content" class="d-flex align-items-center">
<button type="button" class="mx-2 btn btn-lg btn-secondary" id="step" data-toggle="tooltip" title="Step through the recipe"> <button type="button" class="mx-2 btn btn-lg btn-secondary" id="step" data-toggle="tooltip" title="Step through the recipe">
Step Step
@@ -217,7 +217,10 @@
<div id="input" class="split no-select"> <div id="input" class="split no-select">
<div class="title no-select"> <div class="title no-select">
<label for="input-text">Input</label> <label for="input-text">Input</label>
<span class="float-right"> <span class="pane-controls">
<div class="io-info" id="input-files-info"></div>
<div class="io-info" id="input-selection-info"></div>
<div class="io-info" id="input-info"></div>
<button type="button" class="btn btn-primary bmd-btn-icon" id="btn-new-tab" data-toggle="tooltip" title="Add a new input tab"> <button type="button" class="btn btn-primary bmd-btn-icon" id="btn-new-tab" data-toggle="tooltip" title="Add a new input tab">
<i class="material-icons">add</i> <i class="material-icons">add</i>
</button> </button>
@@ -236,9 +239,7 @@
<i class="material-icons">view_compact</i> <i class="material-icons">view_compact</i>
</button> </button>
</span> </span>
<div class="io-info" id="input-files-info"></div>
<div class="io-info" id="input-info"></div>
<div class="io-info" id="input-selection-info"></div>
</div> </div>
<div id="input-tabs-wrapper" style="display: none;" class="no-select"> <div id="input-tabs-wrapper" style="display: none;" class="no-select">
<span id="btn-previous-input-tab" class="input-tab-buttons"> <span id="btn-previous-input-tab" class="input-tab-buttons">
@@ -288,7 +289,10 @@
<div id="output" class="split"> <div id="output" class="split">
<div class="title no-select"> <div class="title no-select">
<label for="output-text">Output</label> <label for="output-text">Output</label>
<span class="float-right"> <span class="pane-controls">
<div class="io-info" id="bake-info"></div>
<div class="io-info" id="output-selection-info"></div>
<div class="io-info" id="output-info"></div>
<button type="button" class="btn btn-primary bmd-btn-icon" id="save-all-to-file" data-toggle="tooltip" title="Save all outputs to a zip file" style="display: none"> <button type="button" class="btn btn-primary bmd-btn-icon" id="save-all-to-file" data-toggle="tooltip" title="Save all outputs to a zip file" style="display: none">
<i class="material-icons">archive</i> <i class="material-icons">archive</i>
</button> </button>
@@ -308,9 +312,7 @@
<i class="material-icons">fullscreen</i> <i class="material-icons">fullscreen</i>
</button> </button>
</span> </span>
<div class="io-info" id="bake-info"></div>
<div class="io-info" id="output-info"></div>
<div class="io-info" id="output-selection-info"></div>
<button type="button" class="btn btn-primary bmd-btn-icon hidden" id="magic" data-toggle="tooltip" title="Magic!" data-html="true"> <button type="button" class="btn btn-primary bmd-btn-icon hidden" id="magic" data-toggle="tooltip" title="Magic!" data-html="true">
<svg width="22" height="22" viewBox="0 0 24 24"> <svg width="22" height="22" viewBox="0 0 24 24">
<path d="M7.5,5.6L5,7L6.4,4.5L5,2L7.5,3.4L10,2L8.6,4.5L10,7L7.5,5.6M19.5,15.4L22,14L20.6,16.5L22,19L19.5,17.6L17,19L18.4,16.5L17,14L19.5,15.4M22,2L20.6,4.5L22,7L19.5,5.6L17,7L18.4,4.5L17,2L19.5,3.4L22,2M13.34,12.78L15.78,10.34L13.66,8.22L11.22,10.66L13.34,12.78M14.37,7.29L16.71,9.63C17.1,10 17.1,10.65 16.71,11.04L5.04,22.71C4.65,23.1 4,23.1 3.63,22.71L1.29,20.37C0.9,20 0.9,19.35 1.29,18.96L12.96,7.29C13.35,6.9 14,6.9 14.37,7.29Z" /> <path d="M7.5,5.6L5,7L6.4,4.5L5,2L7.5,3.4L10,2L8.6,4.5L10,7L7.5,5.6M19.5,15.4L22,14L20.6,16.5L22,19L19.5,17.6L17,19L18.4,16.5L17,14L19.5,15.4M22,2L20.6,4.5L22,7L19.5,5.6L17,7L18.4,4.5L17,2L19.5,3.4L22,2M13.34,12.78L15.78,10.34L13.66,8.22L11.22,10.66L13.34,12.78M14.37,7.29L16.71,9.63C17.1,10 17.1,10.65 16.71,11.04L5.04,22.71C4.65,23.1 4,23.1 3.63,22.71L1.29,20.37C0.9,20 0.9,19.35 1.29,18.96L12.96,7.29C13.35,6.9 14,6.9 14.37,7.29Z" />

View File

@@ -24,9 +24,16 @@
line-height: calc(var(--title-height) - 14px); line-height: calc(var(--title-height) - 14px);
} }
.title>span, .pane-controls {
.title>.btn { position: absolute;
margin-top: -4px; right: 8px;
top: 8px;
display: flex;
flex-direction: row;
}
.pane-controls .btn {
margin-left: 2px;
} }
.list-area { .list-area {
@@ -107,4 +114,4 @@
#files .card-header .float-right a:hover { #files .card-header .float-right a:hover {
text-decoration: none; text-decoration: none;
} }

View File

@@ -58,6 +58,10 @@
border-radius: 30px; border-radius: 30px;
} }
.output-maximised .hide-on-maximised-output {
display: none !important;
}
.spin { .spin {
animation-name: spin; animation-name: spin;
animation-duration: 3s; animation-duration: 3s;

View File

@@ -280,9 +280,8 @@
} }
.io-info { .io-info {
margin-right: 20px; margin-right: 18px;
margin-top: 1px; margin-top: 1px;
float: right;
height: 30px; height: 30px;
text-align: right; text-align: right;
line-height: 12px; line-height: 12px;

View File

@@ -39,8 +39,8 @@ div#output {
.split { .split {
box-sizing: border-box; box-sizing: border-box;
/* overflow: auto; /* overflow: auto; */
Removed to enable Background Magic button pulse to overflow. /* Removed to enable Background Magic button pulse to overflow.
Replace this rule if it seems to be causing problems. */ Replace this rule if it seems to be causing problems. */
position: relative; position: relative;
} }

View File

@@ -109,11 +109,15 @@ class OperationsWaiter {
const matchedOps = []; const matchedOps = [];
const matchedDescs = []; const matchedDescs = [];
// Create version with no whitespace for the fuzzy match
// Helps avoid missing matches e.g. query "TCP " would not find "Parse TCP"
const inStrNWS = inStr.replace(/\s/g, "");
for (const opName in this.app.operations) { for (const opName in this.app.operations) {
const op = this.app.operations[opName]; const op = this.app.operations[opName];
// Match op name using fuzzy match // Match op name using fuzzy match
const [nameMatch, score, idxs] = fuzzyMatch(inStr, opName); const [nameMatch, score, idxs] = fuzzyMatch(inStrNWS, opName);
// Match description based on exact match // Match description based on exact match
const descPos = op.description.toLowerCase().indexOf(inStr.toLowerCase()); const descPos = op.description.toLowerCase().indexOf(inStr.toLowerCase());

View File

@@ -1373,6 +1373,7 @@ class OutputWaiter {
const el = e.target.id === "maximise-output" ? e.target : e.target.parentNode; const el = e.target.id === "maximise-output" ? e.target : e.target.parentNode;
if (el.getAttribute("data-original-title").indexOf("Maximise") === 0) { if (el.getAttribute("data-original-title").indexOf("Maximise") === 0) {
document.body.classList.add("output-maximised");
this.app.initialiseSplitter(true); this.app.initialiseSplitter(true);
this.app.columnSplitter.collapse(0); this.app.columnSplitter.collapse(0);
this.app.columnSplitter.collapse(1); this.app.columnSplitter.collapse(1);
@@ -1381,6 +1382,7 @@ class OutputWaiter {
$(el).attr("data-original-title", "Restore output pane"); $(el).attr("data-original-title", "Restore output pane");
el.querySelector("i").innerHTML = "fullscreen_exit"; el.querySelector("i").innerHTML = "fullscreen_exit";
} else { } else {
document.body.classList.remove("output-maximised");
$(el).attr("data-original-title", "Maximise output pane"); $(el).attr("data-original-title", "Maximise output pane");
el.querySelector("i").innerHTML = "fullscreen"; el.querySelector("i").innerHTML = "fullscreen";
this.app.initialiseSplitter(false); this.app.initialiseSplitter(false);

View File

@@ -23,11 +23,11 @@ class WindowWaiter {
/** /**
* Handler for window resize events. * Handler for window resize events.
* Resets the layout of CyberChef's panes after 200ms (so that continuous resizing doesn't cause * Resets adjustable component sizes after 200ms (so that continuous resizing doesn't cause
* continuous resetting). * continuous resetting).
*/ */
windowResize() { windowResize() {
debounce(this.app.resetLayout, 200, "windowResize", this.app, [])(); debounce(this.app.adjustComponentSizes, 200, "windowResize", this.app, [])();
} }

View File

@@ -96,6 +96,7 @@ import "./tests/Protobuf.mjs";
import "./tests/ParseSSHHostKey.mjs"; import "./tests/ParseSSHHostKey.mjs";
import "./tests/DefangIP.mjs"; import "./tests/DefangIP.mjs";
import "./tests/ParseUDP.mjs"; import "./tests/ParseUDP.mjs";
import "./tests/ParseTCP.mjs";
import "./tests/AvroToJSON.mjs"; import "./tests/AvroToJSON.mjs";
import "./tests/Lorenz.mjs"; import "./tests/Lorenz.mjs";
import "./tests/LuhnChecksum.mjs"; import "./tests/LuhnChecksum.mjs";

View File

@@ -0,0 +1,44 @@
/**
* Parse TCP tests.
*
* @author n1474335
* @copyright Crown Copyright 2022
* @license Apache-2.0
*/
import TestRegister from "../../lib/TestRegister.mjs";
TestRegister.addTests([
{
name: "Parse TCP: No options",
input: "c2eb0050a138132e70dc9fb9501804025ea70000",
expectedMatch: /1026 \(Scaled: 1026\)/,
recipeConfig: [
{
op: "Parse TCP",
args: ["Hex"],
}
],
},
{
name: "Parse TCP: Options",
input: "c2eb0050a1380c1f000000008002faf080950000020405b40103030801010402",
expectedMatch: /1460/,
recipeConfig: [
{
op: "Parse TCP",
args: ["Hex"],
}
],
},
{
name: "Parse TCP: Timestamps",
input: "9e90e11574d57b2c00000000a002ffffe5740000020405b40402080aa4e8c8f50000000001030308",
expectedMatch: /2766719221/,
recipeConfig: [
{
op: "Parse TCP",
args: ["Hex"],
}
],
}
]);

View File

@@ -2,7 +2,6 @@
* Parse UDP tests. * Parse UDP tests.
* *
* @author h345983745 * @author h345983745
*
* @copyright Crown Copyright 2019 * @copyright Crown Copyright 2019
* @license Apache-2.0 * @license Apache-2.0
*/ */
@@ -12,15 +11,11 @@ TestRegister.addTests([
{ {
name: "Parse UDP: No Data - JSON", name: "Parse UDP: No Data - JSON",
input: "04 89 00 35 00 2c 01 01", input: "04 89 00 35 00 2c 01 01",
expectedOutput: "{\"Source port\":1161,\"Destination port\":53,\"Length\":44,\"Checksum\":\"0101\"}", expectedOutput: "{\"Source port\":1161,\"Destination port\":53,\"Length\":44,\"Checksum\":\"0x0101\"}",
recipeConfig: [ recipeConfig: [
{
op: "From Hex",
args: ["Auto"],
},
{ {
op: "Parse UDP", op: "Parse UDP",
args: [], args: ["Hex"],
}, },
{ {
op: "JSON Minify", op: "JSON Minify",
@@ -30,15 +25,11 @@ TestRegister.addTests([
}, { }, {
name: "Parse UDP: With Data - JSON", name: "Parse UDP: With Data - JSON",
input: "04 89 00 35 00 2c 01 01 02 02", input: "04 89 00 35 00 2c 01 01 02 02",
expectedOutput: "{\"Source port\":1161,\"Destination port\":53,\"Length\":44,\"Checksum\":\"0101\",\"Data\":\"0202\"}", expectedOutput: "{\"Source port\":1161,\"Destination port\":53,\"Length\":44,\"Checksum\":\"0x0101\",\"Data\":\"0x0202\"}",
recipeConfig: [ recipeConfig: [
{
op: "From Hex",
args: ["Auto"],
},
{ {
op: "Parse UDP", op: "Parse UDP",
args: [], args: ["Hex"],
}, },
{ {
op: "JSON Minify", op: "JSON Minify",
@@ -51,13 +42,9 @@ TestRegister.addTests([
input: "04 89 00", input: "04 89 00",
expectedOutput: "Need 8 bytes for a UDP Header", expectedOutput: "Need 8 bytes for a UDP Header",
recipeConfig: [ recipeConfig: [
{
op: "From Hex",
args: ["Auto"],
},
{ {
op: "Parse UDP", op: "Parse UDP",
args: [], args: ["Hex"],
}, },
{ {
op: "JSON Minify", op: "JSON Minify",

View File

@@ -1,6 +1,7 @@
const webpack = require("webpack"); const webpack = require("webpack");
const MiniCssExtractPlugin = require("mini-css-extract-plugin"); const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const CopyWebpackPlugin = require("copy-webpack-plugin"); const CopyWebpackPlugin = require("copy-webpack-plugin");
const { ModifySourcePlugin } = require("modify-source-webpack-plugin");
const path = require("path"); const path = require("path");
/** /**
@@ -82,6 +83,16 @@ module.exports = {
to: "assets/forge/" to: "assets/forge/"
} }
] ]
}),
new ModifySourcePlugin({
rules: [
{
// Fix toSpare(0) bug in Split.js by avoiding gutter accomodation
test: /split\.es\.js$/,
modify: (src, path) =>
src.replace("if (pixelSize < elementMinSize)", "if (false)")
}
]
}) })
], ],
resolve: { resolve: {