Compare commits

..

30 Commits

Author SHA1 Message Date
n1474335
1fb1d9cbb7 9.39.6 2022-07-08 15:16:00 +01:00
n1474335
2f097e5dfc Tidied up Base85 issues 2022-07-08 15:15:53 +01:00
n1474335
b71e3241be Merge branch 'master' of https://github.com/benediktwerner/CyberChef 2022-07-08 15:04:09 +01:00
n1474335
4b018bf421 9.39.5 2022-07-08 14:55:32 +01:00
n1474335
f751de896f Merge branch 'base' of https://github.com/john19696/CyberChef 2022-07-08 14:55:20 +01:00
n1474335
65aeae9c1e 9.39.4 2022-07-08 14:53:07 +01:00
n1474335
80943b0c26 Merge branch 'fix-merge' of https://github.com/n1073645/CyberChef 2022-07-08 14:52:56 +01:00
n1474335
a9657ac5c7 9.39.3 2022-07-08 14:51:08 +01:00
n1474335
6fa2e49f3a Merge branch 'webp-extractor' of https://github.com/n1073645/CyberChef 2022-07-08 14:50:57 +01:00
n1474335
50f0f70805 9.39.2 2022-07-08 14:49:50 +01:00
n1474335
fc95d82c49 Tweaked Extract Files minimum size 2022-07-08 14:49:40 +01:00
n1474335
bb6c1c54ff Merge branch 'extract-files-min-size' of https://github.com/n1073645/CyberChef 2022-07-08 13:57:06 +01:00
n1474335
c4414bd910 Fixed dropdown toggle height 2022-07-08 13:53:19 +01:00
n1073645
42c911838d Add min size to Extract Files 2022-06-17 11:18:49 +01:00
n1073645
8917eabfd1 Implemented webp extractor 2022-06-17 09:56:36 +01:00
n1073645
fc91469807 Added nesting to Merge/Fork/Subsection 2022-06-17 09:26:00 +01:00
John L
1735d9c091 remove logging 2022-06-15 15:07:39 +01:00
John L
00d754d466 lint fixes 2022-06-14 15:57:04 +01:00
John L
906727f133 Base85 improvements 2022-06-14 10:23:13 +01:00
n1474335
54fdc05e3a 9.39.1 2022-06-09 16:32:32 +01:00
n1474335
2267569c8d Fixed lint 2022-06-09 16:32:23 +01:00
n1474335
2f53ee3974 Merge branch 'fix_loop_counter' of https://github.com/sec65/CyberChef 2022-06-09 16:23:01 +01:00
sec65
1fea9a25a5 reset loop counter after last run 2022-06-05 17:22:42 +02:00
Benedikt Werner
f5a7db03cd Base85: Only require 15 continuous base85 chars 2020-06-10 15:50:26 +02:00
Benedikt Werner
ee408f7add Base85: Update magic regexes to require 20 non-whitespace base85 chars 2020-05-22 03:30:57 +02:00
Benedikt Werner
1294d764e2 Base85: Only remove start and end markers with standard/ascii85 encoding 2020-05-22 03:30:15 +02:00
Benedikt Werner
eab1be0e2c Magic base85: Remove 'i' flag 2020-05-20 00:23:50 +02:00
Benedikt Werner
15dd9d4c93 Add magic checks for base85 2020-05-16 00:42:50 +02:00
Benedikt Werner
103ecff6a7 Base85: Ignore whitespace 2020-05-16 00:42:31 +02:00
Benedikt Werner
0182cdda69 Base85: Fix alphabetName 2020-05-16 00:42:02 +02:00
18 changed files with 321 additions and 26 deletions

