UNPKG

node-smb2

Version:

A SMB2 implementation in TypeScript

408 lines (407 loc) 16 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.generateServerChallenge = exports.encodeAuthenticationMessage = exports.decodeChallengeMessage = exports.encodeChallengeMessage = exports.decodeNegotiationMessage = exports.encodeNegotiationMessage = void 0; const crypto_1 = __importDefault(require("crypto")); const des_js_1 = __importDefault(require("des.js")); const js_md4_1 = __importDefault(require("js-md4")); const NegotiateFlag_1 = __importDefault(require("./NegotiateFlag")); var AvId; (function (AvId) { AvId[AvId["MsvAvEOL"] = 0] = "MsvAvEOL"; AvId[AvId["MsvAvNbComputerName"] = 1] = "MsvAvNbComputerName"; AvId[AvId["MsvAvNbDomainName"] = 2] = "MsvAvNbDomainName"; AvId[AvId["MsvAvDnsComputerName"] = 3] = "MsvAvDnsComputerName"; AvId[AvId["MsvAvDnsDomainName"] = 4] = "MsvAvDnsDomainName"; AvId[AvId["MsvAvDnsTreeName"] = 5] = "MsvAvDnsTreeName"; AvId[AvId["MsvAvFlags"] = 6] = "MsvAvFlags"; AvId[AvId["MsvAvTimestamp"] = 7] = "MsvAvTimestamp"; AvId[AvId["MsvAvRestrictions"] = 8] = "MsvAvRestrictions"; AvId[AvId["MsvAvTargetName"] = 9] = "MsvAvTargetName"; AvId[AvId["MsvAvChannelBindings"] = 10] = "MsvAvChannelBindings"; })(AvId || (AvId = {})); // Check if we should use NTLMv1 const isNTLMv1 = (negotiateFlags) => { return !(negotiateFlags & NegotiateFlag_1.default.ExtendedSessionSecurity); }; // Create NTLM v1 response const createNTLMv1Response = (ntHash, serverChallenge) => { return createResponse(ntHash, serverChallenge); }; // Create LM v1 response const createLMv1Response = (password, serverChallenge) => { const lmHash = createLmHash(password); return createResponse(lmHash, serverChallenge); }; const createTargetInfo = (hostname, domain) => { // Calculate required buffer size const hostnameLength = Buffer.byteLength(hostname, 'utf16le'); const domainLength = Buffer.byteLength(domain, 'utf16le'); // 4 bytes for each AvId+AvLen pair, plus string lengths, plus 4 bytes for EOL const bufferSize = (4 + hostnameLength) + (4 + domainLength) + 4; const buffer = Buffer.alloc(bufferSize); let offset = 0; // Add computer name buffer.writeUInt16LE(AvId.MsvAvNbComputerName, offset); buffer.writeUInt16LE(hostnameLength, offset + 2); buffer.write(hostname, offset + 4, hostnameLength, 'utf16le'); offset += 4 + hostnameLength; // Add domain name buffer.writeUInt16LE(AvId.MsvAvNbDomainName, offset); buffer.writeUInt16LE(domainLength, offset + 2); buffer.write(domain, offset + 4, domainLength, 'utf16le'); offset += 4 + domainLength; // Add terminator (MsvAvEOL) buffer.writeUInt16LE(AvId.MsvAvEOL, offset); buffer.writeUInt16LE(0, offset + 2); return buffer; }; const encodeNegotiationMessage = (h, d) => { const hostname = h.toUpperCase(); const domain = d.toUpperCase(); const hostnameLength = Buffer.byteLength(hostname, "ascii"); const domainLength = Buffer.byteLength(domain, "ascii"); let offset = 0; const buffer = Buffer.alloc(32 + hostnameLength + domainLength); buffer.write("NTLMSSP", offset, 7, "ascii"); offset += 7; buffer.writeUInt8(0, offset); offset += 1; buffer.writeUInt32LE(1, offset); offset += 4; const negotiateFlags = NegotiateFlag_1.default.UnicodeEncoding | NegotiateFlag_1.default.NTLMSessionSecurity | NegotiateFlag_1.default.AlwaysSign | NegotiateFlag_1.default.ExtendedSessionSecurity | NegotiateFlag_1.default.TargetInfo | NegotiateFlag_1.default.Version; buffer.writeUInt32LE(negotiateFlags, offset); offset += 4; buffer.writeUInt16LE(domainLength, offset); offset += 2; buffer.writeUInt16LE(domainLength, offset); offset += 2; const domainOffset = 0x20 + hostnameLength; buffer.writeUInt32LE(domainOffset, offset); offset += 4; buffer.writeUInt16LE(hostnameLength, offset); offset += 2; buffer.writeUInt16LE(hostnameLength, offset); offset += 2; buffer.writeUInt32LE(0x20, offset); offset += 4; buffer.write(hostname, 0x20, hostnameLength, "ascii"); buffer.write(domain, domainOffset, domainLength, "ascii"); return buffer; }; exports.encodeNegotiationMessage = encodeNegotiationMessage; const decodeNegotiationMessage = (buffer) => { let offset = 0; const protocol = buffer.slice(0, 7).toString("ascii"); if (protocol !== "NTLMSSP" || buffer.readInt8(7) !== 0x00) throw new Error("ntlmssp_header_not_found"); offset += 8; const type = buffer.readUInt32LE(offset); if (type !== 0x01) throw new Error("ntlmssp_type_is_not_one"); offset += 4; const negotiateFlags = buffer.readUInt32LE(offset); offset += 4; const domainLength = buffer.readUInt16LE(offset); offset += 2; const domainMaxLength = buffer.readUInt16LE(offset); offset += 2; const domainOffset = buffer.readUInt32LE(offset); offset += 4; const hostnameLength = buffer.readUInt16LE(offset); offset += 2; const hostnameMaxLength = buffer.readUInt16LE(offset); offset += 2; const hostnameOffset = buffer.readUInt32LE(offset); offset += 4; const domain = buffer.slice(domainOffset, domainOffset + domainLength).toString("ascii"); const hostname = buffer.slice(hostnameOffset, hostnameOffset + hostnameLength).toString("ascii"); return { negotiateFlags, domain, hostname }; }; exports.decodeNegotiationMessage = decodeNegotiationMessage; const encodeChallengeMessage = (negotiateFlags) => { let offset = 0; const buffer = Buffer.alloc(64); buffer.write("NTLMSSP", offset, 7, "ascii"); offset += 7; buffer.writeUInt8(0, offset); offset += 1; buffer.writeUInt32LE(2, offset); offset += 4; buffer.writeUInt16LE(0, offset); offset += 2; buffer.writeUInt16LE(0, offset); offset += 2; buffer.writeUInt32LE(0, offset); offset += 4; buffer.writeUInt32LE(negotiateFlags, offset); offset += 4; (0, exports.generateServerChallenge)().copy(new Uint8Array(buffer), offset); offset += 8; buffer.fill(0, offset, offset + 8); offset += 8; return buffer; }; exports.encodeChallengeMessage = encodeChallengeMessage; const decodeChallengeMessage = (buffer) => { let offset = 0; const protocol = buffer.slice(0, 7).toString("ascii"); if (protocol !== "NTLMSSP" || buffer.readInt8(7) !== 0x00) throw new Error("ntlmssp_header_not_found"); offset += 8; const type = buffer.readUInt32LE(offset); if (type !== 0x02) throw new Error("ntlmssp_type_is_not_two"); offset += 4; const targetNameLength = buffer.readUInt16LE(offset); offset += 2; const targetNameMaxLength = buffer.readUInt16LE(offset); offset += 2; const targetNameOffset = buffer.readUInt32LE(offset); offset += 4; const negotiateFlags = buffer.readUInt32LE(offset); offset += 4; const serverChallenge = buffer.slice(offset, offset + 8); offset += 8; offset += 8; // Reserved return serverChallenge; }; exports.decodeChallengeMessage = decodeChallengeMessage; const encodeAuthenticationMessage = (username, h, d, serverChallenge, password, negotiateFlags = NegotiateFlag_1.default.ExtendedSessionSecurity | NegotiateFlag_1.default.TargetInfo) => { const hostname = h.toUpperCase(); const domain = d.toUpperCase(); const ntHash = createNtHash(password); let ntResponse; let lmResponse; if (isNTLMv1(negotiateFlags)) { // NTLMv1 mode ntResponse = createNTLMv1Response(ntHash, serverChallenge); lmResponse = createLMv1Response(password, serverChallenge); } else { // NTLMv2 mode const ntlmv2Hash = createNtlmV2Hash(username, domain, ntHash); const clientChallenge = crypto_1.default.randomBytes(8); const timestamp = Buffer.alloc(8); const now = new Date().getTime() + 11644473600000; timestamp.writeBigUInt64LE(BigInt(now * 10000)); const targetInfo = createTargetInfo(hostname, domain); ntResponse = createNtlmV2Response(ntlmv2Hash, serverChallenge, clientChallenge, timestamp, targetInfo); lmResponse = createNtlmV2Response(ntlmv2Hash, serverChallenge, clientChallenge, Buffer.alloc(8), Buffer.alloc(0)); } const usernameLength = Buffer.byteLength(username, "ucs2"); const hostnameLength = Buffer.byteLength(hostname, "ucs2"); const domainLength = Buffer.byteLength(domain, "ucs2"); const lmResponseLength = lmResponse.length; const ntResponseLength = ntResponse.length; const domainOffset = 0x40; const usernameOffset = domainOffset + domainLength; const hostnameOffset = usernameOffset + usernameLength; const lmResponseOffset = hostnameOffset + hostnameLength; const ntResponseOffset = lmResponseOffset + lmResponseLength; let offset = 0; const buffer = Buffer.alloc(ntResponseOffset + ntResponseLength); buffer.write("NTLMSSP", offset, 7, "ascii"); offset += 7; buffer.writeUInt8(0, offset); offset += 1; buffer.writeUInt32LE(3, offset); offset += 4; buffer.writeUInt16LE(lmResponseLength, offset); offset += 2; buffer.writeUInt16LE(lmResponseLength, offset); offset += 2; buffer.writeUInt32LE(lmResponseOffset, offset); offset += 4; buffer.writeUInt16LE(ntResponseLength, offset); offset += 2; buffer.writeUInt16LE(ntResponseLength, offset); offset += 2; buffer.writeUInt32LE(ntResponseOffset, offset); offset += 4; buffer.writeUInt16LE(domainLength, offset); offset += 2; buffer.writeUInt16LE(domainLength, offset); offset += 2; buffer.writeUInt32LE(domainOffset, offset); offset += 4; buffer.writeUInt16LE(usernameLength, offset); offset += 2; buffer.writeUInt16LE(usernameLength, offset); offset += 2; buffer.writeUInt32LE(usernameOffset, offset); offset += 4; buffer.writeUInt16LE(hostnameLength, offset); offset += 2; buffer.writeUInt16LE(hostnameLength, offset); offset += 2; buffer.writeUInt32LE(hostnameOffset, offset); offset += 4; buffer.writeUInt16LE(0, offset); offset += 2; buffer.writeUInt16LE(0, offset); offset += 2; buffer.writeUInt32LE(0, offset); offset += 4; buffer.writeUInt32LE(negotiateFlags, offset); offset += 4; // Write domain, username, hostname and responses buffer.write(domain, domainOffset, domainLength, "ucs2"); buffer.write(username, usernameOffset, usernameLength, "ucs2"); buffer.write(hostname, hostnameOffset, hostnameLength, "ucs2"); lmResponse.copy(buffer, lmResponseOffset); ntResponse.copy(buffer, ntResponseOffset); return buffer; }; exports.encodeAuthenticationMessage = encodeAuthenticationMessage; const generateServerChallenge = () => { return crypto_1.default.randomBytes(8); }; exports.generateServerChallenge = generateServerChallenge; const bytes2binaryArray = (buf) => { const hex2binary = { 0: [0, 0, 0, 0], 1: [0, 0, 0, 1], 2: [0, 0, 1, 0], 3: [0, 0, 1, 1], 4: [0, 1, 0, 0], 5: [0, 1, 0, 1], 6: [0, 1, 1, 0], 7: [0, 1, 1, 1], 8: [1, 0, 0, 0], 9: [1, 0, 0, 1], A: [1, 0, 1, 0], B: [1, 0, 1, 1], C: [1, 1, 0, 0], D: [1, 1, 0, 1], E: [1, 1, 1, 0], F: [1, 1, 1, 1], }; const hexString = buf.toString("hex").toUpperCase(); let array = []; for (let i = 0; i < hexString.length; i++) { const hexchar = hexString.charAt(i); array = array.concat(hex2binary[hexchar]); } return array; }; const binaryArray2bytes = (array) => { const binary2hex = { "0000": 0, "0001": 1, "0010": 2, "0011": 3, "0100": 4, "0101": 5, "0110": 6, "0111": 7, "1000": 8, "1001": 9, "1010": "A", "1011": "B", "1100": "C", "1101": "D", "1110": "E", "1111": "F", }; const bufArray = []; for (let i = 0; i < array.length; i += 8) { if (i + 7 > array.length) break; const binString1 = `${array[i]}${array[i + 1]}${array[i + 2]}${array[i + 3]}`; const binString2 = `${array[i + 4]}${array[i + 5]}${array[i + 6]}${array[i + 7]}`; const hexchar1 = binary2hex[binString1]; const hexchar2 = binary2hex[binString2]; const buf = Buffer.from(`${hexchar1}${hexchar2}`, "hex"); bufArray.push(buf); } return Buffer.concat(bufArray.map(buf => new Uint8Array(buf))); }; const insertZerosEvery7Bits = (buf) => { const binaryArray = bytes2binaryArray(buf); const newBinaryArray = []; for (let i = 0; i < binaryArray.length; i++) { newBinaryArray.push(binaryArray[i]); if ((i + 1) % 7 === 0) { newBinaryArray.push(0); } } return binaryArray2bytes(newBinaryArray); }; const createLmHash = (p) => { const password = p.toUpperCase(); const passwordBytes = Buffer.from(password, "ascii"); const passwordBytesPadded = Buffer.alloc(14); passwordBytesPadded.fill("\0"); let sourceEnd = 14; if (passwordBytes.length < 14) sourceEnd = passwordBytes.length; passwordBytes.copy(new Uint8Array(passwordBytesPadded), 0, 0, sourceEnd); const firstPart = passwordBytesPadded.slice(0, 7); const secondPart = passwordBytesPadded.slice(7); function encrypt(buf) { const key = insertZerosEvery7Bits(buf); const des = des_js_1.default.DES.create({ type: "encrypt", key }); const magicKey = Buffer.from("KGS!@#$%", "ascii"); const encrypted = des.update(magicKey); return Buffer.from(encrypted); } const firstPartEncrypted = encrypt(firstPart); const secondPartEncrypted = encrypt(secondPart); return Buffer.concat([new Uint8Array(firstPartEncrypted), new Uint8Array(secondPartEncrypted)]); }; const createNtHash = (password) => { const buf = Buffer.from(password, "utf16le"); const md4 = js_md4_1.default.create(); md4.update(buf); return Buffer.from(md4.digest()); }; const createNtlmV2Hash = (username, domain, ntHash) => { const identity = Buffer.from(username.toUpperCase() + domain.toUpperCase(), 'ucs2'); const hmac = crypto_1.default.createHmac('md5', ntHash); return hmac.update(identity).digest(); }; const createNtlmV2Response = (ntlmv2Hash, serverChallenge, clientChallenge, timestamp, targetInfo) => { const temp = Buffer.concat([ Buffer.from([0x01, 0x01]), Buffer.from([0x00, 0x00, 0x00, 0x00, 0x00, 0x00]), timestamp, clientChallenge, Buffer.from([0x00, 0x00, 0x00, 0x00]), targetInfo ]); const hmac = crypto_1.default.createHmac('md5', ntlmv2Hash); hmac.update(serverChallenge); hmac.update(temp); return Buffer.concat([hmac.digest(), temp]); }; const createResponse = (hash, nonce) => { const passHashPadded = Buffer.alloc(21); passHashPadded.fill("\0"); hash.copy(new Uint8Array(passHashPadded), 0, 0, hash.length); const resArray = []; const des1 = des_js_1.default.DES.create({ type: "encrypt", key: insertZerosEvery7Bits(passHashPadded.slice(0, 7)), }); resArray.push(Buffer.from(des1.update(nonce.slice(0, 8)))); const des2 = des_js_1.default.DES.create({ type: "encrypt", key: insertZerosEvery7Bits(passHashPadded.slice(7, 14)), }); resArray.push(Buffer.from(des2.update(nonce.slice(0, 8)))); const des3 = des_js_1.default.DES.create({ type: "encrypt", key: insertZerosEvery7Bits(passHashPadded.slice(14, 21)), }); resArray.push(Buffer.from(des3.update(nonce.slice(0, 8)))); return Buffer.concat(resArray); };