diff --git a/src/core/config/Categories.json b/src/core/config/Categories.json
index 99ae5103..2602c3c0 100644
--- a/src/core/config/Categories.json
+++ b/src/core/config/Categories.json
@@ -234,6 +234,7 @@
"Parse User Agent",
"Parse IP range",
"Parse IPv6 address",
+ "IPv6 Transition Addresses",
"Parse IPv4 header",
"Strip IPv4 header",
"Parse TCP",
@@ -326,7 +327,8 @@
"Pseudo-Random Number Generator",
"Sleep",
"File Tree",
- "Take nth bytes"
+ "Take nth bytes",
+ "Drop nth bytes"
]
},
{
diff --git a/src/core/operations/BlowfishDecrypt.mjs b/src/core/operations/BlowfishDecrypt.mjs
index 43d6718a..afb26007 100644
--- a/src/core/operations/BlowfishDecrypt.mjs
+++ b/src/core/operations/BlowfishDecrypt.mjs
@@ -76,8 +76,8 @@ class BlowfishDecrypt extends Operation {
Blowfish's key length needs to be between 4 and 56 bytes (32-448 bits).`);
}
- if (iv.length !== 8) {
- throw new OperationError(`Invalid IV length: ${iv.length} bytes. Expected 8 bytes`);
+ if (mode !== "ECB" && iv.length !== 8) {
+ throw new OperationError(`Invalid IV length: ${iv.length} bytes. Expected 8 bytes.`);
}
input = Utils.convertToByteString(input, inputType);
diff --git a/src/core/operations/BlowfishEncrypt.mjs b/src/core/operations/BlowfishEncrypt.mjs
index eab3d286..1d5dcf02 100644
--- a/src/core/operations/BlowfishEncrypt.mjs
+++ b/src/core/operations/BlowfishEncrypt.mjs
@@ -72,12 +72,12 @@ class BlowfishEncrypt extends Operation {
if (key.length < 4 || key.length > 56) {
throw new OperationError(`Invalid key length: ${key.length} bytes
-
+
Blowfish's key length needs to be between 4 and 56 bytes (32-448 bits).`);
}
- if (iv.length !== 8) {
- throw new OperationError(`Invalid IV length: ${iv.length} bytes. Expected 8 bytes`);
+ if (mode !== "ECB" && iv.length !== 8) {
+ throw new OperationError(`Invalid IV length: ${iv.length} bytes. Expected 8 bytes.`);
}
input = Utils.convertToByteString(input, inputType);
diff --git a/src/core/operations/DropNthBytes.mjs b/src/core/operations/DropNthBytes.mjs
new file mode 100644
index 00000000..e6bac1cd
--- /dev/null
+++ b/src/core/operations/DropNthBytes.mjs
@@ -0,0 +1,79 @@
+/**
+ * @author Oshawk [oshawk@protonmail.com]
+ * @copyright Crown Copyright 2019
+ * @license Apache-2.0
+ */
+
+import Operation from "../Operation.mjs";
+import OperationError from "../errors/OperationError.mjs";
+
+/**
+ * Drop nth bytes operation
+ */
+class DropNthBytes extends Operation {
+
+ /**
+ * DropNthBytes constructor
+ */
+ constructor() {
+ super();
+
+ this.name = "Drop nth bytes";
+ this.module = "Default";
+ this.description = "Drops every nth byte starting with a given byte.";
+ this.infoURL = "";
+ this.inputType = "byteArray";
+ this.outputType = "byteArray";
+ this.args = [
+ {
+ name: "Drop every",
+ type: "number",
+ value: 4
+ },
+ {
+ name: "Starting at",
+ type: "number",
+ value: 0
+ },
+ {
+ name: "Apply to each line",
+ type: "boolean",
+ value: false
+ }
+ ];
+ }
+
+ /**
+ * @param {byteArray} input
+ * @param {Object[]} args
+ * @returns {byteArray}
+ */
+ run(input, args) {
+ const n = args[0];
+ const start = args[1];
+ const eachLine = args[2];
+
+ if (parseInt(n, 10) !== n || n <= 0) {
+ throw new OperationError("'Drop every' must be a positive integer.");
+ }
+ if (parseInt(start, 10) !== start || start < 0) {
+ throw new OperationError("'Starting at' must be a positive or zero integer.");
+ }
+
+ let offset = 0;
+ const output = [];
+ for (let i = 0; i < input.length; i++) {
+ if (eachLine && input[i] === 0x0a) {
+ output.push(0x0a);
+ offset = i + 1;
+ } else if (i - offset < start || (i - (start + offset)) % n !== 0) {
+ output.push(input[i]);
+ }
+ }
+
+ return output;
+ }
+
+}
+
+export default DropNthBytes;
diff --git a/src/core/operations/IPv6TransitionAddresses.mjs b/src/core/operations/IPv6TransitionAddresses.mjs
new file mode 100644
index 00000000..80d03fd0
--- /dev/null
+++ b/src/core/operations/IPv6TransitionAddresses.mjs
@@ -0,0 +1,209 @@
+/**
+ * @author jb30795 [jb30795@proton.me]
+ * @copyright Crown Copyright 2024
+ * @license Apache-2.0
+ */
+
+import Operation from "../Operation.mjs";
+
+/**
+ * IPv6 Transition Addresses operation
+ */
+class IPv6TransitionAddresses extends Operation {
+
+ /**
+ * IPv6TransitionAddresses constructor
+ */
+ constructor() {
+ super();
+
+ this.name = "IPv6 Transition Addresses";
+ this.module = "Default";
+ this.description = "Converts IPv4 addresses to their IPv6 Transition addresses. IPv6 Transition addresses can also be converted back into their original IPv4 address. MAC addresses can also be converted into the EUI-64 format, this can them be appended to your IPv6 /64 range to obtain a full /128 address.
Transition technologies enable translation between IPv4 and IPv6 addresses or tunneling to allow traffic to pass through the incompatible network, allowing the two standards to coexist.
Only /24 ranges and currently handled. Remove headers to easily copy out results.";
+ this.infoURL = "https://wikipedia.org/wiki/IPv6_transition_mechanism";
+ this.inputType = "string";
+ this.outputType = "string";
+ this.args = [
+ {
+ "name": "Ignore ranges",
+ "type": "boolean",
+ "value": true
+ },
+ {
+ "name": "Remove headers",
+ "type": "boolean",
+ "value": false
+ }
+ ];
+ }
+
+ /**
+ * @param {string} input
+ * @param {Object[]} args
+ * @returns {string}
+ */
+ run(input, args) {
+ const XOR = {"0": "2", "1": "3", "2": "0", "3": "1", "4": "6", "5": "7", "6": "4", "7": "5", "8": "a", "9": "b", "a": "8", "b": "9", "c": "e", "d": "f", "e": "c", "f": "d"};
+
+ /**
+ * Function to convert to hex
+ */
+ function hexify(octet) {
+ return Number(octet).toString(16).padStart(2, "0");
+ }
+
+ /**
+ * Function to convert Hex to Int
+ */
+ function intify(hex) {
+ return parseInt(hex, 16);
+ }
+
+ /**
+ * Function converts IPv4 to IPv6 Transtion address
+ */
+ function ipTransition(input, range) {
+ let output = "";
+ const HEXIP = input.split(".");
+
+ /**
+ * 6to4
+ */
+ if (!args[1]) {
+ output += "6to4: ";
+ }
+ output += "2002:" + hexify(HEXIP[0]) + hexify(HEXIP[1]) + ":" + hexify(HEXIP[2]);
+ if (range) {
+ output += "00::/40\n";
+ } else {
+ output += hexify(HEXIP[3]) + "::/48\n";
+ }
+
+ /**
+ * Mapped
+ */
+ if (!args[1]) {
+ output += "IPv4 Mapped: ";
+ }
+ output += "::ffff:" + hexify(HEXIP[0]) + hexify(HEXIP[1]) + ":" + hexify(HEXIP[2]);
+ if (range) {
+ output += "00/120\n";
+ } else {
+ output += hexify(HEXIP[3]) + "\n";
+ }
+
+ /**
+ * Translated
+ */
+ if (!args[1]) {
+ output += "IPv4 Translated: ";
+ }
+ output += "::ffff:0:" + hexify(HEXIP[0]) + hexify(HEXIP[1]) + ":" + hexify(HEXIP[2]);
+ if (range) {
+ output += "00/120\n";
+ } else {
+ output += hexify(HEXIP[3]) + "\n";
+ }
+
+ /**
+ * Nat64
+ */
+ if (!args[1]) {
+ output += "Nat 64: ";
+ }
+ output += "64:ff9b::" + hexify(HEXIP[0]) + hexify(HEXIP[1]) + ":" + hexify(HEXIP[2]);
+ if (range) {
+ output += "00/120\n";
+ } else {
+ output += hexify(HEXIP[3]) + "\n";
+ }
+
+ return output;
+ }
+
+ /**
+ * Convert MAC to EUI-64
+ */
+ function macTransition(input) {
+ let output = "";
+ const MACPARTS = input.split(":");
+ if (!args[1]) {
+ output += "EUI-64 Interface ID: ";
+ }
+ const MAC = MACPARTS[0] + MACPARTS[1] + ":" + MACPARTS[2] + "ff:fe" + MACPARTS[3] + ":" + MACPARTS[4] + MACPARTS[5];
+ output += MAC.slice(0, 1) + XOR[MAC.slice(1, 2)] + MAC.slice(2);
+
+ return output;
+ }
+
+
+ /**
+ * Convert IPv6 address to its original IPv4 or MAC address
+ */
+ function unTransition(input) {
+ let output = "";
+ let hextets = "";
+
+ /**
+ * 6to4
+ */
+ if (input.startsWith("2002:")) {
+ if (!args[1]) {
+ output += "IPv4: ";
+ }
+ output += String(intify(input.slice(5, 7))) + "." + String(intify(input.slice(7, 9)))+ "." + String(intify(input.slice(10, 12)))+ "." + String(intify(input.slice(12, 14))) + "\n";
+ } else if (input.startsWith("::ffff:") || input.startsWith("0000:0000:0000:0000:0000:ffff:") || input.startsWith("::ffff:0000:") || input.startsWith("0000:0000:0000:0000:ffff:0000:") || input.startsWith("64:ff9b::") || input.startsWith("0064:ff9b:0000:0000:0000:0000:")) {
+ /**
+ * Mapped/Translated/Nat64
+ */
+ hextets = /:([0-9a-z]{1,4}):[0-9a-z]{1,4}$/.exec(input)[1].padStart(4, "0") + /:([0-9a-z]{1,4})$/.exec(input)[1].padStart(4, "0");
+ if (!args[1]) {
+ output += "IPv4: ";
+ }
+ output += intify(hextets.slice(-8, -7) + hextets.slice(-7, -6)) + "." +intify(hextets.slice(-6, -5) + hextets.slice(-5, -4)) + "." +intify(hextets.slice(-4, -3) + hextets.slice(-3, -2)) + "." +intify(hextets.slice(-2, -1) + hextets.slice(-1,)) + "\n";
+ } else if (input.slice(-12, -7).toUpperCase() === "FF:FE") {
+ /**
+ * EUI-64
+ */
+ if (!args[1]) {
+ output += "Mac Address: ";
+ }
+ const MAC = (input.slice(-19, -17) + ":" + input.slice(-17, -15) + ":" + input.slice(-14, -12) + ":" + input.slice(-7, -5) + ":" + input.slice(-4, -2) + ":" + input.slice(-2,)).toUpperCase();
+ output += MAC.slice(0, 1) + XOR[MAC.slice(1, 2)] + MAC.slice(2) + "\n";
+ }
+
+ return output;
+ }
+
+
+ /**
+ * Main
+ */
+ let output = "";
+ let inputs = input.split("\n");
+ // Remove blank rows
+ inputs = inputs.filter(Boolean);
+
+ for (let i = 0; i < inputs.length; i++) {
+ // if ignore ranges is checked and input is a range, skip
+ if ((args[0] && !inputs[i].includes("/")) || (!args[0])) {
+ if (/^[0-9]{1,3}(?:\.[0-9]{1,3}){3}$/.test(inputs[i])) {
+ output += ipTransition(inputs[i], false);
+ } else if (/\/24$/.test(inputs[i])) {
+ output += ipTransition(inputs[i], true);
+ } else if (/^([0-9a-fA-F]{2}:){5}[0-9a-fA-F]{2}$/.test(inputs[i])) {
+ output += macTransition(inputs[i]);
+ } else if (/^((?:[0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|(?:[0-9a-fA-F]{1,4}:){1,7}:|(?:[0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|(?:[0-9a-fA-F]{1,4}:){1,5}(?::[0-9a-fA-F]{1,4}){1,2}|(?:[0-9a-fA-F]{1,4}:){1,4}(?::[0-9a-fA-F]{1,4}){1,3}|(?:[0-9a-fA-F]{1,4}:){1,3}(?::[0-9a-fA-F]{1,4}){1,4}|(?:[0-9a-fA-F]{1,4}:){1,2}(?::[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:(?:(?::[0-9a-fA-F]{1,4}){1,6})|:(?:(?::[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(?::[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(?:ffff(?::0{1,4}){0,1}:){0,1}(?:(?:25[0-5]|(?:2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(?:25[0-5]|(?:2[0-4]|1{0,1}[0-9]){0,1}[0-9])|(?:[0-9a-fA-F]{1,4}:){1,4}:(?:(?:25[0-5]|(?:2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(?:25[0-5]|(?:2[0-4]|1{0,1}[0-9]){0,1}[0-9]))$/.test(inputs[i])) {
+ output += unTransition(inputs[i]);
+ } else {
+ output = "Enter compressed or expanded IPv6 address, IPv4 address or MAC Address.";
+ }
+ }
+ }
+
+ return output;
+ }
+
+}
+
+export default IPv6TransitionAddresses;
diff --git a/tests/operations/index.mjs b/tests/operations/index.mjs
index b025cffc..ca7509fd 100644
--- a/tests/operations/index.mjs
+++ b/tests/operations/index.mjs
@@ -59,6 +59,7 @@ import "./tests/Crypt.mjs";
import "./tests/CSV.mjs";
import "./tests/DateTime.mjs";
import "./tests/DefangIP.mjs";
+import "./tests/DropNthBytes.mjs";
import "./tests/ECDSA.mjs";
import "./tests/ELFInfo.mjs";
import "./tests/Enigma.mjs";
diff --git a/tests/operations/tests/Crypt.mjs b/tests/operations/tests/Crypt.mjs
index 69123d66..caaf27fc 100644
--- a/tests/operations/tests/Crypt.mjs
+++ b/tests/operations/tests/Crypt.mjs
@@ -1579,19 +1579,31 @@ DES uses a key length of 8 bytes (64 bits).`,
from Crypto.Cipher import Blowfish
import binascii
- input_data = b"The quick brown fox jumps over the lazy dog."
+ # Blowfish cipher parameters - key, mode, iv, segment_size, nonce
key = binascii.unhexlify("0011223344556677")
- iv = binascii.unhexlify("0000000000000000")
mode = Blowfish.MODE_CBC
+ kwargs = {}
+ iv = binascii.unhexlify("ffeeddccbbaa9988")
+ if mode in [Blowfish.MODE_CBC, Blowfish.MODE_CFB, Blowfish.MODE_OFB]:
+ kwargs = {"iv": iv}
+ if mode == Blowfish.MODE_CFB:
+ kwargs["segment_size"] = 64
+ if mode == Blowfish.MODE_CTR:
+ nonce = binascii.unhexlify("0000000000000000")
+ nonce = nonce[:7]
+ kwargs["nonce"] = nonce
+ cipher = Blowfish.new(key, mode, **kwargs)
+
+ # Input data and padding
+ input_data = b"The quick brown fox jumps over the lazy dog."
if mode == Blowfish.MODE_ECB or mode == Blowfish.MODE_CBC:
padding_len = 8-(len(input_data) & 7)
for i in range(padding_len):
input_data += bytes([padding_len])
- cipher = Blowfish.new(key, mode) # set iv, nonce, segment_size etc. here
+ # Encrypted text
cipher_text = cipher.encrypt(input_data)
-
cipher_text = binascii.hexlify(cipher_text).decode("UTF-8")
print("Encrypted: {}".format(cipher_text))
diff --git a/tests/operations/tests/DropNthBytes.mjs b/tests/operations/tests/DropNthBytes.mjs
new file mode 100644
index 00000000..00d4e0ab
--- /dev/null
+++ b/tests/operations/tests/DropNthBytes.mjs
@@ -0,0 +1,123 @@
+/**
+ * @author Oshawk [oshawk@protonmail.com]
+ * @copyright Crown Copyright 2019
+ * @license Apache-2.0
+ */
+
+import TestRegister from "../../lib/TestRegister.mjs";
+
+/**
+ * Drop nth bytes tests
+ */
+TestRegister.addTests([
+ {
+ name: "Drop nth bytes: Nothing",
+ input: "",
+ expectedOutput: "",
+ recipeConfig: [
+ {
+ op: "Drop nth bytes",
+ args: [4, 0, false],
+ },
+ ],
+ },
+ {
+ name: "Drop nth bytes: Nothing (apply to each line)",
+ input: "",
+ expectedOutput: "",
+ recipeConfig: [
+ {
+ op: "Drop nth bytes",
+ args: [4, 0, true],
+ },
+ ],
+ },
+ {
+ name: "Drop nth bytes: Basic single line",
+ input: "0123456789",
+ expectedOutput: "1235679",
+ recipeConfig: [
+ {
+ op: "Drop nth bytes",
+ args: [4, 0, false],
+ },
+ ],
+ },
+ {
+ name: "Drop nth bytes: Basic single line (apply to each line)",
+ input: "0123456789",
+ expectedOutput: "1235679",
+ recipeConfig: [
+ {
+ op: "Drop nth bytes",
+ args: [4, 0, true],
+ },
+ ],
+ },
+ {
+ name: "Drop nth bytes: Complex single line",
+ input: "0123456789",
+ expectedOutput: "01234678",
+ recipeConfig: [
+ {
+ op: "Drop nth bytes",
+ args: [4, 5, false],
+ },
+ ],
+ },
+ {
+ name: "Drop nth bytes: Complex single line (apply to each line)",
+ input: "0123456789",
+ expectedOutput: "01234678",
+ recipeConfig: [
+ {
+ op: "Drop nth bytes",
+ args: [4, 5, true],
+ },
+ ],
+ },
+ {
+ name: "Drop nth bytes: Basic multi line",
+ input: "01234\n56789",
+ expectedOutput: "123\n5689",
+ recipeConfig: [
+ {
+ op: "Drop nth bytes",
+ args: [4, 0, false],
+ },
+ ],
+ },
+ {
+ name: "Drop nth bytes: Basic multi line (apply to each line)",
+ input: "01234\n56789",
+ expectedOutput: "123\n678",
+ recipeConfig: [
+ {
+ op: "Drop nth bytes",
+ args: [4, 0, true],
+ },
+ ],
+ },
+ {
+ name: "Drop nth bytes: Complex multi line",
+ input: "01234\n56789",
+ expectedOutput: "012345679",
+ recipeConfig: [
+ {
+ op: "Drop nth bytes",
+ args: [4, 5, false],
+ },
+ ],
+ },
+ {
+ name: "Drop nth bytes: Complex multi line (apply to each line)",
+ input: "012345\n6789ab",
+ expectedOutput: "01234\n6789a",
+ recipeConfig: [
+ {
+ op: "Drop nth bytes",
+ args: [4, 5, true],
+ },
+ ],
+ }
+]);
diff --git a/tests/operations/tests/IPv6Transition.mjs b/tests/operations/tests/IPv6Transition.mjs
new file mode 100644
index 00000000..f7558c31
--- /dev/null
+++ b/tests/operations/tests/IPv6Transition.mjs
@@ -0,0 +1,63 @@
+/**
+ * IPv6Transition tests.
+ *
+ * @author jb30795
+ *
+ * @copyright Crown Copyright 2024
+ * @license Apache-2.0
+ */
+import TestRegister from "../../lib/TestRegister.mjs";
+
+TestRegister.addTests([
+ {
+ name: "IPv6 Transition: IPv4 to IPv6",
+ input: "198.51.100.7",
+ expectedOutput: "6to4: 2002:c633:6407::/48\nIPv4 Mapped: ::ffff:c633:6407\nIPv4 Translated: ::ffff:0:c633:6407\nNat 64: 64:ff9b::c633:6407",
+ recipeConfig: [
+ {
+ op: "IPv6 Transition Addresses",
+ args: [true, false],
+ },
+ ],
+ }, {
+ name: "IPv6 Transition: IPv4 /24 Range to IPv6",
+ input: "198.51.100.0/24",
+ expectedOutput: "6to4: 2002:c633:6400::/40\nIPv4 Mapped: ::ffff:c633:6400/120\nIPv4 Translated: ::ffff:0:c633:6400/120\nNat 64: 64:ff9b::c633:6400/120",
+ recipeConfig: [
+ {
+ op: "IPv6 Transition Addresses",
+ args: [false, false],
+ },
+ ],
+ }, {
+ name: "IPv6 Transition: IPv4 to IPv6 Remove headers",
+ input: "198.51.100.7",
+ expectedOutput: "2002:c633:6407::/48\n::ffff:c633:6407\n::ffff:0:c633:6407\n64:ff9b::c633:6407",
+ recipeConfig: [
+ {
+ op: "IPv6 Transition Addresses",
+ args: [true, true],
+ },
+ ],
+ }, {
+ name: "IPv6 Transition: IPv6 to IPv4",
+ input: "64:ff9b::c633:6407",
+ expectedOutput: "IPv4: 198.51.100.7",
+ recipeConfig: [
+ {
+ op: "IPv6 Transition Addresses",
+ args: [true, false],
+ },
+ ],
+ }, {
+ name: "IPv6 Transition: MAC to EUI-64",
+ input: "a1:b2:c3:d4:e5:f6",
+ expectedOutput: "EUI-64 Interface ID: a3b2:c3ff:fed4:e5f6",
+ recipeConfig: [
+ {
+ op: "IPv6 Transition Addresses",
+ args: [true, false],
+ },
+ ],
+ },
+]);