/* YST: create_negotiate_message: 4e544c4d53535000010000003582086000000000000000000000000000000000 WRITE: negotiate_message 302fa003020102a12830263024a02204204e544c4d53535000010000003582086000000000000000000000000000000000 READ: read_ts_server_challenge 3081b2a003020106a181aa3081a73081a4a081a104819e4e544c4d53535000020000000e000e003800000035828a62f2290572b3cac375000000000000000058005800460000000a00614a0000000f430045004e005400520041004c0002000e00430045004e005400520041004c0001000e00430045004e005400520041004c0004000e00430065006e007400720061006c0003000e00430065006e007400720061006c0007000800afbc2c2a9256d80100000000 YST: read_challenge_message1: 4e544c4d53535000020000000e000e003800000035828a62f2290572b3cac375000000000000000058005800460000000a00614a0000000f430045004e005400520041004c0002000e00430045004e005400520041004c0001000e00430045004e005400520041004c0004000e00430065006e007400720061006c0003000e00430065006e007400720061006c0007000800afbc2c2a9256d80100000000 YST: target_name: 02000e00430045004e005400520041004c0001000e00430045004e005400520041004c0004000e00430065006e007400720061006c0003000e00430065006e007400720061006c0007000800afbc2c2a9256d80100000000 YST: timestamp: afbc2c2a9256d801 YST: client_challenge: f8d1e2057b9c7169 YST: nt_challenge_response: 32e87ea049485920fca6695ae9daee820101000000000000afbc2c2a9256d801f8d1e2057b9c71690000000002000e00430045004e005400520041004c0001000e00430045004e005400520041004c0004000e00430065006e007400720061006c0003000e00430065006e007400720061006c0007000800afbc2c2a9256d80100000000 YST: lm_challenge_response: 2b0f863797a00d4446a1c9f49de124a5f8d1e2057b9c7169 YST: session_base_key: 52f86f741e92e2e9d8013c1afcecfa13 YST: key_exchange_key: 52f86f741e92e2e9d8013c1afcecfa13 YST: encrypted_random_session_key: d9b2353a7f7ba9569ece01e96686cdef YST: self.is_unicode: true YST: domain: YST: user: 640065006600610075006c007400 YST: tmp_final_auth_message: 4e544c4d53535000030000001800180058000000840084007000000000000000f40000000e000e00f40000000000000002010000100010000201000035828a62060072170000000f000000000000000000000000000000002b0f863797a00d4446a1c9f49de124a5f8d1e2057b9c716932e87ea049485920fca6695ae9daee820101000000000000afbc2c2a9256d801f8d1e2057b9c71690000000002000e00430045004e005400520041004c0001000e00430045004e005400520041004c0004000e00430065006e007400720061006c0003000e00430065006e007400720061006c0007000800afbc2c2a9256d80100000000640065006600610075006c007400d9b2353a7f7ba9569ece01e96686cdef YST: signature: 4cbff4ee21ce4114c7657d7aa427ca3f YST: read_challenge_message2: 4e544c4d53535000030000001800180058000000840084007000000000000000f40000000e000e00f40000000000000002010000100010000201000035828a62060072170000000f4cbff4ee21ce4114c7657d7aa427ca3f2b0f863797a00d4446a1c9f49de124a5f8d1e2057b9c716932e87ea049485920fca6695ae9daee820101000000000000afbc2c2a9256d801f8d1e2057b9c71690000000002000e00430045004e005400520041004c0001000e00430045004e005400520041004c0004000e00430065006e007400720061006c0003000e00430065006e007400720061006c0007000800afbc2c2a9256d80100000000640065006600610075006c007400d9b2353a7f7ba9569ece01e96686cdef WRITE challenge: 30820251a003020102a18201223082011e3082011aa0820116048201124e544c4d53535000030000001800180058000000840084007000000000000000f40000000e000e00f40000000000000002010000100010000201000035828a62060072170000000f4cbff4ee21ce4114c7657d7aa427ca3f2b0f863797a00d4446a1c9f49de124a5f8d1e2057b9c716932e87ea049485920fca6695ae9daee820101000000000000afbc2c2a9256d801f8d1e2057b9c71690000000002000e00430045004e005400520041004c0001000e00430045004e005400520041004c0004000e00430065006e007400720061006c0003000e00430065006e007400720061006c0007000800afbc2c2a9256d80100000000640065006600610075006c007400d9b2353a7f7ba9569ece01e96686cdefa38201220482011e0100000095fb7df84ffcd81d00000000a0588a3354258442e2997c79a4005e91ac49759b3c362228f42aefa5daad071a8eb905d7bc16611df35a50c0c577acf4543609e3ade8ff8289d371d8baed9eb2bd738de6c208f6a9885f1b0ee8aee2b26be87aa189bb41989c79716d938eb68e2f1e4bdc74c25e995c438aa5e5e30c2784214f4763c6737417edac43371f65f22aab68619f022b2f46b4cb9d00d0befe6d7e83d0d2c4ae69b544b1e90b1d5198459722eff903e8550864a46c552d75c83fb8ca8491b573fcd59d17cdc34e2505995fa6922554fdf03016161b430e476875a3752907087e993388f56675957a75b42b062f1f66ef8597a69deb466337396747ec8e46c573344b24fc8fba9305207eb10462ce355299063bab4cf105 READ: read_ts_validate 3081b2a003020106a181aa3081a73081a4a081a104819e4e544c4d53535000020000000e000e003800000035828a62f2290572b3cac375000000000000000058005800460000000a00614a0000000f430045004e005400520041004c0002000e00430045004e005400520041004c0001000e00430045004e005400520041004c0004000e00430065006e007400720061006c0003000e00430065006e007400720061006c0007000800afbc2c2a9256d80100000000 WRITE credentials: 302fa003020102a12830263024a02204204e544c4d53535000010000003582086000000000000000000000000000000000 */ /* "Signature" => Check::new(b"NTLMSSP\x00".to_vec()), "MessageType" => Check::new(U32::LE(2)), "TargetNameLen" => U16::LE(0), "TargetNameLenMax" => U16::LE(0), "TargetNameBufferOffset" => U32::LE(0), "NegotiateFlags" => DynOption::new(U32::LE(0), |node| { if node.inner() & (Negotiate::NtlmsspNegociateVersion as u32) == 0 { return MessageOption::SkipField("Version".to_string()) } return MessageOption::None }), "ServerChallenge" => vec![0; 8], "Reserved" => vec![0; 8], "TargetInfoLen" => U16::LE(0), "TargetInfoMaxLen" => U16::LE(0), "TargetInfoBufferOffset" => U32::LE(0), "Version" => version(), "Payload" => Vec::::new() */ const NegotiateFlags = { NtlmsspNegociate56: 0x80000000, NtlmsspNegociateKeyExch: 0x40000000, NtlmsspNegociate128: 0x20000000, NtlmsspNegociateVersion: 0x02000000, NtlmsspNegociateTargetInfo: 0x00800000, NtlmsspRequestNonNTSessionKey: 0x00400000, NtlmsspNegociateIdentify: 0x00100000, NtlmsspNegociateExtendedSessionSecurity: 0x00080000, NtlmsspTargetTypeServer: 0x00020000, NtlmsspTargetTypeDomain: 0x00010000, NtlmsspNegociateAlwaysSign: 0x00008000, NtlmsspNegociateOEMWorkstationSupplied: 0x00002000, NtlmsspNegociateOEMDomainSupplied: 0x00001000, NtlmsspNegociateNTLM: 0x00000200, NtlmsspNegociateLMKey: 0x00000080, NtlmsspNegociateDatagram: 0x00000040, NtlmsspNegociateSeal: 0x00000020, NtlmsspNegociateSign: 0x00000010, NtlmsspRequestTarget: 0x00000004, NtlmNegotiateOEM: 0x00000002, NtlmsspNegociateUnicode: 0x00000001 } const MajorVersion = { WindowsMajorVersion5: 0x05, WindowsMajorVersion6: 0x06 } const MinorVersion = { WindowsMinorVersion0: 0x00, WindowsMinorVersion1: 0x01, WindowsMinorVersion2: 0x02, WindowsMinorVersion3: 0x03 } const NTLMRevision = { NtlmSspRevisionW2K3: 0x0F } function decodeTargetInfo(targetInfoBuf) { var r = {}, type, len, data, ptr = 0; while (true) { type = targetInfoBuf.readInt16LE(ptr); if (type == 0) break; len = targetInfoBuf.readInt16LE(ptr + 2); r[type] = targetInfoBuf.slice(ptr + 4, ptr + 4 + len); ptr += (4 + len); } return r; } function bufToArr(b) { var r = []; for (var i = 0; i < b.length; i++) { r.push(b.readUInt8(i)); } return r; } // For unit testing function toUnicode(str) { return Buffer.from(str, 'ucs2'); } function md4(str) { return crypto.createHash('md4').update(str).digest(); } function md5(str) { return crypto.createHash('md5').update(str).digest(); } function hmacmd5(key, data) { return crypto.createHmac('md5', key).update(data).digest(); } function ntowfv2(password, user, domain) { return hmacmd5(md4(toUnicode(password)), toUnicode(user.toUpperCase() + domain)); } function lmowfv2(password, user, domain) { return ntowfv2(password, user, domain); } function zeroBuffer(len) { return Buffer.alloc(len); } function compute_response_v2(response_key_nt, response_key_lm, server_challenge, client_challenge, time, server_name) { const response_version = Buffer.from('01', 'hex'); const hi_response_version = Buffer.from('01', 'hex'); const temp = Buffer.concat([response_version, hi_response_version, zeroBuffer(6), time, client_challenge, zeroBuffer(4), server_name]); const nt_proof_str = hmacmd5(response_key_nt, Buffer.concat([server_challenge, temp])); const nt_challenge_response = Buffer.concat([nt_proof_str, temp]); const lm_challenge_response = Buffer.concat([hmacmd5(response_key_lm, Buffer.concat([server_challenge, client_challenge])), client_challenge]); const session_base_key = hmacmd5(response_key_nt, nt_proof_str); return [nt_challenge_response, lm_challenge_response, session_base_key]; } function kx_key_v2(session_base_key, _lm_challenge_response, _server_challenge) { return session_base_key; } function rc4k(key, data) { return crypto.createCipheriv('rc4', key, null).update(data); } function mac(rc4_handle, signing_key, seq_num, data) { const buf = Buffer.alloc(4); buf.writeInt32LE(seq_num, 0); var signature = hmacmd5(signing_key, Buffer.concat([buf, data])); return message_signature_ex(rc4_handle.update(signature.slice(0, 8)), seq_num); } function message_signature_ex(check_sum, seq_num) { const buf = Buffer.alloc(16); buf.writeInt32LE(1, 0); // Version if (check_sum) { check_sum.copy(buf, 4, 0, 8); } // check_sum if (seq_num) { buf.writeInt32LE(seq_num, 12); } // seq_num return buf; } /// Compute a signature of all data exchange during NTLMv2 handshake function mic(exported_session_key, negotiate_message, challenge_message, authenticate_message) { return hmacmd5(exported_session_key, Buffer.concat([negotiate_message, challenge_message, authenticate_message])); } /// NTLMv2 security interface generate a sign key /// By using MD5 of the session key + a static member (sentense) function sign_key(exported_session_key, is_client) { if (is_client) { return md5(Buffer.concat([exported_session_key, Buffer.from("session key to client-to-server signing key magic constant\0")])); } else { return md5(Buffer.concat([exported_session_key, Buffer.from("session key to server-to-client signing key magic constant\0")])); } } /// NTLMv2 security interface generate a seal key /// By using MD5 of the session key + a static member (sentense) function seal_key(exported_session_key, is_client) { if (is_client) { return md5(Buffer.concat([exported_session_key, Buffer.from("session key to client-to-server sealing key magic constant\0")])); } else { return md5(Buffer.concat([exported_session_key, Buffer.from("session key to server-to-client sealing key magic constant\0")])); } } /// We are now able to build a security interface /// that will be used by the CSSP manager to cipherring message (private keys) /// To detect MITM attack function build_security_interface(ntlm) { const obj = {}; if (ntlm) { obj.signing_key = sign_key(ntlm.exported_session_key, true); obj.verify_key = sign_key(ntlm.exported_session_key, false); const client_sealing_key = seal_key(ntlm.exported_session_key, true); const server_sealing_key = seal_key(ntlm.exported_session_key, false); obj.encrypt = crypto.createCipheriv('rc4', client_sealing_key, null); obj.decrypt = crypto.createCipheriv('rc4', server_sealing_key, null); } obj.seq_num = 0; obj.gss_wrapex = function (data) { const encrypted_data = obj.encrypt.update(data); const signature = mac(obj.encrypt, obj.signing_key, obj.seq_num, data); obj.seq_num++; return Buffer.concat([ signature, encrypted_data ] ); } obj.gss_unwrapex = function(data) { const version = data.readInt32LE(0); const checksum = data.slice(4, 12); const seqnum = data.readInt32LE(12); const payload = data.slice(16); const plaintext_payload = obj.decrypt.update(payload); const plaintext_checksum = obj.decrypt.update(checksum); const seqnumbuf = Buffer.alloc(4); seqnumbuf.writeInt32LE(seqnum, 0); const computed_checksum = hmacmd5(obj.verify_key, Buffer.concat([ seqnumbuf, plaintext_payload ])).slice(0, 8); if (!plaintext_checksum.equals(computed_checksum)) { console.log("Invalid checksum on NTLMv2"); } return plaintext_payload.toString(); } return obj; } function Create_Ntlm() { return { /// Microsoft Domain for Active Directory domain: "", //String, /// Username user: "", //String, /// Password password: "", // String, /// Key generated from NTLM hash response_key_nt: null, // Buffer /// Key generated from NTLM hash response_key_lm: null, // Buffer /// Keep trace of each messages to compute a final hash negotiate_message: null, // Buffer /// Key use to ciphering messages exported_session_key: crypto.randomBytes(16), // Buffer /// True if session use unicode is_unicode: false // Boolean } } function authenticate_message(lm_challenge_response, nt_challenge_response, domain, user, workstation, encrypted_random_session_key, flags) { const payload = Buffer.concat([lm_challenge_response, nt_challenge_response, domain, user, workstation, encrypted_random_session_key]); const offset = ((flags & NegotiateFlags.NtlmsspNegociateVersion) == 0) ? 80 : 88; const buf = Buffer.alloc(offset - 16); buf.write('4e544c4d53535000', 0, 8, 'hex'); // Signature buf.writeInt32LE(3, 8); // MessageType buf.writeInt16LE(lm_challenge_response.length, 12); // LmChallengeResponseLen buf.writeInt16LE(lm_challenge_response.length, 14); // LmChallengeResponseMaxLen buf.writeInt32LE(offset, 16); // LmChallengeResponseBufferOffset buf.writeInt16LE(nt_challenge_response.length, 20); // NtChallengeResponseLen buf.writeInt16LE(nt_challenge_response.length, 22); // NtChallengeResponseMaxLen buf.writeInt32LE(offset + lm_challenge_response.length, 24); // NtChallengeResponseBufferOffset buf.writeInt16LE(domain.length, 28); // DomainNameLen buf.writeInt16LE(domain.length, 30); // DomainNameMaxLen buf.writeInt32LE(offset + lm_challenge_response.length + nt_challenge_response.length, 32); // DomainNameBufferOffset buf.writeInt16LE(user.length, 36); // UserNameLen buf.writeInt16LE(user.length, 38); // UserNameMaxLen buf.writeInt32LE(offset + lm_challenge_response.length + nt_challenge_response.length + domain.length, 40); // UserNameBufferOffset buf.writeInt16LE(workstation.length, 44); // WorkstationLen buf.writeInt16LE(workstation.length, 46); // WorkstationMaxLen buf.writeInt32LE(offset + lm_challenge_response.length + nt_challenge_response.length + domain.length + user.length, 48); // WorkstationBufferOffset buf.writeInt16LE(encrypted_random_session_key.length, 52); // EncryptedRandomSessionLen buf.writeInt16LE(encrypted_random_session_key.length, 54); // EncryptedRandomSessionMaxLen buf.writeInt32LE(offset + lm_challenge_response.length + nt_challenge_response.length + domain.length + user.length + workstation.length, 56); // EncryptedRandomSessionBufferOffset buf.writeInt32LE(flags, 60); // NegotiateFlags if ((flags & NegotiateFlags.NtlmsspNegociateVersion) != 0) { buf.writeUInt8(MajorVersion.WindowsMajorVersion6, 64); // ProductMajorVersion buf.writeUInt8(MinorVersion.WindowsMinorVersion0, 65); // ProductMinorVersion buf.writeInt16LE(6002, 66); // ProductBuild //buf.writeInt16LE(0, 68); // Reserved //buf.writeUInt8(0, 70); // Reserved buf.writeUInt8(NTLMRevision.NtlmSspRevisionW2K3, 71); // NTLMRevisionCurrent } return [buf, payload]; } function read_challenge_message(ntlm, derBuffer) { const headerSignature = derBuffer.slice(0, 8); if (headerSignature.toString('hex') != '4e544c4d53535000') { console.log('BAD SIGNATURE'); } const messageType = derBuffer.readInt32LE(8); if (messageType != 2) { console.log('BAD MESSAGE TYPE'); } const targetNameLen = derBuffer.readInt16LE(12); const targetNameLenMax = derBuffer.readInt16LE(14); const targetNameBufferOffset = derBuffer.readInt32LE(16); const negotiateFlags = derBuffer.readInt32LE(20); const serverChallenge = derBuffer.slice(24, 32); const reserved = derBuffer.slice(32, 40); if (reserved.toString('hex') != '0000000000000000') { console.log('BAD RESERVED'); } const targetInfoLen = derBuffer.readInt16LE(40); const targetInfoLenMax = derBuffer.readInt16LE(42); const targetInfoBufferOffset = derBuffer.readInt32LE(44); const targetName = derBuffer.slice(targetNameBufferOffset, targetNameBufferOffset + targetNameLen); const targetInfo = decodeTargetInfo(derBuffer.slice(targetInfoBufferOffset, targetInfoBufferOffset + targetInfoLen)); const timestamp = targetInfo[7]; if (timestamp == null) { console.log('NO TIMESTAMP'); } const clientChallenge = crypto.randomBytes(8); const response_key_nt = ntowfv2(ntlm.password, ntlm.user, ntlm.domain); // Password, Username, Domain const response_key_lm = lmowfv2(ntlm.password, ntlm.user, ntlm.domain); // Password, Username, Domain var resp = compute_response_v2(response_key_nt, response_key_lm, serverChallenge, clientChallenge, timestamp, targetName); const nt_challenge_response = resp[0]; const lm_challenge_response = resp[1]; const session_base_key = resp[2]; const key_exchange_key = kx_key_v2(session_base_key, lm_challenge_response, serverChallenge); const encrypted_random_session_key = rc4k(key_exchange_key, ntlm.exported_session_key); ntlm.is_unicode = ((negotiateFlags & 1) != 0) var xdomain = null; var xuser = null; if (ntlm.is_unicode) { xdomain = toUnicode(ntlm.domain); xuser = toUnicode(ntlm.user); } else { xdomain = Buffer.from(ntlm.domain, 'utf8'); xuser = Buffer.from(ntlm.user, 'utf8'); } const auth_message_compute = authenticate_message(lm_challenge_response, nt_challenge_response, xdomain, xuser, zeroBuffer(0), encrypted_random_session_key, negotiateFlags); // Write a tmp message to compute MIC and then include it into final message const tmp_final_auth_message = Buffer.concat([auth_message_compute[0], zeroBuffer(16), auth_message_compute[1]]); const signature = mic(ntlm.exported_session_key, ntlm.negotiate_message, derBuffer, tmp_final_auth_message); return Buffer.concat([auth_message_compute[0], signature, auth_message_compute[1]]); } // Create create_ts_request const crypto = require('crypto'); const forge = require('node-forge'); const asn1 = forge.asn1; const pki = forge.pki; const entireBuffer = Buffer.from('3081b2a003020106a181aa3081a73081a4a081a104819e4e544c4d53535000020000000e000e003800000035828a62f2290572b3cac375000000000000000058005800460000000a00614a0000000f430045004e005400520041004c0002000e00430045004e005400520041004c0001000e00430045004e005400520041004c0004000e00430065006e007400720061006c0003000e00430065006e007400720061006c0007000800afbc2c2a9256d80100000000', 'hex').toString('binary'); const ntml = Create_Ntlm(); ntml.domain = ""; ntml.user = "default"; ntml.password = ""; ntml.negotiate_message = Buffer.from('4e544c4d53535000010000003582086000000000000000000000000000000000', 'hex'); // We have a full ASN1 data block, decode it now const der = asn1.fromDer(entireBuffer.toString('binary')); const derNum = der.value[0].value[0].value.charCodeAt(0); const derBuffer = Buffer.from(der.value[1].value[0].value[0].value[0].value[0].value, 'binary'); const client_challenge = read_challenge_message(ntml, derBuffer); console.log('client_challenge', client_challenge.toString('hex')); const NTLMv2SecurityInterface = build_security_interface(ntml);