More work on RDP NLA.
Ylian Saint-Hilaire committed
Apr 23, 2022 at 23:22 UTC
844170087993be84c915f08c7826d9cf69c1d067
1 file changed
+94
-14
test.js
+94
-14
@@ -116,6 +116,21 @@ function compute_response_v2(response_key_nt, response_key_lm, server_challenge,
116
function kx_key_v2(session_base_key, _lm_challenge_response, _server_challenge) { return session_base_key; }
117
function rc4k(key, data) { return crypto.createCipheriv('rc4', key, null).update(data); }
118
119
+function mac(rc4_handle, signing_key, seq_num, data) {
120
+ const buf = Buffer.alloc(4);
121
+ buf.writeInt32LE(seq_num, 0);
122
+ var signature = hmacmd5(signing_key, Buffer.concat([buf, data]));
123
+ return message_signature_ex(rc4_handle.update(signature.slice(0, 8)), seq_num);
124
+}
125
+
126
+function message_signature_ex(check_sum, seq_num) {
127
+ const buf = Buffer.alloc(16);
128
+ buf.writeInt32LE(1, 0); // Version
129
+ if (check_sum) { check_sum.copy(buf, 4, 0, 8); } // check_sum
130
+ if (seq_num) { buf.writeInt32LE(seq_num, 12); } // seq_num
131
+ return buf;
132
+}
133
+
134
/// Compute a signature of all data exchange during NTLMv2 handshake
135
function mic(exported_session_key, negotiate_message, challenge_message, authenticate_message) { return hmacmd5(exported_session_key, Buffer.concat([negotiate_message, challenge_message, authenticate_message])); }
136
@@ -139,6 +154,66 @@ function seal_key(exported_session_key, is_client) {
154
}
155
}
156
157
+/// We are now able to build a security interface
158
+/// that will be used by the CSSP manager to cipherring message (private keys)
159
+/// To detect MITM attack
160
+function build_security_interface(ntlm) {
161
+ const obj = {};
162
+ if (ntlm) {
163
+ obj.signing_key = sign_key(ntlm.exported_session_key, true);
164
+ obj.verify_key = sign_key(ntlm.exported_session_key, false);
165
+ const client_sealing_key = seal_key(ntlm.exported_session_key, true);
166
+ const server_sealing_key = seal_key(ntlm.exported_session_key, false);
167
+ obj.encrypt = crypto.createCipheriv('rc4', client_sealing_key, null);
168
+ obj.decrypt = crypto.createCipheriv('rc4', server_sealing_key, null);
169
+ }
170
+ obj.seq_num = 0;
171
+
172
+ obj.gss_wrapex = function (data) {
173
+ const encrypted_data = obj.encrypt.update(data);
174
+ const signature = mac(obj.encrypt, obj.signing_key, obj.seq_num, data);
175
+ obj.seq_num++;
176
+ return Buffer.concat([ signature, encrypted_data ] );
177
+ }
178
+
179
+ obj.gss_unwrapex = function(data) {
180
+ const version = data.readInt32LE(0);
181
+ const checksum = data.slice(4, 12);
182
+ const seqnum = data.readInt32LE(12);
183
+ const payload = data.slice(16);
184
+ const plaintext_payload = obj.decrypt.update(payload);
185
+ const plaintext_checksum = obj.decrypt.update(checksum);
186
+ const seqnumbuf = Buffer.alloc(4);
187
+ seqnumbuf.writeInt32LE(seqnum, 0);
188
+ const computed_checksum = hmacmd5(obj.verify_key, Buffer.concat([ seqnumbuf, plaintext_payload ])).slice(0, 8);
189
+ if (!plaintext_checksum.equals(computed_checksum)) { console.log("Invalid checksum on NTLMv2"); }
190
+ return plaintext_payload.toString();
191
+ }
192
+
193
+ return obj;
194
+}
195
+
196
+function Create_Ntlm() {
197
+ return {
198
+ /// Microsoft Domain for Active Directory
199
+ domain: "", //String,
200
+ /// Username
201
+ user: "", //String,
202
+ /// Password
203
+ password: "", // String,
204
+ /// Key generated from NTLM hash
205
+ response_key_nt: null, // Buffer
206
+ /// Key generated from NTLM hash
207
+ response_key_lm: null, // Buffer
208
+ /// Keep trace of each messages to compute a final hash
209
+ negotiate_message: null, // Buffer
210
+ /// Key use to ciphering messages
211
+ exported_session_key: crypto.randomBytes(16), // Buffer
212
+ /// True if session use unicode
213
+ is_unicode: false // Boolean
214
+ }
215
+}
216
+
217
function authenticate_message(lm_challenge_response, nt_challenge_response, domain, user, workstation, encrypted_random_session_key, flags) {
218
const payload = Buffer.concat([lm_challenge_response, nt_challenge_response, domain, user, workstation, encrypted_random_session_key]);
219
const offset = ((flags & NegotiateFlags.NtlmsspNegociateVersion) == 0) ? 80 : 88;
@@ -175,7 +250,7 @@ function authenticate_message(lm_challenge_response, nt_challenge_response, doma
250
return [buf, payload];
251
}
252
178
-function read_challenge_message(derBuffer, user, pass, domain, negotiate_message) {
253
+function read_challenge_message(ntlm, derBuffer) {
254
const headerSignature = derBuffer.slice(0, 8);
255
if (headerSignature.toString('hex') != '4e544c4d53535000') { console.log('BAD SIGNATURE'); }
256
const messageType = derBuffer.readInt32LE(8);
@@ -195,8 +270,8 @@ function read_challenge_message(derBuffer, user, pass, domain, negotiate_message
270
const timestamp = targetInfo[7];
271
if (timestamp == null) { console.log('NO TIMESTAMP'); }
272
const clientChallenge = crypto.randomBytes(8);
198
- const response_key_nt = ntowfv2(pass, user, domain); // Password, Username, Domain
199
- const response_key_lm = lmowfv2(pass, user, domain); // Password, Username, Domain
273
+ const response_key_nt = ntowfv2(ntlm.password, ntlm.user, ntlm.domain); // Password, Username, Domain
274
+ const response_key_lm = lmowfv2(ntlm.password, ntlm.user, ntlm.domain); // Password, Username, Domain
275
276
var resp = compute_response_v2(response_key_nt, response_key_lm, serverChallenge, clientChallenge, timestamp, targetName);
277
const nt_challenge_response = resp[0];
@@ -204,18 +279,17 @@ function read_challenge_message(derBuffer, user, pass, domain, negotiate_message
279
const session_base_key = resp[2];
280
281
const key_exchange_key = kx_key_v2(session_base_key, lm_challenge_response, serverChallenge);
207
- const exported_session_key = crypto.randomBytes(16);
208
- const encrypted_random_session_key = rc4k(key_exchange_key, exported_session_key);
282
+ const encrypted_random_session_key = rc4k(key_exchange_key, ntlm.exported_session_key);
283
210
- const is_unicode = ((negotiateFlags & 1) != 0)
284
+ ntlm.is_unicode = ((negotiateFlags & 1) != 0)
285
var xdomain = null;
286
var xuser = null;
213
- if (is_unicode) {
214
- xdomain = toUnicode(domain);
215
- xuser = toUnicode(user);
287
+ if (ntlm.is_unicode) {
288
+ xdomain = toUnicode(ntlm.domain);
289
+ xuser = toUnicode(ntlm.user);
290
} else {
217
- xdomain = Buffer.from(domain, 'utf8');
218
- xuser = Buffer.from(domain, 'utf8');
291
+ xdomain = Buffer.from(ntlm.domain, 'utf8');
292
+ xuser = Buffer.from(ntlm.user, 'utf8');
293
}
294
295
const auth_message_compute = authenticate_message(lm_challenge_response, nt_challenge_response, xdomain, xuser, zeroBuffer(0), encrypted_random_session_key, negotiateFlags);
@@ -223,7 +297,7 @@ function read_challenge_message(derBuffer, user, pass, domain, negotiate_message
297
// Write a tmp message to compute MIC and then include it into final message
298
const tmp_final_auth_message = Buffer.concat([auth_message_compute[0], zeroBuffer(16), auth_message_compute[1]]);
299
226
- const signature = mic(exported_session_key, negotiate_message, derBuffer, tmp_final_auth_message);
300
+ const signature = mic(ntlm.exported_session_key, ntlm.negotiate_message, derBuffer, tmp_final_auth_message);
301
return Buffer.concat([auth_message_compute[0], signature, auth_message_compute[1]]);
302
}
303
@@ -236,13 +310,19 @@ const asn1 = forge.asn1;
310
const pki = forge.pki;
311
const entireBuffer = Buffer.from('3081b2a003020106a181aa3081a73081a4a081a104819e4e544c4d53535000020000000e000e003800000035828a62f2290572b3cac375000000000000000058005800460000000a00614a0000000f430045004e005400520041004c0002000e00430045004e005400520041004c0001000e00430045004e005400520041004c0004000e00430065006e007400720061006c0003000e00430065006e007400720061006c0007000800afbc2c2a9256d80100000000', 'hex').toString('binary');
312
313
+const ntml = Create_Ntlm();
314
+ntml.domain = "";
315
+ntml.user = "default";
316
+ntml.password = "";
317
+ntml.negotiate_message = Buffer.from('4e544c4d53535000010000003582086000000000000000000000000000000000', 'hex');
318
+
319
// We have a full ASN1 data block, decode it now
320
const der = asn1.fromDer(entireBuffer.toString('binary'));
321
const derNum = der.value[0].value[0].value.charCodeAt(0);
322
const derBuffer = Buffer.from(der.value[1].value[0].value[0].value[0].value[0].value, 'binary');
243
-const negotiate_message = Buffer.from('4e544c4d53535000010000003582086000000000000000000000000000000000', 'hex');
244
-const client_challenge = read_challenge_message(derBuffer, "default", "", "", negotiate_message);
323
+const client_challenge = read_challenge_message(ntml, derBuffer);
324
325
console.log('client_challenge', client_challenge.toString('hex'));
326
327
+const NTLMv2SecurityInterface = build_security_interface(ntml);
328