From 8509af210530fc92ca9f57b24972cd2eb34d8c9a Mon Sep 17 00:00:00 2001 From: Didier Stevens Date: Sun, 25 Dec 2022 10:41:45 +0100 Subject: [PATCH] Add new operation: Insert bytes --- src/core/config/Categories.json | 1 + src/core/operations/InsertBytes.mjs | 81 ++++++++++++++++++++++++++ tests/operations/index.mjs | 1 + tests/operations/tests/InsertBytes.mjs | 78 +++++++++++++++++++++++++ 4 files changed, 161 insertions(+) create mode 100644 src/core/operations/InsertBytes.mjs create mode 100644 tests/operations/tests/InsertBytes.mjs diff --git a/src/core/config/Categories.json b/src/core/config/Categories.json index 075e8d66..71c41a0c 100644 --- a/src/core/config/Categories.json +++ b/src/core/config/Categories.json @@ -265,6 +265,7 @@ "Expand alphabet range", "Drop bytes", "Take bytes", + "Insert bytes", "Pad lines", "Find / Replace", "Regular expression", diff --git a/src/core/operations/InsertBytes.mjs b/src/core/operations/InsertBytes.mjs new file mode 100644 index 00000000..ea07e935 --- /dev/null +++ b/src/core/operations/InsertBytes.mjs @@ -0,0 +1,81 @@ +/** + * @author Didier Stevens [didier.stevens@gmail.com] + * @copyright Crown Copyright 2022 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import {BITWISE_OP_DELIMS} from "../lib/BitwiseOp.mjs"; +import Utils from "../Utils.mjs"; + +/** + * Insert bytes operation + */ +class InsertBytes extends Operation { + + /** + * InsertBytes constructor + */ + constructor() { + super(); + + this.name = "Insert bytes"; + this.module = "Default"; + this.description = "Insert bytes at arbitrary position. Options 'from end' and 'overwrite' available."; + this.infoURL = ""; + this.inputType = "byteArray"; + this.outputType = "byteArray"; + this.args = [ + { + "name": "Bytes", + "type": "toggleString", + "value": "", + "toggleValues": BITWISE_OP_DELIMS + }, + { + "name": "Start", + "type": "number", + "value": 0 + }, + { + "name": "From end", + "type": "boolean", + "value": false + }, + { + "name": "Overwrite", + "type": "boolean", + "value": false + } + ]; + } + + /** + * @param {byteArray} input + * @param {Object[]} args + * @returns {byteArray} + */ + run(input, args) { + const value = Utils.convertToByteArray(args[0].string || "", args[0].option); + let start = args[1]; + const fromend = args[2]; + const overwrite = args[3]; + + if (start < 0) + throw new OperationError("Start must not be negative"); + if (start > input.length) + throw new OperationError("Start must not be bigger than input"); + if (fromend) + start = input.length - start; + const left = input.slice(0, start); + let right = input.slice(start); + if (overwrite) + right = right.slice(value.length); + + return left.concat(value, right); + } + +} + +export default InsertBytes; diff --git a/tests/operations/index.mjs b/tests/operations/index.mjs index 7a3361f2..04cc3f1d 100644 --- a/tests/operations/index.mjs +++ b/tests/operations/index.mjs @@ -130,6 +130,7 @@ import "./tests/FletcherChecksum.mjs"; import "./tests/CMAC.mjs"; import "./tests/AESKeyWrap.mjs"; import "./tests/Rabbit.mjs"; +import "./tests/InsertBytes.mjs"; // Cannot test operations that use the File type yet // import "./tests/SplitColourChannels.mjs"; diff --git a/tests/operations/tests/InsertBytes.mjs b/tests/operations/tests/InsertBytes.mjs new file mode 100644 index 00000000..8eca3973 --- /dev/null +++ b/tests/operations/tests/InsertBytes.mjs @@ -0,0 +1,78 @@ +/** + * InsertBytes test. + * + * @author Didier Stevens [didier.stevens@gmail.com] + * @copyright Crown Copyright 2022 + * @license Apache-2.0 + */ +import TestRegister from "../../lib/TestRegister.mjs"; + +TestRegister.addTests([ + { + name: "Insert bytes - test 1", + input: "This is a test", + expectedOutput: "This is a test", + recipeConfig: [ + { + op: "Insert bytes", + args: [{"string": "", "option": "Hex"}, 0, false, false], + }, + ], + }, + { + name: "Insert bytes - test 2", + input: "This is a test", + expectedOutput: "AThis is a test", + recipeConfig: [ + { + op: "Insert bytes", + args: [{"string": "41", "option": "Hex"}, 0, false, false], + }, + ], + }, + { + name: "Insert bytes - test 3", + input: "This is a test", + expectedOutput: "This is a testA", + recipeConfig: [ + { + op: "Insert bytes", + args: [{"string": "41", "option": "Hex"}, 0, true, false], + }, + ], + }, + { + name: "Insert bytes - test 4", + input: "This is a test", + expectedOutput: "Ahis is a test", + recipeConfig: [ + { + op: "Insert bytes", + args: [{"string": "41", "option": "Hex"}, 0, false, true], + }, + ], + }, + { + name: "Insert bytes - test 5", + input: "This is a test", + expectedOutput: "This is a tesA", + recipeConfig: [ + { + op: "Insert bytes", + args: [{"string": "41", "option": "Hex"}, 1, true, true], + }, + ], + }, + { + name: "Insert bytes - test 6", + input: "This is a test", + expectedOutput: "This is not a test", + recipeConfig: [ + { + op: "Insert bytes", + args: [{"string": "not ", "option": "Latin1"}, 8, false, false], + }, + ], + }, + +]);