mirror of
https://github.com/Qz3rK/tdesktop.git
synced 2026-06-02 03:53:42 +02:00
implement DPI evasion for MTProto TLS connections
- Increase ClientHello size via padding - Fragment outgoing packets with randomized chunk sizes - Remove extension shuffling for stable fingerprint - Add support for new TLS block schema types - Add random jitter to timestamp generation * Hacky implementation, needs review.
This commit is contained in:
@@ -4,6 +4,7 @@ the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
--- DPI FIX by Qz3rK ---
|
||||
*/
|
||||
#include "mtproto/details/mtproto_tls_socket.h"
|
||||
|
||||
@@ -11,17 +12,21 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "base/openssl_help.h"
|
||||
#include "base/bytes.h"
|
||||
#include "base/invoke_queued.h"
|
||||
#include "base/random.h"
|
||||
#include "base/unixtime.h"
|
||||
|
||||
#include <QtCore/QtEndian>
|
||||
#include <range/v3/algorithm/reverse.hpp>
|
||||
#include <range/v3/range/conversion.hpp>
|
||||
#include <range/v3/algorithm/shuffle.hpp>
|
||||
#include <random>
|
||||
#include <thread>
|
||||
|
||||
namespace MTP::details {
|
||||
namespace {
|
||||
|
||||
constexpr auto kMaxGrease = 8;
|
||||
constexpr auto kClientHelloLimit = 2048;
|
||||
|
||||
constexpr auto kClientHelloLength = 517;
|
||||
constexpr auto kHelloDigestLength = 32;
|
||||
constexpr auto kLengthSize = sizeof(uint16);
|
||||
const auto kServerHelloPart1 = qstr("\x16\x03\x03");
|
||||
@@ -69,15 +74,6 @@ using BigNumContext = openssl::Context;
|
||||
const auto K = [&] {
|
||||
pushToBack(MTP_tlsBlockPublicKey());
|
||||
};
|
||||
const auto M = [&] {
|
||||
pushToBack(MTP_tlsBlockM());
|
||||
};
|
||||
const auto E = [&] {
|
||||
pushToBack(MTP_tlsBlockE());
|
||||
};
|
||||
const auto P = [&] {
|
||||
pushToBack(MTP_tlsBlockPadding());
|
||||
};
|
||||
const auto OpenScope = [&] {
|
||||
stack.emplace_back(Scope());
|
||||
};
|
||||
@@ -122,23 +118,20 @@ using BigNumContext = openssl::Context;
|
||||
|
||||
stack.emplace_back(Scope());
|
||||
|
||||
S("\x16\x03\x01"_q);
|
||||
OpenScope();
|
||||
S("\x01\x00"_q);
|
||||
OpenScope();
|
||||
S("\x03\x03"_q);
|
||||
S("\x16\x03\x01\x02\x00\x01\x00\x01\xfc\x03\x03"_q);
|
||||
Z(32);
|
||||
S("\x20"_q);
|
||||
R(32);
|
||||
|
||||
S("\x00\x20"_q);
|
||||
G(0);
|
||||
S(""
|
||||
"\x13\x01\x13\x02\x13\x03\xc0\x2b\xc0\x2f\xc0\x2c\xc0\x30\xcc\xa9"
|
||||
"\xcc\xa8\xc0\x13\xc0\x14\x00\x9c\x00\x9d\x00\x2f\x00\x35\x01\x00"
|
||||
""_q);
|
||||
OpenScope();
|
||||
"\x01\x93"_q);
|
||||
G(2);
|
||||
S("\x00\x00"_q);
|
||||
|
||||
OpenPermutation(); {
|
||||
StartPermutationElement(); {
|
||||
S("\x00\x00"_q);
|
||||
@@ -155,9 +148,9 @@ using BigNumContext = openssl::Context;
|
||||
S("\x00\x05\x00\x05\x01\x00\x00\x00\x00"_q);
|
||||
}
|
||||
StartPermutationElement(); {
|
||||
S("\x00\x0a\x00\x0c\x00\x0a"_q);
|
||||
S("\x00\x0a\x00\x0a\x00\x08"_q);
|
||||
G(4);
|
||||
S("\x11\xec\x00\x1d\x00\x17\x00\x18"_q);
|
||||
S("\x00\x1d\x00\x17\x00\x18"_q);
|
||||
}
|
||||
StartPermutationElement(); {
|
||||
S("\x00\x0b\x00\x02\x01\x00"_q);
|
||||
@@ -193,39 +186,22 @@ using BigNumContext = openssl::Context;
|
||||
S("\x00\x2d\x00\x02\x01\x01"_q);
|
||||
}
|
||||
StartPermutationElement(); {
|
||||
S("\x00\x33\x04\xef\x04\xed"_q);
|
||||
S("\x00\x33\x00\x2b\x00\x29"_q);
|
||||
G(4);
|
||||
S("\x00\x01\x00\x11\xec\x04\xc0"_q);
|
||||
M();
|
||||
K();
|
||||
S("\x00\x1d\x00\x20"_q);
|
||||
S("\x00\x01\x00\x00\x1d\x00\x20"_q);
|
||||
K();
|
||||
}
|
||||
StartPermutationElement(); {
|
||||
S("\x44\xcd\x00\x05\x00\x03\x02\x68\x32"_q);
|
||||
}
|
||||
StartPermutationElement(); {
|
||||
S("\xfe\x0d"_q);
|
||||
OpenScope();
|
||||
S("\x00\x00\x01\x00\x01"_q);
|
||||
R(1);
|
||||
S("\x00\x20"_q);
|
||||
R(32);
|
||||
OpenScope();
|
||||
E();
|
||||
CloseScope();
|
||||
CloseScope();
|
||||
S("\x44\x69\x00\x05\x00\x03\x02\x68\x32"_q);
|
||||
}
|
||||
StartPermutationElement(); {
|
||||
S("\xff\x01\x00\x01\x00"_q);
|
||||
}
|
||||
} ClosePermutation();
|
||||
|
||||
G(3);
|
||||
S("\x00\x01\x00"_q);
|
||||
P();
|
||||
CloseScope();
|
||||
CloseScope();
|
||||
CloseScope();
|
||||
S("\x00\x01\x00\x00\x15"_q);
|
||||
Z(83);
|
||||
|
||||
return MTP_tlsClientHello(MTP_vector<MTPTlsBlock>(Finish()));
|
||||
}
|
||||
@@ -242,44 +218,85 @@ using BigNumContext = openssl::Context;
|
||||
result[i + 1] = bytes::type(uchar(result[i + 1]) ^ 0x10);
|
||||
}
|
||||
}
|
||||
for (auto i = 0; i != kMaxGrease; ++i) {
|
||||
const auto j = (uchar(result[i]) * 0x9E3779B9) % kMaxGrease;
|
||||
if (i != j) {
|
||||
std::swap(result[i], result[j]);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
[[nodiscard]] BigNum GenerateY2(
|
||||
const BigNum &x,
|
||||
const BigNum &mod,
|
||||
const BigNumContext &context) {
|
||||
auto coef = BigNum(486662);
|
||||
auto y = BigNum::ModAdd(x, coef, mod, context);
|
||||
y.setModMul(y, x, mod, context);
|
||||
coef.setWord(1);
|
||||
y.setModAdd(y, coef, mod, context);
|
||||
return BigNum::ModMul(y, x, mod, context);
|
||||
}
|
||||
|
||||
[[nodiscard]] BigNum GenerateX2(
|
||||
const BigNum &x,
|
||||
const BigNum &mod,
|
||||
const BigNumContext &context) {
|
||||
auto denominator = GenerateY2(x, mod, context);
|
||||
auto coef = BigNum(4);
|
||||
denominator.setModMul(denominator, coef, mod, context);
|
||||
|
||||
auto numerator = BigNum::ModMul(x, x, mod, context);
|
||||
coef.setWord(1);
|
||||
numerator.setModSub(numerator, coef, mod, context);
|
||||
numerator.setModMul(numerator, numerator, mod, context);
|
||||
|
||||
denominator.setModInverse(denominator, mod, context);
|
||||
return BigNum::ModMul(numerator, denominator, mod, context);
|
||||
}
|
||||
|
||||
[[nodiscard]] bytes::vector GeneratePublicKey() {
|
||||
const auto context = EVP_PKEY_CTX_new_id(NID_ED25519, nullptr);
|
||||
if (!context) {
|
||||
return {};
|
||||
}
|
||||
const auto guardContext = gsl::finally([&] {
|
||||
EVP_PKEY_CTX_free(context);
|
||||
});
|
||||
const auto context = BigNumContext();
|
||||
const char modBytes[] = ""
|
||||
"\x7f\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"
|
||||
"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xed";
|
||||
const char powBytes[] = ""
|
||||
"\x3f\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"
|
||||
"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xf6";
|
||||
const auto mod = BigNum(bytes::make_span(modBytes).subspan(0, 32));
|
||||
const auto pow = BigNum(bytes::make_span(powBytes).subspan(0, 32));
|
||||
|
||||
if (EVP_PKEY_keygen_init(context) <= 0) {
|
||||
return {};
|
||||
}
|
||||
auto x = BigNum();
|
||||
do {
|
||||
while (true) {
|
||||
auto random = bytes::vector(32);
|
||||
bytes::set_random(random);
|
||||
random[31] &= bytes::type(0x7FU);
|
||||
x.setBytes(random);
|
||||
x.setModMul(x, x, mod, context);
|
||||
|
||||
auto key = (EVP_PKEY*)nullptr;
|
||||
if (EVP_PKEY_keygen(context, &key) <= 0) {
|
||||
return {};
|
||||
}
|
||||
const auto guardKey = gsl::finally([&] {
|
||||
EVP_PKEY_free(key);
|
||||
});
|
||||
auto y = GenerateY2(x, mod, context);
|
||||
if (BigNum::ModExp(y, pow, mod, context).isOne()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
for (auto i = 0; i != 3; ++i) {
|
||||
x = GenerateX2(x, mod, context);
|
||||
}
|
||||
const auto xBytes = x.getBytes();
|
||||
Assert(!xBytes.empty());
|
||||
Assert(xBytes.size() <= 32);
|
||||
} while (x.bytesSize() == 32);
|
||||
|
||||
const auto xBytes = x.getBytes();
|
||||
auto result = bytes::vector(32, bytes::type());
|
||||
bytes::copy(
|
||||
bytes::make_span(result).subspan(32 - xBytes.size()),
|
||||
xBytes);
|
||||
ranges::reverse(result);
|
||||
|
||||
auto length = size_t(0);
|
||||
if (!EVP_PKEY_get_raw_public_key(key, nullptr, &length)) {
|
||||
return {};
|
||||
}
|
||||
Assert(length == 32);
|
||||
|
||||
auto result = bytes::vector(length);
|
||||
const auto code = EVP_PKEY_get_raw_public_key(
|
||||
key,
|
||||
reinterpret_cast<unsigned char *>(result.data()),
|
||||
&length);
|
||||
if (!code) {
|
||||
return {};
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -324,6 +341,7 @@ private:
|
||||
[[nodiscard]] QByteArray take();
|
||||
|
||||
private:
|
||||
void writePadding();
|
||||
void writeDigest(bytes::const_span key);
|
||||
void injectTimestamp();
|
||||
|
||||
@@ -347,7 +365,7 @@ Generator::Part::Part(
|
||||
const bytes::vector &greases)
|
||||
: _domain(domain)
|
||||
, _greases(greases) {
|
||||
_result.reserve(kClientHelloLimit);
|
||||
_result.reserve(kClientHelloLength);
|
||||
_data = _result.constData();
|
||||
}
|
||||
|
||||
@@ -364,7 +382,7 @@ QByteArray Generator::Part::take() {
|
||||
bytes::span Generator::Part::grow(int size) {
|
||||
if (_error
|
||||
|| size <= 0
|
||||
|| _result.size() + size > kClientHelloLimit) {
|
||||
|| _result.size() + size > kClientHelloLength) {
|
||||
_error = true;
|
||||
return bytes::span();
|
||||
}
|
||||
@@ -381,11 +399,22 @@ void Generator::Part::writeBlocks(const QVector<MTPTlsBlock> &blocks) {
|
||||
}
|
||||
|
||||
void Generator::Part::writeBlock(const MTPTlsBlock &data) {
|
||||
data.match([&](const auto &data) {
|
||||
writeBlock(data);
|
||||
});
|
||||
data.match(
|
||||
[&](const MTPDtlsBlockString &data) { writeBlock(data); },
|
||||
[&](const MTPDtlsBlockZero &data) { writeBlock(data); },
|
||||
[&](const MTPDtlsBlockGrease &data) { writeBlock(data); },
|
||||
[&](const MTPDtlsBlockRandom &data) { writeBlock(data); },
|
||||
[&](const MTPDtlsBlockDomain &data) { writeBlock(data); },
|
||||
[&](const MTPDtlsBlockPublicKey &data) { writeBlock(data); },
|
||||
[&](const MTPDtlsBlockScope &data) { writeBlock(data); },
|
||||
[&](const MTPDtlsBlockPermutation &data) { writeBlock(data); },
|
||||
[&](const MTPDtlsBlockM &data) { writeBlock(data); },
|
||||
[&](const MTPDtlsBlockE &data) { writeBlock(data); },
|
||||
[&](const MTPDtlsBlockPadding &data) { writeBlock(data); }
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
void Generator::Part::writeBlock(const MTPDtlsBlockString &data) {
|
||||
const auto &bytes = data.vdata().v;
|
||||
const auto storage = grow(bytes.size());
|
||||
@@ -470,7 +499,7 @@ void Generator::Part::writeBlock(const MTPDtlsBlockPermutation &data) {
|
||||
}
|
||||
list.push_back(part.take());
|
||||
}
|
||||
ranges::shuffle(list);
|
||||
|
||||
for (const auto &element : list) {
|
||||
const auto storage = grow(element.size());
|
||||
if (storage.empty()) {
|
||||
@@ -481,44 +510,15 @@ void Generator::Part::writeBlock(const MTPDtlsBlockPermutation &data) {
|
||||
}
|
||||
|
||||
void Generator::Part::writeBlock(const MTPDtlsBlockM &data) {
|
||||
constexpr auto kElements = 384;
|
||||
constexpr auto kAdded = 32;
|
||||
|
||||
const auto storage = grow(kElements * 3 + kAdded);
|
||||
if (storage.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto random = bytes::vector(kElements * 8 + kAdded);
|
||||
bytes::set_random(random);
|
||||
|
||||
auto chars = reinterpret_cast<char*>(storage.data());
|
||||
const auto ints = reinterpret_cast<const uint32*>(random.data());
|
||||
for (auto i = 0; i < kElements; ++i) {
|
||||
const auto a = int(ints[i * 2] % 3329);
|
||||
const auto b = int(ints[i * 2 + 1] % 3329);
|
||||
*chars++ = (char)(a & 255);
|
||||
*chars++ = (char)((a >> 8) + ((b & 15) << 4));
|
||||
*chars++ = (char)(b >> 4);
|
||||
}
|
||||
bytes::set_random(storage.subspan(kElements * 3));
|
||||
}
|
||||
|
||||
void Generator::Part::writeBlock(const MTPDtlsBlockE &data) {
|
||||
const auto lengths = std::array{ 144, 176, 208, 240 };
|
||||
const auto length = lengths[base::RandomIndex(lengths.size())];
|
||||
writeBlock(MTP_tlsBlockRandom(MTP_int(length)));
|
||||
}
|
||||
|
||||
void Generator::Part::writeBlock(const MTPDtlsBlockPadding &data) {
|
||||
const auto length = int(_result.size());
|
||||
if (length < 513) {
|
||||
const auto zero = MTP_tlsBlockZero(MTP_int(513 - length));
|
||||
writeBlock(MTP_tlsBlockString(MTP_bytes("\x00\x15"_q)));
|
||||
writeBlock(MTP_tlsBlockScope(MTP_vector<MTPTlsBlock>(1, zero)));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void Generator::Part::finalize(bytes::const_span key) {
|
||||
if (_error) {
|
||||
return;
|
||||
@@ -526,6 +526,7 @@ void Generator::Part::finalize(bytes::const_span key) {
|
||||
_error = true;
|
||||
return;
|
||||
}
|
||||
writePadding();
|
||||
writeDigest(key);
|
||||
injectTimestamp();
|
||||
}
|
||||
@@ -537,6 +538,14 @@ QByteArray Generator::Part::extractDigest() const {
|
||||
return _result.mid(_digestPosition, kHelloDigestLength);
|
||||
}
|
||||
|
||||
void Generator::Part::writePadding() {
|
||||
Expects(_result.size() <= kClientHelloLength - kLengthSize);
|
||||
|
||||
const auto padding = kClientHelloLength - kLengthSize - _result.size();
|
||||
writeBlock(MTP_tlsBlockScope(
|
||||
MTP_vector<MTPTlsBlock>(1, MTP_tlsBlockZero(MTP_int(padding)))));
|
||||
}
|
||||
|
||||
void Generator::Part::writeDigest(bytes::const_span key) {
|
||||
Expects(_digestPosition >= 0);
|
||||
|
||||
@@ -553,7 +562,10 @@ void Generator::Part::injectTimestamp() {
|
||||
sizeof(int32));
|
||||
auto already = int32();
|
||||
bytes::copy(bytes::object_as_span(&already), storage);
|
||||
already ^= qToLittleEndian(int32(base::unixtime::http_now()));
|
||||
auto randomJitter = int32(0);
|
||||
bytes::set_random(bytes::object_as_span(&randomJitter));
|
||||
randomJitter &= 0x3F;
|
||||
already ^= qToLittleEndian(int32(base::unixtime::http_now() + randomJitter));
|
||||
bytes::copy(storage, bytes::object_as_span(&already));
|
||||
}
|
||||
|
||||
@@ -563,7 +575,9 @@ Generator::Generator(
|
||||
bytes::const_span key)
|
||||
: _greases(PrepareGreases())
|
||||
, _result(domain, _greases) {
|
||||
_result.writeBlocks(rules.data().vblocks().v);
|
||||
_result.writeBlocks(rules.match([&](const MTPDtlsClientHello &data) {
|
||||
return data.vblocks().v;
|
||||
}));
|
||||
_result.finalize(key);
|
||||
}
|
||||
|
||||
@@ -594,7 +608,7 @@ ClientHello Generator::take() {
|
||||
*reinterpret_cast<const uint16*>(storage.data()));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
}
|
||||
|
||||
TlsSocket::TlsSocket(
|
||||
not_null<QThread*> thread,
|
||||
@@ -664,10 +678,29 @@ void TlsSocket::plainConnected() {
|
||||
} else {
|
||||
_state = State::WaitingHello;
|
||||
_incoming = hello.digest;
|
||||
_socket.write(hello.data);
|
||||
|
||||
const auto &data = hello.data;
|
||||
int offset = 0;
|
||||
|
||||
auto rng = std::mt19937(std::random_device{}());
|
||||
auto chunkSizeDist = std::uniform_int_distribution<int>(48, 96);
|
||||
auto delayDist = std::uniform_int_distribution<int>(1, 5);
|
||||
|
||||
while (offset < data.size()) {
|
||||
const auto chunkSize = std::min(
|
||||
chunkSizeDist(rng),
|
||||
data.size() - offset);
|
||||
_socket.write(data.mid(offset, chunkSize));
|
||||
offset += chunkSize;
|
||||
|
||||
if (offset < data.size()) {
|
||||
QThread::msleep(delayDist(rng));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void TlsSocket::plainDisconnected() {
|
||||
_state = State::NotConnected;
|
||||
_incoming = QByteArray();
|
||||
|
||||
Reference in New Issue
Block a user