add string index lookup operation
This commit is contained in:
parent
d735496641
commit
abd6667a3e
@ -324,6 +324,7 @@
|
||||
"Filter",
|
||||
"Head",
|
||||
"Tail",
|
||||
"String index lookup",
|
||||
"Count occurrences",
|
||||
"Expand alphabet range",
|
||||
"Drop bytes",
|
||||
|
||||
89
src/core/operations/StringIndexLookup.mjs
Normal file
89
src/core/operations/StringIndexLookup.mjs
Normal file
@ -0,0 +1,89 @@
|
||||
/**
|
||||
* @author skyswordw
|
||||
* @copyright Crown Copyright 2026
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation.mjs";
|
||||
import Utils from "../Utils.mjs";
|
||||
import { DELIM_OPTIONS } from "../lib/Delim.mjs";
|
||||
import OperationError from "../errors/OperationError.mjs";
|
||||
|
||||
/**
|
||||
* String index lookup operation
|
||||
*/
|
||||
class StringIndexLookup extends Operation {
|
||||
|
||||
/**
|
||||
* StringIndexLookup constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "String index lookup";
|
||||
this.module = "Default";
|
||||
this.description = "Looks up characters from a source string using an input list of indexes. This is useful for deobfuscating command lines that build strings by indexing into a lookup string.";
|
||||
this.inputType = "string";
|
||||
this.outputType = "string";
|
||||
this.args = [
|
||||
{
|
||||
"name": "Source string",
|
||||
"type": "binaryString",
|
||||
"value": ""
|
||||
},
|
||||
{
|
||||
"name": "Delimiter",
|
||||
"type": "option",
|
||||
"value": DELIM_OPTIONS
|
||||
},
|
||||
{
|
||||
"name": "Indexing",
|
||||
"type": "option",
|
||||
"value": ["Zero-based", "One-based"]
|
||||
},
|
||||
{
|
||||
"name": "Skip invalid indexes",
|
||||
"type": "boolean",
|
||||
"value": true
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*
|
||||
* @throws {OperationError} if an index is invalid and invalid indexes are not skipped
|
||||
*/
|
||||
run(input, args) {
|
||||
const source = [...(args[0] || "")],
|
||||
delim = Utils.charRep(args[1] || "Space"),
|
||||
indexOffset = args[2] === "One-based" ? 1 : 0,
|
||||
skipInvalid = args[3] !== false;
|
||||
|
||||
return input.split(delim).reduce((output, rawIndex) => {
|
||||
const trimmedIndex = rawIndex.trim();
|
||||
|
||||
if (trimmedIndex.length === 0) return output;
|
||||
|
||||
const parsedIndex = Number(trimmedIndex);
|
||||
|
||||
if (!Number.isInteger(parsedIndex)) {
|
||||
if (skipInvalid) return output;
|
||||
throw new OperationError(`Invalid index: ${trimmedIndex}`);
|
||||
}
|
||||
|
||||
const sourceIndex = parsedIndex - indexOffset;
|
||||
if (sourceIndex < 0 || sourceIndex >= source.length) {
|
||||
if (skipInvalid) return output;
|
||||
throw new OperationError(`Index out of range: ${parsedIndex}`);
|
||||
}
|
||||
|
||||
return output + source[sourceIndex];
|
||||
}, "");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default StringIndexLookup;
|
||||
@ -169,6 +169,7 @@ import "./tests/RC6.mjs";
|
||||
// import "./tests/SplitColourChannels.mjs"; // Cannot test operations that use the File type yet
|
||||
import "./tests/SQLBeautify.mjs";
|
||||
import "./tests/StrUtils.mjs";
|
||||
import "./tests/StringIndexLookup.mjs";
|
||||
import "./tests/StripIPv4Header.mjs";
|
||||
import "./tests/StripTCPHeader.mjs";
|
||||
import "./tests/StripUDPHeader.mjs";
|
||||
|
||||
66
tests/operations/tests/StringIndexLookup.mjs
Normal file
66
tests/operations/tests/StringIndexLookup.mjs
Normal file
@ -0,0 +1,66 @@
|
||||
/**
|
||||
* String index lookup tests.
|
||||
*
|
||||
* @author skyswordw
|
||||
* @copyright Crown Copyright 2026
|
||||
* @licence Apache-2.0
|
||||
*/
|
||||
import TestRegister from "../../lib/TestRegister.mjs";
|
||||
|
||||
TestRegister.addTests([
|
||||
{
|
||||
name: "String index lookup: zero-based semicolon list",
|
||||
input: "0;1;2;3;4;5;6;7;8;9",
|
||||
expectedOutput: "powershell",
|
||||
recipeConfig: [
|
||||
{
|
||||
op: "String index lookup",
|
||||
args: ["powershell", "Semi-colon", "Zero-based", false]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "String index lookup: one-based comma list",
|
||||
input: "1, 2, 3, 4, 5",
|
||||
expectedOutput: "abcde",
|
||||
recipeConfig: [
|
||||
{
|
||||
op: "String index lookup",
|
||||
args: ["abcde", "Comma", "One-based", false]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "String index lookup: skip invalid indexes",
|
||||
input: "0;99;1;-1;2",
|
||||
expectedOutput: "abc",
|
||||
recipeConfig: [
|
||||
{
|
||||
op: "String index lookup",
|
||||
args: ["abc", "Semi-colon", "Zero-based", true]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "String index lookup: default skips invalid tokens",
|
||||
input: "0;foo;1;99;2",
|
||||
expectedOutput: "abc",
|
||||
recipeConfig: [
|
||||
{
|
||||
op: "String index lookup",
|
||||
args: ["abc", "Semi-colon", "Zero-based"]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "String index lookup: strict out of range",
|
||||
input: "3",
|
||||
expectedOutput: "Index out of range: 3",
|
||||
recipeConfig: [
|
||||
{
|
||||
op: "String index lookup",
|
||||
args: ["abc", "Semi-colon", "Zero-based", false]
|
||||
}
|
||||
]
|
||||
}
|
||||
]);
|
||||
Loading…
x
Reference in New Issue
Block a user