4
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{
"name": "cyberchef",
"version": "9.39.0",
"version": "9.39.6",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "cyberchef",
"version": "9.39.0",
"version": "9.39.6",
"hasInstallScript": true,
"license": "Apache-2.0",
"dependencies": {

View File

@@ -1,6 +1,6 @@
{
"name": "cyberchef",
"version": "9.39.0",
"version": "9.39.6",
"description": "The Cyber Swiss Army Knife for encryption, encoding, compression and data analysis.",
"author": "n1474335 <n1474335@gmail.com>",
"homepage": "https://gchq.github.io/CyberChef",

View File

@@ -1,3 +1,5 @@
import Utils from "../Utils.mjs";
/**
* Base85 resources.
*
@@ -32,13 +34,12 @@ export const ALPHABET_OPTIONS = [
* @returns {string}
*/
export function alphabetName(alphabet) {
alphabet = alphabet.replace(/'/g, "&apos;");
alphabet = alphabet.replace(/"/g, "&quot;");
alphabet = alphabet.replace(/\\/g, "&bsol;");
alphabet = escape(alphabet);
let name;
ALPHABET_OPTIONS.forEach(function(a) {
if (escape(alphabet) === escape(a.value)) name = a.name;
const expanded = Utils.expandAlphRange(a.value).join("");
if (alphabet === escape(expanded)) name = a.name;
});
return name;

View File

@@ -70,7 +70,7 @@ export const FILE_SIGNATURES = {
10: 0x42,
11: 0x50
},
extractor: null
extractor: extractWEBP
},
{
name: "Camera Image File Format",
@@ -3032,6 +3032,30 @@ export function extractPNG(bytes, offset) {
}
/**
* WEBP extractor.
*
* @param {Uint8Array} bytes
* @param {number} offset
* @returns {Uint8Array}
*/
export function extractWEBP(bytes, offset) {
const stream = new Stream(bytes.slice(offset));
// Move to file size offset.
stream.moveForwardsBy(4);
// Read file size field.
const fileSize = stream.readInt(4, "le");
// Move to end of file.
// There is no need to minus 8 from the size as the size factors in the offset.
stream.moveForwardsBy(fileSize);
return stream.carve();
}
/**
* BMP extractor.
*

View File

@@ -64,6 +64,7 @@ class ConditionalJump extends Operation {
jmpIndex = getLabelIndex(label, state);
if (state.numJumps >= maxJumps || jmpIndex === -1) {
state.numJumps = 0;
return state;
}
@@ -73,6 +74,8 @@ class ConditionalJump extends Operation {
if (!invert && strMatch || invert && !strMatch) {
state.progress = jmpIndex;
state.numJumps++;
} else {
state.numJumps = 0;
}
}

View File

@@ -38,7 +38,7 @@ class ExtractFiles extends Operation {
<li>
${supportedExts.join("</li><li>")}
</li>
</ul>`;
</ul>Minimum File Size can be used to prune small false positives.`;
this.infoURL = "https://forensicswiki.xyz/wiki/index.php?title=File_Carving";
this.inputType = "ArrayBuffer";
this.outputType = "List<File>";
@@ -54,6 +54,11 @@ class ExtractFiles extends Operation {
name: "Ignore failed extractions",
type: "boolean",
value: true
},
{
name: "Minimum File Size",
type: "number",
value: 100
}
]);
}
@@ -66,6 +71,7 @@ class ExtractFiles extends Operation {
run(input, args) {
const bytes = new Uint8Array(input),
categories = [],
minSize = args.pop(1),
ignoreFailedExtractions = args.pop(1);
args.forEach((cat, i) => {
@@ -80,7 +86,9 @@ class ExtractFiles extends Operation {
const errors = [];
detectedFiles.forEach(detectedFile => {
try {
files.push(extractFile(bytes, detectedFile.fileDetails, detectedFile.offset));
const file = extractFile(bytes, detectedFile.fileDetails, detectedFile.offset);
if (file.size >= minSize)
files.push(file);
} catch (err) {
if (!ignoreFailedExtractions && err.message.indexOf("No extraction algorithm available") < 0) {
errors.push(

View File

@@ -65,12 +65,21 @@ class Fork extends Operation {
if (input)
inputs = input.split(splitDelim);
// Set to 1 as if we are here, then there is one, the current one.
let numOp = 1;
// Create subOpList for each tranche to operate on
// (all remaining operations unless we encounter a Merge)
// 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;
numOp--;
if (numOp === 0 || opList[i].ingValues[0])
break;
else
// Not this Fork's Merge.
subOpList.push(opList[i]);
} else {
if (opList[i].name === "Fork" || opList[i].name === "Subsection")
numOp++;
subOpList.push(opList[i]);
}
}

View File

@@ -32,7 +32,12 @@ class FromBase45 extends Operation {
name: "Alphabet",
type: "string",
value: ALPHABET
}
},
{
name: "Remove non-alphabet chars",
type: "boolean",
value: true
},
];
this.highlight = highlightFromBase45;
@@ -46,10 +51,17 @@ class FromBase45 extends Operation {
*/
run(input, args) {
if (!input) return [];
const alphabet = Utils.expandAlphRange(args[0]);
const alphabet = Utils.expandAlphRange(args[0]).join("");
const removeNonAlphChars = args[1];
const res = [];
// Remove non-alphabet characters
if (removeNonAlphChars) {
const re = new RegExp("[^" + alphabet.replace(/[[\]\\\-^$]/g, "\\$&") + "]", "g");
input = input.replace(re, "");
}
for (const triple of Utils.chunked(input, 3)) {
triple.reverse();
let b = 0;

View File

@@ -32,6 +32,40 @@ class FromBase85 extends Operation {
type: "editableOption",
value: ALPHABET_OPTIONS
},
{
name: "Remove non-alphabet chars",
type: "boolean",
value: true
},
];
this.checks = [
{
pattern:
"^\\s*(?:<~)?" + // Optional whitespace and starting marker
"[\\s!-uz]*" + // Any amount of base85 characters and whitespace
"[!-uz]{15}" + // At least 15 continoues base85 characters without whitespace
"[\\s!-uz]*" + // Any amount of base85 characters and whitespace
"(?:~>)?\\s*$", // Optional ending marker and whitespace
args: ["!-u"],
},
{
pattern:
"^" +
"[\\s0-9a-zA-Z.\\-:+=^!/*?&<>()[\\]{}@%$#]*" +
"[0-9a-zA-Z.\\-:+=^!/*?&<>()[\\]{}@%$#]{15}" + // At least 15 continoues base85 characters without whitespace
"[\\s0-9a-zA-Z.\\-:+=^!/*?&<>()[\\]{}@%$#]*" +
"$",
args: ["0-9a-zA-Z.\\-:+=^!/*?&<>()[]{}@%$#"],
},
{
pattern:
"^" +
"[\\s0-9A-Za-z!#$%&()*+\\-;<=>?@^_`{|}~]*" +
"[0-9A-Za-z!#$%&()*+\\-;<=>?@^_`{|}~]{15}" + // At least 15 continoues base85 characters without whitespace
"[\\s0-9A-Za-z!#$%&()*+\\-;<=>?@^_`{|}~]*" +
"$",
args: ["0-9A-Za-z!#$%&()*+\\-;<=>?@^_`{|}~"],
},
];
}
@@ -43,6 +77,7 @@ class FromBase85 extends Operation {
run(input, args) {
const alphabet = Utils.expandAlphRange(args[0]).join(""),
encoding = alphabetName(alphabet),
removeNonAlphChars = args[1],
result = [];
if (alphabet.length !== 85 ||
@@ -50,11 +85,18 @@ class FromBase85 extends Operation {
throw new OperationError("Alphabet must be of length 85");
}
if (input.length === 0) return [];
const matches = input.match(/<~(.+?)~>/);
// Remove delimiters if present
const matches = input.match(/^<~(.+?)~>$/);
if (matches !== null) input = matches[1];
// Remove non-alphabet characters
if (removeNonAlphChars) {
const re = new RegExp("[^" + alphabet.replace(/[[\]\\\-^$]/g, "\\$&") + "]", "g");
input = input.replace(re, "");
}
if (input.length === 0) return [];
let i = 0;
let block, blockBytes;
while (i < input.length) {
@@ -69,7 +111,7 @@ class FromBase85 extends Operation {
.map((chr, idx) => {
const digit = alphabet.indexOf(chr);
if (digit < 0 || digit > 84) {
throw `Invalid character '${chr}' at index ${idx}`;
throw `Invalid character '${chr}' at index ${i + idx}`;
}
return digit;
});

View File

@@ -52,6 +52,7 @@ class Jump extends Operation {
const jmpIndex = getLabelIndex(label, state);
if (state.numJumps >= maxJumps || jmpIndex === -1) {
state.numJumps = 0;
return state;
}

View File

@@ -20,10 +20,16 @@ class Merge extends Operation {
this.name = "Merge";
this.flowControl = true;
this.module = "Default";
this.description = "Consolidate all branches back into a single trunk. The opposite of Fork.";
this.description = "Consolidate all branches back into a single trunk. The opposite of Fork. Unticking the Merge All checkbox will only consolidate all branches up to the nearest Fork/Subsection.";
this.inputType = "string";
this.outputType = "string";
this.args = [];
this.args = [
{
name: "Merge All",
type: "boolean",
value: true,
}
];
}
/**

View File

@@ -67,12 +67,21 @@ class Subsection extends Operation {
subOpList = [];
if (input && section !== "") {
// Set to 1 as if we are here, then there is one, the current one.
let numOp = 1;
// Create subOpList for each tranche to operate on
// all remaining operations unless we encounter a Merge
for (let i = state.progress + 1; i < opList.length; i++) {
if (opList[i].name === "Merge" && !opList[i].disabled) {
break;
numOp--;
if (numOp === 0 || opList[i].ingValues[0])
break;
else
// Not this subsection's Merge.
subOpList.push(opList[i]);
} else {
if (opList[i].name === "Fork" || opList[i].name === "Subsection")
numOp++;
subOpList.push(opList[i]);
}
}

View File

@@ -186,7 +186,7 @@ div.toggle-string {
}
.ingredients .dropdown-toggle-split {
height: 41px !important;
height: 40px !important;
}
.boolean-arg {

View File

@@ -12,6 +12,7 @@
import Chef from "../../src/core/Chef.mjs";
import Utils from "../../src/core/Utils.mjs";
import cliProgress from "cli-progress";
import log from "loglevel";
/**
* Object to store and run the list of tests.
@@ -50,6 +51,9 @@ class TestRegister {
* Runs all the tests in the register.
*/
async runTests () {
// Turn off logging to avoid messy errors
log.setLevel("silent", false);
const progBar = new cliProgress.SingleBar({
format: formatter,
stopOnComplete: true
@@ -84,7 +88,17 @@ class TestRegister {
if (result.error) {
if (test.expectedError) {
ret.status = "passing";
if (result.error.displayStr === test.expectedOutput) {
ret.status = "passing";
} else {
ret.status = "failing";
ret.output = [
"Expected",
"\t" + test.expectedOutput.replace(/\n/g, "\n\t"),
"Received",
"\t" + result.error.displayStr.replace(/\n/g, "\n\t"),
].join("\n");
}
} else {
ret.status = "erroring";
ret.output = result.error.displayStr;
@@ -118,6 +132,9 @@ class TestRegister {
progBar.increment();
}
// Turn logging back on
log.setLevel("info", false);
return testResults;
}

View File

@@ -24,6 +24,7 @@ import "./tests/Base45.mjs";
import "./tests/Base58.mjs";
import "./tests/Base64.mjs";
import "./tests/Base62.mjs";
import "./tests/Base85.mjs";
import "./tests/BitwiseOp.mjs";
import "./tests/ByteRepr.mjs";
import "./tests/CartesianProduct.mjs";
@@ -114,6 +115,7 @@ import "./tests/HASSH.mjs";
import "./tests/GetAllCasings.mjs";
import "./tests/SIGABA.mjs";
import "./tests/ELFInfo.mjs";
import "./tests/Subsection.mjs";
// Cannot test operations that use the File type yet

View File

@@ -0,0 +1,48 @@
/**
* Base85 tests
*
* @author john19696
* @copyright Crown Copyright 2019
* @license Apache-2.0
*/
import TestRegister from "../../lib/TestRegister.mjs";
// Example from Wikipedia
const wpExample = "Man is distinguished, not only by his reason, but by this singular passion from other animals, which is a lust of the mind, that by a perseverance of delight in the continued and indefatigable generation of knowledge, exceeds the short vehemence of any carnal pleasure.";
// Escape newline, quote & backslash
const wpOutput = "9jqo^BlbD-BleB1DJ+*+F(f,q/0JhKF<GL>Cj@.4Gp$d7F!,L7@<6@)/0JDEF<G%<+EV:2F!,O<\
DJ+*.@<*K0@<6L(Df-\\0Ec5e;DffZ(EZee.Bl.9pF\"AGXBPCsi+DGm>@3BB/F*&OCAfu2/AKYi(\
DIb:@FD,*)+C]U=@3BN#EcYf8ATD3s@q?d$AftVqCh[NqF<G:8+EV:.+Cf>-FD5W8ARlolDIal(\
DId<j@<?3r@:F%a+D58'ATD4$Bl@l3De:,-DJs`8ARoFb/0JMK@qB4^F!,R<AKZ&-DfTqBG%G>u\
D.RTpAKYo'+CT/5+Cei#DII?(E,9)oF*2M7/c";
TestRegister.addTests([
{
name: "To Base85",
input: wpExample,
expectedOutput: wpOutput,
recipeConfig: [
{ "op": "To Base85",
"args": ["!-u"] }
]
},
{
name: "From Base85",
input: wpOutput + "\n",
expectedOutput: wpExample,
recipeConfig: [
{ "op": "From Base85",
"args": ["!-u", true] }
]
},
{
name: "From Base85",
input: wpOutput + "v",
expectedError: true,
expectedOutput: "From Base85 - Invalid character 'v' at index 337",
recipeConfig: [
{ "op": "From Base85",
"args": ["!-u", false] }
]
},
]);

View File

@@ -31,7 +31,7 @@ TestRegister.addTests([
},
{
op: "Merge",
args: [],
args: [true],
},
],
},
@@ -50,7 +50,7 @@ TestRegister.addTests([
},
{
op: "Merge",
args: [],
args: [true],
},
],
},
@@ -66,5 +66,16 @@ TestRegister.addTests([
{"op": "Label", "args": ["skipReturn"]},
{"op": "To Base64", "args": ["A-Za-z0-9+/="]}
]
}
},
{
name: "Fork, Partial Merge",
input: "Hello World",
expectedOutput: "48656c6c6f 576f726c64",
recipeConfig: [
{ "op": "Fork", "args": [" ", " ", false] },
{ "op": "Fork", "args": ["l", "l", false] },
{ "op": "Merge", "args": [false] },
{ "op": "To Hex", "args": ["None", 0] },
]
},
]);

View File

@@ -0,0 +1,102 @@
/**
* Subsection Tests.
*
* @author n1073645 [n1073645@gmail.com]
* @copyright Crown Copyright 2022
* @license Apache-2.0
*/
import TestRegister from "../../lib/TestRegister.mjs";
TestRegister.addTests([
{
name: "Subsection: nothing",
input: "",
expectedOutput: "",
recipeConfig: [
{
"op": "Subsection",
"args": ["", true, true, false],
},
],
},
{
name: "Subsection, Full Merge: nothing",
input: "",
expectedOutput: "",
recipeConfig: [
{
"op": "Subsection",
"args": ["", true, true, false],
},
{
"op": "Merge",
"args": [true],
},
],
},
{
name: "Subsection, Partial Merge: nothing",
input: "",
expectedOutput: "",
recipeConfig: [
{
"op": "Subsection",
"args": ["", true, true, false],
},
{
"op": "Merge",
"args": [false],
},
],
},
{
name: "Subsection, Full Merge: Base64 with Hex",
input: "SGVsbG38675629ybGQ=",
expectedOutput: "Hello World",
recipeConfig: [
{
"op": "Subsection",
"args": ["386756", true, true, false],
},
{
"op": "From Hex",
"args": ["Auto"],
},
{
"op": "Merge",
"args": [true],
},
{
"op": "From Base64",
"args": ["A-Za-z0-9+/=", true, false],
},
],
},
{
name: "Subsection, Partial Merge: Base64 with Hex surrounded by binary data.",
input: "000000000SGVsbG38675629ybGQ=0000000000",
expectedOutput: "000000000Hello World0000000000",
recipeConfig: [
{
"op": "Subsection",
"args": ["SGVsbG38675629ybGQ=", true, true, false],
},
{
"op": "Subsection",
"args": ["386756", true, true, false],
},
{
"op": "From Hex",
"args": ["Auto"],
},
{
"op": "Merge",
"args": [false],
},
{
"op": "From Base64",
"args": ["A-Za-z0-9+/=", true, false],
},
],
},
]);