diff --git a/src/core/config/Categories.json b/src/core/config/Categories.json
index 6ad3adb7..c756b591 100644
--- a/src/core/config/Categories.json
+++ b/src/core/config/Categories.json
@@ -165,6 +165,7 @@
"Derive PBKDF2 key",
"Derive EVP key",
"Derive HKDF key",
+ "Generate NTLMv2 SMB Session Key",
"Bcrypt",
"Scrypt",
"JWT Sign",
diff --git a/src/core/operations/GenerateNTLMv2SMBSessionKey.mjs b/src/core/operations/GenerateNTLMv2SMBSessionKey.mjs
new file mode 100644
index 00000000..1fbbf1fd
--- /dev/null
+++ b/src/core/operations/GenerateNTLMv2SMBSessionKey.mjs
@@ -0,0 +1,299 @@
+/**
+ * @author mansiverma897993
+ * @copyright Crown Copyright 2026
+ * @license Apache-2.0
+ */
+
+import Operation from "../Operation.mjs";
+import OperationError from "../errors/OperationError.mjs";
+import Utils from "../Utils.mjs";
+import CryptoApi from "crypto-api/src/crypto-api.mjs";
+import CryptoJS from "crypto-js";
+import {runHash} from "../lib/Hash.mjs";
+
+/**
+ * Converts a string to a UTF-16LE binary string.
+ *
+ * @param {string} str
+ * @returns {string}
+ */
+function toUtf16LE(str) {
+ let out = "";
+ for (let i = 0; i < str.length; i++) {
+ const code = str.charCodeAt(i);
+ out += String.fromCharCode(code & 0xff) + String.fromCharCode((code >> 8) & 0xff);
+ }
+ return out;
+}
+
+/**
+ * Calculates the NT Hash (MD4 of UTF-16LE representation) of a password.
+ *
+ * @param {string} password
+ * @returns {string} Hex encoded NT Hash
+ */
+function calculateNTHash(password) {
+ const buf = new ArrayBuffer(password.length * 2);
+ const bufView = new Uint16Array(buf);
+ for (let i = 0; i < password.length; i++) {
+ bufView[i] = password.charCodeAt(i);
+ }
+ return runHash("md4", buf);
+}
+
+/**
+ * Generate NTLMv2 SMB Session Key operation
+ */
+class GenerateNTLMv2SMBSessionKey extends Operation {
+
+ /**
+ * GenerateNTLMv2SMBSessionKey constructor
+ */
+ constructor() {
+ super();
+
+ this.name = "Generate NTLMv2 SMB Session Key";
+ this.module = "Crypto";
+ this.description = "Calculates the NTLMv2 ResponseKeyNT, SessionBaseKey (KeyExchangeKey) and decrypts the random session key/exported session key to generate the final SMB session key for Wireshark decryption.
Pass either a full NTLMv2 hash line (e.g. from responder/hashcat) as the input, or use the individual fields in the arguments.
If no Encrypted Session Key is provided, the SessionBaseKey itself will be returned as the final session key.";
+ this.infoURL = "https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-nlmp/c0250a97-2940-40c7-82fb-20d208c71e96";
+ this.inputType = "string";
+ this.outputType = "string";
+ this.args = [
+ {
+ "name": "Username",
+ "type": "string",
+ "value": ""
+ },
+ {
+ "name": "Domain",
+ "type": "string",
+ "value": ""
+ },
+ {
+ "name": "Password / NT Hash",
+ "type": "toggleString",
+ "value": "",
+ "toggleValues": ["Hex (NT Hash)", "UTF8 (Plaintext)"]
+ },
+ {
+ "name": "NTProofStr",
+ "type": "string",
+ "value": ""
+ },
+ {
+ "name": "Encrypted Session Key",
+ "type": "string",
+ "value": ""
+ },
+ {
+ "name": "Session ID",
+ "type": "string",
+ "value": ""
+ },
+ {
+ "name": "Output Format",
+ "type": "option",
+ "value": [
+ "Raw Session Key",
+ "Wireshark UAT line (SessionID,SessionKey)",
+ "TShark Command Line Option",
+ "JSON",
+ "Detailed Text"
+ ]
+ }
+ ];
+ }
+
+ /**
+ * @param {string} input
+ * @param {Object[]} args
+ * @returns {string}
+ */
+ run(input, args) {
+ const cleanHex = (val) => {
+ if (!val) return "";
+ return val.trim().replace(/^0x/i, "").replace(/[\s:]/g, "");
+ };
+
+ let username = "";
+ let domain = "";
+ let ntProofStr = "";
+ let encryptedSessionKey = "";
+ let sessionId = "";
+
+ // 1. Attempt to parse main input first
+ if (input && input.trim()) {
+ const lines = input.trim().split(/\r?\n/);
+ const firstLine = lines[0].trim();
+ if (firstLine.includes("::") && firstLine.split(":").length >= 5) {
+ const parts = firstLine.split(":");
+ username = parts[0];
+ domain = parts[2];
+ ntProofStr = parts[4];
+ } else {
+ for (const line of lines) {
+ const colonIdx = line.indexOf(":");
+ if (colonIdx > 0) {
+ const key = line.substring(0, colonIdx).trim().toLowerCase().replace(/[^a-z0-9]/g, "");
+ const val = line.substring(colonIdx + 1).trim();
+ if (key === "username" || key === "user") username = val;
+ else if (key === "domain" || key === "workgroup") domain = val;
+ else if (key === "ntproofstr" || key === "ntproof") ntProofStr = val;
+ else if (key === "encryptedsessionkey" || key === "sessionkeyencrypted" || key === "encsessionkey") encryptedSessionKey = val;
+ else if (key === "sessionid" || key === "id") sessionId = val;
+ }
+ }
+ }
+ }
+
+ // 2. Fall back to UI args if not parsed from input
+ username = username || args[0];
+ domain = domain || args[1];
+ ntProofStr = cleanHex(ntProofStr || args[3]);
+ encryptedSessionKey = cleanHex(encryptedSessionKey || args[4]);
+ sessionId = cleanHex(sessionId || args[5]);
+
+ const passwordHashArg = args[2];
+ const passwordHashString = passwordHashArg.string.trim();
+ const passwordHashOption = passwordHashArg.option;
+ const outputFormat = args[6];
+
+ if (!passwordHashString) {
+ throw new OperationError("Password or NT Hash is required.");
+ }
+
+ let ntHashHex = "";
+ if (passwordHashOption === "Hex (NT Hash)") {
+ const cleanNtHash = cleanHex(passwordHashString);
+ if (!/^[a-fA-F0-9]{32}$/.test(cleanNtHash)) {
+ throw new OperationError("NT Hash must be a 32-character hex string.");
+ }
+ ntHashHex = cleanNtHash;
+ } else {
+ ntHashHex = calculateNTHash(passwordHashString);
+ }
+
+ if (!username) {
+ throw new OperationError("Username is required.");
+ }
+ if (!ntProofStr) {
+ throw new OperationError("NTProofStr is required.");
+ }
+ if (!/^[a-fA-F0-9]{32}$/.test(ntProofStr)) {
+ throw new OperationError("NTProofStr must be a 32-character hex string.");
+ }
+ if (encryptedSessionKey && !/^[a-fA-F0-9]{32}$/.test(encryptedSessionKey)) {
+ throw new OperationError("Encrypted Session Key must be a 32-character hex string.");
+ }
+
+ let cleanedSessionId = cleanHex(sessionId);
+ if (cleanedSessionId) {
+ if (!/^[a-fA-F0-9]+$/.test(cleanedSessionId)) {
+ throw new OperationError("Session ID must be a hex string.");
+ }
+ if (cleanedSessionId.length < 16) {
+ cleanedSessionId = cleanedSessionId.padStart(16, "0");
+ }
+ }
+
+ // Step 1: Calculate ResponseKeyNT
+ // ResponseKeyNT = HMAC-MD5(unicode(upper(UserName) + Domain), NT Hash)
+ const ntHashBytes = Utils.convertToByteString(ntHashHex, "hex");
+ const userDomainBytes = toUtf16LE(username.toUpperCase() + domain);
+ const hasher1 = CryptoApi.getHasher("md5");
+ const mac1 = CryptoApi.getHmac(ntHashBytes, hasher1);
+ mac1.update(userDomainBytes);
+ const responseKeyNT = mac1.finalize();
+ const responseKeyNTHex = CryptoApi.encoder.toHex(responseKeyNT);
+
+ // Step 2: Calculate SessionBaseKey (also called KeyExchangeKey)
+ // SessionBaseKey = HMAC-MD5(ResponseKeyNT, NTProofStr)
+ const ntProofStrBytes = Utils.convertToByteString(ntProofStr, "hex");
+ const hasher2 = CryptoApi.getHasher("md5");
+ const mac2 = CryptoApi.getHmac(responseKeyNT, hasher2);
+ mac2.update(ntProofStrBytes);
+ const sessionBaseKey = mac2.finalize();
+ const sessionBaseKeyHex = CryptoApi.encoder.toHex(sessionBaseKey);
+
+ // Step 3: Decrypt Encrypted Session Key via RC4 (if provided)
+ let decryptedSessionKey = "";
+ if (encryptedSessionKey) {
+ const ciphertextWA = CryptoJS.enc.Hex.parse(encryptedSessionKey);
+ const keyWA = CryptoJS.enc.Hex.parse(sessionBaseKeyHex);
+ const decrypted = CryptoJS.RC4.encrypt(ciphertextWA, keyWA);
+ decryptedSessionKey = decrypted.ciphertext.toString(CryptoJS.enc.Hex);
+ }
+
+ const finalKey = encryptedSessionKey ? decryptedSessionKey : sessionBaseKeyHex;
+
+ // Output formatting
+ switch (outputFormat) {
+ case "Raw Session Key":
+ return finalKey;
+ case "Wireshark UAT line (SessionID,SessionKey)":
+ return `${cleanedSessionId || "0000000000000000"},${finalKey}`;
+ case "TShark Command Line Option":
+ return `-o "uat:smb2_seskey_list:${cleanedSessionId || "0000000000000000"},${finalKey},\\"\\",\\"\\""`;
+ case "JSON":
+ return JSON.stringify({
+ username: username,
+ domain: domain,
+ ntHash: ntHashHex,
+ ntProofStr: ntProofStr,
+ responseKeyNT: responseKeyNTHex,
+ sessionBaseKey: sessionBaseKeyHex,
+ encryptedSessionKey: encryptedSessionKey || null,
+ decryptedSessionKey: decryptedSessionKey || null,
+ finalSessionKey: finalKey,
+ sessionId: cleanedSessionId || null,
+ wiresharkUat: `${cleanedSessionId || "0000000000000000"},${finalKey}`
+ }, null, 4);
+ case "Detailed Text": {
+ let out = `NTLMv2 / SMB Session Key Derivation Details:
+---------------------------------------------
+Username: ${username}
+Domain: ${domain}
+NT Hash: ${ntHashHex}
+NTProofStr: ${ntProofStr}
+
+ResponseKeyNT: ${responseKeyNTHex}
+ (HMAC-MD5 of Username + Domain using NT Hash as key)
+
+SessionBaseKey: ${sessionBaseKeyHex}
+ (HMAC-MD5 of NTProofStr using ResponseKeyNT as key)
+`;
+ if (encryptedSessionKey) {
+ out += `
+Encrypted SessionKey: ${encryptedSessionKey}
+Decrypted SessionKey: ${decryptedSessionKey}
+ (RC4 decrypted Encrypted SessionKey using SessionBaseKey as key)
+
+Final Session Key: ${finalKey}
+`;
+ } else {
+ out += `
+Final Session Key: ${finalKey}
+ (Using SessionBaseKey directly since no Encrypted SessionKey was provided)
+`;
+ }
+ if (cleanedSessionId) {
+ out += `
+Session ID: ${cleanedSessionId}
+
+Wireshark SMB2 Preferences entry:
+Session ID: ${cleanedSessionId}
+Session Key: ${finalKey}
+
+TShark Option:
+-o "uat:smb2_seskey_list:${cleanedSessionId},${finalKey},\\"\\",\\"\\""
+`;
+ }
+ return out;
+ }
+ default:
+ throw new OperationError(`Unknown output format: ${outputFormat}`);
+ }
+ }
+}
+
+export default GenerateNTLMv2SMBSessionKey;
diff --git a/tests/operations/tests/GenerateNTLMv2SMBSessionKey.mjs b/tests/operations/tests/GenerateNTLMv2SMBSessionKey.mjs
new file mode 100644
index 00000000..9de5564e
--- /dev/null
+++ b/tests/operations/tests/GenerateNTLMv2SMBSessionKey.mjs
@@ -0,0 +1,88 @@
+/**
+ * Generate NTLMv2 SMB Session Key operation tests
+ *
+ * @author mansiverma897993
+ * @copyright Crown Copyright 2026
+ * @license Apache-2.0
+ */
+
+import TestRegister from "../../lib/TestRegister.mjs";
+
+TestRegister.addTests([
+ {
+ name: "Generate NTLMv2 SMB Session Key: Manual fields with Plaintext Password (mrealman)",
+ input: "",
+ expectedOutput: "20a642c086ef74eee26277bf1d0cff8c",
+ recipeConfig: [
+ {
+ op: "Generate NTLMv2 SMB Session Key",
+ args: [
+ "mrealman",
+ "WORKGROUP",
+ { string: "Blockbuster1", option: "UTF8 (Plaintext)" },
+ "16e816dead16d4ca7d5d6dee4a015c14",
+ "fde53b54cb676b9bbf0fb1fbef384698",
+ "",
+ "Raw Session Key"
+ ],
+ },
+ ],
+ },
+ {
+ name: "Generate NTLMv2 SMB Session Key: Manual fields with NT Hash (eshellstrop)",
+ input: "",
+ expectedOutput: "facfbdf010d00aa2574c7c41201099e8",
+ recipeConfig: [
+ {
+ op: "Generate NTLMv2 SMB Session Key",
+ args: [
+ "eshellstrop",
+ "WORKGROUP",
+ { string: "3f29138a04aadc19214e9c04028bf381", option: "Hex (NT Hash)" },
+ "0ca6227a4f00b9654a48908c4801a0ac",
+ "c24f5102a22d286336aac2dfa4dc2e04",
+ "",
+ "Raw Session Key"
+ ],
+ },
+ ],
+ },
+ {
+ name: "Generate NTLMv2 SMB Session Key: Input NTLMv2 Hashcat Line (eshellstrop)",
+ input: "eshellstrop::WORKGROUP:1122334455667788:0ca6227a4f00b9654a48908c4801a0ac:0101000000000000",
+ expectedOutput: "facfbdf010d00aa2574c7c41201099e8",
+ recipeConfig: [
+ {
+ op: "Generate NTLMv2 SMB Session Key",
+ args: [
+ "",
+ "",
+ { string: "3f29138a04aadc19214e9c04028bf381", option: "Hex (NT Hash)" },
+ "",
+ "c24f5102a22d286336aac2dfa4dc2e04",
+ "",
+ "Raw Session Key"
+ ],
+ },
+ ],
+ },
+ {
+ name: "Generate NTLMv2 SMB Session Key: Wireshark UAT line output with Session ID (mrealman)",
+ input: "",
+ expectedOutput: "0000100000000041,20a642c086ef74eee26277bf1d0cff8c",
+ recipeConfig: [
+ {
+ op: "Generate NTLMv2 SMB Session Key",
+ args: [
+ "mrealman",
+ "WORKGROUP",
+ { string: "Blockbuster1", option: "UTF8 (Plaintext)" },
+ "16e816dead16d4ca7d5d6dee4a015c14",
+ "fde53b54cb676b9bbf0fb1fbef384698",
+ "0x0000100000000041",
+ "Wireshark UAT line (SessionID,SessionKey)"
+ ],
+ },
+ ],
+ },
+]);