From 5adde27f88d8264666a83b96a2b7a5734c524355 Mon Sep 17 00:00:00 2001 From: Wizou <11647984+wiz0u@users.noreply.github.com> Date: Sat, 29 Apr 2023 16:45:03 +0200 Subject: [PATCH] new TLS client hello generation --- src/Client.Helpers.cs | 18 +++---- src/TlsStream.cs | 115 +++++++++++++++++++++++++++++------------- 2 files changed, 88 insertions(+), 45 deletions(-) diff --git a/src/Client.Helpers.cs b/src/Client.Helpers.cs index 17f7db1..bfb1d5e 100644 --- a/src/Client.Helpers.cs +++ b/src/Client.Helpers.cs @@ -152,17 +152,7 @@ namespace WTelegram reply_to_msg_id: reply_to_msg_id == 0 ? null : reply_to_msg_id, schedule_date: schedule_date == default ? null : schedule_date); RaiseUpdate(updates); int msgId = -1; - foreach (var update in updates.UpdateList) - { - switch (update) - { - case UpdateMessageID updMsgId when updMsgId.random_id == random_id: msgId = updMsgId.id; break; - case UpdateNewMessage { message: Message message } when message.id == msgId: return message; - case UpdateNewScheduledMessage { message: Message schedMsg } when schedMsg.id == msgId: return schedMsg; - } - } if (updates is UpdateShortSentMessage sent) - { return new Message { flags = (Message.Flags)sent.flags | (reply_to_msg_id == 0 ? 0 : Message.Flags.has_reply_to) | (peer is InputPeerSelf ? 0 : Message.Flags.has_from_id), @@ -171,6 +161,14 @@ namespace WTelegram from_id = peer is InputPeerSelf ? null : new PeerUser { user_id = _session.UserId }, peer_id = InputToPeer(peer) }; + foreach (var update in updates.UpdateList) + { + switch (update) + { + case UpdateMessageID updMsgId when updMsgId.random_id == random_id: msgId = updMsgId.id; break; + case UpdateNewMessage { message: Message message } when message.id == msgId: return message; + case UpdateNewScheduledMessage { message: Message schedMsg } when schedMsg.id == msgId: return schedMsg; + } } return null; } diff --git a/src/TlsStream.cs b/src/TlsStream.cs index a4b4e9f..97ed1e5 100644 --- a/src/TlsStream.cs +++ b/src/TlsStream.cs @@ -85,39 +85,43 @@ namespace WTelegram throw new WTException("TLS Handshake failed"); } - static readonly byte[] TlsClientHello1 = new byte[11] { + static readonly byte[] TlsClientHello1 = new byte[11] { // https://tls13.xargs.org/#client-hello/annotated 0x16, 0x03, 0x01, 0x02, 0x00, 0x01, 0x00, 0x01, 0xfc, 0x03, 0x03 }; // digest[32] // 0x20 // random[32] - // 0x00, 0x20, grease(0) GREASE are two identical bytes ending with nibble 'A' + // 0x00, 0x20 + // grease(0) GREASE are two identical bytes ending with nibble 'A' static readonly byte[] TlsClientHello2 = new byte[34] { 0x13, 0x01, 0x13, 0x02, 0x13, 0x03, 0xc0, 0x2b, 0xc0, 0x2f, 0xc0, 0x2c, 0xc0, 0x30, 0xcc, 0xa9, - 0xcc, 0xa8, 0xc0, 0x13, 0xc0, 0x14, 0x00, 0x9c, 0x00, 0x9d, 0x00, 0x2f, 0x00, 0x35, 0x01, 0x00, 0x01, 0x93 }; - // grease(2), 0x00, 0x00, 0x00, 0x00 - // len { len { 0x00 len { domain } } } len is 16-bit big-endian length of the following block of data - static readonly byte[] TlsClientHello3 = new byte[101] { - 0x00, 0x17, 0x00, 0x00, 0xff, 0x01, 0x00, 0x01, 0x00, 0x00, 0x0a, 0x00, 0x0a, 0x00, 0x08, - 0x4A, 0x4A, // = grease(4) - 0x00, 0x1d, 0x00, 0x17, 0x00, 0x18, 0x00, 0x0b, 0x00, 0x02, 0x01, 0x00, 0x00, 0x23, 0x00, 0x00, - 0x00, 0x10, 0x00, 0x0e, 0x00, 0x0c, 0x02, 0x68, 0x32, 0x08, 0x68, 0x74, 0x74, 0x70, 0x2f, 0x31, - 0x2e, 0x31, 0x00, 0x05, 0x00, 0x05, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x12, 0x00, - 0x10, 0x04, 0x03, 0x08, 0x04, 0x04, 0x01, 0x05, 0x03, 0x08, 0x05, 0x05, 0x01, 0x08, 0x06, 0x06, - 0x01, 0x00, 0x12, 0x00, 0x00, 0x00, 0x33, 0x00, 0x2b, 0x00, 0x29, - 0x4A, 0x4A, // = grease(4) - 0x00, 0x01, 0x00, 0x00, 0x1d, 0x00, 0x20 }; - // random[32] = public key - static readonly byte[] TlsClientHello4 = new byte[35] { - 0x00, 0x2d, 0x00, 0x02, 0x01, 0x01, 0x00, 0x2b, 0x00, 0x0b, 0x0a, - 0x6A, 0x6A, // = grease(6) - 0x03, 0x04, 0x03, 0x03, 0x03, 0x02, 0x03, 0x01, 0x00, 0x1b, 0x00, 0x03, 0x02, 0x00, 0x02, - 0x3A, 0x3A, // = grease(3) + 0xcc, 0xa8, 0xc0, 0x13, 0xc0, 0x14, 0x00, 0x9c, 0x00, 0x9d, 0x00, 0x2f, 0x00, 0x35, + 0x01, 0x00, 0x01, 0x93 }; + // grease(2) + // 0x00, 0x00 + static readonly byte[] TlsClientHello3 = new byte[134] { + // 0x00, 0x00, len { len { 0x00 len { domain } } } len is 16-bit big-endian length of the following block of data + 0x00, 0x05, 0x00, 0x05, 0x01, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x0a, 0x00, 0x0a, 0x00, 0x08, 0x4A, 0x4A,/*=grease(4)*/ 0x00, 0x1d, 0x00, 0x17, 0x00, 0x18, + 0x00, 0x0b, 0x00, 0x02, 0x01, 0x00, + 0x00, 0x0d, 0x00, 0x12, 0x00, 0x10, 0x04, 0x03, 0x08, 0x04, 0x04, 0x01, 0x05, 0x03, 0x08, 0x05, 0x05, 0x01, 0x08, 0x06, 0x06, 0x01, + 0x00, 0x10, 0x00, 0x0e, 0x00, 0x0c, 0x02, 0x68, 0x32, 0x08, 0x68, 0x74, 0x74, 0x70, 0x2f, 0x31, 0x2e, 0x31, + 0x00, 0x12, 0x00, 0x00, + 0x00, 0x17, 0x00, 0x00, + 0x00, 0x1b, 0x00, 0x03, 0x02, 0x00, 0x02, + 0x00, 0x23, 0x00, 0x00, + 0x00, 0x2b, 0x00, 0x07, 0x06, 0x6A, 0x6A,/*=grease(6) */ 0x03, 0x04, 0x03, 0x03, + 0x00, 0x2d, 0x00, 0x02, 0x01, 0x01, + 0x00, 0x33, 0x00, 0x2b, 0x00, 0x29, 0x4A, 0x4A,/*=grease(4) */ 0x00, 0x01, 0x00, 0x00, 0x1d, 0x00, 0x20, /* random[32] */ + 0x44, 0x69, 0x00, 0x05, 0x00, 0x03, 0x02, 0x68, 0x32, + 0xff, 0x01, 0x00, 0x01, 0x00, + }; + // grease(3) + static readonly byte[] TlsClientHello4 = new byte[5] { 0x00, 0x01, 0x00, 0x00, 0x15 }; // len { padding } padding with NUL bytes to reach 517 bytes static byte[] TlsClientHello(byte[] key, byte[] domain) { - int dlen = domain.Length; var greases = new byte[7]; Encryption.RNG.GetBytes(greases); for (int i = 0; i < 7; i++) greases[i] = (byte)((greases[i] & 0xF0) + 0x0A); @@ -130,19 +134,54 @@ namespace WTelegram buffer[78] = buffer[79] = greases[0]; TlsClientHello2.CopyTo(buffer, 80); buffer[114] = buffer[115] = greases[2]; - buffer[121] = (byte)(dlen + 5); - buffer[123] = (byte)(dlen + 3); - buffer[126] = (byte)dlen; - domain.CopyTo(buffer, 127); - TlsClientHello3.CopyTo(buffer, 127 + dlen); - buffer[142 + dlen] = buffer[143 + dlen] = greases[4]; - buffer[219 + dlen] = buffer[220 + dlen] = greases[4]; - Encryption.RNG.GetBytes(buffer, 228 + dlen, 32); // public key - buffer[228 + dlen + 31] &= 0x7F; // must be positive - TlsClientHello4.CopyTo(buffer, 260 + dlen); - buffer[271 + dlen] = buffer[272 + dlen] = greases[6]; - buffer[288 + dlen] = buffer[289 + dlen] = greases[3]; - buffer[296 + dlen] = (byte)(220 - dlen); + + int dlen = domain.Length; + var server_name = new byte[dlen + 9]; + server_name[3] = (byte)(dlen + 5); + server_name[5] = (byte)(dlen + 3); + server_name[8] = (byte)dlen; + domain.CopyTo(server_name, 9); + + var key_share = new byte[47]; + Array.Copy(TlsClientHello3, 105, key_share, 0, 15); + key_share[6] = key_share[7] = greases[4]; + Encryption.RNG.GetBytes(key_share, 15, 32); // public key + key_share[46] &= 0x7F; // must be positive + + var random = new Random(); + var permutations = new ArraySegment[15]; + for (var i = 0; i < permutations.Length; i++) + { + var j = random.Next(0, i + 1); + if (i != j) permutations[i] = permutations[j]; + permutations[j] = i switch + { + 0 => new(server_name), + 1 => new(TlsClientHello3, 0, 9), + 2 => PatchGrease(TlsClientHello3[9..23], 6, greases[4]), + 3 => new(TlsClientHello3, 23, 6), + 4 => new(TlsClientHello3, 29, 22), + 5 => new(TlsClientHello3, 51, 18), + 6 => new(TlsClientHello3, 69, 4), + 7 => new(TlsClientHello3, 73, 4), + 8 => new(TlsClientHello3, 77, 7), + 9 => new(TlsClientHello3, 84, 4), + 10 => PatchGrease(TlsClientHello3[88..99], 5, greases[6]), + 11 => new(TlsClientHello3, 99, 6), + 12 => new(key_share), + 13 => new(TlsClientHello3, 120, 9), + _ => new(TlsClientHello3, 129, 5), + }; + } + int offset = 118; + foreach (var perm in permutations) + { + Array.Copy(perm.Array, perm.Offset, buffer, offset, perm.Count); + offset += perm.Count; + } + buffer[offset++] = buffer[offset++] = greases[3]; + TlsClientHello4.CopyTo(buffer, offset); + buffer[offset + 6] = (byte)(510 - offset); // patch-in digest with timestamp using var hmac = new HMACSHA256(key); @@ -152,6 +191,12 @@ namespace WTelegram BinaryPrimitives.WriteInt32LittleEndian(digest.AsSpan(28), stamp); digest.CopyTo(buffer, 11); return buffer; + + static ArraySegment PatchGrease(byte[] buffer, int offset, byte grease) + { + buffer[offset] = buffer[offset + 1] = grease; + return new(buffer); + } } } }